From e42c6d4446dd1b5b9f68b1dfd2822c79036c2c3c Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Mon, 4 Jan 2016 02:15:19 -0800 Subject: [PATCH] Flows between RN Threads Reviewed By: tadeuzagallo Differential Revision: D2743733 fb-gh-sync-id: df4ae69a3501a37e08286857a8d4be3cd27c0ac3 --- Libraries/Utilities/MessageQueue.js | 39 ++++++++++++------- React/Base/RCTBatchedBridge.m | 13 +++---- .../react/bridge/CatalystInstanceImpl.java | 23 +++++++++++ .../react/uimanager/events/Event.java | 10 +++++ .../uimanager/events/EventDispatcher.java | 18 +++++++++ .../src/main/jni/react/MethodCall.cpp | 18 ++++++++- ReactAndroid/src/main/jni/react/MethodCall.h | 6 ++- .../src/main/jni/react/jni/OnLoad.cpp | 4 ++ 8 files changed, 106 insertions(+), 25 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 1bd46424d..1eb3c5c65 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -24,8 +24,11 @@ let stringifySafe = require('stringifySafe'); let MODULE_IDS = 0; let METHOD_IDS = 1; let PARAMS = 2; +let CALL_IDS = 3; let MIN_TIME_BETWEEN_FLUSHES_MS = 5; +let TRACE_TAG_REACT_APPS = 1 << 17; + let SPY_MODE = false; let MethodTypes = keyMirror({ @@ -47,11 +50,12 @@ class MessageQueue { this.RemoteModules = {}; this._callableModules = {}; - this._queue = [[],[],[]]; + this._queue = [[], [], [], 0]; this._moduleTable = {}; this._methodTable = {}; this._callbacks = []; this._callbackID = 0; + this._callID = 0; this._lastFlush = 0; this._eventLoopStartTime = new Date().getTime(); @@ -100,7 +104,7 @@ class MessageQueue { this.__callImmediates(); let queue = this._queue; - this._queue = [[],[],[]]; + this._queue = [[], [], [], this._callID]; return queue[0].length ? queue : null; } @@ -136,6 +140,11 @@ class MessageQueue { onSucc && params.push(this._callbackID); this._callbacks[this._callbackID++] = onSucc; } + + global.nativeTraceBeginAsyncFlow && + global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID); + this._callID++; + this._queue[MODULE_IDS].push(module); this._queue[METHOD_IDS].push(method); this._queue[PARAMS].push(params); @@ -144,7 +153,7 @@ class MessageQueue { if (global.nativeFlushQueueImmediate && now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) { global.nativeFlushQueueImmediate(this._queue); - this._queue = [[],[],[]]; + this._queue = [[], [], [], this._callID]; this._lastFlush = now; } Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length); @@ -176,22 +185,22 @@ class MessageQueue { } __invokeCallback(cbID, args) { - Systrace.beginEvent(`MessageQueue.invokeCallback(${cbID})`); this._lastFlush = new Date().getTime(); this._eventLoopStartTime = this._lastFlush; let callback = this._callbacks[cbID]; - if (!callback || __DEV__) { - let debug = this._debugInfo[cbID >> 1]; - let module = debug && this._remoteModuleTable[debug[0]]; - let method = debug && this._remoteMethodTable[debug[0]][debug[1]]; - invariant( - callback, - `Callback with id ${cbID}: ${module}.${method}() not found` - ); - if (callback && SPY_MODE) { - console.log('N->JS : (' + JSON.stringify(args) + ')'); - } + let debug = this._debugInfo[cbID >> 1]; + let module = debug && this._remoteModuleTable[debug[0]]; + let method = debug && this._remoteMethodTable[debug[0]][debug[1]]; + invariant( + callback, + `Callback with id ${cbID}: ${module}.${method}() not found` + ); + let profileName = debug ? '' : cbID; + if (callback && SPY_MODE && __DEV__) { + console.log('N->JS : ' + profileName + '(' + JSON.stringify(args) + ')'); } + Systrace.beginEvent( + `MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`); this._callbacks[cbID & ~1] = null; this._callbacks[cbID | 1] = null; callback.apply(null, args); diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 4946cd012..f4ac4e2c1 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -778,19 +778,18 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR } } -- (void)handleBuffer:(NSArray *)buffer +- (void)handleBuffer:(NSArray *)buffer { - NSArray *requestsArray = [RCTConvert NSArrayArray:buffer]; - + NSArray *requestsArray = [RCTConvert NSArray:buffer]; if (RCT_DEBUG && requestsArray.count <= RCTBridgeFieldParamss) { RCTLogError(@"Buffer should contain at least %tu sub-arrays. Only found %tu", - RCTBridgeFieldParamss + 1, requestsArray.count); + RCTBridgeFieldParamss + 1, requestsArray.count); return; } - NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs]; - NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs]; - NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss]; + NSArray *moduleIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldRequestModuleIDs]]; + NSArray *methodIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldMethodIDs]]; + NSArray *paramsArrays = [RCTConvert NSArrayArray:requestsArray[RCTBridgeFieldParamss]]; if (RCT_DEBUG && (moduleIDs.count != methodIDs.count || moduleIDs.count != paramsArrays.count)) { RCTLogError(@"Invalid data message - all must be length: %zd", moduleIDs.count); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 772640285..da3750cfe 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -56,6 +56,7 @@ public class CatalystInstanceImpl implements CatalystInstance { private final TraceListener mTraceListener; private final JavaScriptModuleRegistry mJSModuleRegistry; private final JSBundleLoader mJSBundleLoader; + private volatile int mTraceID = 0; // Access from native modules thread private final NativeModuleRegistry mJavaRegistry; @@ -176,12 +177,23 @@ public class CatalystInstanceImpl implements CatalystInstance { incrementPendingJSCalls(); + final int traceID = mTraceID++; + Systrace.startAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + tracingName, + traceID); + mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( new Runnable() { @Override public void run() { mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); + Systrace.endAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + tracingName, + traceID); + if (mDestroyed) { return; } @@ -208,12 +220,23 @@ public class CatalystInstanceImpl implements CatalystInstance { incrementPendingJSCalls(); + final int traceID = mTraceID++; + Systrace.startAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "", + traceID); + mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( new Runnable() { @Override public void run() { mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); + Systrace.endAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "", + traceID); + if (mDestroyed) { return; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java index 58dd958a6..40c2845c8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java @@ -18,9 +18,12 @@ package com.facebook.react.uimanager.events; */ public abstract class Event { + private static int sUniqueID = 0; + private boolean mInitialized; private int mViewTag; private long mTimestampMs; + private int mUniqueID = sUniqueID++; protected Event() { } @@ -80,6 +83,13 @@ public abstract class Event { return 0; } + /** + * @return The unique id of this event. + */ + public int getUniqueID() { + return mUniqueID; + } + /** * Called when the EventDispatcher is done with an event, either because it was dispatched or * because it was coalesced with another Event. 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 35c0a2897..c8635d051 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 @@ -100,6 +100,7 @@ public class EventDispatcher implements LifecycleEventListener { private volatile @Nullable ScheduleDispatchFrameCallback mCurrentFrameCallback; private short mNextEventTypeId = 0; private volatile boolean mHasDispatchScheduled = false; + private volatile int mHasDispatchScheduledCount = 0; public EventDispatcher(ReactApplicationContext reactContext) { mReactContext = reactContext; @@ -113,6 +114,10 @@ public class EventDispatcher implements LifecycleEventListener { Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized"); synchronized (mEventsStagingLock) { mEventStaging.add(event); + Systrace.startAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + event.getEventName(), + event.getUniqueID()); } } @@ -242,6 +247,10 @@ public class EventDispatcher implements LifecycleEventListener { if (!mHasDispatchScheduled) { mHasDispatchScheduled = true; + Systrace.startAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "ScheduleDispatchFrameCallback", + mHasDispatchScheduledCount); mReactContext.runOnJSQueueThread(mDispatchEventsRunnable); } @@ -263,7 +272,12 @@ public class EventDispatcher implements LifecycleEventListener { public void run() { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchEventsRunnable"); try { + Systrace.endAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "ScheduleDispatchFrameCallback", + mHasDispatchScheduledCount); mHasDispatchScheduled = false; + mHasDispatchScheduledCount++; Assertions.assertNotNull(mRCTEventEmitter); synchronized (mEventsToDispatchLock) { // We avoid allocating an array and iterator, and "sorting" if we don't need to. @@ -277,6 +291,10 @@ public class EventDispatcher implements LifecycleEventListener { if (event == null) { continue; } + Systrace.endAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + event.getEventName(), + event.getUniqueID()); event.dispatch(mRCTEventEmitter); event.dispose(); } diff --git a/ReactAndroid/src/main/jni/react/MethodCall.cpp b/ReactAndroid/src/main/jni/react/MethodCall.cpp index ce12d3683..ac9b13edd 100644 --- a/ReactAndroid/src/main/jni/react/MethodCall.cpp +++ b/ReactAndroid/src/main/jni/react/MethodCall.cpp @@ -12,6 +12,7 @@ namespace react { #define REQUEST_MODULE_IDS 0 #define REQUEST_METHOD_IDS 1 #define REQUEST_PARAMSS 2 +#define REQUEST_CALLID 3 std::vector parseMethodCalls(const std::string& json) { folly::dynamic jsonData = folly::parseJson(json); @@ -33,6 +34,7 @@ std::vector parseMethodCalls(const std::string& json) { auto moduleIds = jsonData[REQUEST_MODULE_IDS]; auto methodIds = jsonData[REQUEST_METHOD_IDS]; auto params = jsonData[REQUEST_PARAMSS]; + int callId = -1; if (!moduleIds.isArray() || !methodIds.isArray() || !params.isArray()) { jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException, @@ -40,6 +42,16 @@ std::vector parseMethodCalls(const std::string& json) { json.c_str()); } + if (jsonData.size() > REQUEST_CALLID) { + if (!jsonData[REQUEST_CALLID].isInt()) { + jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException, + "Did not get valid calls back from JS: %s", + json.c_str()); + } else { + callId = jsonData[REQUEST_CALLID].getInt(); + } + } + std::vector methodCalls; for (size_t i = 0; i < moduleIds.size(); i++) { auto paramsValue = params[i]; @@ -51,7 +63,11 @@ std::vector parseMethodCalls(const std::string& json) { methodCalls.emplace_back( moduleIds[i].getInt(), methodIds[i].getInt(), - std::move(params[i])); + std::move(params[i]), + callId); + + // only incremement callid if contains valid callid as callid is optional + callId += (callId != -1) ? 1 : 0; } return methodCalls; diff --git a/ReactAndroid/src/main/jni/react/MethodCall.h b/ReactAndroid/src/main/jni/react/MethodCall.h index 7bb47dca9..02e86eb98 100644 --- a/ReactAndroid/src/main/jni/react/MethodCall.h +++ b/ReactAndroid/src/main/jni/react/MethodCall.h @@ -15,11 +15,13 @@ struct MethodCall { int moduleId; int methodId; folly::dynamic arguments; + int callId; - MethodCall(int mod, int meth, folly::dynamic args) + MethodCall(int mod, int meth, folly::dynamic args, int cid) : moduleId(mod) , methodId(meth) - , arguments(std::move(args)) {} + , arguments(std::move(args)) + , callId(cid) {} }; std::vector parseMethodCalls(const std::string& json); diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index b5ad6a11f..e4368d682 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -17,6 +17,7 @@ #include "ReadableNativeArray.h" #include "ProxyExecutor.h" #include "OnLoad.h" +#include #ifdef WITH_FBSYSTRACE #include @@ -561,6 +562,9 @@ static void makeJavaCall(JNIEnv* env, jobject callback, MethodCall&& call) { if (call.arguments.isNull()) { return; } + if (call.callId != -1) { + fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", call.callId); + } auto newArray = ReadableNativeArray::newObjectCxxArgs(std::move(call.arguments)); env->CallVoidMethod(callback, gCallbackMethod, call.moduleId, call.methodId, newArray.get()); }