From a04ad8d8fbf789ce0e2f83ba766c7a8b4b647f85 Mon Sep 17 00:00:00 2001 From: David Vacca Date: Wed, 30 May 2018 21:48:57 -0700 Subject: [PATCH] First implementation of scheduleWork method Reviewed By: shergin Differential Revision: D7799412 fbshipit-source-id: b78a0bc0e80868f6877a31f862d7e6104fd4a049 --- .../com/facebook/react/fabric/Scheduler.java | 72 ++++++++++++++++--- .../fabric/events/FabricEventEmitter.java | 14 +++- .../uimanager/events/EventDispatcher.java | 11 +-- .../uimanager/events/ReactEventEmitter.java | 19 ++++- .../react/views/image/ImageLoadEvent.java | 5 +- 5 files changed, 97 insertions(+), 24 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/Scheduler.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/Scheduler.java index 87f7ce6db..9fb7bddb3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/Scheduler.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/Scheduler.java @@ -1,5 +1,13 @@ package com.facebook.react.fabric; +import android.util.Log; +import com.facebook.react.bridge.ReactContext; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + /** * This class allows Native code to schedule work in JS. * Work (following JS naming) represents a task that needs to be executed in JS. @@ -7,23 +15,59 @@ package com.facebook.react.fabric; * Four types of work are supported by this class: * * Synchronous: - * - Sync work -> flushSync - * - Work work -> flushSerial + * - Sync work -> flushSync() + * - Work work -> flushSerial() * * Asynchronous: - * - Interactive work (serial): -> scheduleSerial - * - Deferred work: -> scheduleWork + * - Interactive work (serial): -> scheduleSerial() + * - Deferred work: -> scheduleWork() + * */ public class Scheduler { - public Scheduler() { + private static final String TAG = Scheduler.class.getSimpleName(); + // The usage of this executor might change in the near future. + private final ExecutorService mExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque()); + private final ReactContext mReactContext; + + public Scheduler(ReactContext reactContext) { + mReactContext = reactContext; } - public void scheduleWork(Work work) { - // TODO T26717866 this method needs to be implemented. The current implementation is just for - // testing purpose. - - work.run(); + /** + * This method schedules work to be executed with the lowest priority in the JS Thread. + * + * The current implementation queues "work"s in an unbounded queue tight to a SingleThreadExecutor. + * Work objects are going to be submitted one-by-one at the end of the JS Queue Thread. + * + * Notice that the current implementation might experience some delays in JS work execution, + * depending on the size of the JS Queue and the time it takes to execute each work in JS. + * + * TODO: This implementation is very likely to change in the near future. + */ + public void scheduleWork(final Work work) { + try { + mExecutor.execute(new Runnable() { + @Override + public void run() { + mReactContext.runOnJSQueueThread(new Runnable() { + @Override + public void run() { + try { + work.run(); + } catch (Exception ex) { + Log.w(TAG, "Exception running work in JS.", ex); + throw ex; + } + } + }); + } + }); + } catch (RejectedExecutionException ex) { + // This can happen if a Work is scheduled when the Scheduler is being shutdown. + // For now, we log and do not take any action. + Log.i(TAG, "Unable to schedule task."); + } } public void flushSync(Work work) { @@ -40,4 +84,12 @@ public class Scheduler { // TODO T26717866 this method needs to be implemented. The current implementation is just for // testing purpose. } + + /** + * Shutdowns the {@link Scheduler}. this operation will attempt to stop all "active executing" + * Works items and it will "halts:" all the Works items waiting to be executed. + */ + public void shutdown() { + mExecutor.shutdownNow(); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java index a9cd49ab3..59c701e67 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java @@ -17,6 +17,7 @@ import android.annotation.TargetApi; import android.os.Build; import android.util.Log; import android.util.Pair; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; @@ -26,21 +27,23 @@ import com.facebook.react.fabric.FabricUIManager; import com.facebook.react.fabric.Scheduler; import com.facebook.react.fabric.Work; import com.facebook.react.uimanager.events.RCTEventEmitter; +import java.io.Closeable; +import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.annotation.Nullable; @TargetApi(Build.VERSION_CODES.ECLAIR) -public class FabricEventEmitter implements RCTEventEmitter { +public class FabricEventEmitter implements RCTEventEmitter, Closeable { private static final String TAG = FabricEventEmitter.class.getSimpleName(); private final FabricUIManager mFabricUIManager; private final Scheduler mScheduler; - public FabricEventEmitter(Scheduler scheduler, FabricUIManager fabricUIManager) { - mScheduler = scheduler; + public FabricEventEmitter(ReactApplicationContext context, FabricUIManager fabricUIManager) { + mScheduler = new Scheduler(context); mFabricUIManager = fabricUIManager; } @@ -51,6 +54,11 @@ public class FabricEventEmitter implements RCTEventEmitter { mScheduler.scheduleWork(new FabricUIManagerWork(instanceHandle, eventName, params) ); } + @Override + public void close() { + mScheduler.shutdown(); + } + private class FabricUIManagerWork implements Work { private final int mInstanceHandle; private final String mEventName; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java index c613f8685..ad15ce0a8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java @@ -97,7 +97,7 @@ public class EventDispatcher implements LifecycleEventListener, private Event[] mEventsToDispatch = new Event[16]; private int mEventsToDispatchSize = 0; - private volatile @Nullable ReactEventEmitter mRCTEventEmitter = new ReactEventEmitter(); + private volatile @Nullable ReactEventEmitter mReactEventEmitter = new ReactEventEmitter(); private short mNextEventTypeId = 0; private volatile boolean mHasDispatchScheduled = false; @@ -123,7 +123,7 @@ public class EventDispatcher implements LifecycleEventListener, event.getEventName(), event.getUniqueID()); } - if (mRCTEventEmitter != null) { + if (mReactEventEmitter != null) { // If the host activity is paused, the frame callback may not be currently // posted. Ensure that it is so that this event gets delivered promptly. mCurrentFrameCallback.maybePostFromNonUI(); @@ -162,6 +162,7 @@ public class EventDispatcher implements LifecycleEventListener, @Override public void onHostDestroy() { stopFrameCallback(); + mReactEventEmitter.stop(); } public void onCatalystInstanceDestroyed() { @@ -251,7 +252,7 @@ public class EventDispatcher implements LifecycleEventListener, } public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { - mRCTEventEmitter.register(uiManagerType, eventEmitter); + mReactEventEmitter.register(uiManagerType, eventEmitter); } private class ScheduleDispatchFrameCallback extends ChoreographerCompat.FrameCallback { @@ -331,7 +332,7 @@ public class EventDispatcher implements LifecycleEventListener, "ScheduleDispatchFrameCallback", mHasDispatchScheduledCount.getAndIncrement()); mHasDispatchScheduled = false; - Assertions.assertNotNull(mRCTEventEmitter); + Assertions.assertNotNull(mReactEventEmitter); synchronized (mEventsToDispatchLock) { // We avoid allocating an array and iterator, and "sorting" if we don't need to. // This occurs when the size of mEventsToDispatch is zero or one. @@ -348,7 +349,7 @@ public class EventDispatcher implements LifecycleEventListener, Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); - event.dispatch(mRCTEventEmitter); + event.dispatch(mReactEventEmitter); event.dispose(); } clearEventsToDispatch(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java index 713ce32db..c54af2212 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java @@ -9,20 +9,23 @@ package com.facebook.react.uimanager.events; import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_KEY; +import android.util.Log; import android.util.SparseArray; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.common.ViewUtil; +import java.io.Closeable; +import java.io.IOException; import javax.annotation.Nullable; public class ReactEventEmitter implements RCTEventEmitter { + private static final String TAG = ReactEventEmitter.class.getSimpleName(); private final SparseArray mEventEmitters = new SparseArray<>(); - public ReactEventEmitter() { - } + public ReactEventEmitter() { } public void register(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { mEventEmitters.put(uiManagerType, eventEmitter); @@ -50,4 +53,16 @@ public class ReactEventEmitter implements RCTEventEmitter { return mEventEmitters.get(type); } + public void stop() { + for (int i = 0 ; i < mEventEmitters.size() ; i++) { + RCTEventEmitter eventEmitter = mEventEmitters.valueAt(i); + if (eventEmitter instanceof Closeable) { + try { + ((Closeable) eventEmitter).close(); + } catch (IOException e) { + Log.i(TAG, "Exception when closing EventEmitter: " + eventEmitter, e); + } + } + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java index d21984a3f..2c85c4ad7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java @@ -7,17 +7,14 @@ package com.facebook.react.views.image; -import javax.annotation.Nullable; - import android.support.annotation.IntDef; - import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.RCTEventEmitter; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import javax.annotation.Nullable; public class ImageLoadEvent extends Event { @IntDef({ON_ERROR, ON_LOAD, ON_LOAD_END, ON_LOAD_START, ON_PROGRESS})