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 "base/test/multiprocess_test.h" | 5 #include "base/test/multiprocess_test.h" |
6 | 6 |
| 7 #include <errno.h> |
| 8 #include <string.h> |
| 9 #include <sys/types.h> |
| 10 #include <sys/socket.h> |
7 #include <unistd.h> | 11 #include <unistd.h> |
8 | 12 |
| 13 #include <memory> |
| 14 #include <utility> |
| 15 #include <vector> |
| 16 |
9 #include "base/base_switches.h" | 17 #include "base/base_switches.h" |
10 #include "base/command_line.h" | 18 #include "base/command_line.h" |
11 #include "base/containers/hash_tables.h" | 19 #include "base/containers/hash_tables.h" |
| 20 #include "base/lazy_instance.h" |
12 #include "base/logging.h" | 21 #include "base/logging.h" |
| 22 #include "base/macros.h" |
| 23 #include "base/pickle.h" |
13 #include "base/posix/global_descriptors.h" | 24 #include "base/posix/global_descriptors.h" |
| 25 #include "base/posix/unix_domain_socket_linux.h" |
14 #include "testing/multiprocess_func_list.h" | 26 #include "testing/multiprocess_func_list.h" |
15 | 27 |
16 namespace base { | 28 namespace base { |
17 | 29 |
| 30 namespace { |
| 31 |
| 32 const int kMaxMessageSize = 1024 * 1024; |
| 33 const int kFragmentSize = 4096; |
| 34 |
| 35 // Message sent between parent process and helper child process. |
| 36 enum class MessageType : uint32_t { |
| 37 START_REQUEST, |
| 38 START_RESPONSE, |
| 39 WAIT_REQUEST, |
| 40 WAIT_RESPONSE, |
| 41 }; |
| 42 |
| 43 struct MessageHeader { |
| 44 uint32_t size; |
| 45 MessageType type; |
| 46 }; |
| 47 |
| 48 struct StartProcessRequest { |
| 49 MessageHeader header = |
| 50 {sizeof(StartProcessRequest), MessageType::START_REQUEST}; |
| 51 |
| 52 uint32_t num_args = 0; |
| 53 uint32_t num_fds = 0; |
| 54 }; |
| 55 |
| 56 struct StartProcessResponse { |
| 57 MessageHeader header = |
| 58 {sizeof(StartProcessResponse), MessageType::START_RESPONSE}; |
| 59 |
| 60 pid_t child_pid; |
| 61 }; |
| 62 |
| 63 struct WaitProcessRequest { |
| 64 MessageHeader header = |
| 65 {sizeof(WaitProcessRequest), MessageType::WAIT_REQUEST}; |
| 66 |
| 67 pid_t pid; |
| 68 uint64_t timeout_ms; |
| 69 }; |
| 70 |
| 71 struct WaitProcessResponse { |
| 72 MessageHeader header = |
| 73 {sizeof(WaitProcessResponse), MessageType::WAIT_RESPONSE}; |
| 74 |
| 75 bool success = false; |
| 76 int32_t exit_code = 0; |
| 77 }; |
| 78 |
| 79 // Helper class that implements an alternate test child launcher for |
| 80 // multi-process tests. The default implementation doesn't work if the child is |
| 81 // launched after starting threads. However, for some tests (i.e. Mojo), this |
| 82 // is necessary. This implementation works around that issue by forking a helper |
| 83 // process very early in main(), before any real work is done. Then, when a |
| 84 // child needs to be spawned, a message is sent to that helper process, which |
| 85 // then forks and returns the result to the parent. The forked child then calls |
| 86 // main() and things look as though a brand new process has been fork/exec'd. |
| 87 class LaunchHelper { |
| 88 public: |
| 89 using MainFunction = int (*)(int, char**); |
| 90 |
| 91 LaunchHelper() {} |
| 92 |
| 93 // Initialise the alternate test child implementation. |
| 94 void Init(MainFunction main); |
| 95 |
| 96 // Starts a child test helper process. |
| 97 Process StartChildTestHelper(const std::string& procname, |
| 98 const CommandLine& base_command_line, |
| 99 const LaunchOptions& options); |
| 100 |
| 101 // Waits for a child test helper process. |
| 102 bool WaitForChildExitWithTimeout(const Process& process, TimeDelta timeout, |
| 103 int* exit_code); |
| 104 |
| 105 bool IsReady() const { return child_fd_ != -1; } |
| 106 bool IsChild() const { return parent_fd_ != -1; } |
| 107 |
| 108 private: |
| 109 // Wrappers around sendmsg/recvmsg that supports message fragmentation. |
| 110 void Send(int fd, const MessageHeader* msg, const std::vector<int>& fds); |
| 111 ssize_t Recv(int fd, void* buf, std::vector<ScopedFD>* fds); |
| 112 |
| 113 // Parent process implementation. |
| 114 void DoParent(int fd); |
| 115 // Helper process implementation. |
| 116 void DoHelper(int fd); |
| 117 |
| 118 void StartProcessInHelper(const StartProcessRequest* request, |
| 119 std::vector<ScopedFD> fds); |
| 120 void WaitForChildInHelper(const WaitProcessRequest* request); |
| 121 |
| 122 // Parent vars. |
| 123 int child_fd_ = -1; |
| 124 |
| 125 // Helper vars. |
| 126 int parent_fd_ = -1; |
| 127 MainFunction main_ = nullptr; |
| 128 |
| 129 DISALLOW_COPY_AND_ASSIGN(LaunchHelper); |
| 130 }; |
| 131 |
| 132 void LaunchHelper::Init(MainFunction main) { |
| 133 main_ = main; |
| 134 |
| 135 // Create a communication channel between the parent and child launch helper. |
| 136 // fd[0] belongs to the parent, fd[1] belongs to the child. |
| 137 int fds[2] = {-1, -1}; |
| 138 int rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds); |
| 139 PCHECK(rv == 0); |
| 140 CHECK_NE(-1, fds[0]); |
| 141 CHECK_NE(-1, fds[1]); |
| 142 |
| 143 pid_t pid = fork(); |
| 144 PCHECK(pid >= 0) << "Fork failed"; |
| 145 if (pid) { |
| 146 // Parent. |
| 147 rv = close(fds[1]); |
| 148 PCHECK(rv == 0); |
| 149 DoParent(fds[0]); |
| 150 } else { |
| 151 // Helper. |
| 152 rv = close(fds[0]); |
| 153 PCHECK(rv == 0); |
| 154 DoHelper(fds[1]); |
| 155 NOTREACHED(); |
| 156 _exit(0); |
| 157 } |
| 158 } |
| 159 |
| 160 void LaunchHelper::Send( |
| 161 int fd, const MessageHeader* msg, const std::vector<int>& fds) { |
| 162 uint32_t bytes_remaining = msg->size; |
| 163 const char* buf = reinterpret_cast<const char*>(msg); |
| 164 while (bytes_remaining) { |
| 165 size_t send_size = |
| 166 (bytes_remaining > kFragmentSize) ? kFragmentSize : bytes_remaining; |
| 167 bool success = UnixDomainSocket::SendMsg( |
| 168 fd, buf, send_size, |
| 169 (bytes_remaining == msg->size) ? fds : std::vector<int>()); |
| 170 CHECK(success); |
| 171 bytes_remaining -= send_size; |
| 172 buf += send_size; |
| 173 } |
| 174 } |
| 175 |
| 176 ssize_t LaunchHelper::Recv(int fd, void* buf, std::vector<ScopedFD>* fds) { |
| 177 ssize_t size = UnixDomainSocket::RecvMsg(fd, buf, kFragmentSize, fds); |
| 178 if (size <= 0) |
| 179 return size; |
| 180 |
| 181 const MessageHeader* header = reinterpret_cast<const MessageHeader*>(buf); |
| 182 CHECK(header->size < kMaxMessageSize); |
| 183 uint32_t bytes_remaining = header->size - size; |
| 184 char* buffer = reinterpret_cast<char*>(buf); |
| 185 buffer += size; |
| 186 while (bytes_remaining) { |
| 187 std::vector<ScopedFD> dummy_fds; |
| 188 size = UnixDomainSocket::RecvMsg(fd, buffer, kFragmentSize, &dummy_fds); |
| 189 if (size <= 0) |
| 190 return size; |
| 191 |
| 192 CHECK(dummy_fds.empty()); |
| 193 CHECK(size == kFragmentSize || |
| 194 static_cast<size_t>(size) == bytes_remaining); |
| 195 bytes_remaining -= size; |
| 196 buffer += size; |
| 197 } |
| 198 return header->size; |
| 199 } |
| 200 |
| 201 void LaunchHelper::DoParent(int fd) { |
| 202 child_fd_ = fd; |
| 203 } |
| 204 |
| 205 void LaunchHelper::DoHelper(int fd) { |
| 206 parent_fd_ = fd; |
| 207 std::unique_ptr<char[]> buf(new char[kMaxMessageSize]); |
| 208 while (true) { |
| 209 // Wait for a message from the parent. |
| 210 std::vector<ScopedFD> fds; |
| 211 ssize_t size = Recv(parent_fd_, buf.get(), &fds); |
| 212 if (size == 0 || (size < 0 && errno == ECONNRESET)) { |
| 213 _exit(0); |
| 214 } |
| 215 PCHECK(size > 0); |
| 216 |
| 217 const MessageHeader* header = |
| 218 reinterpret_cast<const MessageHeader*>(buf.get()); |
| 219 CHECK_EQ(static_cast<ssize_t>(header->size), size); |
| 220 switch (header->type) { |
| 221 case MessageType::START_REQUEST: |
| 222 StartProcessInHelper( |
| 223 reinterpret_cast<const StartProcessRequest*>(buf.get()), |
| 224 std::move(fds)); |
| 225 break; |
| 226 case MessageType::WAIT_REQUEST: |
| 227 WaitForChildInHelper( |
| 228 reinterpret_cast<const WaitProcessRequest*>(buf.get())); |
| 229 break; |
| 230 default: |
| 231 LOG(FATAL) << "Unsupported message type: " |
| 232 << static_cast<uint32_t>(header->type); |
| 233 } |
| 234 } |
| 235 } |
| 236 |
| 237 void LaunchHelper::StartProcessInHelper(const StartProcessRequest* request, |
| 238 std::vector<ScopedFD> fds) { |
| 239 pid_t pid = fork(); |
| 240 PCHECK(pid >= 0) << "Fork failed"; |
| 241 if (pid) { |
| 242 // Helper. |
| 243 StartProcessResponse resp; |
| 244 resp.child_pid = pid; |
| 245 Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp), |
| 246 std::vector<int>()); |
| 247 } else { |
| 248 // Child. |
| 249 PCHECK(close(parent_fd_) == 0); |
| 250 CommandLine::Reset(); |
| 251 |
| 252 Pickle serialised_extra(reinterpret_cast<const char*>(request + 1), |
| 253 request->header.size - sizeof(StartProcessRequest)); |
| 254 PickleIterator iter(serialised_extra); |
| 255 std::vector<std::string> args; |
| 256 for (size_t i = 0; i < request->num_args; i++) { |
| 257 std::string arg; |
| 258 CHECK(iter.ReadString(&arg)); |
| 259 args.push_back(std::move(arg)); |
| 260 } |
| 261 |
| 262 CHECK_EQ(request->num_fds, fds.size()); |
| 263 for (size_t i = 0; i < request->num_fds; i++) { |
| 264 int new_fd; |
| 265 CHECK(iter.ReadInt(&new_fd)); |
| 266 int old_fd = fds[i].release(); |
| 267 if (dup2(old_fd, new_fd) < 0) { |
| 268 PLOG(FATAL) << "dup2"; |
| 269 } |
| 270 PCHECK(close(old_fd) == 0); |
| 271 } |
| 272 |
| 273 std::unique_ptr<char*[]> argv(new char*[args.size()]); |
| 274 for (size_t i = 0; i < args.size(); i++) { |
| 275 argv[i] = const_cast<char*>(args[i].c_str()); |
| 276 } |
| 277 _exit(main_(args.size(), argv.get())); |
| 278 NOTREACHED(); |
| 279 } |
| 280 } |
| 281 |
| 282 void LaunchHelper::WaitForChildInHelper(const WaitProcessRequest* request) { |
| 283 Process process(request->pid); |
| 284 TimeDelta timeout = TimeDelta::FromMilliseconds(request->timeout_ms); |
| 285 int exit_code = -1; |
| 286 bool success = process.WaitForExitWithTimeout(timeout, &exit_code); |
| 287 |
| 288 WaitProcessResponse resp; |
| 289 resp.exit_code = exit_code; |
| 290 resp.success = success; |
| 291 Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp), |
| 292 std::vector<int>()); |
| 293 } |
| 294 |
| 295 Process LaunchHelper::StartChildTestHelper(const std::string& procname, |
| 296 const CommandLine& base_command_line, |
| 297 const LaunchOptions& options) { |
| 298 |
| 299 CommandLine command_line(base_command_line); |
| 300 if (!command_line.HasSwitch(switches::kTestChildProcess)) |
| 301 command_line.AppendSwitchASCII(switches::kTestChildProcess, procname); |
| 302 |
| 303 StartProcessRequest request; |
| 304 Pickle serialised_extra; |
| 305 const CommandLine::StringVector& argv = command_line.argv(); |
| 306 for (const auto& arg : argv) |
| 307 CHECK(serialised_extra.WriteString(arg)); |
| 308 request.num_args = argv.size(); |
| 309 |
| 310 std::vector<int> fds_to_send; |
| 311 if (options.fds_to_remap) { |
| 312 for (auto p : *options.fds_to_remap) { |
| 313 CHECK(serialised_extra.WriteInt(p.second)); |
| 314 fds_to_send.push_back(p.first); |
| 315 } |
| 316 request.num_fds = options.fds_to_remap->size(); |
| 317 } |
| 318 |
| 319 size_t buf_size = sizeof(StartProcessRequest) + serialised_extra.size(); |
| 320 request.header.size = buf_size; |
| 321 std::unique_ptr<char[]> buffer(new char[buf_size]); |
| 322 memcpy(buffer.get(), &request, sizeof(StartProcessRequest)); |
| 323 memcpy(buffer.get() + sizeof(StartProcessRequest), serialised_extra.data(), |
| 324 serialised_extra.size()); |
| 325 |
| 326 // Send start message. |
| 327 Send(child_fd_, reinterpret_cast<const MessageHeader*>(buffer.get()), |
| 328 fds_to_send); |
| 329 |
| 330 // Synchronously get response. |
| 331 StartProcessResponse response; |
| 332 std::vector<ScopedFD> recv_fds; |
| 333 ssize_t resp_size = Recv(child_fd_, &response, &recv_fds); |
| 334 PCHECK(resp_size == sizeof(StartProcessResponse)); |
| 335 |
| 336 return Process(response.child_pid); |
| 337 } |
| 338 |
| 339 bool LaunchHelper::WaitForChildExitWithTimeout( |
| 340 const Process& process, TimeDelta timeout, int* exit_code) { |
| 341 |
| 342 WaitProcessRequest request; |
| 343 request.pid = process.Handle(); |
| 344 request.timeout_ms = timeout.InMilliseconds(); |
| 345 |
| 346 Send(child_fd_, reinterpret_cast<const MessageHeader*>(&request), |
| 347 std::vector<int>()); |
| 348 |
| 349 WaitProcessResponse response; |
| 350 std::vector<ScopedFD> recv_fds; |
| 351 ssize_t resp_size = Recv(child_fd_, &response, &recv_fds); |
| 352 PCHECK(resp_size == sizeof(WaitProcessResponse)); |
| 353 |
| 354 if (!response.success) |
| 355 return false; |
| 356 |
| 357 *exit_code = response.exit_code; |
| 358 return true; |
| 359 } |
| 360 |
| 361 LazyInstance<LaunchHelper>::Leaky g_launch_helper; |
| 362 |
| 363 } // namespace |
| 364 |
| 365 void InitAndroidMultiProcessTestHelper(int (*main)(int, char**)) { |
| 366 DCHECK(main); |
| 367 // Don't allow child processes to themselves create new child processes. |
| 368 if (g_launch_helper.Get().IsChild()) |
| 369 return; |
| 370 g_launch_helper.Get().Init(main); |
| 371 } |
| 372 |
| 373 bool AndroidWaitForChildExitWithTimeout( |
| 374 const Process& process, TimeDelta timeout, int* exit_code) { |
| 375 CHECK(g_launch_helper.Get().IsReady()); |
| 376 return g_launch_helper.Get().WaitForChildExitWithTimeout( |
| 377 process, timeout, exit_code); |
| 378 } |
| 379 |
18 // A very basic implementation for Android. On Android tests can run in an APK | 380 // A very basic implementation for Android. On Android tests can run in an APK |
19 // and we don't have an executable to exec*. This implementation does the bare | 381 // and we don't have an executable to exec*. This implementation does the bare |
20 // minimum to execute the method specified by procname (in the child process). | 382 // minimum to execute the method specified by procname (in the child process). |
21 // - All options except |fds_to_remap| are ignored. | 383 // - All options except |fds_to_remap| are ignored. |
22 Process SpawnMultiProcessTestChild(const std::string& procname, | 384 Process SpawnMultiProcessTestChild(const std::string& procname, |
23 const CommandLine& base_command_line, | 385 const CommandLine& base_command_line, |
24 const LaunchOptions& options) { | 386 const LaunchOptions& options) { |
| 387 if (g_launch_helper.Get().IsReady()) { |
| 388 return g_launch_helper.Get().StartChildTestHelper( |
| 389 procname, base_command_line, options); |
| 390 } |
| 391 |
25 // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of | 392 // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of |
26 // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576 | 393 // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576 |
27 FileHandleMappingVector empty; | 394 FileHandleMappingVector empty; |
28 const FileHandleMappingVector* fds_to_remap = | 395 const FileHandleMappingVector* fds_to_remap = |
29 options.fds_to_remap ? options.fds_to_remap : ∅ | 396 options.fds_to_remap ? options.fds_to_remap : ∅ |
30 | 397 |
31 pid_t pid = fork(); | 398 pid_t pid = fork(); |
32 | 399 |
33 if (pid < 0) { | 400 if (pid < 0) { |
34 PLOG(ERROR) << "fork"; | 401 PLOG(ERROR) << "fork"; |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
66 CommandLine* command_line = CommandLine::ForCurrentProcess(); | 433 CommandLine* command_line = CommandLine::ForCurrentProcess(); |
67 command_line->InitFromArgv(base_command_line.argv()); | 434 command_line->InitFromArgv(base_command_line.argv()); |
68 if (!command_line->HasSwitch(switches::kTestChildProcess)) | 435 if (!command_line->HasSwitch(switches::kTestChildProcess)) |
69 command_line->AppendSwitchASCII(switches::kTestChildProcess, procname); | 436 command_line->AppendSwitchASCII(switches::kTestChildProcess, procname); |
70 | 437 |
71 _exit(multi_process_function_list::InvokeChildProcessTest(procname)); | 438 _exit(multi_process_function_list::InvokeChildProcessTest(procname)); |
72 return Process(); | 439 return Process(); |
73 } | 440 } |
74 | 441 |
75 } // namespace base | 442 } // namespace base |
OLD | NEW |