From f0e4a6cd2c2f74d85d8141054c8c1b5bea78cd46 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Tue, 30 May 2017 03:04:38 -0700 Subject: [PATCH] Add missing Java files to RN fbjni sync Reviewed By: mhorowitz Differential Revision: D5129224 fbshipit-source-id: d9fb5f95505f6be7d3d87ead67dbfaa951c03434 --- .../com/facebook/jni/CpuCapabilitiesJni.java | 27 ++++ .../com/facebook/jni/DestructorThread.java | 132 ++++++++++++++++++ .../com/facebook/jni/HybridClassBase.java | 8 ++ .../java/com/facebook/jni/HybridData.java | 58 ++++++-- .../com/facebook/jni/JniTerminateHandler.java | 15 ++ 5 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/jni/CpuCapabilitiesJni.java create mode 100644 ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java create mode 100644 ReactAndroid/src/main/java/com/facebook/jni/HybridClassBase.java create mode 100644 ReactAndroid/src/main/java/com/facebook/jni/JniTerminateHandler.java diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CpuCapabilitiesJni.java b/ReactAndroid/src/main/java/com/facebook/jni/CpuCapabilitiesJni.java new file mode 100644 index 0000000000..f8f3054c88 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/CpuCapabilitiesJni.java @@ -0,0 +1,27 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; + +/** + * Utility class to determine CPU capabilities + */ +@DoNotStrip +public class CpuCapabilitiesJni { + + static { + SoLoader.loadLibrary("fb"); + } + + @DoNotStrip + public static native boolean nativeDeviceSupportsNeon(); + + @DoNotStrip + public static native boolean nativeDeviceSupportsVFPFP16(); + + @DoNotStrip + public static native boolean nativeDeviceSupportsX86(); + +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java b/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java new file mode 100644 index 0000000000..6b00078f0b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/DestructorThread.java @@ -0,0 +1,132 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A thread which invokes the "destruct" routine for objects after they have been garbage collected. + * + * An object which needs to be destructed should create a static subclass of {@link Destructor}. + * Once the referent object is garbage collected, the DestructorThread will callback to the + * {@link Destructor#destruct()} method. + * + * The underlying thread in DestructorThread starts when the first Destructor is constructed + * and then runs indefinitely. + */ +public class DestructorThread { + + /** + * N.B The Destructor SHOULD NOT refer back to its referent object either explicitly or + * implicitly (for example, as a non-static inner class). This will create a reference cycle where + * the referent object will never be garbage collected. + */ + public abstract static class Destructor extends PhantomReference { + + private Destructor next; + private Destructor previous; + + Destructor(Object referent) { + super(referent, sReferenceQueue); + sDestructorStack.push(this); + } + + private Destructor() { + super(null, sReferenceQueue); + } + + /** Callback which is invoked when the original object has been garbage collected. */ + abstract void destruct(); + } + + /** A list to keep all active Destructors in memory confined to the Destructor thread. */ + private static DestructorList sDestructorList; + /** A thread safe stack where new Destructors are placed before being add to sDestructorList. */ + private static DestructorStack sDestructorStack; + private static ReferenceQueue sReferenceQueue; + private static Thread sThread; + + static { + sDestructorStack = new DestructorStack(); + sReferenceQueue = new ReferenceQueue(); + sDestructorList = new DestructorList(); + sThread = new Thread("HybridData DestructorThread") { + @Override + public void run() { + while (true) { + try { + Destructor current = (Destructor) sReferenceQueue.remove(); + current.destruct(); + + // If current is in the sDestructorStack, + // transfer all the Destructors in the stack to the list. + if (current.previous == null) { + sDestructorStack.transferAllToList(); + } + + DestructorList.drop(current); + } catch (InterruptedException e) { + // Continue. This thread should never be terminated. + } + } + } + }; + + sThread.start(); + } + + private static class Terminus extends Destructor { + @Override + void destruct() { + throw new IllegalStateException("Cannot destroy Terminus Destructor."); + } + } + + /** This is a thread safe, lock-free Treiber-like Stack of Destructors. */ + private static class DestructorStack { + private AtomicReference mHead = new AtomicReference<>(); + + public void push(Destructor newHead) { + Destructor oldHead; + do { + oldHead = mHead.get(); + newHead.next = oldHead; + } while (!mHead.compareAndSet(oldHead, newHead)); + } + + public void transferAllToList() { + Destructor current = mHead.getAndSet(null); + while (current != null) { + Destructor next = current.next; + sDestructorList.enqueue(current); + current = next; + } + } + } + + /** A doubly-linked list of Destructors. */ + private static class DestructorList { + private Destructor mHead; + + public DestructorList() { + mHead = new Terminus(); + mHead.next = new Terminus(); + mHead.next.previous = mHead; + } + + public void enqueue(Destructor current) { + current.next = mHead.next; + mHead.next = current; + + current.next.previous = current; + current.previous = mHead; + } + + private static void drop(Destructor current) { + current.next.previous = current.previous; + current.previous.next = current.next; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/HybridClassBase.java b/ReactAndroid/src/main/java/com/facebook/jni/HybridClassBase.java new file mode 100644 index 0000000000..a20b92b8f1 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/HybridClassBase.java @@ -0,0 +1,8 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; +import com.facebook.proguard.annotations.DoNotStrip; + +@DoNotStrip +public abstract class HybridClassBase extends HybridData { +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java b/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java index e0b864b08d..9ace5f3ddc 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java +++ b/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java @@ -2,6 +2,8 @@ package com.facebook.jni; +import android.util.Log; + import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.soloader.SoLoader; @@ -10,11 +12,9 @@ import com.facebook.soloader.SoLoader; * * NB: THREAD SAFETY * - * {@link #dispose} deletes the corresponding native object on whatever thread - * the method is called on. In the common case when this is called by - * HybridData#finalize(), this will be called on the system finalizer - * thread. If you manually call resetNative() on the Java object, the C++ - * object will be deleted synchronously on that thread. + * {@link #resetNative} deletes the corresponding native object synchronously on whatever thread + * the method is called on. Otherwise, deletion will occur on the {@link DestructorThread} + * thread. */ @DoNotStrip public class HybridData { @@ -23,27 +23,57 @@ public class HybridData { SoLoader.loadLibrary("fb"); } - // Private C++ instance @DoNotStrip - private long mNativePointer = 0; + private Destructor mDestructor = new Destructor(this); /** * To explicitly delete the instance, call resetNative(). If the C++ * instance is referenced after this is called, a NullPointerException will * be thrown. resetNative() may be called multiple times safely. Because - * {@link #finalize} calls resetNative, the instance will not leak if this is + * the {@link DestructorThread} also calls resetNative, the instance will not leak if this is * not called, but timing of deletion and the thread the C++ dtor is called * on will be at the whim of the Java GC. If you want to control the thread * and timing of the destructor, you should call resetNative() explicitly. */ - public native void resetNative(); - - protected void finalize() throws Throwable { - resetNative(); - super.finalize(); + public synchronized void resetNative() { + mDestructor.destruct(); } + /** + * N.B. Thread safety. + * If you call isValid from a different thread than {@link #resetNative()} then be sure to + * do so while synchronizing on the hybrid. For example: + *

+   * synchronized(hybrid) {
+   *   if (hybrid.isValid) {
+   *     // Do stuff.
+   *   }
+   * }
+   * 
+ */ public boolean isValid() { - return mNativePointer != 0; + return mDestructor.mNativePointer != 0; + } + + public static class Destructor extends DestructorThread.Destructor { + + // Private C++ instance + @DoNotStrip + private long mNativePointer; + + Destructor(Object referent) { + super(referent); + } + + @Override + void destruct() { + // When invoked from the DestructorThread instead of resetNative, + // the DestructorThread has exclusive ownership of the HybridData + // so synchronization is not necessary. + deleteNative(mNativePointer); + mNativePointer = 0; + } + + static native void deleteNative(long pointer); } } diff --git a/ReactAndroid/src/main/java/com/facebook/jni/JniTerminateHandler.java b/ReactAndroid/src/main/java/com/facebook/jni/JniTerminateHandler.java new file mode 100644 index 0000000000..29f66003ae --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/JniTerminateHandler.java @@ -0,0 +1,15 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.jni; + +public class JniTerminateHandler { + public static void handleTerminate(Throwable t) throws Throwable { + Thread.UncaughtExceptionHandler h = Thread.getDefaultUncaughtExceptionHandler(); + if (h == null) { + // Odd. Let the default std::terminate_handler deal with it. + return; + } + h.uncaughtException(Thread.currentThread(), t); + // That should exit. If it doesn't, let the default handler deal with it. + } +}