Make sure Sandy plugis can be initialized

Summary:
This diff makes sure sandy plugins are initialized.

Sandy plugins are stored directly in the client for two reasons
1. we want to decouple any plugin state updates from our central redux store, as redux is particularly bad in handling high frequency updates.
2. The lifecycle of a plugin is now bound to the lifecycle of a client. This means that we don't need 'persistedStore' to make sure state is preserved; that is now the default. Switching plugins will no longer reinitialize them (but only run specific hooks, see later diffs).
3. PersistedState will be introduced for sandy plugins as well later, but primarily for import / export / debug reasons.

A significant difference with the current persistent state, is that if a client crashes and reconnects, plugins will loose their state. We can prevent this (again, since state persisting will be reintroduced), but I'm not sure we need that for the specific reconnect scenario. Because
1. we should fix reconnecting clients anyway, and from stats it looks to happen less already
2. reconnects are usually caused by plugins that aggregate a lot of data and get slower over time. Restoring old state also restores those unstabilites.

For the overview bringing back the archi picture of earlier diff:
{F241508042}

Also fixed a bug where enabling background plugins didn't enable them on all devices with that app.

Reviewed By: jknoxville

Differential Revision: D22186276

fbshipit-source-id: 3fd42b577f86920e5280aa8cce1a0bc4d5564ed9
This commit is contained in:
Michel Weststrate
2020-07-01 08:58:40 -07:00
committed by Facebook GitHub Bot
parent bf79c9472e
commit 1dc9e899b8
10 changed files with 333 additions and 70 deletions

View File

@@ -32,7 +32,7 @@ import {sideEffect} from './utils/sideEffect';
import {emitBytesReceived} from './dispatcher/tracking';
import {debounce} from 'lodash';
import {batch} from 'react-redux';
import {SandyPluginDefinition} from 'flipper-plugin';
import {SandyPluginDefinition, SandyPluginInstance} from 'flipper-plugin';
type Plugins = Array<string>;
@@ -135,6 +135,7 @@ export default class Client extends EventEmitter {
messages: Params[];
}
> = {};
sandyPluginStates = new Map<string /*pluginID*/, SandyPluginInstance>();
requestCallbacks: Map<
number,
@@ -253,18 +254,26 @@ export default class Client extends EventEmitter {
return this.backgroundPlugins.includes(pluginId);
}
isEnabledPlugin(pluginId: string) {
return this.store
.getState()
.connections.userStarredPlugins[this.query.app]?.includes(pluginId);
}
shouldConnectAsBackgroundPlugin(pluginId: string) {
return (
defaultEnabledBackgroundPlugins.includes(pluginId) ||
this.store
.getState()
.connections.userStarredPlugins[this.query.app]?.includes(pluginId)
this.isEnabledPlugin(pluginId)
);
}
async init() {
this.setMatchingDevice();
await this.loadPlugins();
// this starts all sandy enabled plugin
this.plugins.forEach((pluginId) =>
this.startPluginIfNeeded(this.getPlugin(pluginId)),
);
this.backgroundPlugins = await this.getBackgroundPlugins();
this.backgroundPlugins.forEach((plugin) => {
if (this.shouldConnectAsBackgroundPlugin(plugin)) {
@@ -298,6 +307,47 @@ export default class Client extends EventEmitter {
return plugins;
}
startPluginIfNeeded(
plugin: PluginDefinition | undefined,
isEnabled = plugin ? this.isEnabledPlugin(plugin.id) : false,
) {
// start a plugin on start if it is a SandyPlugin, which is starred, and doesn't have persisted state yet
if (
plugin instanceof SandyPluginDefinition &&
isEnabled &&
!this.sandyPluginStates.has(plugin.id)
) {
// TODO: needs to be wrapped in error tracking T68955280
// TODO: pick up any existing persisted state T68683449
this.sandyPluginStates.set(
plugin.id,
new SandyPluginInstance(this, plugin),
);
}
}
stopPluginIfNeeded(pluginId: string) {
const instance = this.sandyPluginStates.get(pluginId);
if (instance) {
instance.destroy();
// TODO: make sure persisted state is writtenT68683449
this.sandyPluginStates.delete(pluginId);
}
}
close() {
this.emit('close');
this.plugins.forEach((pluginId) => this.stopPluginIfNeeded(pluginId));
}
// gets a plugin by pluginId
getPlugin(pluginId: string): PluginDefinition | undefined {
const plugins = this.store.getState().plugins;
return (
plugins.clientPlugins.get(pluginId) || plugins.devicePlugins.get(pluginId)
);
}
// get the supported background plugins
async getBackgroundPlugins(): Promise<Plugins> {
if (this.sdkVersion < 4) {
@@ -313,6 +363,9 @@ export default class Client extends EventEmitter {
async refreshPlugins() {
const oldBackgroundPlugins = this.backgroundPlugins;
await this.loadPlugins();
this.plugins.forEach((pluginId) =>
this.startPluginIfNeeded(this.getPlugin(pluginId)),
);
const newBackgroundPlugins = await this.getBackgroundPlugins();
this.backgroundPlugins = newBackgroundPlugins;
// diff the background plugin list, disconnect old, connect new ones
@@ -616,9 +669,11 @@ export default class Client extends EventEmitter {
initPlugin(pluginId: string) {
this.activePlugins.add(pluginId);
this.rawSend('init', {plugin: pluginId});
// TODO: call sandyOnConnect
}
deinitPlugin(pluginId: string) {
// TODO: call sandyOnDisconnect
this.activePlugins.delete(pluginId);
this.rawSend('deinit', {plugin: pluginId});
}