Skip to content

Commit

Permalink
Bug 1404144 - 1. Refactor child process code to support preloading; r…
Browse files Browse the repository at this point in the history
…=rbarker

Refactor the code in GeckoProcessManager and GeckoServiceChildProcess so
that, we can have a ChildConnection object that's bound but not started.
This way we can bind the connection to preload Gecko child process, but
hold off starting until told by Gecko main process.

Some code is simplified. For example, `IChildProcess.stop` is removed in
favor of killing the child process directly.

MozReview-Commit-ID: 4XgmTuT0IAs

--HG--
extra : rebase_source : 94fe748556c66f639d1f8e5bb26c28ea3ed950b3
  • Loading branch information
Jim Chen committed Oct 5, 2017
1 parent c252244 commit ffe3e86
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import org.mozilla.gecko.process.IProcessManager;
import android.os.ParcelFileDescriptor;

interface IChildProcess {
void stop();
int getPid();
void start(in IProcessManager procMan, in String[] args, in ParcelFileDescriptor crashReporterPfd, in ParcelFileDescriptor ipcPfd);
boolean start(in IProcessManager procMan, in String[] args, in ParcelFileDescriptor crashReporterPfd, in ParcelFileDescriptor ipcPfd);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.v4.util.SimpleArrayMap;
import android.view.Surface;
import android.util.Log;

import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.Map;

public final class GeckoProcessManager extends IProcessManager.Stub {
private static final String LOGTAG = "GeckoProcessManager";
Expand All @@ -44,139 +41,180 @@ public IGeckoEditableParent getEditableParent(final long contentId, final long t
return nativeGetEditableParent(contentId, tabId);
}

private static final class ChildConnection implements ServiceConnection, IBinder.DeathRecipient {
public final String mType;
private boolean mWait = false;
public IChildProcess mChild = null;
public int mPid = 0;
private static final class ChildConnection implements ServiceConnection,
IBinder.DeathRecipient {
private static final int WAIT_TIMEOUT = 5000; // 5 seconds

private final String mType;
private boolean mWaiting;
private IChildProcess mChild;
private int mPid;

public ChildConnection(String type) {
mType = type;
}

void prepareToWait() {
mWait = true;
public synchronized int getPid() {
if (mPid == 0) {
try {
mPid = mChild.getPid();
} catch (final RemoteException e) {
Log.e(LOGTAG, "Cannot get pid for " + mType, e);
}
}
return mPid;
}

void waitForChild() {
ThreadUtils.assertNotOnUiThread();
synchronized (this) {
if (mWait) {
try {
this.wait(5000); // 5 seconds
} catch (final InterruptedException e) {
Log.e(LOGTAG, "Interrupted while waiting for child service", e);
}
public synchronized IChildProcess bind() {
if (mChild != null) {
return mChild;
}

final Context context = GeckoAppShell.getApplicationContext();
final Intent intent = new Intent();
intent.setClassName(context,
GeckoServiceChildProcess.class.getName() + '$' + mType);
GeckoLoader.addEnvironmentToIntent(intent);

if (context.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
waitForChildLocked();
if (mChild != null) {
return mChild;
}
}

Log.e(LOGTAG, "Cannot connect to process " + mType);
context.unbindService(this);
return null;
}

void clearWait() {
mWait = false;
public synchronized void unbind() {
final int pid = getPid();
if (pid != 0) {
Process.killProcess(pid);
waitForChildLocked();
}
}

private void waitForChildLocked() {
ThreadUtils.assertNotOnUiThread();
mWaiting = true;

final long startTime = SystemClock.uptimeMillis();
while (mWaiting && SystemClock.uptimeMillis() - startTime < WAIT_TIMEOUT) {
try {
wait(WAIT_TIMEOUT);
} catch (final InterruptedException e) {
}
}
mWaiting = false;
}

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
public synchronized void onServiceConnected(ComponentName name, IBinder service) {
try {
service.linkToDeath(this, 0);
} catch (final RemoteException e) {
Log.e(LOGTAG, "Failed to link ChildConnection to death of service.", e);
Log.e(LOGTAG, "Cannot link to death for " + mType, e);
}

mChild = IChildProcess.Stub.asInterface(service);
try {
mPid = mChild.getPid();
} catch (final RemoteException e) {
Log.e(LOGTAG, "Failed to get child " + mType + " process PID. Process may have died.", e);
}
synchronized (this) {
if (mWait) {
mWait = false;
this.notifyAll();
}
}
mWaiting = false;
notifyAll();
}

@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (INSTANCE.mConnections) {
INSTANCE.mConnections.remove(mType);
}
synchronized (this) {
if (mWait) {
mWait = false;
this.notifyAll();
}
}
public synchronized void onServiceDisconnected(ComponentName name) {
mChild = null;
mPid = 0;
mWaiting = false;
notifyAll();
}

@Override
public void binderDied() {
Log.e(LOGTAG, "Binder died. Attempt to unbind service: " + mType + " " + mPid);
try {
public synchronized void binderDied() {
Log.i(LOGTAG, "Binder died for " + mType);
if (mChild != null) {
mChild = null;
GeckoAppShell.getApplicationContext().unbindService(this);
} catch (final java.lang.IllegalArgumentException e) {
Log.e(LOGTAG, "Looks like connection was already unbound", e);
}
}
}

SimpleArrayMap<String, ChildConnection> mConnections;
private final SimpleArrayMap<String, ChildConnection> mConnections;

private GeckoProcessManager() {
mConnections = new SimpleArrayMap<String, ChildConnection>();
}

public int start(String type, String[] args, int crashFd, int ipcFd) {
ChildConnection connection = null;
private ChildConnection getConnection(final String type) {
synchronized (mConnections) {
connection = mConnections.get(type);
}
if (connection != null) {
Log.w(LOGTAG, "Attempting to start a child process service that is already running. Attempting to kill existing process first");
connection.prepareToWait();
try {
connection.mChild.stop();
connection.waitForChild();
} catch (final RemoteException e) {
connection.clearWait();
ChildConnection connection = mConnections.get(type);
if (connection == null) {
connection = new ChildConnection(type);
mConnections.put(type, connection);
}
return connection;
}
}

public void preload(final String... types) {
for (final String type : types) {
final ChildConnection connection = getConnection(type);
connection.bind();
connection.getPid();
}
}

@WrapForJNI
private static int start(final String type, final String[] args,
final int crashFd, final int ipcFd) {
return INSTANCE.start(type, args, crashFd, ipcFd, /* retry */ false);
}

private int start(final String type, final String[] args, final int crashFd,
final int ipcFd, final boolean retry) {
final ChildConnection connection = getConnection(type);
final IChildProcess child = connection.bind();
if (child == null) {
return 0;
}

final ParcelFileDescriptor crashPfd;
final ParcelFileDescriptor ipcPfd;
try {
connection = new ChildConnection(type);
Intent intent = new Intent();
intent.setClassName(GeckoAppShell.getApplicationContext(),
"org.mozilla.gecko.process.GeckoServiceChildProcess$" + type);
GeckoLoader.addEnvironmentToIntent(intent);
connection.prepareToWait();
GeckoAppShell.getApplicationContext().bindService(intent, connection, Context.BIND_AUTO_CREATE);
connection.waitForChild();
if (connection.mChild == null) {
// FAILED TO CONNECT.
Log.e(LOGTAG, "Failed to connect to child process of '" + type + "'");
GeckoAppShell.getApplicationContext().unbindService(connection);
crashPfd = (crashFd >= 0) ? ParcelFileDescriptor.fromFd(crashFd) : null;
ipcPfd = ParcelFileDescriptor.fromFd(ipcFd);
} catch (final IOException e) {
Log.e(LOGTAG, "Cannot create fd for " + type, e);
return 0;
}

boolean started = false;
try {
started = child.start(this, args, crashPfd, ipcPfd);
} catch (final RemoteException e) {
}

if (!started) {
if (retry) {
Log.e(LOGTAG, "Cannot restart child " + type);
return 0;
}
ParcelFileDescriptor crashPfd = null;
if (crashFd >= 0) {
crashPfd = ParcelFileDescriptor.fromFd(crashFd);
}
ParcelFileDescriptor ipcPfd = ParcelFileDescriptor.fromFd(ipcFd);
connection.mChild.start(this, args, crashPfd, ipcPfd);
Log.w(LOGTAG, "Attempting to kill running child " + type);
connection.unbind();
return start(type, args, crashFd, ipcFd, /* retry */ true);
}

try {
if (crashPfd != null) {
crashPfd.close();
}
ipcPfd.close();
synchronized (mConnections) {
mConnections.put(type, connection);
}
return connection.mPid;
} catch (final RemoteException e) {
Log.e(LOGTAG, "Unable to create child process for: '" + type + "'. Remote Exception:", e);
} catch (final IOException e) {
Log.e(LOGTAG, "Unable to create child process for: '" + type + "'. Error creating ParcelFileDescriptor needed to create intent:", e);
}

return 0;
return connection.getPid();
}

} // GeckoProcessManager
Loading

0 comments on commit ffe3e86

Please sign in to comment.