Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(207)

Side by Side Diff: base/test/multiprocess_test_android.cc

Issue 2613183002: Revert of Multiprocess test client: Android child process launcher rework. (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « base/test/multiprocess_test.cc ('k') | base/test/test_support_android.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 : &empty;
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
OLDNEW
« no previous file with comments | « base/test/multiprocess_test.cc ('k') | base/test/test_support_android.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698