| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "build/build_config.h" | 5 #include "build/build_config.h" |
| 6 | 6 |
| 7 #if defined(OS_WIN) | 7 #if defined(OS_WIN) |
| 8 #include <windows.h> | 8 #include <windows.h> |
| 9 #elif defined(OS_POSIX) | |
| 10 #include <sys/types.h> | |
| 11 #include <unistd.h> | |
| 12 #endif | 9 #endif |
| 13 | 10 |
| 14 #include <stdio.h> | |
| 15 #include <string> | 11 #include <string> |
| 16 #include <utility> | |
| 17 | 12 |
| 18 #include "base/command_line.h" | 13 #include "base/message_loop.h" |
| 19 #include "base/pickle.h" | 14 #include "base/pickle.h" |
| 20 #include "base/threading/thread.h" | 15 #include "base/threading/thread.h" |
| 21 #include "base/time.h" | 16 #include "ipc/ipc_message.h" |
| 22 #include "ipc/ipc_descriptors.h" | |
| 23 #include "ipc/ipc_channel.h" | |
| 24 #include "ipc/ipc_channel_proxy.h" | |
| 25 #include "ipc/ipc_message_utils.h" | |
| 26 #include "ipc/ipc_multiprocess_test.h" | |
| 27 #include "ipc/ipc_sender.h" | |
| 28 #include "ipc/ipc_switches.h" | |
| 29 #include "ipc/ipc_test_base.h" | 17 #include "ipc/ipc_test_base.h" |
| 30 | 18 |
| 31 namespace { | 19 namespace { |
| 32 | 20 |
| 33 const size_t kLongMessageStringNumBytes = 50000; | 21 const size_t kLongMessageStringNumBytes = 50000; |
| 34 | 22 |
| 35 class IPCChannelTest : public IPCTestBase { | |
| 36 }; | |
| 37 | |
| 38 TEST_F(IPCChannelTest, BasicMessageTest) { | |
| 39 int v1 = 10; | |
| 40 std::string v2("foobar"); | |
| 41 std::wstring v3(L"hello world"); | |
| 42 | |
| 43 IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); | |
| 44 EXPECT_TRUE(m.WriteInt(v1)); | |
| 45 EXPECT_TRUE(m.WriteString(v2)); | |
| 46 EXPECT_TRUE(m.WriteWString(v3)); | |
| 47 | |
| 48 PickleIterator iter(m); | |
| 49 | |
| 50 int vi; | |
| 51 std::string vs; | |
| 52 std::wstring vw; | |
| 53 | |
| 54 EXPECT_TRUE(m.ReadInt(&iter, &vi)); | |
| 55 EXPECT_EQ(v1, vi); | |
| 56 | |
| 57 EXPECT_TRUE(m.ReadString(&iter, &vs)); | |
| 58 EXPECT_EQ(v2, vs); | |
| 59 | |
| 60 EXPECT_TRUE(m.ReadWString(&iter, &vw)); | |
| 61 EXPECT_EQ(v3, vw); | |
| 62 | |
| 63 // should fail | |
| 64 EXPECT_FALSE(m.ReadInt(&iter, &vi)); | |
| 65 EXPECT_FALSE(m.ReadString(&iter, &vs)); | |
| 66 EXPECT_FALSE(m.ReadWString(&iter, &vw)); | |
| 67 } | |
| 68 | |
| 69 static void Send(IPC::Sender* sender, const char* text) { | 23 static void Send(IPC::Sender* sender, const char* text) { |
| 70 static int message_index = 0; | 24 static int message_index = 0; |
| 71 | 25 |
| 72 IPC::Message* message = new IPC::Message(0, | 26 IPC::Message* message = new IPC::Message(0, |
| 73 2, | 27 2, |
| 74 IPC::Message::PRIORITY_NORMAL); | 28 IPC::Message::PRIORITY_NORMAL); |
| 75 message->WriteInt(message_index++); | 29 message->WriteInt(message_index++); |
| 76 message->WriteString(std::string(text)); | 30 message->WriteString(std::string(text)); |
| 77 | 31 |
| 78 // Make sure we can handle large messages. | 32 // Make sure we can handle large messages. |
| 79 char junk[kLongMessageStringNumBytes]; | 33 char junk[kLongMessageStringNumBytes]; |
| 80 memset(junk, 'a', sizeof(junk)-1); | 34 memset(junk, 'a', sizeof(junk)-1); |
| 81 junk[sizeof(junk)-1] = 0; | 35 junk[sizeof(junk)-1] = 0; |
| 82 message->WriteString(std::string(junk)); | 36 message->WriteString(std::string(junk)); |
| 83 | 37 |
| 84 // DEBUG: printf("[%u] sending message [%s]\n", GetCurrentProcessId(), text); | 38 // DEBUG: printf("[%u] sending message [%s]\n", GetCurrentProcessId(), text); |
| 85 sender->Send(message); | 39 sender->Send(message); |
| 86 } | 40 } |
| 87 | 41 |
| 88 class MyChannelListener : public IPC::Listener { | 42 // A generic listener that expects messages of a certain type (see |
| 43 // OnMessageReceived()), and either sends a generic response or quits after the |
| 44 // 50th message (or on channel error). |
| 45 class GenericChannelListener : public IPC::Listener { |
| 89 public: | 46 public: |
| 90 virtual bool OnMessageReceived(const IPC::Message& message) { | 47 GenericChannelListener() : sender_(NULL), messages_left_(50) {} |
| 48 virtual ~GenericChannelListener() {} |
| 49 |
| 50 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { |
| 91 PickleIterator iter(message); | 51 PickleIterator iter(message); |
| 92 | 52 |
| 93 int ignored; | 53 int ignored; |
| 94 EXPECT_TRUE(iter.ReadInt(&ignored)); | 54 EXPECT_TRUE(iter.ReadInt(&ignored)); |
| 95 std::string data; | 55 std::string data; |
| 96 EXPECT_TRUE(iter.ReadString(&data)); | 56 EXPECT_TRUE(iter.ReadString(&data)); |
| 97 std::string big_string; | 57 std::string big_string; |
| 98 EXPECT_TRUE(iter.ReadString(&big_string)); | 58 EXPECT_TRUE(iter.ReadString(&big_string)); |
| 99 EXPECT_EQ(kLongMessageStringNumBytes - 1, big_string.length()); | 59 EXPECT_EQ(kLongMessageStringNumBytes - 1, big_string.length()); |
| 100 | 60 |
| 101 | 61 SendNextMessage(); |
| 102 if (--messages_left_ == 0) { | |
| 103 MessageLoop::current()->Quit(); | |
| 104 } else { | |
| 105 Send(sender_, "Foo"); | |
| 106 } | |
| 107 return true; | 62 return true; |
| 108 } | 63 } |
| 109 | 64 |
| 110 virtual void OnChannelError() { | 65 virtual void OnChannelError() OVERRIDE { |
| 111 // There is a race when closing the channel so the last message may be lost. | 66 // There is a race when closing the channel so the last message may be lost. |
| 112 EXPECT_LE(messages_left_, 1); | 67 EXPECT_LE(messages_left_, 1); |
| 113 MessageLoop::current()->Quit(); | 68 MessageLoop::current()->Quit(); |
| 114 } | 69 } |
| 115 | 70 |
| 116 void Init(IPC::Sender* s) { | 71 void Init(IPC::Sender* s) { |
| 117 sender_ = s; | 72 sender_ = s; |
| 118 messages_left_ = 50; | 73 } |
| 74 |
| 75 protected: |
| 76 void SendNextMessage() { |
| 77 if (--messages_left_ <= 0) |
| 78 MessageLoop::current()->Quit(); |
| 79 else |
| 80 Send(sender_, "Foo"); |
| 119 } | 81 } |
| 120 | 82 |
| 121 private: | 83 private: |
| 122 IPC::Sender* sender_; | 84 IPC::Sender* sender_; |
| 123 int messages_left_; | 85 int messages_left_; |
| 124 }; | 86 }; |
| 125 | 87 |
| 88 class IPCChannelTest : public IPCTestBase { |
| 89 }; |
| 90 |
| 91 // TODO(viettrungluu): Move to a separate IPCMessageTest. |
| 92 TEST_F(IPCChannelTest, BasicMessageTest) { |
| 93 int v1 = 10; |
| 94 std::string v2("foobar"); |
| 95 std::wstring v3(L"hello world"); |
| 96 |
| 97 IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); |
| 98 EXPECT_TRUE(m.WriteInt(v1)); |
| 99 EXPECT_TRUE(m.WriteString(v2)); |
| 100 EXPECT_TRUE(m.WriteWString(v3)); |
| 101 |
| 102 PickleIterator iter(m); |
| 103 |
| 104 int vi; |
| 105 std::string vs; |
| 106 std::wstring vw; |
| 107 |
| 108 EXPECT_TRUE(m.ReadInt(&iter, &vi)); |
| 109 EXPECT_EQ(v1, vi); |
| 110 |
| 111 EXPECT_TRUE(m.ReadString(&iter, &vs)); |
| 112 EXPECT_EQ(v2, vs); |
| 113 |
| 114 EXPECT_TRUE(m.ReadWString(&iter, &vw)); |
| 115 EXPECT_EQ(v3, vw); |
| 116 |
| 117 // should fail |
| 118 EXPECT_FALSE(m.ReadInt(&iter, &vi)); |
| 119 EXPECT_FALSE(m.ReadString(&iter, &vs)); |
| 120 EXPECT_FALSE(m.ReadWString(&iter, &vw)); |
| 121 } |
| 122 |
| 126 TEST_F(IPCChannelTest, ChannelTest) { | 123 TEST_F(IPCChannelTest, ChannelTest) { |
| 127 MyChannelListener channel_listener; | 124 Init("GenericClient"); |
| 128 // Setup IPC channel. | |
| 129 IPC::Channel chan(kTestClientChannel, IPC::Channel::MODE_SERVER, | |
| 130 &channel_listener); | |
| 131 ASSERT_TRUE(chan.Connect()); | |
| 132 | 125 |
| 133 channel_listener.Init(&chan); | 126 // Set up IPC channel and start client. |
| 127 GenericChannelListener listener; |
| 128 CreateChannel(&listener); |
| 129 listener.Init(sender()); |
| 130 ASSERT_TRUE(ConnectChannel()); |
| 131 ASSERT_TRUE(StartClient()); |
| 134 | 132 |
| 135 base::ProcessHandle process_handle = SpawnChild(TEST_CLIENT, &chan); | 133 Send(sender(), "hello from parent"); |
| 136 ASSERT_TRUE(process_handle); | |
| 137 | |
| 138 Send(&chan, "hello from parent"); | |
| 139 | 134 |
| 140 // Run message loop. | 135 // Run message loop. |
| 141 MessageLoop::current()->Run(); | 136 MessageLoop::current()->Run(); |
| 142 | 137 |
| 143 // Close Channel so client gets its OnChannelError() callback fired. | 138 // Close the channel so the client's OnChannelError() gets fired. |
| 144 chan.Close(); | 139 channel()->Close(); |
| 145 | 140 |
| 146 // Cleanup child process. | 141 EXPECT_TRUE(WaitForClientShutdown()); |
| 147 EXPECT_TRUE(base::WaitForSingleProcess( | 142 DestroyChannel(); |
| 148 process_handle, base::TimeDelta::FromSeconds(5))); | |
| 149 base::CloseProcessHandle(process_handle); | |
| 150 } | 143 } |
| 151 | 144 |
| 145 // TODO(viettrungluu): Move to a separate IPCChannelWinTest. |
| 152 #if defined(OS_WIN) | 146 #if defined(OS_WIN) |
| 153 TEST_F(IPCChannelTest, ChannelTestExistingPipe) { | 147 TEST_F(IPCChannelTest, ChannelTestExistingPipe) { |
| 154 MyChannelListener channel_listener; | 148 Init("GenericClient"); |
| 155 // Setup IPC channel with existing pipe. Specify name in Chrome format. | 149 |
| 150 // Create pipe manually using the standard Chromium name and set up IPC |
| 151 // channel. |
| 152 GenericChannelListener listener; |
| 156 std::string name("\\\\.\\pipe\\chrome."); | 153 std::string name("\\\\.\\pipe\\chrome."); |
| 157 name.append(kTestClientChannel); | 154 name.append(GetChannelName("GenericClient")); |
| 158 const DWORD open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | | |
| 159 FILE_FLAG_FIRST_PIPE_INSTANCE; | |
| 160 HANDLE pipe = CreateNamedPipeA(name.c_str(), | 155 HANDLE pipe = CreateNamedPipeA(name.c_str(), |
| 161 open_mode, | 156 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | |
| 157 FILE_FLAG_FIRST_PIPE_INSTANCE, |
| 162 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, | 158 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, |
| 163 1, | 159 1, |
| 164 4096, | 160 4096, |
| 165 4096, | 161 4096, |
| 166 5000, | 162 5000, |
| 167 NULL); | 163 NULL); |
| 168 IPC::Channel chan(IPC::ChannelHandle(pipe), IPC::Channel::MODE_SERVER, | 164 CreateChannelFromChannelHandle(IPC::ChannelHandle(pipe), &listener); |
| 169 &channel_listener); | 165 CloseHandle(pipe); // The channel duplicates the handle. |
| 170 // Channel will duplicate the handle. | 166 listener.Init(sender()); |
| 171 CloseHandle(pipe); | |
| 172 ASSERT_TRUE(chan.Connect()); | |
| 173 | 167 |
| 174 channel_listener.Init(&chan); | 168 // Connect to channel and start client. |
| 169 ASSERT_TRUE(ConnectChannel()); |
| 170 ASSERT_TRUE(StartClient()); |
| 175 | 171 |
| 176 base::ProcessHandle process_handle = SpawnChild(TEST_CLIENT, &chan); | 172 Send(sender(), "hello from parent"); |
| 177 ASSERT_TRUE(process_handle); | |
| 178 | |
| 179 Send(&chan, "hello from parent"); | |
| 180 | 173 |
| 181 // Run message loop. | 174 // Run message loop. |
| 182 MessageLoop::current()->Run(); | 175 MessageLoop::current()->Run(); |
| 183 | 176 |
| 184 // Close Channel so client gets its OnChannelError() callback fired. | 177 // Close the channel so the client's OnChannelError() gets fired. |
| 185 chan.Close(); | 178 channel()->Close(); |
| 186 | 179 |
| 187 // Cleanup child process. | 180 EXPECT_TRUE(WaitForClientShutdown()); |
| 188 EXPECT_TRUE(base::WaitForSingleProcess( | 181 DestroyChannel(); |
| 189 process_handle, base::TimeDelta::FromSeconds(5))); | |
| 190 base::CloseProcessHandle(process_handle); | |
| 191 } | 182 } |
| 192 #endif // defined (OS_WIN) | 183 #endif // defined (OS_WIN) |
| 193 | 184 |
| 194 TEST_F(IPCChannelTest, ChannelProxyTest) { | 185 TEST_F(IPCChannelTest, ChannelProxyTest) { |
| 195 MyChannelListener channel_listener; | 186 Init("GenericClient"); |
| 196 | 187 |
| 197 // The thread needs to out-live the ChannelProxy. | |
| 198 base::Thread thread("ChannelProxyTestServer"); | 188 base::Thread thread("ChannelProxyTestServer"); |
| 199 base::Thread::Options options; | 189 base::Thread::Options options; |
| 200 options.message_loop_type = MessageLoop::TYPE_IO; | 190 options.message_loop_type = MessageLoop::TYPE_IO; |
| 201 thread.StartWithOptions(options); | 191 thread.StartWithOptions(options); |
| 202 { | |
| 203 // setup IPC channel proxy | |
| 204 IPC::ChannelProxy chan(kTestClientChannel, IPC::Channel::MODE_SERVER, | |
| 205 &channel_listener, thread.message_loop_proxy()); | |
| 206 | 192 |
| 207 channel_listener.Init(&chan); | 193 // Set up IPC channel proxy. |
| 194 GenericChannelListener listener; |
| 195 CreateChannelProxy(&listener, thread.message_loop_proxy()); |
| 196 listener.Init(sender()); |
| 208 | 197 |
| 209 #if defined(OS_WIN) | 198 ASSERT_TRUE(StartClient()); |
| 210 base::ProcessHandle process_handle = SpawnChild(TEST_CLIENT, NULL); | |
| 211 #elif defined(OS_POSIX) | |
| 212 bool debug_on_start = CommandLine::ForCurrentProcess()->HasSwitch( | |
| 213 switches::kDebugChildren); | |
| 214 base::FileHandleMappingVector fds_to_map; | |
| 215 const int ipcfd = chan.GetClientFileDescriptor(); | |
| 216 if (ipcfd > -1) { | |
| 217 fds_to_map.push_back(std::pair<int, int>(ipcfd, kPrimaryIPCChannel + 3)); | |
| 218 } | |
| 219 | 199 |
| 220 base::ProcessHandle process_handle = MultiProcessTest::SpawnChild( | 200 Send(sender(), "hello from parent"); |
| 221 "RunTestClient", | |
| 222 fds_to_map, | |
| 223 debug_on_start); | |
| 224 #endif // defined(OS_POSIX) | |
| 225 | 201 |
| 226 ASSERT_TRUE(process_handle); | 202 // Run message loop. |
| 203 MessageLoop::current()->Run(); |
| 227 | 204 |
| 228 Send(&chan, "hello from parent"); | 205 EXPECT_TRUE(WaitForClientShutdown()); |
| 229 | 206 |
| 230 // run message loop | 207 // Destroy the channel proxy before shutting down the thread. |
| 231 MessageLoop::current()->Run(); | 208 DestroyChannelProxy(); |
| 232 | |
| 233 // cleanup child process | |
| 234 EXPECT_TRUE(base::WaitForSingleProcess( | |
| 235 process_handle, base::TimeDelta::FromSeconds(5))); | |
| 236 base::CloseProcessHandle(process_handle); | |
| 237 } | |
| 238 thread.Stop(); | 209 thread.Stop(); |
| 239 } | 210 } |
| 240 | 211 |
| 241 class ChannelListenerWithOnConnectedSend : public IPC::Listener { | 212 class ChannelListenerWithOnConnectedSend : public GenericChannelListener { |
| 242 public: | 213 public: |
| 214 ChannelListenerWithOnConnectedSend() {} |
| 215 virtual ~ChannelListenerWithOnConnectedSend() {} |
| 216 |
| 243 virtual void OnChannelConnected(int32 peer_pid) OVERRIDE { | 217 virtual void OnChannelConnected(int32 peer_pid) OVERRIDE { |
| 244 SendNextMessage(); | 218 SendNextMessage(); |
| 245 } | 219 } |
| 246 | |
| 247 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { | |
| 248 PickleIterator iter(message); | |
| 249 | |
| 250 int ignored; | |
| 251 EXPECT_TRUE(iter.ReadInt(&ignored)); | |
| 252 std::string data; | |
| 253 EXPECT_TRUE(iter.ReadString(&data)); | |
| 254 std::string big_string; | |
| 255 EXPECT_TRUE(iter.ReadString(&big_string)); | |
| 256 EXPECT_EQ(kLongMessageStringNumBytes - 1, big_string.length()); | |
| 257 SendNextMessage(); | |
| 258 return true; | |
| 259 } | |
| 260 | |
| 261 virtual void OnChannelError() OVERRIDE { | |
| 262 // There is a race when closing the channel so the last message may be lost. | |
| 263 EXPECT_LE(messages_left_, 1); | |
| 264 MessageLoop::current()->Quit(); | |
| 265 } | |
| 266 | |
| 267 void Init(IPC::Sender* s) { | |
| 268 sender_ = s; | |
| 269 messages_left_ = 50; | |
| 270 } | |
| 271 | |
| 272 private: | |
| 273 void SendNextMessage() { | |
| 274 if (--messages_left_ == 0) { | |
| 275 MessageLoop::current()->Quit(); | |
| 276 } else { | |
| 277 Send(sender_, "Foo"); | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 IPC::Sender* sender_; | |
| 282 int messages_left_; | |
| 283 }; | 220 }; |
| 284 | 221 |
| 285 #if defined(OS_WIN) | 222 #if defined(OS_WIN) |
| 286 // Acting flakey in Windows. http://crbug.com/129595 | 223 // Acting flakey in Windows. http://crbug.com/129595 |
| 287 #define MAYBE_SendMessageInChannelConnected DISABLED_SendMessageInChannelConnect
ed | 224 #define MAYBE_SendMessageInChannelConnected DISABLED_SendMessageInChannelConnect
ed |
| 288 #else | 225 #else |
| 289 #define MAYBE_SendMessageInChannelConnected SendMessageInChannelConnected | 226 #define MAYBE_SendMessageInChannelConnected SendMessageInChannelConnected |
| 290 #endif | 227 #endif |
| 228 // This tests the case of a listener sending back an event in its |
| 229 // OnChannelConnected handler. |
| 291 TEST_F(IPCChannelTest, MAYBE_SendMessageInChannelConnected) { | 230 TEST_F(IPCChannelTest, MAYBE_SendMessageInChannelConnected) { |
| 292 // This tests the case of a listener sending back an event in it's | 231 Init("GenericClient"); |
| 293 // OnChannelConnected handler. | |
| 294 | 232 |
| 295 ChannelListenerWithOnConnectedSend channel_listener; | 233 // Set up IPC channel and start client. |
| 296 // Setup IPC channel. | 234 ChannelListenerWithOnConnectedSend listener; |
| 297 IPC::Channel channel(kTestClientChannel, IPC::Channel::MODE_SERVER, | 235 CreateChannel(&listener); |
| 298 &channel_listener); | 236 listener.Init(sender()); |
| 299 channel_listener.Init(&channel); | 237 ASSERT_TRUE(ConnectChannel()); |
| 300 ASSERT_TRUE(channel.Connect()); | 238 ASSERT_TRUE(StartClient()); |
| 301 | 239 |
| 302 base::ProcessHandle process_handle = SpawnChild(TEST_CLIENT, &channel); | 240 Send(sender(), "hello from parent"); |
| 303 ASSERT_TRUE(process_handle); | |
| 304 | |
| 305 Send(&channel, "hello from parent"); | |
| 306 | 241 |
| 307 // Run message loop. | 242 // Run message loop. |
| 308 MessageLoop::current()->Run(); | 243 MessageLoop::current()->Run(); |
| 309 | 244 |
| 310 // Close Channel so client gets its OnChannelError() callback fired. | 245 // Close the channel so the client's OnChannelError() gets fired. |
| 311 channel.Close(); | 246 channel()->Close(); |
| 312 | 247 |
| 313 // Cleanup child process. | 248 EXPECT_TRUE(WaitForClientShutdown()); |
| 314 EXPECT_TRUE(base::WaitForSingleProcess( | 249 DestroyChannel(); |
| 315 process_handle, base::TimeDelta::FromSeconds(5))); | |
| 316 base::CloseProcessHandle(process_handle); | |
| 317 } | 250 } |
| 318 | 251 |
| 319 MULTIPROCESS_IPC_TEST_MAIN(RunTestClient) { | 252 MULTIPROCESS_IPC_TEST_CLIENT_MAIN(GenericClient) { |
| 320 MessageLoopForIO main_message_loop; | 253 MessageLoopForIO main_message_loop; |
| 321 MyChannelListener channel_listener; | 254 GenericChannelListener listener; |
| 322 | 255 |
| 323 // setup IPC channel | 256 // Set up IPC channel. |
| 324 IPC::Channel chan(kTestClientChannel, IPC::Channel::MODE_CLIENT, | 257 IPC::Channel channel(IPCTestBase::GetChannelName("GenericClient"), |
| 325 &channel_listener); | 258 IPC::Channel::MODE_CLIENT, |
| 326 CHECK(chan.Connect()); | 259 &listener); |
| 327 channel_listener.Init(&chan); | 260 CHECK(channel.Connect()); |
| 328 Send(&chan, "hello from child"); | 261 listener.Init(&channel); |
| 329 // run message loop | 262 Send(&channel, "hello from child"); |
| 263 |
| 330 MessageLoop::current()->Run(); | 264 MessageLoop::current()->Run(); |
| 331 return 0; | 265 return 0; |
| 332 } | 266 } |
| 333 | 267 |
| 334 } // namespace | 268 } // namespace |
| OLD | NEW |