Bug 1594820: Part 1 - Modify XPCOMEventTarget to accept method calls before JNI is ready; r=snorp

Since `XPCOMEventTarget` uses JNI, this patch makes it possible for consumers to
retrieve and invoke methods on one without needing to worry about whether JNI
is actually up yet.

To achieve this, we create the `IXPCOMEventTarget` interface, and observe that
both of its methods can be handled by a proxy if JNI is not ready:

* Calls to `dispatch` may be enqueued until JNI is up;
* Observe that, when JNI is not up yet, the result of `isOnCurrentThread`
  can never be `true`.

Once JNI is up and the event targets have been resolved, the proxies are
replaced with the real, concrete `XPCOMEventTarget`s and are no longer used for
the remainder of the Gecko instance's lifetime.

Differential Revision: https://phabricator.services.mozilla.com/D57837

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Aaron Klotz 2020-01-09 21:39:31 +00:00
Родитель d447a96547
Коммит cec0194852
3 изменённых файлов: 113 добавлений и 24 удалений

Просмотреть файл

@ -0,0 +1,11 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util;
public interface IXPCOMEventTarget {
public void dispatch(final Runnable runnable);
public boolean isOnCurrentThread();
}

Просмотреть файл

@ -6,43 +6,81 @@
package org.mozilla.gecko.util; package org.mozilla.gecko.util;
import org.mozilla.gecko.annotation.WrapForJNI; import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.mozglue.JNIObject; import org.mozilla.gecko.mozglue.JNIObject;
import org.mozilla.geckoview.BuildConfig;
/** /**
* Wrapper for nsIEventTarget, enabling seamless dispatch of java runnables to * Wrapper for nsIEventTarget, enabling seamless dispatch of java runnables to
* Gecko event queues. * Gecko event queues.
*/ */
@WrapForJNI @WrapForJNI
public final class XPCOMEventTarget extends JNIObject { public final class XPCOMEventTarget extends JNIObject implements IXPCOMEventTarget {
@Override
public void dispatch(final Runnable runnable) { public void dispatch(final Runnable runnable) {
dispatchNative(new JNIRunnable(runnable)); dispatchNative(new JNIRunnable(runnable));
} }
public static synchronized XPCOMEventTarget mainThread() { public static synchronized IXPCOMEventTarget mainThread() {
if (mMainThread == null) { if (mMainThread == null) {
mMainThread = createWrapper("main"); mMainThread = new AsyncProxy("main");
} }
return mMainThread; return mMainThread;
} }
private static XPCOMEventTarget mMainThread = null; private static IXPCOMEventTarget mMainThread = null;
public static synchronized XPCOMEventTarget launcherThread() { public static synchronized IXPCOMEventTarget launcherThread() {
if (mLauncherThread == null) { if (mLauncherThread == null) {
mLauncherThread = createWrapper("launcher"); mLauncherThread = new AsyncProxy("launcher");
} }
return mLauncherThread; return mLauncherThread;
} }
private static XPCOMEventTarget mLauncherThread = null; private static IXPCOMEventTarget mLauncherThread = null;
public static void assertOnLauncherThread() {
if (BuildConfig.DEBUG && !launcherThread().isOnCurrentThread()) {
throw new IllegalThreadStateException("Expected to be running on XPCOM launcher thread");
}
}
private static synchronized IXPCOMEventTarget getTarget(final String name) {
if (name.equals("launcher")) {
return mLauncherThread;
} else if (name.equals("main")) {
return mMainThread;
} else {
throw new RuntimeException("Attempt to assign to unknown thread named " + name);
}
}
@WrapForJNI
private static synchronized void setTarget(final String name, final XPCOMEventTarget target) {
if (name.equals("main")) {
mMainThread = target;
} else if (name.equals("launcher")) {
mLauncherThread = target;
} else {
throw new RuntimeException("Attempt to assign to unknown thread named " + name);
}
}
@Override
public native boolean isOnCurrentThread(); public native boolean isOnCurrentThread();
private native void dispatchNative(JNIRunnable runnable);
private static native XPCOMEventTarget createWrapper(String name); private native void dispatchNative(final JNIRunnable runnable);
@WrapForJNI
private static synchronized void resolveAndDispatch(final String name, final Runnable runnable) {
getTarget(name).dispatch(runnable);
}
private static native void resolveAndDispatchNative(final String name, final Runnable runnable);
@Override @Override
protected native void disposeNative(); protected native void disposeNative();
@WrapForJNI @WrapForJNI
final class JNIRunnable { private static final class JNIRunnable {
JNIRunnable(final Runnable inner) { JNIRunnable(final Runnable inner) {
mInner = inner; mInner = inner;
} }
@ -54,5 +92,43 @@ public final class XPCOMEventTarget extends JNIObject {
private Runnable mInner; private Runnable mInner;
} }
private static final class AsyncProxy implements IXPCOMEventTarget {
private String mTargetName;
public AsyncProxy(final String targetName) {
mTargetName = targetName;
}
@Override
public void dispatch(final Runnable runnable) {
final IXPCOMEventTarget target = XPCOMEventTarget.getTarget(mTargetName);
if (target != null && target instanceof XPCOMEventTarget) {
target.dispatch(runnable);
return;
}
GeckoThread.queueNativeCallUntil(GeckoThread.State.JNI_READY,
XPCOMEventTarget.class, "resolveAndDispatchNative",
String.class, mTargetName, Runnable.class, runnable);
}
@Override
public boolean isOnCurrentThread() {
final IXPCOMEventTarget target = XPCOMEventTarget.getTarget(mTargetName);
// If target is not yet a XPCOMEventTarget then JNI is not
// initialized yet. If JNI is not initialized yet, then we cannot
// possibly be running on a target with an XPCOMEventTarget.
if (target == null || !(target instanceof XPCOMEventTarget)) {
return false;
}
// Otherwise we have a real XPCOMEventTarget, so we can delegate
// this call to it.
return target.isOnCurrentThread();
}
}
} }

Просмотреть файл

@ -363,24 +363,26 @@ class XPCOMEventTargetWrapper final
bool IsOnCurrentThread() { return mTarget->IsOnCurrentThread(); } bool IsOnCurrentThread() { return mTarget->IsOnCurrentThread(); }
// Instantiates a wrapper. The Java code calls this only once per wrapped static void Init() {
// thread, and caches the result. java::XPCOMEventTarget::Natives<XPCOMEventTargetWrapper>::Init();
static java::XPCOMEventTarget::LocalRef CreateWrapper( CreateWrapper(NS_LITERAL_STRING("main"), do_GetMainThread());
mozilla::jni::String::Param aName) { if (XRE_IsParentProcess()) {
nsString name(aName->ToString()); CreateWrapper(NS_LITERAL_STRING("launcher"), ipc::GetIPCLauncher());
nsCOMPtr<nsIEventTarget> target;
if (name.EqualsLiteral("main")) {
target = do_GetMainThread();
} else if (name.EqualsLiteral("launcher")) {
target = ipc::GetIPCLauncher();
} else {
MOZ_CRASH("Trying to create JNI wrapper for unknown XPCOM thread");
} }
}
static void CreateWrapper(mozilla::jni::String::Param aName,
nsCOMPtr<nsIEventTarget> aTarget) {
auto java = java::XPCOMEventTarget::New(); auto java = java::XPCOMEventTarget::New();
auto native = MakeUnique<XPCOMEventTargetWrapper>(target.forget()); auto native = MakeUnique<XPCOMEventTargetWrapper>(aTarget.forget());
AttachNative(java, std::move(native)); AttachNative(java, std::move(native));
return java;
java::XPCOMEventTarget::SetTarget(aName, java);
}
static void ResolveAndDispatchNative(mozilla::jni::String::Param aName,
mozilla::jni::Object::Param aRunnable) {
java::XPCOMEventTarget::ResolveAndDispatch(aName, aRunnable);
} }
explicit XPCOMEventTargetWrapper(already_AddRefed<nsIEventTarget> aTarget) explicit XPCOMEventTargetWrapper(already_AddRefed<nsIEventTarget> aTarget)