зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1404144 - 1. Refactor child process code to support preloading; r=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
This commit is contained in:
Родитель
c252244f67
Коммит
ffe3e86076
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -14,18 +14,15 @@ import android.content.ComponentName;
|
|||
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";
|
||||
|
@ -44,139 +41,180 @@ public final class GeckoProcessManager extends IProcessManager.Stub {
|
|||
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;
|
||||
}
|
||||
|
||||
public synchronized void unbind() {
|
||||
final int pid = getPid();
|
||||
if (pid != 0) {
|
||||
Process.killProcess(pid);
|
||||
waitForChildLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void clearWait() {
|
||||
mWait = false;
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
Log.w(LOGTAG, "Attempting to kill running child " + type);
|
||||
connection.unbind();
|
||||
return start(type, args, crashFd, ipcFd, /* retry */ true);
|
||||
}
|
||||
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
ParcelFileDescriptor crashPfd = null;
|
||||
if (crashFd >= 0) {
|
||||
crashPfd = ParcelFileDescriptor.fromFd(crashFd);
|
||||
}
|
||||
ParcelFileDescriptor ipcPfd = ParcelFileDescriptor.fromFd(ipcFd);
|
||||
connection.mChild.start(this, args, crashPfd, ipcPfd);
|
||||
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
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
package org.mozilla.gecko.process;
|
||||
|
||||
import org.mozilla.gecko.annotation.JNITarget;
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.IGeckoEditableParent;
|
||||
|
@ -29,15 +28,6 @@ public class GeckoServiceChildProcess extends Service {
|
|||
|
||||
private static IProcessManager sProcessManager;
|
||||
|
||||
static private void stop() {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Process.killProcess(Process.myPid());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
private static IGeckoEditableParent getEditableParent(final long contentId,
|
||||
final long tabId) {
|
||||
|
@ -49,69 +39,69 @@ public class GeckoServiceChildProcess extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
GeckoAppShell.ensureCrashHandling();
|
||||
GeckoAppShell.setApplicationContext(getApplicationContext());
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
||||
return Service.START_STICKY;
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private Binder mBinder = new IChildProcess.Stub() {
|
||||
@Override
|
||||
public void stop() {
|
||||
GeckoServiceChildProcess.stop();
|
||||
}
|
||||
|
||||
private final Binder mBinder = new IChildProcess.Stub() {
|
||||
@Override
|
||||
public int getPid() {
|
||||
return Process.myPid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(final IProcessManager procMan,
|
||||
final String[] args,
|
||||
final ParcelFileDescriptor crashReporterPfd,
|
||||
final ParcelFileDescriptor ipcPfd) {
|
||||
if (sProcessManager != null) {
|
||||
Log.e(LOGTAG, "Attempting to start a service that has already been started.");
|
||||
return;
|
||||
public boolean start(final IProcessManager procMan,
|
||||
final String[] args,
|
||||
final ParcelFileDescriptor crashReporterPfd,
|
||||
final ParcelFileDescriptor ipcPfd) {
|
||||
synchronized (GeckoServiceChildProcess.class) {
|
||||
if (sProcessManager != null) {
|
||||
Log.e(LOGTAG, "Child process already started");
|
||||
return false;
|
||||
}
|
||||
sProcessManager = procMan;
|
||||
}
|
||||
sProcessManager = procMan;
|
||||
|
||||
final int crashReporterFd = crashReporterPfd != null ? crashReporterPfd.detachFd() : -1;
|
||||
final int crashReporterFd = crashReporterPfd != null ?
|
||||
crashReporterPfd.detachFd() : -1;
|
||||
final int ipcFd = ipcPfd != null ? ipcPfd.detachFd() : -1;
|
||||
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GeckoAppShell.ensureCrashHandling();
|
||||
GeckoAppShell.setApplicationContext(getApplicationContext());
|
||||
if (GeckoThread.initChildProcess(args, crashReporterFd, ipcFd)) {
|
||||
GeckoThread.launch();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(final Intent intent) {
|
||||
GeckoLoader.setLastIntent(new SafeIntent(intent));
|
||||
GeckoThread.launch(); // Preload Gecko.
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
Log.i(LOGTAG, "Service has been unbound. Stopping.");
|
||||
stop();
|
||||
Process.killProcess(Process.myPid());
|
||||
return false;
|
||||
}
|
||||
|
||||
@JNITarget
|
||||
static public final class geckomediaplugin extends GeckoServiceChildProcess {}
|
||||
public static final class geckomediaplugin extends GeckoServiceChildProcess {}
|
||||
|
||||
@JNITarget
|
||||
static public final class tab extends GeckoServiceChildProcess {}
|
||||
public static final class tab extends GeckoServiceChildProcess {}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче