From 5bbfa589090216f75c48a0842490cff791db6054 Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Thu, 11 Oct 2018 15:19:19 -0700 Subject: [PATCH] Setup sdk for background plugin Summary: This diff sets up flipper for running plugins in background. This diff does the following - Adds a function named `runInBackground` to the interface `FlipperPlugin` to make the plugins opt in to be run in background, default is false - Changes the javascript side of the flipper to store the messages received by the plugins in background - Process the stored messages when the plugin in background becomes active - Currently I have just turned on network plugin to be in background mode. - Remove the buffering from the network plugin, as it will run in background - Write a batching layer to batch the messages and send to flipper. Note: I haven't tested the wilde app yet, but the sample app works. I will remove the "[WIP]" from the title once I have tested it in wilde Reviewed By: danielbuechele Differential Revision: D10301403 fbshipit-source-id: 034eebf659a545d6b480a4ac1b73b0aa4b2f9797 --- .../CppBridge/FlipperCppWrapperPlugin.h | 7 ++++ iOS/FlipperKit/FlipperClient.mm | 1 + iOS/FlipperKit/FlipperPlugin.h | 6 +++ .../SKBufferingPlugin.mm | 5 +++ src/Client.js | 41 ++++++++++++++++++- src/PluginContainer.js | 11 ++++- src/dispatcher/server.js | 6 +++ xplat/Flipper/FlipperClient.cpp | 22 +++++++++- xplat/Flipper/FlipperClient.h | 1 + .../Flipper/FlipperConnectionManagerImpl.cpp | 2 +- xplat/Flipper/FlipperPlugin.h | 7 ++++ 11 files changed, 104 insertions(+), 5 deletions(-) diff --git a/iOS/FlipperKit/CppBridge/FlipperCppWrapperPlugin.h b/iOS/FlipperKit/CppBridge/FlipperCppWrapperPlugin.h index c1f06a840..e6161d73d 100644 --- a/iOS/FlipperKit/CppBridge/FlipperCppWrapperPlugin.h +++ b/iOS/FlipperKit/CppBridge/FlipperCppWrapperPlugin.h @@ -38,6 +38,13 @@ public: void didDisconnect() override { [_objCPlugin didDisconnect]; } + bool runInBackground() override { + if ([_objCPlugin respondsToSelector:@selector(runInBackground)]) { + return [_objCPlugin runInBackground]; + } + return false; + } + ObjCPlugin getObjCPlugin() { return _objCPlugin; } private: diff --git a/iOS/FlipperKit/FlipperClient.mm b/iOS/FlipperKit/FlipperClient.mm index f8cc30f6a..fbacf7ee2 100644 --- a/iOS/FlipperKit/FlipperClient.mm +++ b/iOS/FlipperKit/FlipperClient.mm @@ -124,6 +124,7 @@ using WrapperPlugin = facebook::flipper::FlipperCppWrapperPlugin; _cppClient->start(); } + - (void)stop { _cppClient->stop(); diff --git a/iOS/FlipperKit/FlipperPlugin.h b/iOS/FlipperKit/FlipperPlugin.h index 6ad6deb32..364396d4c 100644 --- a/iOS/FlipperKit/FlipperPlugin.h +++ b/iOS/FlipperKit/FlipperPlugin.h @@ -36,4 +36,10 @@ longer valid to use. */ - (void)didDisconnect; +/** + Returns true if the plugin is meant to be run in background too, otherwise it returns false. + */ +@optional +- (BOOL)runInBackground; + @end diff --git a/iOS/Plugins/FlipperKitNetworkPlugin/FlipperKitNetworkPlugin/SKBufferingPlugin.mm b/iOS/Plugins/FlipperKitNetworkPlugin/FlipperKitNetworkPlugin/SKBufferingPlugin.mm index 7276dd67d..c21e9f0eb 100644 --- a/iOS/Plugins/FlipperKitNetworkPlugin/FlipperKitNetworkPlugin/SKBufferingPlugin.mm +++ b/iOS/Plugins/FlipperKitNetworkPlugin/FlipperKitNetworkPlugin/SKBufferingPlugin.mm @@ -58,6 +58,11 @@ static const NSUInteger bufferSize = 500; }); } +- (BOOL)runInBackground { + return YES; +} + + - (void)send:(NSString *)method sonarObject:(NSDictionary *)sonarObject { _connectionAccessQueue->async(^{ diff --git a/src/Client.js b/src/Client.js index 7dc7ba7cd..19296a909 100644 --- a/src/Client.js +++ b/src/Client.js @@ -43,6 +43,7 @@ export default class Client extends EventEmitter { this.messageIdCounter = 0; this.logger = logger; + this.bufferedMessages = new Map(); this.broadcastCallbacks = new Map(); this.requestCallbacks = new Map(); @@ -77,6 +78,7 @@ export default class Client extends EventEmitter { connection: ReactiveSocket; responder: PartialResponder; + bufferedMessages: Map>; broadcastCallbacks: Map>>; requestCallbacks: Map< @@ -163,7 +165,11 @@ export default class Client extends EventEmitter { } const methodCallbacks: ?Set = apiCallbacks.get(params.method); - if (methodCallbacks) { + if (this.selectedPlugin != params.api) { + this.bufferMessage(params); + return; + } + if (methodCallbacks && methodCallbacks.size > 0) { for (const callback of methodCallbacks) { callback(params.params); } @@ -188,6 +194,39 @@ export default class Client extends EventEmitter { } } + readBufferedMessages(id: string) { + const paramsArray = this.bufferedMessages.get(id); + if (!paramsArray) { + return; + } + paramsArray.forEach((params, i) => + setTimeout(() => { + const apiCallbacks = this.broadcastCallbacks.get(params.api); + if (!apiCallbacks) { + return; + } + + const methodCallbacks: ?Set = apiCallbacks.get(params.method); + if (methodCallbacks) { + for (const callback of methodCallbacks) { + callback(params.params); + } + } + }, i * 20), + ); + this.bufferedMessages.delete(id); + } + + bufferMessage(msg: Object) { + const arr = this.bufferedMessages.get(msg.api); + if (arr) { + arr.push(msg); + this.bufferedMessages.set(msg.api, arr); + } else { + this.bufferedMessages.set(msg.api, [msg]); + } + } + toJSON() { return ``; } diff --git a/src/PluginContainer.js b/src/PluginContainer.js index e7d62a971..283660666 100644 --- a/src/PluginContainer.js +++ b/src/PluginContainer.js @@ -119,6 +119,9 @@ class PluginContainer extends Component { if (ref && target) { activateMenuItems(ref); ref._init(); + if (target instanceof Client) { + target.readBufferedMessages(ref.constructor.id); + } this.props.logger.trackTimeSince(`activePlugin-${ref.constructor.id}`); this.plugin = ref; } @@ -136,7 +139,13 @@ class PluginContainer extends Component { key: pluginKey, logger: this.props.logger, persistedState: pluginStates[pluginKey] || {}, - setPersistedState: state => setPluginState({pluginKey, state}), + setPersistedState: state => { + // We are using setTimout here to wait for previous state updated to + // finish before triggering a new state update. Otherwise this can + // cause race conditions, with multiple state updates happening at the + // same time. + setTimeout(() => setPluginState({pluginKey, state}), 0); + }, target, deepLinkPayload: this.props.deepLinkPayload, ref: this.refChanged, diff --git a/src/dispatcher/server.js b/src/dispatcher/server.js index fb856089a..a2f996f84 100644 --- a/src/dispatcher/server.js +++ b/src/dispatcher/server.js @@ -13,6 +13,12 @@ import type Client from '../Client.js'; export default (store: Store, logger: Logger) => { const server = new Server(logger); + store.subscribe(() => { + let currentState = store.getState(); + currentState.connections.clients.forEach((client: Client) => { + client.selectedPlugin = currentState.connections.selectedPlugin; + }); + }); server.addListener('new-client', (client: Client) => { store.dispatch({ type: 'NEW_CLIENT', diff --git a/xplat/Flipper/FlipperClient.cpp b/xplat/Flipper/FlipperClient.cpp index 0a9afada0..52d5ee13d 100644 --- a/xplat/Flipper/FlipperClient.cpp +++ b/xplat/Flipper/FlipperClient.cpp @@ -79,6 +79,19 @@ void FlipperClient::removePlugin(std::shared_ptr plugin) { }); } + void FlipperClient::startBackgroundPlugins() { + std::cout << "Activating Background Plugins..." << std::endl; + for (std::map>::iterator it=plugins_.begin(); it!=plugins_.end(); ++it) { + std::cout << it->first << std::endl; + if (it->second.get()->runInBackground()) { + auto& conn = connections_[it->first]; + conn = std::make_shared(socket_.get(),it->first); + it->second.get()->didConnect(conn); + } + + } + } + std::shared_ptr FlipperClient::getPlugin( const std::string& identifier) { std::lock_guard lock(mutex_); @@ -113,6 +126,7 @@ void FlipperClient::onConnected() { std::lock_guard lock(mutex_); connected_ = true; + startBackgroundPlugins(); } void FlipperClient::onDisconnected() { @@ -161,7 +175,9 @@ void FlipperClient::onMessageReceived(const dynamic& message) { auto& conn = connections_[plugin->identifier()]; conn = std::make_shared( socket_.get(), plugin->identifier()); - plugin->didConnect(conn); + if (!plugin.get()->runInBackground()) { + plugin->didConnect(conn); + } return; } @@ -173,7 +189,9 @@ void FlipperClient::onMessageReceived(const dynamic& message) { method.getString()); } const auto plugin = plugins_.at(identifier); - disconnect(plugin); + if (!plugin.get()->runInBackground()) { + disconnect(plugin); + } return; } diff --git a/xplat/Flipper/FlipperClient.h b/xplat/Flipper/FlipperClient.h index 0449b6f3b..49c518328 100644 --- a/xplat/Flipper/FlipperClient.h +++ b/xplat/Flipper/FlipperClient.h @@ -98,6 +98,7 @@ class FlipperClient : public FlipperConnectionManager::Callbacks { void performAndReportError(const std::function& func); void disconnect(std::shared_ptr plugin); + void startBackgroundPlugins(); }; } // namespace flipper diff --git a/xplat/Flipper/FlipperConnectionManagerImpl.cpp b/xplat/Flipper/FlipperConnectionManagerImpl.cpp index 9650e192e..d3a27682a 100644 --- a/xplat/Flipper/FlipperConnectionManagerImpl.cpp +++ b/xplat/Flipper/FlipperConnectionManagerImpl.cpp @@ -92,7 +92,7 @@ void FlipperConnectionManagerImpl::start() { folly::makeFuture() .via(flipperEventBase_->getEventBase()) .delayed(std::chrono::milliseconds(0)) - .thenValue([this, step](auto&&){ step->complete(); startSync(); }); + .thenValue([this, step](auto&&){ step->complete(); startSync();}); } void FlipperConnectionManagerImpl::startSync() { diff --git a/xplat/Flipper/FlipperPlugin.h b/xplat/Flipper/FlipperPlugin.h index 9ae10693a..3be789aa8 100644 --- a/xplat/Flipper/FlipperPlugin.h +++ b/xplat/Flipper/FlipperPlugin.h @@ -36,6 +36,13 @@ class FlipperPlugin { provided in didConnect is no longer valid to use. */ virtual void didDisconnect() = 0; + + /** + Returns true if the plugin is meant to be run in background too, otherwise it returns false. + */ + virtual bool runInBackground() { + return false; + } }; } // namespace flipper