Unload uninstalled plugins from Electron cache

Summary: Electron/Node.js does not garbage collects unloaded modules by default. Need to cleanup require.cache to fully unload them.

Reviewed By: passy

Differential Revision: D25545962

fbshipit-source-id: 4dce32f39e22adcd2b4f5a55853551379e786e7b
This commit is contained in:
Anton Nikolaev
2020-12-15 09:28:58 -08:00
committed by Facebook GitHub Bot
parent 965559ee65
commit 756edf9860
3 changed files with 41 additions and 1 deletions

View File

@@ -28,6 +28,7 @@ import {_SandyPluginDefinition} from 'flipper-plugin';
import BaseDevice from './devices/BaseDevice'; import BaseDevice from './devices/BaseDevice';
import {State as PluginStates} from './reducers/pluginStates'; import {State as PluginStates} from './reducers/pluginStates';
import {ActivatablePluginDetails} from 'flipper-plugin-lib'; import {ActivatablePluginDetails} from 'flipper-plugin-lib';
import {unloadModule} from './utils/electronModuleCache';
export const store: Store = createStore<StoreState, Actions, any, any>( export const store: Store = createStore<StoreState, Actions, any, any>(
rootReducer, rootReducer,
@@ -168,6 +169,11 @@ function updateClientPlugin(
]; ];
}); });
cleanupPluginStates(draft.pluginStates, plugin.id); cleanupPluginStates(draft.pluginStates, plugin.id);
const previousVersion = draft.plugins.clientPlugins.get(plugin.id);
if (previousVersion) {
// unload previous version from Electron cache
unloadPluginModule(previousVersion.details);
}
// update plugin definition // update plugin definition
draft.plugins.clientPlugins.set(plugin.id, plugin); draft.plugins.clientPlugins.set(plugin.id, plugin);
// start plugin for each client // start plugin for each client
@@ -191,6 +197,7 @@ function uninstallPlugin(state: StoreState, plugin: PluginDefinition) {
delete draft.pluginMessageQueue[pluginKey]; delete draft.pluginMessageQueue[pluginKey];
}); });
cleanupPluginStates(draft.pluginStates, plugin.id); cleanupPluginStates(draft.pluginStates, plugin.id);
unloadPluginModule(plugin.details);
draft.plugins.clientPlugins.delete(plugin.id); draft.plugins.clientPlugins.delete(plugin.id);
draft.plugins.devicePlugins.delete(plugin.id); draft.plugins.devicePlugins.delete(plugin.id);
draft.pluginManager.uninstalledPlugins.add(plugin.details.name); draft.pluginManager.uninstalledPlugins.add(plugin.details.name);
@@ -207,6 +214,11 @@ function updateDevicePlugin(state: StoreState, plugin: DevicePluginDefinition) {
d.unloadDevicePlugin(plugin.id); d.unloadDevicePlugin(plugin.id);
}); });
cleanupPluginStates(draft.pluginStates, plugin.id); cleanupPluginStates(draft.pluginStates, plugin.id);
const previousVersion = draft.plugins.devicePlugins.get(plugin.id);
if (previousVersion) {
// unload previous version from Electron cache
unloadPluginModule(previousVersion.details);
}
draft.plugins.devicePlugins.set(plugin.id, plugin); draft.plugins.devicePlugins.set(plugin.id, plugin);
devicesWithEnabledPlugin.forEach((d) => { devicesWithEnabledPlugin.forEach((d) => {
d.loadDevicePlugin(plugin); d.loadDevicePlugin(plugin);
@@ -245,3 +257,11 @@ function cleanupPluginStates(pluginStates: PluginStates, pluginId: string) {
} }
}); });
} }
function unloadPluginModule(plugin: ActivatablePluginDetails) {
if (plugin.isBundled) {
// We cannot unload bundled plugin.
return;
}
unloadModule(plugin.entry);
}

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
export function unloadModule(path: string) {
const resolvedPath = global.electronRequire.resolve(path);
if (!resolvedPath || !global.electronRequire.cache[resolvedPath]) {
return;
}
delete global.electronRequire.cache[resolvedPath];
}

View File

@@ -11,7 +11,11 @@ declare module NodeJS {
interface Global { interface Global {
__REVISION__: string | undefined; __REVISION__: string | undefined;
__VERSION__: string; __VERSION__: string;
electronRequire: (name: string) => any; electronRequire: {
(name: string): any;
resolve: (module: string) => string;
cache: {[module: string]: any};
};
window: Window | undefined; window: Window | undefined;
WebSocket: any; WebSocket: any;
fetch: any; fetch: any;