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; ...@@ -26,7 +26,6 @@ import org.chromium.base.process_launcher.IChildProcessService;
import java.io.IOException; import java.io.IOException;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
/** /**
* Manages a connection between the browser activity and a child service. * Manages a connection between the browser activity and a child service.
...@@ -188,52 +187,39 @@ public abstract class BaseChildProcessConnection { ...@@ -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(). // This is set in start() and is used in onServiceConnected().
@GuardedBy("mLock")
private StartCallback mStartCallback; private StartCallback mStartCallback;
// This is set in setupConnection() and is later used in doConnectionSetupLocked(), after which // 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. // the variable is cleared. Therefore this is only valid while the connection is being set up.
@GuardedBy("mLock")
private ConnectionParams mConnectionParams; private ConnectionParams mConnectionParams;
// Callback provided in setupConnection() that will communicate the result to the caller. This // 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 // 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 // caller can free up resources associated with the setup attempt. This is set to null after the
// call. // call.
@GuardedBy("mLock")
private ConnectionCallback mConnectionCallback; private ConnectionCallback mConnectionCallback;
@GuardedBy("mLock")
private IChildProcessService mService; private IChildProcessService mService;
// Set to true when the service connection callback runs. This differs from // Set to true when the service connection callback runs. This differs from
// mServiceConnectComplete, which tracks that the connection completed successfully. // mServiceConnectComplete, which tracks that the connection completed successfully.
@GuardedBy("mLock")
private boolean mDidOnServiceConnected; private boolean mDidOnServiceConnected;
// Set to true when the service connected successfully. // Set to true when the service connected successfully.
@GuardedBy("mLock")
private boolean mServiceConnectComplete; private boolean mServiceConnectComplete;
// Set to true when the service disconnects, as opposed to being properly closed. This happens // 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. // when the process crashes or gets killed by the system out-of-memory killer.
@GuardedBy("mLock")
private boolean mServiceDisconnected; private boolean mServiceDisconnected;
// Process ID of the corresponding child process. // Process ID of the corresponding child process.
@GuardedBy("mLock")
private int mPid; private int mPid;
protected BaseChildProcessConnection(Context context, int number, boolean sandboxed, protected BaseChildProcessConnection(Context context, int number, boolean sandboxed,
DeathCallback deathCallback, String serviceClassName, DeathCallback deathCallback, String serviceClassName,
Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) { Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) {
assert LauncherThread.runningOnLauncherThread();
mContext = context; mContext = context;
mServiceNumber = number; mServiceNumber = number;
mSandboxed = sandboxed; mSandboxed = sandboxed;
...@@ -246,33 +232,38 @@ public abstract class BaseChildProcessConnection { ...@@ -246,33 +232,38 @@ public abstract class BaseChildProcessConnection {
} }
public final Context getContext() { public final Context getContext() {
assert LauncherThread.runningOnLauncherThread();
return mContext; return mContext;
} }
public final int getServiceNumber() { public final int getServiceNumber() {
assert LauncherThread.runningOnLauncherThread();
return mServiceNumber; return mServiceNumber;
} }
public final boolean isSandboxed() { public final boolean isSandboxed() {
assert LauncherThread.runningOnLauncherThread();
return mSandboxed; return mSandboxed;
} }
public final String getPackageName() { public final String getPackageName() {
assert LauncherThread.runningOnLauncherThread();
return mCreationParams != null ? mCreationParams.getPackageName() return mCreationParams != null ? mCreationParams.getPackageName()
: mContext.getPackageName(); : mContext.getPackageName();
} }
public final ChildProcessCreationParams getCreationParams() { public final ChildProcessCreationParams getCreationParams() {
assert LauncherThread.runningOnLauncherThread();
return mCreationParams; return mCreationParams;
} }
public final IChildProcessService getService() { public final IChildProcessService getService() {
synchronized (mLock) { assert LauncherThread.runningOnLauncherThread();
return mService; return mService;
}
} }
public final ComponentName getServiceName() { public final ComponentName getServiceName() {
assert LauncherThread.runningOnLauncherThread();
return mServiceName; return mServiceName;
} }
...@@ -280,9 +271,8 @@ public abstract class BaseChildProcessConnection { ...@@ -280,9 +271,8 @@ public abstract class BaseChildProcessConnection {
* @return the connection pid, or 0 if not yet connected * @return the connection pid, or 0 if not yet connected
*/ */
public int getPid() { public int getPid() {
synchronized (mLock) { assert LauncherThread.runningOnLauncherThread();
return mPid; return mPid;
}
} }
/** /**
...@@ -293,22 +283,21 @@ public abstract class BaseChildProcessConnection { ...@@ -293,22 +283,21 @@ public abstract class BaseChildProcessConnection {
* @param startCallback (optional) callback when the child process starts or fails to start. * @param startCallback (optional) callback when the child process starts or fails to start.
*/ */
public void start(StartCallback startCallback) { public void start(StartCallback startCallback) {
assert LauncherThread.runningOnLauncherThread();
try { try {
TraceEvent.begin("BaseChildProcessConnection.start"); TraceEvent.begin("BaseChildProcessConnection.start");
assert LauncherThread.runningOnLauncherThread(); assert LauncherThread.runningOnLauncherThread();
synchronized (mLock) { assert mConnectionParams
assert mConnectionParams == null
== null : "setupConnection() called before start() in BaseChildProcessConnection.";
: "setupConnection() called before start() in BaseChildProcessConnection.";
mStartCallback = startCallback;
mStartCallback = startCallback;
if (!bind()) {
if (!bind()) { Log.e(TAG, "Failed to establish the service connection.");
Log.e(TAG, "Failed to establish the service connection."); // We have to notify the caller so that they can free-up associated resources.
// We have to notify the caller so that they can free-up associated resources. // TODO(ppi): Can we hard-fail here?
// TODO(ppi): Can we hard-fail here? mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this);
mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this);
}
} }
} finally { } finally {
TraceEvent.end("BaseChildProcessConnection.start"); TraceEvent.end("BaseChildProcessConnection.start");
...@@ -327,25 +316,23 @@ public abstract class BaseChildProcessConnection { ...@@ -327,25 +316,23 @@ public abstract class BaseChildProcessConnection {
public void setupConnection(String[] commandLine, FileDescriptorInfo[] filesToBeMapped, public void setupConnection(String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
@Nullable IBinder callback, ConnectionCallback connectionCallback) { @Nullable IBinder callback, ConnectionCallback connectionCallback) {
assert LauncherThread.runningOnLauncherThread(); assert LauncherThread.runningOnLauncherThread();
synchronized (mLock) { assert mConnectionParams == null;
assert mConnectionParams == null; if (mServiceDisconnected) {
if (mServiceDisconnected) { Log.w(TAG, "Tried to setup a connection that already disconnected.");
Log.w(TAG, "Tried to setup a connection that already disconnected."); connectionCallback.onConnected(null);
connectionCallback.onConnected(null); return;
return; }
} try {
try { TraceEvent.begin("BaseChildProcessConnection.setupConnection");
TraceEvent.begin("BaseChildProcessConnection.setupConnection"); mConnectionCallback = connectionCallback;
mConnectionCallback = connectionCallback; mConnectionParams = new ConnectionParams(commandLine, filesToBeMapped, callback);
mConnectionParams = new ConnectionParams(commandLine, filesToBeMapped, callback); // Run the setup if the service is already connected. If not,
// Run the setup if the service is already connected. If not, // doConnectionSetupLocked() will be called from onServiceConnected().
// doConnectionSetupLocked() will be called from onServiceConnected(). if (mServiceConnectComplete) {
if (mServiceConnectComplete) { doConnectionSetupLocked();
doConnectionSetupLocked();
}
} finally {
TraceEvent.end("BaseChildProcessConnection.setupConnection");
} }
} finally {
TraceEvent.end("BaseChildProcessConnection.setupConnection");
} }
} }
...@@ -354,99 +341,91 @@ public abstract class BaseChildProcessConnection { ...@@ -354,99 +341,91 @@ public abstract class BaseChildProcessConnection {
* this multiple times. * this multiple times.
*/ */
public void stop() { public void stop() {
synchronized (mLock) { assert LauncherThread.runningOnLauncherThread();
unbind(); unbind();
mService = null; mService = null;
mConnectionParams = null; mConnectionParams = null;
}
} }
private void onServiceConnectedOnLauncherThread(IBinder service) { private void onServiceConnectedOnLauncherThread(IBinder service) {
assert LauncherThread.runningOnLauncherThread(); assert LauncherThread.runningOnLauncherThread();
synchronized (mLock) { // A flag from the parent class ensures we run the post-connection logic only once
// A flag from the parent class ensures we run the post-connection logic only once // (instead of once per each ChildServiceConnection).
// (instead of once per each ChildServiceConnection). if (mDidOnServiceConnected) {
if (mDidOnServiceConnected) { return;
return; }
} try {
try { TraceEvent.begin(
TraceEvent.begin( "BaseChildProcessConnection.ChildServiceConnection.onServiceConnected");
"BaseChildProcessConnection.ChildServiceConnection.onServiceConnected"); mDidOnServiceConnected = true;
mDidOnServiceConnected = true; mService = IChildProcessService.Stub.asInterface(service);
mService = IChildProcessService.Stub.asInterface(service);
StartCallback startCallback = mStartCallback; StartCallback startCallback = mStartCallback;
mStartCallback = null; mStartCallback = null;
final boolean bindCheck = final boolean bindCheck =
mCreationParams != null && mCreationParams.getBindToCallerCheck(); mCreationParams != null && mCreationParams.getBindToCallerCheck();
boolean boundToUs = false; boolean boundToUs = false;
try { try {
boundToUs = bindCheck ? mService.bindToCaller() : true; boundToUs = bindCheck ? mService.bindToCaller() : true;
} catch (RemoteException ex) { } catch (RemoteException ex) {
// Do not trigger the StartCallback here, since the service is already // Do not trigger the StartCallback here, since the service is already
// dead and the DeathCallback will run from onServiceDisconnected(). // dead and the DeathCallback will run from onServiceDisconnected().
Log.e(TAG, "Failed to bind service to connection.", ex); Log.e(TAG, "Failed to bind service to connection.", ex);
return; return;
} }
if (startCallback != null) { if (startCallback != null) {
if (boundToUs) { if (boundToUs) {
startCallback.onChildStarted(); startCallback.onChildStarted();
} else { } else {
startCallback.onChildStartFailed(); startCallback.onChildStartFailed();
}
} }
}
if (!boundToUs) { if (!boundToUs) {
return; return;
} }
mServiceConnectComplete = true; mServiceConnectComplete = true;
// Run the setup if the connection parameters have already been provided. If // Run the setup if the connection parameters have already been provided. If
// not, doConnectionSetupLocked() will be called from setupConnection(). // not, doConnectionSetupLocked() will be called from setupConnection().
if (mConnectionParams != null) { if (mConnectionParams != null) {
doConnectionSetupLocked(); doConnectionSetupLocked();
}
} finally {
TraceEvent.end(
"BaseChildProcessConnection.ChildServiceConnection.onServiceConnected");
} }
} finally {
TraceEvent.end("BaseChildProcessConnection.ChildServiceConnection.onServiceConnected");
} }
} }
private void onServiceDisconnectedOnLauncherThread() { private void onServiceDisconnectedOnLauncherThread() {
assert LauncherThread.runningOnLauncherThread(); assert LauncherThread.runningOnLauncherThread();
synchronized (mLock) { // Ensure that the disconnection logic runs only once (instead of once per each
// Ensure that the disconnection logic runs only once (instead of once per each // ChildServiceConnection).
// ChildServiceConnection). if (mServiceDisconnected) {
if (mServiceDisconnected) { return;
return;
}
mServiceDisconnected = true;
Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=%d", mPid);
stop(); // We don't want to auto-restart on crash. Let the browser do that.
mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this);
// If we have a pending connection callback, we need to communicate the failure to
// the caller.
if (mConnectionCallback != null) {
mConnectionCallback.onConnected(null);
}
mConnectionCallback = null;
} }
mServiceDisconnected = true;
Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=%d", mPid);
stop(); // We don't want to auto-restart on crash. Let the browser do that.
mDeathCallback.onChildProcessDied(BaseChildProcessConnection.this);
// If we have a pending connection callback, we need to communicate the failure to
// the caller.
if (mConnectionCallback != null) {
mConnectionCallback.onConnected(null);
}
mConnectionCallback = null;
} }
private void onSetupConnectionResult(int pid) { private void onSetupConnectionResult(int pid) {
synchronized (mLock) { mPid = pid;
mPid = pid; assert mPid != 0 : "Child service claims to be run by a process of pid=0.";
assert mPid != 0 : "Child service claims to be run by a process of pid=0.";
if (mConnectionCallback != null) { if (mConnectionCallback != null) {
mConnectionCallback.onConnected(this); mConnectionCallback.onConnected(this);
}
mConnectionCallback = null;
} }
mConnectionCallback = null;
} }
/** /**
...@@ -454,7 +433,6 @@ public abstract class BaseChildProcessConnection { ...@@ -454,7 +433,6 @@ public abstract class BaseChildProcessConnection {
* connection has been established (as signaled by onServiceConnected()). These two events can * connection has been established (as signaled by onServiceConnected()). These two events can
* happen in any order. Has to be called with mLock. * happen in any order. Has to be called with mLock.
*/ */
@GuardedBy("mLock")
private void doConnectionSetupLocked() { private void doConnectionSetupLocked() {
try { try {
TraceEvent.begin("BaseChildProcessConnection.doConnectionSetupLocked"); TraceEvent.begin("BaseChildProcessConnection.doConnectionSetupLocked");
...@@ -498,10 +476,12 @@ public abstract class BaseChildProcessConnection { ...@@ -498,10 +476,12 @@ public abstract class BaseChildProcessConnection {
protected abstract void unbind(); protected abstract void unbind();
protected ChildServiceConnection createServiceConnection(int bindFlags) { protected ChildServiceConnection createServiceConnection(int bindFlags) {
assert LauncherThread.runningOnLauncherThread();
return new ChildServiceConnectionImpl(bindFlags); return new ChildServiceConnectionImpl(bindFlags);
} }
protected boolean shouldBindAsExportedService() { protected boolean shouldBindAsExportedService() {
assert LauncherThread.runningOnLauncherThread();
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getCreationParams() != null return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getCreationParams() != null
&& getCreationParams().getIsExternalService() && getCreationParams().getIsExternalService()
&& isExportedService(isSandboxed(), getContext(), getServiceName()); && isExportedService(isSandboxed(), getContext(), getServiceName());
...@@ -529,15 +509,11 @@ public abstract class BaseChildProcessConnection { ...@@ -529,15 +509,11 @@ public abstract class BaseChildProcessConnection {
@VisibleForTesting @VisibleForTesting
public void crashServiceForTesting() throws RemoteException { public void crashServiceForTesting() throws RemoteException {
synchronized (mLock) { mService.crashIntentionallyForTesting();
mService.crashIntentionallyForTesting();
}
} }
@VisibleForTesting @VisibleForTesting
public boolean isConnected() { boolean isConnected() {
synchronized (mLock) { return mService != null;
return mService != null;
}
} }
} }
...@@ -11,11 +11,10 @@ import org.chromium.base.Log; ...@@ -11,11 +11,10 @@ import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.base.process_launcher.ChildProcessCreationParams; 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 * 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. * 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 { public class ManagedChildProcessConnection extends BaseChildProcessConnection {
private static final String TAG = "ManChildProcessConn"; private static final String TAG = "ManChildProcessConn";
...@@ -25,45 +24,40 @@ public class ManagedChildProcessConnection extends BaseChildProcessConnection { ...@@ -25,45 +24,40 @@ public class ManagedChildProcessConnection extends BaseChildProcessConnection {
public BaseChildProcessConnection create(Context context, int number, boolean sandboxed, public BaseChildProcessConnection create(Context context, int number, boolean sandboxed,
DeathCallback deathCallback, String serviceClassName, DeathCallback deathCallback, String serviceClassName,
Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) { Bundle childProcessCommonParameters, ChildProcessCreationParams creationParams) {
assert LauncherThread.runningOnLauncherThread();
return new ManagedChildProcessConnection(context, number, sandboxed, deathCallback, return new ManagedChildProcessConnection(context, number, sandboxed, deathCallback,
serviceClassName, childProcessCommonParameters, creationParams); 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, // 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(). // it is maintained between calls to start() and removeInitialBinding().
@GuardedBy("mBindingLock")
private final ChildServiceConnection mInitialBinding; private final ChildServiceConnection mInitialBinding;
// Strong binding will make the service priority equal to the priority of the activity. We want // 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 // 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 // bindings are maintained only for services that are active at the moment (between
// addStrongBinding() and removeStrongBinding()). // addStrongBinding() and removeStrongBinding()).
@GuardedBy("mBindingLock")
private final ChildServiceConnection mStrongBinding; private final ChildServiceConnection mStrongBinding;
// Low priority binding maintained in the entire lifetime of the connection, i.e. between calls // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
// to start() and stop(). // to start() and stop().
@GuardedBy("mBindingLock")
private final ChildServiceConnection mWaivedBinding; private final ChildServiceConnection mWaivedBinding;
// Incremented on addStrongBinding(), decremented on removeStrongBinding(). // Incremented on addStrongBinding(), decremented on removeStrongBinding().
@GuardedBy("mBindingLock")
private int mStrongBindingCount; private int mStrongBindingCount;
// Moderate binding will make the service priority equal to the priority of a visible process // 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 // 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. // foreground to protect a background process from the system out-of-memory killer.
@GuardedBy("mBindingLock")
private final ChildServiceConnection mModerateBinding; private final ChildServiceConnection mModerateBinding;
@GuardedBy("mBindingLock") // Indicates whether the connection is OOM protected (if the connection is unbound, it contains
private boolean mWasOomProtectedOnUnbind; // the state at time of unbinding).
private boolean mOomProtected;
// Set to true once unbind() was called.
private boolean mUnbound;
@VisibleForTesting @VisibleForTesting
ManagedChildProcessConnection(Context context, int number, boolean sandboxed, ManagedChildProcessConnection(Context context, int number, boolean sandboxed,
...@@ -74,152 +68,130 @@ public class ManagedChildProcessConnection extends BaseChildProcessConnection { ...@@ -74,152 +68,130 @@ public class ManagedChildProcessConnection extends BaseChildProcessConnection {
int initialFlags = Context.BIND_AUTO_CREATE; int initialFlags = Context.BIND_AUTO_CREATE;
int extraBindFlags = shouldBindAsExportedService() ? Context.BIND_EXTERNAL_SERVICE : 0; int extraBindFlags = shouldBindAsExportedService() ? Context.BIND_EXTERNAL_SERVICE : 0;
mInitialBinding = createServiceConnection(initialFlags | extraBindFlags);
synchronized (mBindingLock) { mStrongBinding = createServiceConnection(
mInitialBinding = createServiceConnection(initialFlags | extraBindFlags); Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT | extraBindFlags);
mStrongBinding = createServiceConnection( mWaivedBinding = createServiceConnection(
Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT | extraBindFlags); Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | extraBindFlags);
mWaivedBinding = createServiceConnection( mModerateBinding = createServiceConnection(Context.BIND_AUTO_CREATE | extraBindFlags);
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | extraBindFlags);
mModerateBinding = createServiceConnection(Context.BIND_AUTO_CREATE | extraBindFlags);
}
} }
@Override @Override
protected boolean bind() { protected boolean bind() {
synchronized (mBindingLock) { assert LauncherThread.runningOnLauncherThread();
if (!mInitialBinding.bind()) { assert !mUnbound;
return false; if (!mInitialBinding.bind()) {
} return false;
mWaivedBinding.bind();
} }
updateOomProtectedState();
mWaivedBinding.bind();
return true; return true;
} }
@Override @Override
public void unbind() { public void unbind() {
synchronized (mBindingLock) { assert LauncherThread.runningOnLauncherThread();
if (!isBound()) { mUnbound = true;
return; mInitialBinding.unbind();
} mStrongBinding.unbind();
mWasOomProtectedOnUnbind = isCurrentlyOomProtected(); // Note that we don't update the OOM state here as to preserve the last OOM state.
mInitialBinding.unbind(); mWaivedBinding.unbind();
mStrongBinding.unbind(); mModerateBinding.unbind();
mWaivedBinding.unbind(); mStrongBindingCount = 0;
mModerateBinding.unbind();
mStrongBindingCount = 0;
}
}
@GuardedBy("mBindingLock")
private boolean isBound() {
return mInitialBinding.isBound() || mStrongBinding.isBound() || mWaivedBinding.isBound()
|| mModerateBinding.isBound();
} }
public boolean isInitialBindingBound() { public boolean isInitialBindingBound() {
synchronized (mBindingLock) { assert LauncherThread.runningOnLauncherThread();
return mInitialBinding.isBound(); return mInitialBinding.isBound();
}
} }
public boolean isStrongBindingBound() { public boolean isStrongBindingBound() {
synchronized (mBindingLock) { assert LauncherThread.runningOnLauncherThread();
return mStrongBinding.isBound(); return mStrongBinding.isBound();
}
} }
public void removeInitialBinding() { public void removeInitialBinding() {
synchronized (mBindingLock) { assert LauncherThread.runningOnLauncherThread();
mInitialBinding.unbind(); mInitialBinding.unbind();
} updateOomProtectedState();
} }
/**
* @return true if the connection is bound and OOM protected or was OOM protected when unbound.
*/
public boolean isOomProtectedOrWasWhenDied() { public boolean isOomProtectedOrWasWhenDied() {
// Call isConnected() outside of the synchronized block or we could deadlock. // WARNING: this method can be called from a thread other than the launcher thread.
final boolean isConnected = isConnected(); // Note that it returns the current OOM protected state and is racy. This not really
synchronized (mBindingLock) { // preventable without changing the caller's API, short of blocking.
if (isConnected) { return mOomProtected;
return isCurrentlyOomProtected();
}
return mWasOomProtectedOnUnbind;
}
}
@GuardedBy("mBindingLock")
private boolean isCurrentlyOomProtected() {
return mInitialBinding.isBound() || mStrongBinding.isBound();
} }
public void dropOomBindings() { public void dropOomBindings() {
synchronized (mBindingLock) { assert LauncherThread.runningOnLauncherThread();
mInitialBinding.unbind(); mInitialBinding.unbind();
mStrongBindingCount = 0; mStrongBindingCount = 0;
mStrongBinding.unbind(); mStrongBinding.unbind();
updateOomProtectedState();
mModerateBinding.unbind(); mModerateBinding.unbind();
}
} }
public void addStrongBinding() { public void addStrongBinding() {
// Call isConnected() outside of the synchronized block or we could deadlock. assert LauncherThread.runningOnLauncherThread();
final boolean isConnected = isConnected(); if (!isConnected()) {
synchronized (mBindingLock) { Log.w(TAG, "The connection is not bound for %d", getPid());
if (!isConnected) { return;
Log.w(TAG, "The connection is not bound for %d", getPid()); }
return; if (mStrongBindingCount == 0) {
} mStrongBinding.bind();
if (mStrongBindingCount == 0) { updateOomProtectedState();
mStrongBinding.bind();
}
mStrongBindingCount++;
} }
mStrongBindingCount++;
} }
public void removeStrongBinding() { public void removeStrongBinding() {
// Call isConnected() outside of the synchronized block or we could deadlock. assert LauncherThread.runningOnLauncherThread();
final boolean isConnected = isConnected(); if (!isConnected()) {
synchronized (mBindingLock) { Log.w(TAG, "The connection is not bound for %d", getPid());
if (!isConnected) { return;
Log.w(TAG, "The connection is not bound for %d", getPid());
return;
}
assert mStrongBindingCount > 0;
mStrongBindingCount--;
if (mStrongBindingCount == 0) {
mStrongBinding.unbind();
}
} }
assert mStrongBindingCount > 0;
mStrongBindingCount--;
if (mStrongBindingCount == 0) {
mStrongBinding.unbind();
updateOomProtectedState();
}
updateOomProtectedState();
} }
public boolean isModerateBindingBound() { public boolean isModerateBindingBound() {
synchronized (mBindingLock) { assert LauncherThread.runningOnLauncherThread();
return mModerateBinding.isBound(); return mModerateBinding.isBound();
}
} }
public void addModerateBinding() { public void addModerateBinding() {
// Call isConnected() outside of the synchronized block or we could deadlock. assert LauncherThread.runningOnLauncherThread();
final boolean isConnected = isConnected(); if (!isConnected()) {
synchronized (mBindingLock) { Log.w(TAG, "The connection is not bound for %d", getPid());
if (!isConnected) { return;
Log.w(TAG, "The connection is not bound for %d", getPid());
return;
}
mModerateBinding.bind();
} }
mModerateBinding.bind();
} }
public void removeModerateBinding() { public void removeModerateBinding() {
// Call isConnected() outside of the synchronized block or we could deadlock. assert LauncherThread.runningOnLauncherThread();
final boolean isConnected = isConnected(); if (!isConnected()) {
synchronized (mBindingLock) { Log.w(TAG, "The connection is not bound for %d", getPid());
if (!isConnected) { return;
Log.w(TAG, "The connection is not bound for %d", getPid()); }
return; mModerateBinding.unbind();
} }
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; ...@@ -35,6 +35,7 @@ import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper; import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.common.ContentSwitches; import org.chromium.content.common.ContentSwitches;
import org.chromium.content_shell_apk.ChildProcessLauncherTestHelperService; import org.chromium.content_shell_apk.ChildProcessLauncherTestHelperService;
import org.chromium.content_shell_apk.ChildProcessLauncherTestUtils;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
...@@ -106,7 +107,7 @@ public class ChildProcessLauncherTest { ...@@ -106,7 +107,7 @@ public class ChildProcessLauncherTest {
Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount()); Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
// Verify that the service is not yet set up. // 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()); Assert.assertEquals(0, ChildProcessLauncher.connectedServicesCountForTesting());
// Crash the service. // Crash the service.
...@@ -156,7 +157,7 @@ public class ChildProcessLauncherTest { ...@@ -156,7 +157,7 @@ public class ChildProcessLauncherTest {
new Criteria("The connection failed to get a pid in setup.") { new Criteria("The connection failed to get a pid in setup.") {
@Override @Override
public boolean isSatisfied() { public boolean isSatisfied() {
return connection.getPid() != 0; return ChildProcessLauncherTestUtils.getConnectionPid(connection) != 0;
} }
}); });
...@@ -179,7 +180,7 @@ public class ChildProcessLauncherTest { ...@@ -179,7 +180,7 @@ public class ChildProcessLauncherTest {
})); }));
// Verify that the connection pid remains set after termination. // 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 { ...@@ -220,7 +221,7 @@ public class ChildProcessLauncherTest {
new Criteria("The connection failed to get a pid in setup.") { new Criteria("The connection failed to get a pid in setup.") {
@Override @Override
public boolean isSatisfied() { public boolean isSatisfied() {
return connection.getPid() != 0; return ChildProcessLauncherTestUtils.getConnectionPid(connection) != 0;
} }
}); });
...@@ -284,8 +285,10 @@ public class ChildProcessLauncherTest { ...@@ -284,8 +285,10 @@ public class ChildProcessLauncherTest {
// Verify that connections allocated for an external APK and the regular tab are from // Verify that connections allocated for an external APK and the regular tab are from
// different ChildConnectionAllocators, since both ChildConnectionAllocators start // different ChildConnectionAllocators, since both ChildConnectionAllocators start
// allocating connections from number 0. // allocating connections from number 0.
Assert.assertEquals(0, externalApkConnection.getServiceNumber()); Assert.assertEquals(
Assert.assertEquals(0, tabConnection.getServiceNumber()); 0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(externalApkConnection));
Assert.assertEquals(
0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(tabConnection));
} }
/** /**
...@@ -406,18 +409,18 @@ public class ChildProcessLauncherTest { ...@@ -406,18 +409,18 @@ public class ChildProcessLauncherTest {
context.getPackageName(), false /* isExternalService */, context.getPackageName(), false /* isExternalService */,
LibraryProcessType.PROCESS_CHILD, true /* bindToCallerCheck */); LibraryProcessType.PROCESS_CHILD, true /* bindToCallerCheck */);
final BaseChildProcessConnection conn = final BaseChildProcessConnection conn =
ChildProcessLauncherTestHelperService.startInternalForTesting( ChildProcessLauncherTestUtils.startInternalForTesting(
context, sProcessWaitArguments, new FileDescriptorInfo[0], creationParams); context, sProcessWaitArguments, new FileDescriptorInfo[0], creationParams);
CriteriaHelper.pollInstrumentationThread( CriteriaHelper.pollInstrumentationThread(
new Criteria("Failed waiting for instrumentation-bound service") { new Criteria("Failed waiting for instrumentation-bound service") {
@Override @Override
public boolean isSatisfied() { 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 = final BaseChildProcessConnection[] sandboxedConnections =
getSandboxedConnectionArrayForTesting(context, context.getPackageName()); getSandboxedConnectionArrayForTesting(context, context.getPackageName());
...@@ -430,7 +433,9 @@ public class ChildProcessLauncherTest { ...@@ -430,7 +433,9 @@ public class ChildProcessLauncherTest {
boolean allChildrenConnected = true; boolean allChildrenConnected = true;
for (int i = 0; i <= 1; ++i) { for (int i = 0; i <= 1; ++i) {
BaseChildProcessConnection conn = sandboxedConnections[i]; BaseChildProcessConnection conn = sandboxedConnections[i];
allChildrenConnected &= conn != null && conn.getService() != null; allChildrenConnected &= conn != null
&& ChildProcessLauncherTestUtils.getConnectionService(conn)
!= null;
} }
return allChildrenConnected; return allChildrenConnected;
} }
...@@ -441,7 +446,8 @@ public class ChildProcessLauncherTest { ...@@ -441,7 +446,8 @@ public class ChildProcessLauncherTest {
BaseChildProcessConnection sandboxedConn = sandboxedConnections[i]; BaseChildProcessConnection sandboxedConn = sandboxedConnections[i];
if (i <= 1) { if (i <= 1) {
Assert.assertNotNull(sandboxedConn); Assert.assertNotNull(sandboxedConn);
Assert.assertNotNull(sandboxedConn.getService()); Assert.assertNotNull(
ChildProcessLauncherTestUtils.getConnectionService(sandboxedConn));
} else { } else {
Assert.assertNull(sandboxedConn); Assert.assertNull(sandboxedConn);
} }
...@@ -452,20 +458,22 @@ public class ChildProcessLauncherTest { ...@@ -452,20 +458,22 @@ public class ChildProcessLauncherTest {
Assert.assertFalse(conn == retryConn); Assert.assertFalse(conn == retryConn);
Assert.assertEquals(0, conn.getServiceNumber()); Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn));
Assert.assertEquals(0, conn.getPid()); Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionPid(conn));
Assert.assertFalse(conn.getService().bindToCaller()); Assert.assertFalse(ChildProcessLauncherTestUtils.getConnectionService(conn).bindToCaller());
Assert.assertEquals(1, retryConn.getServiceNumber()); Assert.assertEquals(1, ChildProcessLauncherTestUtils.getConnectionServiceNumber(retryConn));
CriteriaHelper.pollInstrumentationThread( CriteriaHelper.pollInstrumentationThread(
new Criteria("Failed waiting retry connection to get pid") { new Criteria("Failed waiting retry connection to get pid") {
@Override @Override
public boolean isSatisfied() { public boolean isSatisfied() {
return retryConn.getPid() > 0; return ChildProcessLauncherTestUtils.getConnectionPid(retryConn) > 0;
} }
}); });
Assert.assertTrue(retryConn.getPid() != helperConnPid); Assert.assertTrue(
Assert.assertTrue(retryConn.getService().bindToCaller()); ChildProcessLauncherTestUtils.getConnectionPid(retryConn) != helperConnPid);
Assert.assertTrue(
ChildProcessLauncherTestUtils.getConnectionService(retryConn).bindToCaller());
} }
private static void warmUpOnUiThreadBlocking(final Context context) { private static void warmUpOnUiThreadBlocking(final Context context) {
...@@ -483,18 +491,18 @@ public class ChildProcessLauncherTest { ...@@ -483,18 +491,18 @@ public class ChildProcessLauncherTest {
public void testWarmUp() { public void testWarmUp() {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
warmUpOnUiThreadBlocking(context); warmUpOnUiThreadBlocking(context);
ChildProcessLauncherTestHelperService.runOnLauncherThreadBlocking(new Runnable() { ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
@Override @Override
public void run() { public void run() {
Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount()); Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
final BaseChildProcessConnection conn = final BaseChildProcessConnection conn =
ChildProcessLauncherTestHelperService.startInternalForTesting( ChildProcessLauncherTestUtils.startInternalForTesting(
context, new String[0], new FileDescriptorInfo[0], null); context, new String[0], new FileDescriptorInfo[0], null);
Assert.assertEquals( Assert.assertEquals(
1, allocatedChromeSandboxedConnectionsCount()); // Used warmup connection. 1, allocatedChromeSandboxedConnectionsCount()); // Used warmup connection.
ChildProcessLauncher.stop(conn.getPid()); ChildProcessLauncher.stop(ChildProcessLauncherTestUtils.getConnectionPid(conn));
} }
}); });
} }
...@@ -512,7 +520,7 @@ public class ChildProcessLauncherTest { ...@@ -512,7 +520,7 @@ public class ChildProcessLauncherTest {
getDefaultChildProcessCreationParams(context.getPackageName())); getDefaultChildProcessCreationParams(context.getPackageName()));
warmUpOnUiThreadBlocking(context); warmUpOnUiThreadBlocking(context);
ChildProcessLauncherTestHelperService.runOnLauncherThreadBlocking(new Runnable() { ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
@Override @Override
public void run() { public void run() {
Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount()); Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
...@@ -558,7 +566,7 @@ public class ChildProcessLauncherTest { ...@@ -558,7 +566,7 @@ public class ChildProcessLauncherTest {
private static BaseChildProcessConnection allocateBoundConnectionForTesting( private static BaseChildProcessConnection allocateBoundConnectionForTesting(
final Context context, final ChildProcessCreationParams creationParams) { final Context context, final ChildProcessCreationParams creationParams) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult( return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<BaseChildProcessConnection>() { new Callable<BaseChildProcessConnection>() {
@Override @Override
public BaseChildProcessConnection call() { public BaseChildProcessConnection call() {
...@@ -579,7 +587,7 @@ public class ChildProcessLauncherTest { ...@@ -579,7 +587,7 @@ public class ChildProcessLauncherTest {
* connection is allocated properly for different application packages. * connection is allocated properly for different application packages.
*/ */
private BaseChildProcessConnection allocateConnection(final String packageName) { private BaseChildProcessConnection allocateConnection(final String packageName) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult( return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<BaseChildProcessConnection>() { new Callable<BaseChildProcessConnection>() {
@Override @Override
public BaseChildProcessConnection call() { public BaseChildProcessConnection call() {
...@@ -602,7 +610,7 @@ public class ChildProcessLauncherTest { ...@@ -602,7 +610,7 @@ public class ChildProcessLauncherTest {
private static void enqueuePendingSpawnForTesting(final Context context, private static void enqueuePendingSpawnForTesting(final Context context,
final String[] commandLine, final ChildProcessCreationParams creationParams, final String[] commandLine, final ChildProcessCreationParams creationParams,
final boolean inSandbox) { final boolean inSandbox) {
ChildProcessLauncherTestHelperService.runOnLauncherThreadBlocking(new Runnable() { ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
@Override @Override
public void run() { public void run() {
String packageName = creationParams != null ? creationParams.getPackageName() String packageName = creationParams != null ? creationParams.getPackageName()
...@@ -619,7 +627,7 @@ public class ChildProcessLauncherTest { ...@@ -619,7 +627,7 @@ public class ChildProcessLauncherTest {
private static int allocatedSandboxedConnectionsCountForTesting( private static int allocatedSandboxedConnectionsCountForTesting(
final Context context, final String packageName) { final Context context, final String packageName) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult( return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<Integer>() { new Callable<Integer>() {
@Override @Override
public Integer call() { public Integer call() {
...@@ -632,7 +640,7 @@ public class ChildProcessLauncherTest { ...@@ -632,7 +640,7 @@ public class ChildProcessLauncherTest {
private static BaseChildProcessConnection[] getSandboxedConnectionArrayForTesting( private static BaseChildProcessConnection[] getSandboxedConnectionArrayForTesting(
final Context context, final String packageName) { final Context context, final String packageName) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult( return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<BaseChildProcessConnection[]>() { new Callable<BaseChildProcessConnection[]>() {
@Override @Override
public BaseChildProcessConnection[] call() { public BaseChildProcessConnection[] call() {
...@@ -645,7 +653,7 @@ public class ChildProcessLauncherTest { ...@@ -645,7 +653,7 @@ public class ChildProcessLauncherTest {
private static int pendingSpawnsCountForTesting( private static int pendingSpawnsCountForTesting(
final Context context, final String packageName, final boolean inSandbox) { final Context context, final String packageName, final boolean inSandbox) {
return ChildProcessLauncherTestHelperService.runOnLauncherAndGetResult( return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
new Callable<Integer>() { new Callable<Integer>() {
@Override @Override
public Integer call() { public Integer call() {
...@@ -670,7 +678,7 @@ public class ChildProcessLauncherTest { ...@@ -670,7 +678,7 @@ public class ChildProcessLauncherTest {
} }
private void triggerConnectionSetup(final BaseChildProcessConnection connection) { private void triggerConnectionSetup(final BaseChildProcessConnection connection) {
ChildProcessLauncherTestHelperService.runOnLauncherThreadBlocking(new Runnable() { ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
@Override @Override
public void run() { public void run() {
ChildProcessLauncher.triggerConnectionSetup(connection, sProcessWaitArguments, ChildProcessLauncher.triggerConnectionSetup(connection, sProcessWaitArguments,
......
...@@ -96,6 +96,7 @@ android_library("content_shell_apk_java") { ...@@ -96,6 +96,7 @@ android_library("content_shell_apk_java") {
java_files = [ java_files = [
"shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java", "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/ContentShellActivity.java",
"shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java", "shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java",
] ]
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package org.chromium.content_shell_apk; package org.chromium.content_shell_apk;
import android.app.Service; import android.app.Service;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
...@@ -22,12 +21,6 @@ import org.chromium.base.library_loader.ProcessInitException; ...@@ -22,12 +21,6 @@ import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.base.process_launcher.ChildProcessCreationParams; import org.chromium.base.process_launcher.ChildProcessCreationParams;
import org.chromium.base.process_launcher.FileDescriptorInfo; import org.chromium.base.process_launcher.FileDescriptorInfo;
import org.chromium.content.browser.BaseChildProcessConnection; 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 * A Service that assists the ChildProcessLauncherTest that responds to one message, which
...@@ -53,53 +46,6 @@ public class ChildProcessLauncherTestHelperService extends Service { ...@@ -53,53 +46,6 @@ public class ChildProcessLauncherTestHelperService extends Service {
private final HandlerThread mHandlerThread = new HandlerThread("Helper Service Handler"); 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 @Override
public void onCreate() { public void onCreate() {
CommandLine.init(null); CommandLine.init(null);
...@@ -125,7 +71,8 @@ public class ChildProcessLauncherTestHelperService extends Service { ...@@ -125,7 +71,8 @@ public class ChildProcessLauncherTestHelperService extends Service {
ChildProcessCreationParams params = new ChildProcessCreationParams( ChildProcessCreationParams params = new ChildProcessCreationParams(
getPackageName(), false, LibraryProcessType.PROCESS_CHILD, bindToCaller); getPackageName(), false, LibraryProcessType.PROCESS_CHILD, bindToCaller);
final BaseChildProcessConnection conn = 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 // 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 // has bound the connection to this service, manages the timeout via the lifetime of this
...@@ -136,10 +83,11 @@ public class ChildProcessLauncherTestHelperService extends Service { ...@@ -136,10 +83,11 @@ public class ChildProcessLauncherTestHelperService extends Service {
@Override @Override
public void run() { public void run() {
if (conn.getPid() != 0) { int pid = ChildProcessLauncherTestUtils.getConnectionPid(conn);
if (pid != 0) {
try { try {
mReplyTo.send(Message.obtain(null, MSG_BIND_SERVICE_REPLY, conn.getPid(), mReplyTo.send(Message.obtain(null, MSG_BIND_SERVICE_REPLY, pid,
conn.getServiceNumber())); ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn)));
} catch (RemoteException ex) { } catch (RemoteException ex) {
throw new RuntimeException(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