Bug 1314466 - part 5, Add service process manager r=snorp

This commit is contained in:
Randall Barker 2016-10-05 16:46:53 -07:00
parent 26e454ec14
commit de17af98b5
10 changed files with 468 additions and 53 deletions

View File

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.process;
import android.os.ParcelFileDescriptor;
interface IChildProcess {
void stop();
int getPid();
void start(in String[] args, in ParcelFileDescriptor crashReporterPfd, in ParcelFileDescriptor ipcPfd);
}

View File

@ -34,6 +34,8 @@ import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PanZoomController;
import org.mozilla.gecko.permissions.Permissions;
import org.mozilla.gecko.process.GeckoProcessManager;
import org.mozilla.gecko.process.GeckoServiceChildProcess;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoRequest;
import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
@ -84,6 +86,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.os.Vibrator;
import android.provider.Settings;
@ -2274,4 +2277,9 @@ public class GeckoAppShell
}
return sScreenSize;
}
@WrapForJNI
private static int startGeckoServiceChildProcess(String type, String[] args, int crashFd, int ipcFd) {
return GeckoProcessManager.getInstance().start(type, args, crashFd, ipcFd);
}
}

View File

@ -21,6 +21,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
@ -32,6 +33,7 @@ import java.util.ArrayList;
import java.util.Locale;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.StringTokenizer;
public class GeckoThread extends Thread {
private static final String LOGTAG = "GeckoThread";
@ -134,11 +136,18 @@ public class GeckoThread extends Thread {
private final String mAction;
private final boolean mDebugging;
private String[] mChildProcessArgs;
private int mCrashFileDescriptor;
private int mIPCFileDescriptor;
GeckoThread(GeckoProfile profile, String args, String action, boolean debugging) {
mProfile = profile;
mArgs = args;
mAction = action;
mDebugging = debugging;
mChildProcessArgs = null;
mCrashFileDescriptor = -1;
mIPCFileDescriptor = -1;
setName("Gecko");
}
@ -152,6 +161,16 @@ public class GeckoThread extends Thread {
return false;
}
public static boolean initChildProcess(GeckoProfile profile, String[] args, int crashFd, int ipcFd, boolean debugging) {
if (init(profile, null, null, debugging)) {
sGeckoThread.mChildProcessArgs = args;
sGeckoThread.mCrashFileDescriptor = crashFd;
sGeckoThread.mIPCFileDescriptor = ipcFd;
return true;
}
return false;
}
private static boolean canUseProfile(final Context context, final GeckoProfile profile,
final String profileName, final File profileDir) {
if (profileDir != null && !profileDir.isDirectory()) {
@ -403,35 +422,45 @@ public class GeckoThread extends Thread {
return resourcePath;
}
private String addCustomProfileArg(String args) {
String profileArg = "";
private void addCustomProfileArg(String args, ArrayList<String> list) {
// Make sure a profile exists.
final GeckoProfile profile = getProfile();
profile.getDir(); // call the lazy initializer
// If args don't include the profile, make sure it's included.
if (args == null || !args.matches(".*\\B-(P|profile)\\s+\\S+.*")) {
if (profile.isCustomProfile()) {
profileArg = " -profile " + profile.getDir().getAbsolutePath();
} else {
profileArg = " -P " + profile.getName();
boolean needsProfile = true;
if (args != null) {
StringTokenizer st = new StringTokenizer(args);
while (st.hasMoreTokens()) {
String token = st.nextToken();
if ("-P".equals(token) || "-profile".equals(token)) {
needsProfile = false;
}
list.add(token);
}
}
return (args != null ? args : "") + profileArg;
// If args don't include the profile, make sure it's included.
if (args == null || needsProfile) {
if (profile.isCustomProfile()) {
list.add("-profile");
list.add(profile.getDir().getAbsolutePath());
} else {
list.add("-P");
list.add(profile.getName());
}
}
}
private String getGeckoArgs(final String apkPath) {
private String[] getGeckoArgs(final String apkPath) {
// argv[0] is the program name, which for us is the package name.
final Context context = GeckoAppShell.getApplicationContext();
final StringBuilder args = new StringBuilder(context.getPackageName());
args.append(" -greomni ").append(apkPath);
final ArrayList<String> args = new ArrayList<String>();
args.add(context.getPackageName());
args.add("-greomni");
args.add(apkPath);
final String userArgs = addCustomProfileArg(mArgs);
if (userArgs != null) {
args.append(' ').append(userArgs);
}
addCustomProfileArg(mArgs, args);
// In un-official builds, we want to load Javascript resources fresh
// with each build. In official builds, the startup cache is purged by
@ -440,10 +469,10 @@ public class GeckoThread extends Thread {
if (!AppConstants.MOZILLA_OFFICIAL) {
Log.w(LOGTAG, "STARTUP PERFORMANCE WARNING: un-official build: purging the " +
"startup (JavaScript) caches.");
args.append(" -purgecaches");
args.add("-purgecaches");
}
return args.toString();
return args.toArray(new String[args.size()]);
}
public static GeckoProfile getActiveProfile() {
@ -493,7 +522,13 @@ public class GeckoThread extends Thread {
}
}
final String args = getGeckoArgs(initGeckoEnvironment());
final String[] args;
if (mChildProcessArgs != null) {
initGeckoEnvironment();
args = mChildProcessArgs;
} else {
args = getGeckoArgs(initGeckoEnvironment());
}
// This can only happen after the call to initGeckoEnvironment
// above, because otherwise the JNI code hasn't been loaded yet.
@ -506,11 +541,12 @@ public class GeckoThread extends Thread {
Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - runGecko");
if (!AppConstants.MOZILLA_OFFICIAL) {
Log.i(LOGTAG, "RunGecko - args = " + args);
String msg = new String("RunGecko - args =" + TextUtils.join(" ", args));
Log.i(LOGTAG, msg);
}
// And go.
GeckoLoader.nativeRun(args);
GeckoLoader.nativeRun(args, mCrashFileDescriptor, mIPCFileDescriptor);
// And... we're done.
setState(State.EXITED);

View File

@ -16,8 +16,10 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Environment;
import java.util.ArrayList;
import android.util.Log;
import org.mozilla.gecko.annotation.JNITarget;
@ -35,6 +37,7 @@ public final class GeckoLoader {
private static boolean sSQLiteLibsLoaded;
private static boolean sNSSLibsLoaded;
private static boolean sMozGlueLoaded;
private static String[] sEnvList;
private GeckoLoader() {
// prevent instantiation
@ -123,18 +126,31 @@ public final class GeckoLoader {
sIntent = intent;
}
public static void addEnvironmentToIntent(Intent intent) {
if (sEnvList != null) {
for (int ix = 0; ix < sEnvList.length; ix++) {
intent.putExtra("env" + ix, sEnvList[ix]);
}
}
}
public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) {
// if we have an intent (we're being launched by an activity)
// read in any environmental variables from it here
final SafeIntent intent = sIntent;
if (intent != null) {
final ArrayList<String> envList = new ArrayList<String>();
String env = intent.getStringExtra("env0");
Log.d(LOGTAG, "Gecko environment env0: " + env);
for (int c = 1; env != null; c++) {
envList.add(env);
putenv(env);
env = intent.getStringExtra("env" + c);
Log.d(LOGTAG, "env" + c + ": " + env);
}
if (envList.size() > 0) {
sEnvList = envList.toArray(new String[envList.size()]);
}
}
putenv("MOZ_ANDROID_PACKAGE_NAME=" + context.getPackageName());
@ -548,7 +564,7 @@ public final class GeckoLoader {
private static native void putenv(String map);
// These methods are implemented in mozglue/android/APKOpen.cpp
public static native void nativeRun(String args);
public static native void nativeRun(String[] args, int crashFd, int ipcFd);
private static native void loadGeckoLibsNative(String apkName);
private static native void loadSQLiteLibsNative(String apkName);
private static native void loadNSSLibsNative(String apkName);

View File

@ -0,0 +1,175 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.process;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.util.ThreadUtils;
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.RemoteException;
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 {
private static final String LOGTAG = "GeckoProcessManager";
private static final GeckoProcessManager INSTANCE = new GeckoProcessManager();
public static GeckoProcessManager getInstance() {
return INSTANCE;
}
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;
public ChildConnection(String type) {
mType = type;
}
void prepareToWait() {
mWait = true;
}
void waitForChild() {
ThreadUtils.assertNotOnUiThread();
synchronized(this) {
if (mWait) {
try {
this.wait(5000); // 5 seconds
} catch (final InterruptedException e) {
Log.e(LOGTAG, "Interrupted waiting for child service to start", e);
}
}
}
}
void clearWait() {
mWait = false;
}
@Override
public 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);
}
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();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (mChild != null) {
synchronized(INSTANCE.mConnections) {
INSTANCE.mConnections.remove(mType);
}
mChild.asBinder().unlinkToDeath(this, 0);
mChild = null;
}
synchronized(this) {
if (mWait) {
mWait = false;
this.notifyAll();
}
}
}
@Override
public void binderDied() {
Log.e(LOGTAG,"Binder died. Attempt to unbind service: " + mType + " " + mPid);
try {
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 GeckoProcessManager() {
mConnections = new SimpleArrayMap<String, ChildConnection>();
}
public int start(String type, String[] args, int crashFd, int ipcFd) {
ChildConnection connection = null;
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();
}
}
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(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;
}
} // GeckoProcessManager

View File

@ -0,0 +1,101 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.process;
import org.mozilla.gecko.annotation.JNITarget;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.mozglue.SafeIntent;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.util.Log;
public class GeckoServiceChildProcess extends Service {
static private String LOGTAG = "GeckoServiceChildProcess";
private boolean serviceStarted;
static private void stop() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Process.killProcess(Process.myPid());;
}
});
}
public void onCreate() {
super.onCreate();
}
public void onDestroy() {
super.onDestroy();
}
public int onStartCommand(final Intent intent, final int flags, final int startId) {
return Service.START_STICKY;
}
private Binder mBinder = new IChildProcess.Stub() {
@Override
public void stop() {
GeckoServiceChildProcess.stop();
}
@Override
public int getPid() {
return Process.myPid();
}
@Override
public void start(final String[] args,
final ParcelFileDescriptor crashReporterPfd,
final ParcelFileDescriptor ipcPfd) {
if (serviceStarted) {
Log.e(LOGTAG, "Attempting to start a service that has already been started.");
return;
}
serviceStarted = true;
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(null, args, crashReporterFd, ipcFd, false)) {
GeckoThread.launch();
}
}
});
}
};
public IBinder onBind(final Intent intent) {
GeckoLoader.setLastIntent(new SafeIntent(intent));
return mBinder;
}
public boolean onUnbind(Intent intent) {
Log.i(LOGTAG, "Service has been unbound. Stopping.");
stop();
return false;
}
@JNITarget
static public final class geckomediaplugin extends GeckoServiceChildProcess {}
@JNITarget
static public final class tab extends GeckoServiceChildProcess {}
}

View File

@ -405,30 +405,86 @@ Java_org_mozilla_gecko_mozglue_GeckoLoader_loadNSSLibsNative(JNIEnv *jenv, jclas
jenv->ReleaseStringUTFChars(jApkName, str);
}
typedef void (*GeckoStart_t)(JNIEnv*, char*, const nsXREAppData*);
extern "C" NS_EXPORT void MOZ_JNICALL
Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jstring jargs)
static char**
CreateArgvFromObjectArray(JNIEnv *jenv, jobjectArray jargs, int* length)
{
GeckoStart_t GeckoStart;
xul_dlsym("GeckoStart", &GeckoStart);
if (GeckoStart == nullptr)
return;
// XXX: java doesn't give us true UTF8, we should figure out something
// better to do here
int len = jenv->GetStringUTFLength(jargs);
// GeckoStart needs to write in the args buffer, so we need a copy.
char *args = (char *) malloc(len + 1);
jenv->GetStringUTFRegion(jargs, 0, len, args);
args[len] = '\0';
ElfLoader::Singleton.ExpectShutdown(false);
GeckoStart(jenv, args, &sAppData);
ElfLoader::Singleton.ExpectShutdown(true);
free(args);
size_t stringCount = jenv->GetArrayLength(jargs);
if (length) {
*length = stringCount;
}
if (!stringCount) {
return nullptr;
}
char** argv = new char*[stringCount + 1];
argv[stringCount] = nullptr;
for (size_t ix = 0; ix < stringCount; ix++) {
jstring string = (jstring) (jenv->GetObjectArrayElement(jargs, ix));
const char* rawString = jenv->GetStringUTFChars(string, nullptr);
const int strLength = jenv->GetStringUTFLength(string);
argv[ix] = strndup(rawString, strLength);
jenv->ReleaseStringUTFChars(string, rawString);
jenv->DeleteLocalRef(string);
}
return argv;
}
static void
FreeArgv(char** argv, int argc)
{
for (int ix=0; ix < argc; ix++) {
// String was allocated with strndup, so need to use free to deallocate.
free(argv[ix]);
}
delete[](argv);
}
typedef void (*GeckoStart_t)(JNIEnv*, char**, int, const nsXREAppData*);
typedef int GeckoProcessType;
extern "C" NS_EXPORT void MOZ_JNICALL
Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jobjectArray jargs, int crashFd, int ipcFd)
{
int argc = 0;
char** argv = CreateArgvFromObjectArray(jenv, jargs, &argc);
if (ipcFd < 0) {
GeckoStart_t GeckoStart;
xul_dlsym("GeckoStart", &GeckoStart);
if (GeckoStart == nullptr) {
FreeArgv(argv, argc);
return;
}
ElfLoader::Singleton.ExpectShutdown(false);
GeckoStart(jenv, argv, argc, &sAppData);
ElfLoader::Singleton.ExpectShutdown(true);
} else {
void (*fXRE_SetAndroidChildFds)(int, int);
xul_dlsym("XRE_SetAndroidChildFds", &fXRE_SetAndroidChildFds);
void (*fXRE_SetProcessType)(char*);
xul_dlsym("XRE_SetProcessType", &fXRE_SetProcessType);
mozglueresult (*fXRE_InitChildProcess)(int, char**, void*);
xul_dlsym("XRE_InitChildProcess", &fXRE_InitChildProcess);
fXRE_SetAndroidChildFds(crashFd, ipcFd);
fXRE_SetProcessType(argv[argc - 1]);
XREChildData childData;
fXRE_InitChildProcess(argc - 1, argv, &childData);
}
FreeArgv(argv, argc);
}
extern "C" NS_EXPORT mozglueresult
ChildProcessInit(int argc, char* argv[])
{

View File

@ -22,7 +22,7 @@
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, MOZ_APP_NAME, args)
extern "C" NS_EXPORT void
GeckoStart(JNIEnv* env, char* data, const nsXREAppData* appData)
GeckoStart(JNIEnv* env, char** argv, int argc, const nsXREAppData* appData)
{
mozilla::jni::SetGeckoThreadEnv(env);
@ -35,20 +35,12 @@ GeckoStart(JNIEnv* env, char* data, const nsXREAppData* appData)
}
#endif
if (!data) {
if (!argv) {
LOG("Failed to get arguments for GeckoStart\n");
return;
}
nsTArray<char *> targs;
char *arg = strtok(data, " ");
while (arg) {
targs.AppendElement(arg);
arg = strtok(nullptr, " ");
}
targs.AppendElement(static_cast<char *>(nullptr));
int result = XRE_main(targs.Length() - 1, targs.Elements(), appData, 0);
int result = XRE_main(argc, argv, appData, 0);
if (result)
LOG("XRE_main returned %d", result);

View File

@ -50,6 +50,9 @@
#include "base/message_loop.h"
#include "base/process_util.h"
#include "chrome/common/child_process.h"
#if defined(MOZ_WIDGET_ANDROID)
#include "chrome/common/ipc_channel.h"
#endif // defined(MOZ_WIDGET_ANDROID)
#include "mozilla/ipc/BrowserProcessSubThread.h"
#include "mozilla/ipc/GeckoChildProcessHost.h"
@ -222,6 +225,17 @@ GeckoProcessType sChildProcessType = GeckoProcessType_Default;
} // namespace startup
} // namespace mozilla
#if defined(MOZ_WIDGET_ANDROID)
void
XRE_SetAndroidChildFds (int crashFd, int ipcFd)
{
#if defined(MOZ_CRASHREPORTER)
CrashReporter::SetNotificationPipeForChild(crashFd);
#endif // defined(MOZ_CRASHREPORTER)
IPC::Channel::SetClientChannelFd(ipcFd);
}
#endif // defined(MOZ_WIDGET_ANDROID)
void
XRE_SetProcessType(const char* aProcessTypeString)
{

View File

@ -423,6 +423,11 @@ static_assert(MOZ_ARRAY_LENGTH(kGeckoProcessTypeString) ==
XRE_API(const char*,
XRE_ChildProcessTypeToString, (GeckoProcessType aProcessType))
#if defined(MOZ_WIDGET_ANDROID)
XRE_API(void,
XRE_SetAndroidChildFds, (int crashFd, int ipcFd))
#endif // defined(MOZ_WIDGET_ANDROID)
XRE_API(void,
XRE_SetProcessType, (const char* aProcessTypeString))