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

Side by Side Diff: base/test/android/java/src/org/chromium/base/MultiprocessTestClientLauncher.java

Issue 2549363004: Multiprocess test client: Android child process launcher rework. (Closed)
Patch Set: More bot fixing. Created 4 years 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
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.base;
6
7 import android.content.ComponentName;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.ServiceConnection;
11 import android.os.IBinder;
12 import android.os.ParcelFileDescriptor;
13 import android.os.RemoteException;
14
15 import org.chromium.base.annotations.CalledByNative;
16 import org.chromium.base.annotations.JNINamespace;
17
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Queue;
23
24 import javax.annotation.concurrent.GuardedBy;
25
26 /**
27 * Helper class for launching test client processes for multiprocess unit tests.
28 */
29 @JNINamespace("base::android")
30 public final class MultiprocessTestClientLauncher {
31 private static final String TAG = "cr_MProcTCLauncher";
32
33 private static ConnectionAllocator sConnectionAllocator = new ConnectionAllo cator();
34
35 // Not supposed to be instantiated.
36 private MultiprocessTestClientLauncher() {}
37
38 private static class ConnectionAllocator {
39 // Services are identified by a slot number, which is used in the servic e name to
40 // differentiate them (MultiprocessTestClientService0, MultiprocessTestC lientService1, ...).
41 // They are stored in a FIFO queue in order to minimize the risk of the framework reusing a
42 // service without restarting its associated process (which can cause al l kind of problems
43 // with static native variables already being initialized).
44 private static final int MAX_SUBPROCESS_COUNT = 5;
45
46 private final Object mLock = new Object();
47
48 @GuardedBy("mLock")
49 private final Queue<Integer> mFreeServiceSlot = new LinkedList<>();
50 @GuardedBy("mLock")
51 private final List<ClientServiceConnection> mConnections = new ArrayList <>();
52
53 public ConnectionAllocator() {
54 synchronized (mLock) {
55 for (int i = 0; i < MAX_SUBPROCESS_COUNT; i++) {
56 mFreeServiceSlot.add(i);
57 }
58 }
59 }
60
61 public ClientServiceConnection allocateConnection(
62 String[] commandLine, FileDescriptorInfo[] filesToMap) {
63 synchronized (mLock) {
64 while (mFreeServiceSlot.isEmpty()) {
65 try {
66 mLock.wait();
67 } catch (InterruptedException ie) {
68 Log.e(TAG, "Interrupted while waiting for a free connect ion.");
69 }
70 }
71
72 int slot = mFreeServiceSlot.remove();
73 ClientServiceConnection connection =
74 new ClientServiceConnection(slot, commandLine, filesToMa p);
75 mConnections.add(connection);
76 return connection;
77 }
78 }
79
80 public void freeConnection(ClientServiceConnection connection) {
81 synchronized (mLock) {
82 mFreeServiceSlot.add(connection.getSlot());
83 mConnections.remove(connection);
84 }
85 }
86
87 public ClientServiceConnection getConnectionByPid(int pid) {
88 synchronized (mLock) {
89 // List of connections is short, iterating is OK.
90 for (ClientServiceConnection connection : mConnections) {
91 if (connection.getPid() == pid) {
92 return connection;
93 }
94 }
95 }
96 return null;
97 }
98 }
99
100 private static class ClientServiceConnection implements ServiceConnection {
101 private final String[] mCommandLine;
102 private final FileDescriptorInfo[] mFilesToMap;
103 private final Object mConnectedLock = new Object();
104 private final int mSlot;
105 private ITestClient mService = null;
106 @GuardedBy("mConnectedLock")
107 private boolean mConnected;
108 private int mPid;
109
110 ClientServiceConnection(int slot, String[] commandLine, FileDescriptorIn fo[] filesToMap) {
111 mSlot = slot;
112 mCommandLine = commandLine;
113 mFilesToMap = filesToMap;
114 }
115
116 public void waitForConnection() {
117 synchronized (mConnectedLock) {
118 while (!mConnected) {
119 try {
120 mConnectedLock.wait();
121 } catch (InterruptedException ie) {
122 Log.e(TAG, "Interrupted while waiting for connection.");
123 }
124 }
125 }
126 }
127
128 @Override
129 public void onServiceConnected(ComponentName className, IBinder service) {
130 try {
131 mService = ITestClient.Stub.asInterface(service);
132 mPid = mService.launch(mCommandLine, mFilesToMap);
133 synchronized (mConnectedLock) {
134 mConnected = true;
135 mConnectedLock.notifyAll();
136 }
137 } catch (RemoteException e) {
138 Log.e(TAG, "Connect failed");
139 }
140 }
141
142 @Override
143 public void onServiceDisconnected(ComponentName className) {
nyquist 2016/12/14 23:26:18 Should this clear mService? Or are we assuming sin
Jay Civelli 2016/12/15 01:28:14 Yes, once freed this instance should be going away
144 if (mPid == 0) {
145 Log.e(TAG, "Early ClientServiceConnection disconnection.");
146 return;
147 }
148 sConnectionAllocator.freeConnection(this);
149 }
150
151 public ITestClient getService() {
152 return mService;
153 }
154
155 public String getServiceClassName() {
156 // In order to use different processes, we have to declare multiple services in the
157 // AndroidManifest.xml file, each service associated with its own pr ocess. The various
158 // services are functionnaly identical but need to each have their o wn class.
159 // We differentiate them by their class name having a trailing numbe r.
160 return MultiprocessTestClientService.class.getName() + mSlot;
161 }
162
163 public boolean isConnected() {
164 synchronized (mConnectedLock) {
165 return mConnected;
166 }
167 }
168
169 public int getSlot() {
170 return mSlot;
171 }
172
173 public int getPid() {
174 return mPid;
175 }
176 }
177
178 /**
179 * Spawns and connects to a child process.
180 * May not be called from the main thread.
181 *
182 * @param context context used to obtain the application context.
183 * @param commandLine the child process command line argv.
184 * @return the PID of the started process or 0 if the process could not be s tarted.
185 */
186 @CalledByNative
187 private static int launchClient(final Context context, final String[] comman dLine,
188 final FileDescriptorInfo[] filesToMap) {
189 if (ThreadUtils.runningOnUiThread()) {
190 // This can't be called on the main thread as the native side will b lock until
191 // onServiceConnected above is called, which cannot happen if the ma in thread is
192 // blocked.
193 throw new RuntimeException("launchClient cannot be called on the mai n thread");
194 }
195
196 ClientServiceConnection connection =
197 sConnectionAllocator.allocateConnection(commandLine, filesToMap) ;
198 Intent intent = new Intent();
199 String className = connection.getServiceClassName();
200 intent.setComponent(new ComponentName(context.getPackageName(), classNam e));
201 if (!context.bindService(
202 intent, connection, Context.BIND_AUTO_CREATE | Context.BIND_ IMPORTANT)) {
203 Log.e(TAG, "Failed to bind service: " + context.getPackageName() + " ." + className);
204 sConnectionAllocator.freeConnection(connection);
205 return 0;
206 }
207
208 connection.waitForConnection();
209
210 return connection.getPid();
211 }
212
213 /**
214 * Blocks until the main method invoked by a previous call to launchClient t erminates or until
215 * the specified time-out expires.
216 * Returns immediately if main has already returned.
217 * @param context context used to obtain the application context.
218 * @param pid the process ID that was returned by the call to launchClient
219 * @param timeoutMs the timeout in milliseconds after which the method retur ns even if main has
220 * not returned.
221 * @return the return code returned by the main method or whether it timed-o ut.
222 */
223 @CalledByNative
224 private static MainReturnCodeResult waitForMainToReturn(
225 Context context, int pid, int timeoutMs) {
226 ClientServiceConnection connection = sConnectionAllocator.getConnectionB yPid(pid);
227 if (connection == null) {
228 Log.e(TAG, "waitForMainToReturn called on unknown connection for pid " + pid);
229 return null;
230 }
231 try {
232 return connection.getService().waitForMainToReturn(timeoutMs);
233 } catch (RemoteException e) {
234 Log.e(TAG, "Remote call to waitForMainToReturn failed.");
235 return null;
236 } finally {
237 freeConnection(context, connection);
238 }
239 }
240
241 @CalledByNative
242 private static boolean terminate(Context context, int pid, int exitCode, boo lean wait) {
243 ClientServiceConnection connection = sConnectionAllocator.getConnectionB yPid(pid);
244 if (connection == null) {
245 Log.e(TAG, "terminate called on unknown connection for pid " + pid);
246 return false;
247 }
248 try {
249 if (wait) {
250 connection.getService().forceStopSynchronous(exitCode);
251 } else {
252 connection.getService().forceStop(exitCode);
253 }
254 } catch (RemoteException e) {
255 // We expect this failure, since the forceStop's service implementat ion calls
256 // System.exit().
257 } finally {
258 freeConnection(context, connection);
259 }
260 return true;
261 }
262
263 private static void freeConnection(Context context, ClientServiceConnection connection) {
264 context.unbindService(connection);
265 sConnectionAllocator.freeConnection(connection);
266 }
267
268 /** Does not take ownership of of fds. */
269 @CalledByNative
270 private static FileDescriptorInfo[] makeFdInfoArray(int[] ids, int[] fds) {
271 FileDescriptorInfo[] fdInfos = new FileDescriptorInfo[ids.length];
272 for (int i = 0; i < ids.length; i++) {
273 FileDescriptorInfo fdInfo = makeFdInfo(ids[i], fds[i]);
274 if (fdInfo == null) {
275 Log.e(TAG, "Failed to make file descriptor (" + ids[i] + ", " + fds[i] + ").");
276 return null;
277 }
278 fdInfos[i] = fdInfo;
279 }
280 return fdInfos;
281 }
282
283 private static FileDescriptorInfo makeFdInfo(int id, int fd) {
284 ParcelFileDescriptor parcelableFd = null;
285 try {
286 parcelableFd = ParcelFileDescriptor.fromFd(fd);
287 } catch (IOException e) {
288 Log.e(TAG, "Invalid FD provided for process connection, aborting con nection.", e);
289 return null;
290 }
291 return new FileDescriptorInfo(id, parcelableFd);
292 }
293 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698