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

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

Powered by Google App Engine
This is Rietveld 408576698