Commit 93637728 authored by jcivelli's avatar jcivelli Committed by Commit bot

Making ChildProcessConnection only accessed from the launcher thread.

As a result, removing locks and synchronizations.
Also changing the way we store and report the OOM protected state.
We now update the OOM protected state every time it changes as long as we
are bound (that happens only on the launcher thread).
When retrieving that OOM protected state (which happens on the IO
thread), we return that state directly without the need of a lock.

BUG=714657

Review-Url: https://codereview.chromium.org/2840303002
Cr-Commit-Position: refs/heads/master@{#467892}
parent d97cc734
......@@ -26,7 +26,6 @@ import org.chromium.base.process_launcher.IChildProcessService;
import java.io.IOException;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
/**
* Manages a connection between the browser activity and a child service.
......@@ -188,52 +187,39 @@ public abstract class BaseChildProcessConnection {
}
}
// Synchronization: While most internal flow occurs on the UI thread, the public API
// (specifically start and stop) may be called from any thread, hence all entry point methods
// into the class are synchronized on the lock to protect access to these members.
// TODO(jcivelli): crbug.com/714657 remove this lock.
private final Object mLock = new Object();
// This is set in start() and is used in onServiceConnected().
@GuardedBy("mLock")
private StartCallback mStartCallback;
// This is set in setupConnection() and is later used in doConnectionSetupLocked(), after which
// the variable is cleared. Therefore this is only valid while the connection is being set up.
@GuardedBy("mLock")
private ConnectionParams mConnectionParams;
// Callback provided in setupConnection() that will communicate the result to the caller. This
// has to be called exactly once after setupConnection(), even if setup fails, so that the
// caller can free up resources associated with the setup attempt. This is set to null after the
// call.
@GuardedBy("mLock")
private ConnectionCallback mConnectionCallback;
@GuardedBy("mLock")
private IChildProcessService mService;
// Set to true when the service connection callback runs. This differs from
// mServiceConnectComplete, which tracks that the connection completed successfully.
@GuardedBy("mLock")
private boolean mDidOnServiceConnected;
// Set to true when the service connected successfully.
@GuardedBy("mLock")
private boolean mServiceConnectComplete;
// Set to true when the service disconnects, as opposed to being properly closed. This happens
// when the process crashes or gets killed by the system out-of-memory killer.
@GuardedBy("mLock")
private boolean mServiceDisconnected;
// Process ID of the corresponding child process.
@GuardedBy("mLock")
private int mPid;
protected BaseChildProcessConnection(Context context, int number, boolean sandboxed,
DeathCallback deathCallback, String serviceClassName,
Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) {
assert LauncherThread.runningOnLauncherThread();
mContext = context;
mServiceNumber = number;
mSandboxed = sandboxed;
......@@ -246,33 +232,38 @@ public abstract class BaseChildProcessConnection {
}
public final Context getContext() {
assert LauncherThread.runningOnLauncherThread();
return mContext;
}
public final int getServiceNumber() {
assert LauncherThread.runningOnLauncherThread();
return mServiceNumber;
}
public final boolean isSandboxed() {
assert LauncherThread.runningOnLauncherThread();
return mSandboxed;
}
public final String getPackageName() {
assert LauncherThread.runningOnLauncherThread();
return mCreationParams != null ? mCreationParams.getPackageName()
: mContext.getPackageName();
}
public final ChildProcessCreationParams getCreationParams() {
assert LauncherThread.runningOnLauncherThread();
return mCreationParams;
}
public final IChildProcessService getService() {
synchronized (mLock) {
assert LauncherThread.runningOnLauncherThread();
return mService;
}
}
public final ComponentName getServiceName() {
assert LauncherThread.runningOnLauncherThread();
return mServiceName;
}
......@@ -280,10 +271,9 @@ public abstract class BaseChildProcessConnection {
* @return the connection pid, or 0 if not yet connected
*/
public int getPid() {
synchronized (mLock) {
assert LauncherThread.runningOnLauncherThread();
return mPid;
}
}
/**
* Starts a connection to an IChildProcessService. This must be followed by a call to
......@@ -293,10 +283,10 @@ public abstract class BaseChildProcessConnection {
* @param startCallback (optional) callback when the child process starts or fails to start.
*/
public void start(StartCallback startCallback) {
assert LauncherThread.runningOnLauncherThread();
try {
TraceEvent.begin("BaseChildProcessConnection.start");
assert LauncherThread.runningOnLauncherThread();
synchronized (mLock) {
assert mConnectionParams
== null
: "setupConnection() called before start() in BaseChildProcessConnection.";
......@@ -309,7 +299,6 @@ public abstract class BaseChildProcessConnection {
// TODO(ppi): Can we hard-fail here?
mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this);
}
}
} finally {
TraceEvent.end("BaseChildProcessConnection.start");
}
......@@ -327,7 +316,6 @@ public abstract class BaseChildProcessConnection {
public void setupConnection(String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
@Nullable IBinder callback, ConnectionCallback connectionCallback) {
assert LauncherThread.runningOnLauncherThread();
synchronized (mLock) {
assert mConnectionParams == null;
if (mServiceDisconnected) {
Log.w(TAG, "Tried to setup a connection that already disconnected.");
......@@ -347,23 +335,20 @@ public abstract class BaseChildProcessConnection {
TraceEvent.end("BaseChildProcessConnection.setupConnection");
}
}
}
/**
* Terminates the connection to IChildProcessService, closing all bindings. It is safe to call
* this multiple times.
*/
public void stop() {
synchronized (mLock) {
assert LauncherThread.runningOnLauncherThread();
unbind();
mService = null;
mConnectionParams = null;
}
}
private void onServiceConnectedOnLauncherThread(IBinder service) {
assert LauncherThread.runningOnLauncherThread();
synchronized (mLock) {
// A flag from the parent class ensures we run the post-connection logic only once
// (instead of once per each ChildServiceConnection).
if (mDidOnServiceConnected) {
......@@ -410,15 +395,12 @@ public abstract class BaseChildProcessConnection {
doConnectionSetupLocked();
}
} finally {
TraceEvent.end(
"BaseChildProcessConnection.ChildServiceConnection.onServiceConnected");
}
TraceEvent.end("BaseChildProcessConnection.ChildServiceConnection.onServiceConnected");
}
}
private void onServiceDisconnectedOnLauncherThread() {
assert LauncherThread.runningOnLauncherThread();
synchronized (mLock) {
// Ensure that the disconnection logic runs only once (instead of once per each
// ChildServiceConnection).
if (mServiceDisconnected) {
......@@ -435,10 +417,8 @@ public abstract class BaseChildProcessConnection {
}
mConnectionCallback = null;
}
}
private void onSetupConnectionResult(int pid) {
synchronized (mLock) {
mPid = pid;
assert mPid != 0 : "Child service claims to be run by a process of pid=0.";
......@@ -447,14 +427,12 @@ public abstract class BaseChildProcessConnection {
}
mConnectionCallback = null;
}
}
/**
* Called after the connection parameters have been set (in setupConnection()) *and* a
* connection has been established (as signaled by onServiceConnected()). These two events can
* happen in any order. Has to be called with mLock.
*/
@GuardedBy("mLock")
private void doConnectionSetupLocked() {
try {
TraceEvent.begin("BaseChildProcessConnection.doConnectionSetupLocked");
......@@ -498,10 +476,12 @@ public abstract class BaseChildProcessConnection {
protected abstract void unbind();
protected ChildServiceConnection createServiceConnection(int bindFlags) {
assert LauncherThread.runningOnLauncherThread();
return new ChildServiceConnectionImpl(bindFlags);
}
protected boolean shouldBindAsExportedService() {
assert LauncherThread.runningOnLauncherThread();
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getCreationParams() != null
&& getCreationParams().getIsExternalService()
&& isExportedService(isSandboxed(), getContext(), getServiceName());
......@@ -529,15 +509,11 @@ public abstract class BaseChildProcessConnection {
@VisibleForTesting
public void crashServiceForTesting() throws RemoteException {
synchronized (mLock) {
mService.crashIntentionallyForTesting();
}
}
@VisibleForTesting
public boolean isConnected() {
synchronized (mLock) {
boolean isConnected() {
return mService != null;
}
}
}
......@@ -11,11 +11,10 @@ import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.process_launcher.ChildProcessCreationParams;
import javax.annotation.concurrent.GuardedBy;
/**
* ManagedChildProcessConnection is a connection to a child service that can hold several bindings
* to the service so it can be more or less agressively protected against OOM.
* Accessed from the launcher thread. (but for isOomProtectedOrWasWhenDied()).
*/
public class ManagedChildProcessConnection extends BaseChildProcessConnection {
private static final String TAG = "ManChildProcessConn";
......@@ -25,45 +24,40 @@ public class ManagedChildProcessConnection extends BaseChildProcessConnection {
public BaseChildProcessConnection create(Context context, int number, boolean sandboxed,
DeathCallback deathCallback, String serviceClassName,
Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) {
assert LauncherThread.runningOnLauncherThread();
return new ManagedChildProcessConnection(context, number, sandboxed, deathCallback,
serviceClassName, childProcessCommonParameters, creationParams);
}
};
// Synchronization: While most internal flow occurs on the UI thread, the public API
// (specifically start and stop) may be called from any thread, hence all entry point methods
// into the class are synchronized on the lock to protect access to these members.
private final Object mBindingLock = new Object();
// Initial binding protects the newly spawned process from being killed before it is put to use,
// it is maintained between calls to start() and removeInitialBinding().
@GuardedBy("mBindingLock")
private final ChildServiceConnection mInitialBinding;
// Strong binding will make the service priority equal to the priority of the activity. We want
// the OS to be able to kill background renderers as it kills other background apps, so strong
// bindings are maintained only for services that are active at the moment (between
// addStrongBinding() and removeStrongBinding()).
@GuardedBy("mBindingLock")
private final ChildServiceConnection mStrongBinding;
// Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
// to start() and stop().
@GuardedBy("mBindingLock")
private final ChildServiceConnection mWaivedBinding;
// Incremented on addStrongBinding(), decremented on removeStrongBinding().
@GuardedBy("mBindingLock")
private int mStrongBindingCount;
// Moderate binding will make the service priority equal to the priority of a visible process
// while the app is in the foreground. It will stay bound only while the app is in the
// foreground to protect a background process from the system out-of-memory killer.
@GuardedBy("mBindingLock")
private final ChildServiceConnection mModerateBinding;
@GuardedBy("mBindingLock")
private boolean mWasOomProtectedOnUnbind;
// Indicates whether the connection is OOM protected (if the connection is unbound, it contains
// the state at time of unbinding).
private boolean mOomProtected;
// Set to true once unbind() was called.
private boolean mUnbound;
@VisibleForTesting
ManagedChildProcessConnection(Context context, int number, boolean sandboxed,
......@@ -74,8 +68,6 @@ public class ManagedChildProcessConnection extends BaseChildProcessConnection {
int initialFlags = Context.BIND_AUTO_CREATE;
int extraBindFlags = shouldBindAsExportedService() ? Context.BIND_EXTERNAL_SERVICE : 0;
synchronized (mBindingLock) {
mInitialBinding = createServiceConnection(initialFlags | extraBindFlags);
mStrongBinding = createServiceConnection(
Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT | extraBindFlags);
......@@ -83,105 +75,84 @@ public class ManagedChildProcessConnection extends BaseChildProcessConnection {
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | extraBindFlags);
mModerateBinding = createServiceConnection(Context.BIND_AUTO_CREATE | extraBindFlags);
}
}
@Override
protected boolean bind() {
synchronized (mBindingLock) {
assert LauncherThread.runningOnLauncherThread();
assert !mUnbound;
if (!mInitialBinding.bind()) {
return false;
}
updateOomProtectedState();
mWaivedBinding.bind();
}
return true;
}
@Override
public void unbind() {
synchronized (mBindingLock) {
if (!isBound()) {
return;
}
mWasOomProtectedOnUnbind = isCurrentlyOomProtected();
assert LauncherThread.runningOnLauncherThread();
mUnbound = true;
mInitialBinding.unbind();
mStrongBinding.unbind();
// Note that we don't update the OOM state here as to preserve the last OOM state.
mWaivedBinding.unbind();
mModerateBinding.unbind();
mStrongBindingCount = 0;
}
}
@GuardedBy("mBindingLock")
private boolean isBound() {
return mInitialBinding.isBound() || mStrongBinding.isBound() || mWaivedBinding.isBound()
|| mModerateBinding.isBound();
}
public boolean isInitialBindingBound() {
synchronized (mBindingLock) {
assert LauncherThread.runningOnLauncherThread();
return mInitialBinding.isBound();
}
}
public boolean isStrongBindingBound() {
synchronized (mBindingLock) {
assert LauncherThread.runningOnLauncherThread();
return mStrongBinding.isBound();
}
}
public void removeInitialBinding() {
synchronized (mBindingLock) {
assert LauncherThread.runningOnLauncherThread();
mInitialBinding.unbind();
}
updateOomProtectedState();
}
/**
* @return true if the connection is bound and OOM protected or was OOM protected when unbound.
*/
public boolean isOomProtectedOrWasWhenDied() {
// Call isConnected() outside of the synchronized block or we could deadlock.
final boolean isConnected = isConnected();
synchronized (mBindingLock) {
if (isConnected) {
return isCurrentlyOomProtected();
}
return mWasOomProtectedOnUnbind;
}
}
@GuardedBy("mBindingLock")
private boolean isCurrentlyOomProtected() {
return mInitialBinding.isBound() || mStrongBinding.isBound();
// WARNING: this method can be called from a thread other than the launcher thread.
// Note that it returns the current OOM protected state and is racy. This not really
// preventable without changing the caller's API, short of blocking.
return mOomProtected;
}
public void dropOomBindings() {
synchronized (mBindingLock) {
assert LauncherThread.runningOnLauncherThread();
mInitialBinding.unbind();
mStrongBindingCount = 0;
mStrongBinding.unbind();
updateOomProtectedState();
mModerateBinding.unbind();
}
}
public void addStrongBinding() {
// Call isConnected() outside of the synchronized block or we could deadlock.
final boolean isConnected = isConnected();
synchronized (mBindingLock) {
if (!isConnected) {
assert LauncherThread.runningOnLauncherThread();
if (!isConnected()) {
Log.w(TAG, "The connection is not bound for %d", getPid());
return;
}
if (mStrongBindingCount == 0) {
mStrongBinding.bind();
updateOomProtectedState();
}
mStrongBindingCount++;
}
}
public void removeStrongBinding() {
// Call isConnected() outside of the synchronized block or we could deadlock.
final boolean isConnected = isConnected();
synchronized (mBindingLock) {
if (!isConnected) {
assert LauncherThread.runningOnLauncherThread();
if (!isConnected()) {
Log.w(TAG, "The connection is not bound for %d", getPid());
return;
}
......@@ -189,37 +160,38 @@ public class ManagedChildProcessConnection extends BaseChildProcessConnection {
mStrongBindingCount--;
if (mStrongBindingCount == 0) {
mStrongBinding.unbind();
updateOomProtectedState();
}
}
updateOomProtectedState();
}
public boolean isModerateBindingBound() {
synchronized (mBindingLock) {
assert LauncherThread.runningOnLauncherThread();
return mModerateBinding.isBound();
}
}
public void addModerateBinding() {
// Call isConnected() outside of the synchronized block or we could deadlock.
final boolean isConnected = isConnected();
synchronized (mBindingLock) {
if (!isConnected) {
assert LauncherThread.runningOnLauncherThread();
if (!isConnected()) {
Log.w(TAG, "The connection is not bound for %d", getPid());
return;
}
mModerateBinding.bind();
}
}
public void removeModerateBinding() {
// Call isConnected() outside of the synchronized block or we could deadlock.
final boolean isConnected = isConnected();
synchronized (mBindingLock) {
if (!isConnected) {
assert LauncherThread.runningOnLauncherThread();
if (!isConnected()) {
Log.w(TAG, "The connection is not bound for %d", getPid());
return;
}
mModerateBinding.unbind();
}
// Should be called every time the mInitialBinding or mStrongBinding are bound/unbound.
private void updateOomProtectedState() {
if (!mUnbound) {
mOomProtected = mInitialBinding.isBound() || mStrongBinding.isBound();
}
}
}
......@@ -35,6 +35,7 @@ import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.common.ContentSwitches;
import org.chromium.content_shell_apk.ChildProcessLauncherTestHelperService;
import org.chromium.content_shell_apk.ChildProcessLauncherTestUtils;
import java.util.concurrent.Callable;
......@@ -106,7 +107,7 @@ public class ChildProcessLauncherTest {
Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
// Verify that the service is not yet set up.
Assert.assertEquals(0, connection.getPid());
Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionPid(connection));
Assert.assertEquals(0, ChildProcessLauncher.connectedServicesCountForTesting());
// Crash the service.
......@@ -156,7 +157,7 @@ public class ChildProcessLauncherTest {
new Criteria("The connection failed to get a pid in setup.") {
@Override
public boolean isSatisfied() {
return connection.getPid() != 0;
return ChildProcessLauncherTestUtils.getConnectionPid(connection) != 0;
}
});
......@@ -179,7 +180,7 @@ public class ChildProcessLauncherTest {
}));
// Verify that the connection pid remains set after termination.
Assert.assertTrue(connection.getPid() != 0);
Assert.assertTrue(ChildProcessLauncherTestUtils.getConnectionPid(connection) != 0);
}
/**
......@@ -220,7 +221,7 @@ public class ChildProcessLauncherTest {
new Criteria("The connection failed to get a pid in setup.") {
@Override
public boolean isSatisfied() {
return connection.getPid() != 0;
return ChildProcessLauncherTestUtils.getConnectionPid(connection) != 0;
}
});
......@@ -284,8 +285,10 @@ public class ChildProcessLauncherTest {
// Verify that connections allocated for an external APK and the regular tab are from
// different ChildConnectionAllocators, since both ChildConnectionAllocators start
// allocating connections from number 0.
Assert.assertEquals(0, externalApkConnection.getServiceNumber());
Assert.assertEquals(0, tabConnection.getServiceNumber());
Assert.assertEquals(
0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(externalApkConnection));
Assert.assertEquals(
0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(tabConnection));
}
/**
......@@ -406,18 +409,18 @@ public class ChildProcessLauncherTest {
context.getPackageName(), false /* isExternalService */,
LibraryProcessType.PROCESS_CHILD, true /* bindToCallerCheck */);
final BaseChildProcessConnection conn =
ChildProcessLauncherTestHelperService.startInternalForTesting(
ChildProcessLauncherTestUtils.startInternalForTesting(
context, sProcessWaitArguments, new FileDescriptorInfo[0], creationParams);
CriteriaHelper.pollInstrumentationThread(
new Criteria("Failed waiting for instrumentation-bound service") {
@Override
public boolean isSatisfied() {
return conn.getService() != null;
return ChildProcessLauncherTestUtils.getConnectionService(conn) != null;
}
});
Assert.assertEquals(0, conn.getServiceNumber());
Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn));
final BaseChildProcessConnection[] sandboxedConnections =
getSandboxedConnectionArrayForTesting(context, context.getPackageName());
......@@ -430,7 +433,9 @@ public class ChildProcessLauncherTest {
boolean allChildrenConnected = true;
for (int i = 0; i <= 1; ++i) {
BaseChildProcessConnection conn = sandboxedConnections[i];
allChildrenConnected &= conn != null && conn.getService() != null;
allChildrenConnected &= conn != null
&& ChildProcessLauncherTestUtils.getConnectionService(conn)
!= null;
}
return allChildrenConnected;
}
......@@ -441,7 +446,8 @@ public class ChildProcessLauncherTest {
BaseChildProcessConnection sandboxedConn = sandboxedConnections[i];
if (i <= 1) {
Assert.assertNotNull(sandboxedConn);
Assert.assertNotNull(sandboxedConn.getService());
Assert.assertNotNull(
ChildProcessLauncherTestUtils.getConnectionService(sandboxedConn));
} else {
Assert.assertNull(sandboxedConn);
}
......@@ -452,20 +458,22 @@ public class ChildProcessLauncherTest {
Assert.assertFalse(conn == retryConn);
Assert.assertEquals(0, conn.getServiceNumber());
Assert.assertEquals(0, conn.getPid());
Assert.assertFalse(conn.getService().bindToCaller());
Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn));
Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionPid(conn));
Assert.assertFalse(ChildProcessLauncherTestUtils.getConnectionService(conn).bindToCaller());
Assert.assertEquals(1, retryConn.getServiceNumber());
Assert.assertEquals(1, ChildProcessLauncherTestUtils.getConnectionServiceNumber(retryConn));
CriteriaHelper.pollInstrumentationThread(
new Criteria("Failed waiting retry connection to get pid") {
@Override
public boolean isSatisfied() {
return retryConn.getPid() > 0;
return ChildProcessLauncherTestUtils.getConnectionPid(retryConn) > 0;
}
});
Assert.assertTrue(retryConn.getPid() != helperConnPid);
Assert.assertTrue(retryConn.getService().bindToCaller());
Assert.assertTrue(
ChildProcessLauncherTestUtils.getConnectionPid(retryConn) != helperConnPid);
Assert.assertTrue(
ChildProcessLauncherTestUtils.getConnectionService(retryConn).bindToCaller());
}
private static void warmUpOnUiThreadBlocking(final Context context) {
......@@ -483,18 +491,18 @@ public class ChildProcessLauncherTest {
public void testWarmUp() {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
warmUpOnUiThreadBlocking(context);
ChildProcessLauncherTestHelperService.runOnLauncherThreadBlocking(new Runnable() {
ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
@Override
public void run() {
Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
final BaseChildProcessConnection conn =
ChildProcessLauncherTestHelperService.startInternalForTesting(
ChildProcessLauncherTestUtils.startInternalForTesting(
context, new String[0], new FileDescriptorInfo[0], null);
Assert.assertEquals(
1, allocatedChromeSandboxedConnectionsCount()); // Used warmup connection.
ChildProcessLauncher.stop(conn.getPid());
ChildProcessLauncher.stop(ChildProcessLauncherTestUtils.getConnectionPid(conn));
}
});
}
......@@ -512,7 +520,7 @@ public class ChildProcessLauncherTest {
getDefaultChildProcessCreationParams(context.getPackageName()));
warmUpOnUiThreadBlocking(context);
ChildProcessLauncherTestHelperService.runOnLauncherThreadBlocking(new Runnable() {
ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
@Override
public void run() {
Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
......@@ -558,7 +566,7 @@ public class ChildProcessLauncherTest {
private static BaseChildProcessConnection allocateBoundConnectionForTesting(
final Context context, final ChildProcessCreationParams creationParams) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult(
return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<BaseChildProcessConnection>() {
@Override
public BaseChildProcessConnection call() {
......@@ -579,7 +587,7 @@ public class ChildProcessLauncherTest {
* connection is allocated properly for different application packages.
*/
private BaseChildProcessConnection allocateConnection(final String packageName) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult(
return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<BaseChildProcessConnection>() {
@Override
public BaseChildProcessConnection call() {
......@@ -602,7 +610,7 @@ public class ChildProcessLauncherTest {
private static void enqueuePendingSpawnForTesting(final Context context,
final String[] commandLine, final ChildProcessCreationParams creationParams,
final boolean inSandbox) {
ChildProcessLauncherTestHelperService.runOnLauncherThreadBlocking(new Runnable() {
ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
@Override
public void run() {
String packageName = creationParams != null ? creationParams.getPackageName()
......@@ -619,7 +627,7 @@ public class ChildProcessLauncherTest {
private static int allocatedSandboxedConnectionsCountForTesting(
final Context context, final String packageName) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult(
return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<Integer>() {
@Override
public Integer call() {
......@@ -632,7 +640,7 @@ public class ChildProcessLauncherTest {
private static BaseChildProcessConnection[] getSandboxedConnectionArrayForTesting(
final Context context, final String packageName) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult(
return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<BaseChildProcessConnection[]>() {
@Override
public BaseChildProcessConnection[] call() {
......@@ -645,7 +653,7 @@ public class ChildProcessLauncherTest {
private static int pendingSpawnsCountForTesting(
final Context context, final String packageName, final boolean inSandbox) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult(
return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<Integer>() {
@Override
public Integer call() {
......@@ -670,7 +678,7 @@ public class ChildProcessLauncherTest {
}
private void triggerConnectionSetup(final BaseChildProcessConnection connection) {
ChildProcessLauncherTestHelperService.runOnLauncherThreadBlocking(new Runnable() {
ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
@Override
public void run() {
ChildProcessLauncher.triggerConnectionSetup(connection, sProcessWaitArguments,
......
......@@ -96,6 +96,7 @@ android_library("content_shell_apk_java") {
java_files = [
"shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java",
"shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java",
"shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java",
"shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java",
]
......
......@@ -5,7 +5,6 @@
package org.chromium.content_shell_apk;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
......@@ -22,12 +21,6 @@ import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.base.process_launcher.ChildProcessCreationParams;
import org.chromium.base.process_launcher.FileDescriptorInfo;
import org.chromium.content.browser.BaseChildProcessConnection;
import org.chromium.content.browser.ChildProcessLauncher;
import org.chromium.content.browser.LauncherThread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Semaphore;
/**
* A Service that assists the ChildProcessLauncherTest that responds to one message, which
......@@ -53,53 +46,6 @@ public class ChildProcessLauncherTestHelperService extends Service {
private final HandlerThread mHandlerThread = new HandlerThread("Helper Service Handler");
public static void runOnLauncherThreadBlocking(final Runnable runnable) {
if (LauncherThread.runningOnLauncherThread()) {
runnable.run();
return;
}
final Semaphore done = new Semaphore(0);
LauncherThread.post(new Runnable() {
@Override
public void run() {
runnable.run();
done.release();
}
});
done.acquireUninterruptibly();
}
public static <R> R runOnLauncherAndGetResult(Callable<R> callable) {
if (LauncherThread.runningOnLauncherThread()) {
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
try {
FutureTask<R> task = new FutureTask<R>(callable);
LauncherThread.post(task);
return task.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static BaseChildProcessConnection startInternalForTesting(final Context context,
final String[] commandLine, final FileDescriptorInfo[] filesToMap,
final ChildProcessCreationParams params) {
return runOnLauncherAndGetResult(new Callable<BaseChildProcessConnection>() {
@Override
public BaseChildProcessConnection call() {
return ChildProcessLauncher.startInternal(context, commandLine,
0 /* childProcessId */, filesToMap, null /* launchCallback */,
null /* childProcessCallback */, true /* inSandbox */,
false /* alwaysInForeground */, params);
}
});
}
@Override
public void onCreate() {
CommandLine.init(null);
......@@ -125,7 +71,8 @@ public class ChildProcessLauncherTestHelperService extends Service {
ChildProcessCreationParams params = new ChildProcessCreationParams(
getPackageName(), false, LibraryProcessType.PROCESS_CHILD, bindToCaller);
final BaseChildProcessConnection conn =
startInternalForTesting(this, commandLine, new FileDescriptorInfo[0], params);
ChildProcessLauncherTestUtils.startInternalForTesting(
this, commandLine, new FileDescriptorInfo[0], params);
// Poll the connection until it is set up. The main test in ChildProcessLauncherTest, which
// has bound the connection to this service, manages the timeout via the lifetime of this
......@@ -136,10 +83,11 @@ public class ChildProcessLauncherTestHelperService extends Service {
@Override
public void run() {
if (conn.getPid() != 0) {
int pid = ChildProcessLauncherTestUtils.getConnectionPid(conn);
if (pid != 0) {
try {
mReplyTo.send(Message.obtain(null, MSG_BIND_SERVICE_REPLY, conn.getPid(),
conn.getServiceNumber()));
mReplyTo.send(Message.obtain(null, MSG_BIND_SERVICE_REPLY, pid,
ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn)));
} catch (RemoteException ex) {
throw new RuntimeException(ex);
}
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content_shell_apk;
import android.content.Context;
import org.chromium.base.process_launcher.ChildProcessCreationParams;
import org.chromium.base.process_launcher.FileDescriptorInfo;
import org.chromium.base.process_launcher.IChildProcessService;
import org.chromium.content.browser.BaseChildProcessConnection;
import org.chromium.content.browser.ChildProcessLauncher;
import org.chromium.content.browser.LauncherThread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Semaphore;
/** An assortment of static methods used in tests that deal with launching child processes. */
public final class ChildProcessLauncherTestUtils {
// Do not instanciate, use static methods instead.
private ChildProcessLauncherTestUtils() {}
public static void runOnLauncherThreadBlocking(final Runnable runnable) {
if (LauncherThread.runningOnLauncherThread()) {
runnable.run();
return;
}
final Semaphore done = new Semaphore(0);
LauncherThread.post(new Runnable() {
@Override
public void run() {
runnable.run();
done.release();
}
});
done.acquireUninterruptibly();
}
public static <R> R runOnLauncherAndGetResult(Callable<R> callable) {
if (LauncherThread.runningOnLauncherThread()) {
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
try {
FutureTask<R> task = new FutureTask<R>(callable);
LauncherThread.post(task);
return task.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static BaseChildProcessConnection startInternalForTesting(final Context context,
final String[] commandLine, final FileDescriptorInfo[] filesToMap,
final ChildProcessCreationParams params) {
return runOnLauncherAndGetResult(new Callable<BaseChildProcessConnection>() {
@Override
public BaseChildProcessConnection call() {
return ChildProcessLauncher.startInternal(context, commandLine,
0 /* childProcessId */, filesToMap, null /* launchCallback */,
null /* childProcessCallback */, true /* inSandbox */,
false /* alwaysInForeground */, params);
}
});
}
// Retrieves the PID of the passed in connection on the launcher thread as to not assert.
public static int getConnectionPid(final BaseChildProcessConnection connection) {
return runOnLauncherAndGetResult(new Callable<Integer>() {
@Override
public Integer call() {
return connection.getPid();
}
});
}
// Retrieves the service number of the passed in connection on the launcher thread as to not
// assert.
public static int getConnectionServiceNumber(final BaseChildProcessConnection connection) {
return runOnLauncherAndGetResult(new Callable<Integer>() {
@Override
public Integer call() {
return connection.getServiceNumber();
}
});
}
// Retrieves the service of the passed in connection on the launcher thread as to not assert.
public static IChildProcessService getConnectionService(
final BaseChildProcessConnection connection) {
return runOnLauncherAndGetResult(new Callable<IChildProcessService>() {
@Override
public IChildProcessService call() {
return connection.getService();
}
});
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment