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> |
7 #include <string.h> | 8 #include <string.h> |
| 9 #include <sys/types.h> |
| 10 #include <sys/socket.h> |
| 11 #include <unistd.h> |
| 12 |
| 13 #include <memory> |
| 14 #include <utility> |
8 #include <vector> | 15 #include <vector> |
9 | 16 |
10 #include "base/android/context_utils.h" | |
11 #include "base/android/jni_android.h" | |
12 #include "base/android/jni_array.h" | |
13 #include "base/android/scoped_java_ref.h" | |
14 #include "base/base_switches.h" | 17 #include "base/base_switches.h" |
15 #include "base/command_line.h" | 18 #include "base/command_line.h" |
| 19 #include "base/containers/hash_tables.h" |
| 20 #include "base/lazy_instance.h" |
16 #include "base/logging.h" | 21 #include "base/logging.h" |
17 #include "jni/MainReturnCodeResult_jni.h" | 22 #include "base/macros.h" |
18 #include "jni/MultiprocessTestClientLauncher_jni.h" | 23 #include "base/pickle.h" |
| 24 #include "base/posix/global_descriptors.h" |
| 25 #include "base/posix/unix_domain_socket_linux.h" |
| 26 #include "testing/multiprocess_func_list.h" |
19 | 27 |
20 namespace base { | 28 namespace base { |
21 | 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 is_child_; } |
| 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 bool is_child_ = false; |
| 123 |
| 124 // Parent vars. |
| 125 int child_fd_ = -1; |
| 126 |
| 127 // Helper vars. |
| 128 int parent_fd_ = -1; |
| 129 MainFunction main_ = nullptr; |
| 130 |
| 131 DISALLOW_COPY_AND_ASSIGN(LaunchHelper); |
| 132 }; |
| 133 |
| 134 void LaunchHelper::Init(MainFunction main) { |
| 135 main_ = main; |
| 136 |
| 137 // Create a communication channel between the parent and child launch helper. |
| 138 // fd[0] belongs to the parent, fd[1] belongs to the child. |
| 139 int fds[2] = {-1, -1}; |
| 140 int rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds); |
| 141 PCHECK(rv == 0); |
| 142 CHECK_NE(-1, fds[0]); |
| 143 CHECK_NE(-1, fds[1]); |
| 144 |
| 145 pid_t pid = fork(); |
| 146 PCHECK(pid >= 0) << "Fork failed"; |
| 147 if (pid) { |
| 148 // Parent. |
| 149 rv = close(fds[1]); |
| 150 PCHECK(rv == 0); |
| 151 DoParent(fds[0]); |
| 152 } else { |
| 153 // Helper. |
| 154 rv = close(fds[0]); |
| 155 PCHECK(rv == 0); |
| 156 DoHelper(fds[1]); |
| 157 NOTREACHED(); |
| 158 _exit(0); |
| 159 } |
| 160 } |
| 161 |
| 162 void LaunchHelper::Send( |
| 163 int fd, const MessageHeader* msg, const std::vector<int>& fds) { |
| 164 uint32_t bytes_remaining = msg->size; |
| 165 const char* buf = reinterpret_cast<const char*>(msg); |
| 166 while (bytes_remaining) { |
| 167 size_t send_size = |
| 168 (bytes_remaining > kFragmentSize) ? kFragmentSize : bytes_remaining; |
| 169 bool success = UnixDomainSocket::SendMsg( |
| 170 fd, buf, send_size, |
| 171 (bytes_remaining == msg->size) ? fds : std::vector<int>()); |
| 172 CHECK(success); |
| 173 bytes_remaining -= send_size; |
| 174 buf += send_size; |
| 175 } |
| 176 } |
| 177 |
| 178 ssize_t LaunchHelper::Recv(int fd, void* buf, std::vector<ScopedFD>* fds) { |
| 179 ssize_t size = UnixDomainSocket::RecvMsg(fd, buf, kFragmentSize, fds); |
| 180 if (size <= 0) |
| 181 return size; |
| 182 |
| 183 const MessageHeader* header = reinterpret_cast<const MessageHeader*>(buf); |
| 184 CHECK(header->size < kMaxMessageSize); |
| 185 uint32_t bytes_remaining = header->size - size; |
| 186 char* buffer = reinterpret_cast<char*>(buf); |
| 187 buffer += size; |
| 188 while (bytes_remaining) { |
| 189 std::vector<ScopedFD> dummy_fds; |
| 190 size = UnixDomainSocket::RecvMsg(fd, buffer, kFragmentSize, &dummy_fds); |
| 191 if (size <= 0) |
| 192 return size; |
| 193 |
| 194 CHECK(dummy_fds.empty()); |
| 195 CHECK(size == kFragmentSize || |
| 196 static_cast<size_t>(size) == bytes_remaining); |
| 197 bytes_remaining -= size; |
| 198 buffer += size; |
| 199 } |
| 200 return header->size; |
| 201 } |
| 202 |
| 203 void LaunchHelper::DoParent(int fd) { |
| 204 child_fd_ = fd; |
| 205 } |
| 206 |
| 207 void LaunchHelper::DoHelper(int fd) { |
| 208 parent_fd_ = fd; |
| 209 is_child_ = true; |
| 210 std::unique_ptr<char[]> buf(new char[kMaxMessageSize]); |
| 211 while (true) { |
| 212 // Wait for a message from the parent. |
| 213 std::vector<ScopedFD> fds; |
| 214 ssize_t size = Recv(parent_fd_, buf.get(), &fds); |
| 215 if (size == 0 || (size < 0 && errno == ECONNRESET)) { |
| 216 _exit(0); |
| 217 } |
| 218 PCHECK(size > 0); |
| 219 |
| 220 const MessageHeader* header = |
| 221 reinterpret_cast<const MessageHeader*>(buf.get()); |
| 222 CHECK_EQ(static_cast<ssize_t>(header->size), size); |
| 223 switch (header->type) { |
| 224 case MessageType::START_REQUEST: |
| 225 StartProcessInHelper( |
| 226 reinterpret_cast<const StartProcessRequest*>(buf.get()), |
| 227 std::move(fds)); |
| 228 break; |
| 229 case MessageType::WAIT_REQUEST: |
| 230 WaitForChildInHelper( |
| 231 reinterpret_cast<const WaitProcessRequest*>(buf.get())); |
| 232 break; |
| 233 default: |
| 234 LOG(FATAL) << "Unsupported message type: " |
| 235 << static_cast<uint32_t>(header->type); |
| 236 } |
| 237 } |
| 238 } |
| 239 |
| 240 void LaunchHelper::StartProcessInHelper(const StartProcessRequest* request, |
| 241 std::vector<ScopedFD> fds) { |
| 242 pid_t pid = fork(); |
| 243 PCHECK(pid >= 0) << "Fork failed"; |
| 244 if (pid) { |
| 245 // Helper. |
| 246 StartProcessResponse resp; |
| 247 resp.child_pid = pid; |
| 248 Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp), |
| 249 std::vector<int>()); |
| 250 } else { |
| 251 // Child. |
| 252 PCHECK(close(parent_fd_) == 0); |
| 253 parent_fd_ = -1; |
| 254 CommandLine::Reset(); |
| 255 |
| 256 Pickle serialised_extra(reinterpret_cast<const char*>(request + 1), |
| 257 request->header.size - sizeof(StartProcessRequest)); |
| 258 PickleIterator iter(serialised_extra); |
| 259 std::vector<std::string> args; |
| 260 for (size_t i = 0; i < request->num_args; i++) { |
| 261 std::string arg; |
| 262 CHECK(iter.ReadString(&arg)); |
| 263 args.push_back(std::move(arg)); |
| 264 } |
| 265 |
| 266 CHECK_EQ(request->num_fds, fds.size()); |
| 267 for (size_t i = 0; i < request->num_fds; i++) { |
| 268 int new_fd; |
| 269 CHECK(iter.ReadInt(&new_fd)); |
| 270 int old_fd = fds[i].release(); |
| 271 if (new_fd != old_fd) { |
| 272 if (dup2(old_fd, new_fd) < 0) { |
| 273 PLOG(FATAL) << "dup2"; |
| 274 } |
| 275 PCHECK(close(old_fd) == 0); |
| 276 } |
| 277 } |
| 278 |
| 279 // argv has argc+1 elements, where the last element is NULL. |
| 280 std::unique_ptr<char*[]> argv(new char*[args.size() + 1]); |
| 281 for (size_t i = 0; i < args.size(); i++) { |
| 282 argv[i] = const_cast<char*>(args[i].c_str()); |
| 283 } |
| 284 argv[args.size()] = nullptr; |
| 285 _exit(main_(args.size(), argv.get())); |
| 286 NOTREACHED(); |
| 287 } |
| 288 } |
| 289 |
| 290 void LaunchHelper::WaitForChildInHelper(const WaitProcessRequest* request) { |
| 291 Process process(request->pid); |
| 292 TimeDelta timeout = TimeDelta::FromMilliseconds(request->timeout_ms); |
| 293 int exit_code = -1; |
| 294 bool success = process.WaitForExitWithTimeout(timeout, &exit_code); |
| 295 |
| 296 WaitProcessResponse resp; |
| 297 resp.exit_code = exit_code; |
| 298 resp.success = success; |
| 299 Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp), |
| 300 std::vector<int>()); |
| 301 } |
| 302 |
| 303 Process LaunchHelper::StartChildTestHelper(const std::string& procname, |
| 304 const CommandLine& base_command_line, |
| 305 const LaunchOptions& options) { |
| 306 |
| 307 CommandLine command_line(base_command_line); |
| 308 if (!command_line.HasSwitch(switches::kTestChildProcess)) |
| 309 command_line.AppendSwitchASCII(switches::kTestChildProcess, procname); |
| 310 |
| 311 StartProcessRequest request; |
| 312 Pickle serialised_extra; |
| 313 const CommandLine::StringVector& argv = command_line.argv(); |
| 314 for (const auto& arg : argv) |
| 315 CHECK(serialised_extra.WriteString(arg)); |
| 316 request.num_args = argv.size(); |
| 317 |
| 318 std::vector<int> fds_to_send; |
| 319 if (options.fds_to_remap) { |
| 320 for (auto p : *options.fds_to_remap) { |
| 321 CHECK(serialised_extra.WriteInt(p.second)); |
| 322 fds_to_send.push_back(p.first); |
| 323 } |
| 324 request.num_fds = options.fds_to_remap->size(); |
| 325 } |
| 326 |
| 327 size_t buf_size = sizeof(StartProcessRequest) + serialised_extra.size(); |
| 328 request.header.size = buf_size; |
| 329 std::unique_ptr<char[]> buffer(new char[buf_size]); |
| 330 memcpy(buffer.get(), &request, sizeof(StartProcessRequest)); |
| 331 memcpy(buffer.get() + sizeof(StartProcessRequest), serialised_extra.data(), |
| 332 serialised_extra.size()); |
| 333 |
| 334 // Send start message. |
| 335 Send(child_fd_, reinterpret_cast<const MessageHeader*>(buffer.get()), |
| 336 fds_to_send); |
| 337 |
| 338 // Synchronously get response. |
| 339 StartProcessResponse response; |
| 340 std::vector<ScopedFD> recv_fds; |
| 341 ssize_t resp_size = Recv(child_fd_, &response, &recv_fds); |
| 342 PCHECK(resp_size == sizeof(StartProcessResponse)); |
| 343 |
| 344 return Process(response.child_pid); |
| 345 } |
| 346 |
| 347 bool LaunchHelper::WaitForChildExitWithTimeout( |
| 348 const Process& process, TimeDelta timeout, int* exit_code) { |
| 349 |
| 350 WaitProcessRequest request; |
| 351 request.pid = process.Handle(); |
| 352 request.timeout_ms = timeout.InMilliseconds(); |
| 353 |
| 354 Send(child_fd_, reinterpret_cast<const MessageHeader*>(&request), |
| 355 std::vector<int>()); |
| 356 |
| 357 WaitProcessResponse response; |
| 358 std::vector<ScopedFD> recv_fds; |
| 359 ssize_t resp_size = Recv(child_fd_, &response, &recv_fds); |
| 360 PCHECK(resp_size == sizeof(WaitProcessResponse)); |
| 361 |
| 362 if (!response.success) |
| 363 return false; |
| 364 |
| 365 *exit_code = response.exit_code; |
| 366 return true; |
| 367 } |
| 368 |
| 369 LazyInstance<LaunchHelper>::Leaky g_launch_helper; |
| 370 |
| 371 } // namespace |
| 372 |
| 373 void InitAndroidMultiProcessTestHelper(int (*main)(int, char**)) { |
| 374 DCHECK(main); |
| 375 // Don't allow child processes to themselves create new child processes. |
| 376 if (g_launch_helper.Get().IsChild()) |
| 377 return; |
| 378 g_launch_helper.Get().Init(main); |
| 379 } |
| 380 |
| 381 bool AndroidIsChildProcess() { |
| 382 return g_launch_helper.Get().IsChild(); |
| 383 } |
| 384 |
| 385 bool AndroidWaitForChildExitWithTimeout( |
| 386 const Process& process, TimeDelta timeout, int* exit_code) { |
| 387 CHECK(g_launch_helper.Get().IsReady()); |
| 388 return g_launch_helper.Get().WaitForChildExitWithTimeout( |
| 389 process, timeout, exit_code); |
| 390 } |
| 391 |
22 // A very basic implementation for Android. On Android tests can run in an APK | 392 // A very basic implementation for Android. On Android tests can run in an APK |
23 // and we don't have an executable to exec*. This implementation does the bare | 393 // and we don't have an executable to exec*. This implementation does the bare |
24 // minimum to execute the method specified by procname (in the child process). | 394 // minimum to execute the method specified by procname (in the child process). |
25 // - All options except |fds_to_remap| are ignored. | 395 // - All options except |fds_to_remap| are ignored. |
26 // | |
27 // NOTE: This MUST NOT run on the main thread of the NativeTest application. | |
28 Process SpawnMultiProcessTestChild(const std::string& procname, | 396 Process SpawnMultiProcessTestChild(const std::string& procname, |
29 const CommandLine& base_command_line, | 397 const CommandLine& base_command_line, |
30 const LaunchOptions& options) { | 398 const LaunchOptions& options) { |
31 JNIEnv* env = android::AttachCurrentThread(); | 399 if (g_launch_helper.Get().IsReady()) { |
32 DCHECK(env); | 400 return g_launch_helper.Get().StartChildTestHelper( |
33 | 401 procname, base_command_line, options); |
34 std::vector<int> fd_keys; | 402 } |
35 std::vector<int> fd_fds; | 403 |
36 if (options.fds_to_remap) { | 404 // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of |
37 for (auto& iter : *options.fds_to_remap) { | 405 // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576 |
38 fd_keys.push_back(iter.second); | 406 FileHandleMappingVector empty; |
39 fd_fds.push_back(iter.first); | 407 const FileHandleMappingVector* fds_to_remap = |
40 } | 408 options.fds_to_remap ? options.fds_to_remap : ∅ |
41 } | 409 |
42 | 410 pid_t pid = fork(); |
43 android::ScopedJavaLocalRef<jobjectArray> fds = | 411 |
44 android::Java_MultiprocessTestClientLauncher_makeFdInfoArray( | 412 if (pid < 0) { |
45 env, base::android::ToJavaIntArray(env, fd_keys), | 413 PLOG(ERROR) << "fork"; |
46 base::android::ToJavaIntArray(env, fd_fds)); | 414 return Process(); |
47 | 415 } |
48 CommandLine command_line(base_command_line); | 416 if (pid > 0) { |
49 if (!command_line.HasSwitch(switches::kTestChildProcess)) { | 417 // Parent process. |
50 command_line.AppendSwitchASCII(switches::kTestChildProcess, procname); | 418 return Process(pid); |
51 } | 419 } |
52 | 420 // Child process. |
53 android::ScopedJavaLocalRef<jobjectArray> j_argv = | 421 base::hash_set<int> fds_to_keep_open; |
54 android::ToJavaArrayOfStrings(env, command_line.argv()); | 422 for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin(); |
55 jint pid = android::Java_MultiprocessTestClientLauncher_launchClient( | 423 it != fds_to_remap->end(); ++it) { |
56 env, android::GetApplicationContext(), j_argv, fds); | 424 fds_to_keep_open.insert(it->first); |
57 return Process(pid); | 425 } |
58 } | 426 // Keep standard FDs (stdin, stdout, stderr, etc.) open since this |
59 | 427 // is not meant to spawn a daemon. |
60 bool WaitForMultiprocessTestChildExit(const Process& process, | 428 int base = GlobalDescriptors::kBaseDescriptor; |
61 TimeDelta timeout, | 429 for (int fd = base; fd < sysconf(_SC_OPEN_MAX); ++fd) { |
62 int* exit_code) { | 430 if (fds_to_keep_open.find(fd) == fds_to_keep_open.end()) { |
63 JNIEnv* env = android::AttachCurrentThread(); | 431 close(fd); |
64 DCHECK(env); | 432 } |
65 | 433 } |
66 base::android::ScopedJavaLocalRef<jobject> result_code = | 434 for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin(); |
67 android::Java_MultiprocessTestClientLauncher_waitForMainToReturn( | 435 it != fds_to_remap->end(); ++it) { |
68 env, android::GetApplicationContext(), process.Pid(), | 436 int old_fd = it->first; |
69 static_cast<int32_t>(timeout.InMilliseconds())); | 437 int new_fd = it->second; |
70 if (result_code.is_null() || | 438 if (dup2(old_fd, new_fd) < 0) { |
71 Java_MainReturnCodeResult_hasTimedOut(env, result_code)) { | 439 PLOG(FATAL) << "dup2"; |
72 return false; | 440 } |
73 } | 441 close(old_fd); |
74 if (exit_code) { | 442 } |
75 *exit_code = Java_MainReturnCodeResult_getReturnCode(env, result_code); | 443 CommandLine::Reset(); |
76 } | 444 CommandLine::Init(0, nullptr); |
77 return true; | 445 CommandLine* command_line = CommandLine::ForCurrentProcess(); |
78 } | 446 command_line->InitFromArgv(base_command_line.argv()); |
79 | 447 if (!command_line->HasSwitch(switches::kTestChildProcess)) |
80 bool TerminateMultiProcessTestChild(const Process& process, | 448 command_line->AppendSwitchASCII(switches::kTestChildProcess, procname); |
81 int exit_code, | 449 |
82 bool wait) { | 450 _exit(multi_process_function_list::InvokeChildProcessTest(procname)); |
83 JNIEnv* env = android::AttachCurrentThread(); | 451 return Process(); |
84 DCHECK(env); | |
85 | |
86 return android::Java_MultiprocessTestClientLauncher_terminate( | |
87 env, android::GetApplicationContext(), process.Pid(), exit_code, wait); | |
88 } | 452 } |
89 | 453 |
90 } // namespace base | 454 } // namespace base |
OLD | NEW |