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> | 7 #include <string.h> |
9 #include <sys/types.h> | |
10 #include <sys/socket.h> | |
11 #include <unistd.h> | |
12 | |
13 #include <memory> | |
14 #include <utility> | |
15 #include <vector> | 8 #include <vector> |
16 | 9 |
| 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" |
17 #include "base/base_switches.h" | 14 #include "base/base_switches.h" |
18 #include "base/command_line.h" | 15 #include "base/command_line.h" |
19 #include "base/containers/hash_tables.h" | |
20 #include "base/lazy_instance.h" | |
21 #include "base/logging.h" | 16 #include "base/logging.h" |
22 #include "base/macros.h" | 17 #include "jni/MainReturnCodeResult_jni.h" |
23 #include "base/pickle.h" | 18 #include "jni/MultiprocessTestClientLauncher_jni.h" |
24 #include "base/posix/global_descriptors.h" | |
25 #include "base/posix/unix_domain_socket_linux.h" | |
26 #include "testing/multiprocess_func_list.h" | |
27 | 19 |
28 namespace base { | 20 namespace base { |
29 | 21 |
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 | |
392 // A very basic implementation for Android. On Android tests can run in an APK | 22 // A very basic implementation for Android. On Android tests can run in an APK |
393 // and we don't have an executable to exec*. This implementation does the bare | 23 // and we don't have an executable to exec*. This implementation does the bare |
394 // minimum to execute the method specified by procname (in the child process). | 24 // minimum to execute the method specified by procname (in the child process). |
395 // - All options except |fds_to_remap| are ignored. | 25 // - All options except |fds_to_remap| are ignored. |
| 26 // |
| 27 // NOTE: This MUST NOT run on the main thread of the NativeTest application. |
396 Process SpawnMultiProcessTestChild(const std::string& procname, | 28 Process SpawnMultiProcessTestChild(const std::string& procname, |
397 const CommandLine& base_command_line, | 29 const CommandLine& base_command_line, |
398 const LaunchOptions& options) { | 30 const LaunchOptions& options) { |
399 if (g_launch_helper.Get().IsReady()) { | 31 JNIEnv* env = android::AttachCurrentThread(); |
400 return g_launch_helper.Get().StartChildTestHelper( | 32 DCHECK(env); |
401 procname, base_command_line, options); | 33 |
| 34 std::vector<int> fd_ids; |
| 35 std::vector<int> fd_fds; |
| 36 if (options.fds_to_remap) { |
| 37 for (auto& iter : *options.fds_to_remap) { |
| 38 fd_ids.push_back(iter.second); |
| 39 fd_fds.push_back(iter.first); |
| 40 } |
402 } | 41 } |
403 | 42 |
404 // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of | 43 android::ScopedJavaLocalRef<jobjectArray> fds = |
405 // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576 | 44 android::Java_MultiprocessTestClientLauncher_makeFdInfoArray( |
406 FileHandleMappingVector empty; | 45 env, base::android::ToJavaIntArray(env, fd_ids), |
407 const FileHandleMappingVector* fds_to_remap = | 46 base::android::ToJavaIntArray(env, fd_fds)); |
408 options.fds_to_remap ? options.fds_to_remap : ∅ | |
409 | 47 |
410 pid_t pid = fork(); | 48 CommandLine command_line(base_command_line); |
| 49 if (!command_line.HasSwitch(switches::kTestChildProcess)) { |
| 50 command_line.AppendSwitchASCII(switches::kTestChildProcess, procname); |
| 51 } |
411 | 52 |
412 if (pid < 0) { | 53 android::ScopedJavaLocalRef<jobjectArray> j_argv = |
413 PLOG(ERROR) << "fork"; | 54 android::ToJavaArrayOfStrings(env, command_line.argv()); |
414 return Process(); | 55 jint pid = android::Java_MultiprocessTestClientLauncher_launchClient( |
| 56 env, android::GetApplicationContext(), j_argv, fds); |
| 57 return Process(pid); |
| 58 } |
| 59 |
| 60 bool WaitForMultiprocessTestChildExit(const Process& process, |
| 61 TimeDelta timeout, |
| 62 int* exit_code) { |
| 63 JNIEnv* env = android::AttachCurrentThread(); |
| 64 DCHECK(env); |
| 65 |
| 66 base::android::ScopedJavaLocalRef<jobject> result_code = |
| 67 android::Java_MultiprocessTestClientLauncher_waitForMainToReturn( |
| 68 env, android::GetApplicationContext(), process.Pid(), |
| 69 timeout.InMilliseconds()); |
| 70 if (result_code.is_null() || |
| 71 Java_MainReturnCodeResult_hasTimedOut(env, result_code)) { |
| 72 return false; |
415 } | 73 } |
416 if (pid > 0) { | 74 if (exit_code) |
417 // Parent process. | 75 *exit_code = Java_MainReturnCodeResult_getReturnCode(env, result_code); |
418 return Process(pid); | 76 return true; |
419 } | 77 } |
420 // Child process. | |
421 base::hash_set<int> fds_to_keep_open; | |
422 for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin(); | |
423 it != fds_to_remap->end(); ++it) { | |
424 fds_to_keep_open.insert(it->first); | |
425 } | |
426 // Keep standard FDs (stdin, stdout, stderr, etc.) open since this | |
427 // is not meant to spawn a daemon. | |
428 int base = GlobalDescriptors::kBaseDescriptor; | |
429 for (int fd = base; fd < sysconf(_SC_OPEN_MAX); ++fd) { | |
430 if (fds_to_keep_open.find(fd) == fds_to_keep_open.end()) { | |
431 close(fd); | |
432 } | |
433 } | |
434 for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin(); | |
435 it != fds_to_remap->end(); ++it) { | |
436 int old_fd = it->first; | |
437 int new_fd = it->second; | |
438 if (dup2(old_fd, new_fd) < 0) { | |
439 PLOG(FATAL) << "dup2"; | |
440 } | |
441 close(old_fd); | |
442 } | |
443 CommandLine::Reset(); | |
444 CommandLine::Init(0, nullptr); | |
445 CommandLine* command_line = CommandLine::ForCurrentProcess(); | |
446 command_line->InitFromArgv(base_command_line.argv()); | |
447 if (!command_line->HasSwitch(switches::kTestChildProcess)) | |
448 command_line->AppendSwitchASCII(switches::kTestChildProcess, procname); | |
449 | 78 |
450 _exit(multi_process_function_list::InvokeChildProcessTest(procname)); | 79 bool TerminateMultiProcessTestChild(const Process& process, |
451 return Process(); | 80 int exit_code, |
| 81 bool wait) { |
| 82 JNIEnv* env = android::AttachCurrentThread(); |
| 83 DCHECK(env); |
| 84 |
| 85 return android::Java_MultiprocessTestClientLauncher_terminate( |
| 86 env, android::GetApplicationContext(), process.Pid(), exit_code, wait); |
452 } | 87 } |
453 | 88 |
454 } // namespace base | 89 } // namespace base |
OLD | NEW |