Skip to content

Commit

Permalink
handle plugin host activation exceptions
Browse files Browse the repository at this point in the history
Signed-off-by: Rob Moran <rob.moran@arm.com>
  • Loading branch information
thegecko authored and westbury committed Jul 27, 2020
1 parent 1ba3a06 commit ed0a065
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 70 deletions.
60 changes: 29 additions & 31 deletions packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,45 +97,43 @@ export class PluginHostRPC {
const pluginManager = new PluginManagerExtImpl({
loadPlugin(plugin: Plugin): void {
console.log('PLUGIN_HOST(' + process.pid + '): PluginManagerExtImpl/loadPlugin(' + plugin.pluginPath + ')');
try {
// cleaning the cache for all files of that plug-in.
Object.keys(require.cache).forEach(function (key): void {
const mod: NodeJS.Module = require.cache[key];
// cleaning the cache for all files of that plug-in.
Object.keys(require.cache).forEach(function (key): void {
const mod: NodeJS.Module = require.cache[key];

// attempting to reload a native module will throw an error, so skip them
if (mod.id.endsWith('.node')) {
return;
}
// attempting to reload a native module will throw an error, so skip them
if (mod.id.endsWith('.node')) {
return;
}

// remove children that are part of the plug-in
let i = mod.children.length;
while (i--) {
const childMod: NodeJS.Module = mod.children[i];
// ensure the child module is not null, is in the plug-in folder, and is not a native module (see above)
if (childMod && childMod.id.startsWith(plugin.pluginFolder) && !childMod.id.endsWith('.node')) {
// cleanup exports - note that some modules (e.g. ansi-styles) define their
// exports in an immutable manner, so overwriting the exports throws an error
delete childMod.exports;
mod.children.splice(i, 1);
for (let j = 0; j < childMod.children.length; j++) {
delete childMod.children[j];
}
// remove children that are part of the plug-in
let i = mod.children.length;
while (i--) {
const childMod: NodeJS.Module = mod.children[i];
// ensure the child module is not null, is in the plug-in folder, and is not a native module (see above)
if (childMod && childMod.id.startsWith(plugin.pluginFolder) && !childMod.id.endsWith('.node')) {
// cleanup exports - note that some modules (e.g. ansi-styles) define their
// exports in an immutable manner, so overwriting the exports throws an error
delete childMod.exports;
mod.children.splice(i, 1);
for (let j = 0; j < childMod.children.length; j++) {
delete childMod.children[j];
}
}
}

if (key.startsWith(plugin.pluginFolder)) {
// delete entry
delete require.cache[key];
const ix = mod.parent!.children.indexOf(mod);
if (ix >= 0) {
mod.parent!.children.splice(ix, 1);
}
if (key.startsWith(plugin.pluginFolder)) {
// delete entry
delete require.cache[key];
const ix = mod.parent!.children.indexOf(mod);
if (ix >= 0) {
mod.parent!.children.splice(ix, 1);
}
}

});
});
if (plugin.pluginPath) {
return require(plugin.pluginPath);
} catch (e) {
console.error(e);
}
},
async init(raw: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> {
Expand Down
72 changes: 33 additions & 39 deletions packages/plugin-ext/src/plugin/plugin-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,31 +220,35 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager {
let loading = this.loadedPlugins.get(plugin.model.id);
if (!loading) {
loading = (async () => {
if (plugin.rawModel.extensionDependencies) {
for (const dependencyId of plugin.rawModel.extensionDependencies) {
const dependency = this.registry.get(dependencyId.toLowerCase());
const id = plugin.model.displayName || plugin.model.id;
if (dependency) {
const depId = dependency.model.displayName || dependency.model.id;
const loadedSuccessfully = await this.loadPlugin(dependency, configStorage, visited);
if (!loadedSuccessfully) {
const message = `Cannot activate extension '${id}' because it depends on extension '${depId}', which failed to activate.`;
this.messageRegistryProxy.$showMessage(MainMessageType.Error, message, {}, []);
return false;
try {
if (plugin.rawModel.extensionDependencies) {
for (const dependencyId of plugin.rawModel.extensionDependencies) {
const dependency = this.registry.get(dependencyId.toLowerCase());
if (dependency) {
const loadedSuccessfully = await this.loadPlugin(dependency, configStorage, visited);
if (!loadedSuccessfully) {
throw new Error(`Dependent extension '${dependency.model.displayName || dependency.model.id}' failed to activate.`);
}
} else {
throw new Error(`Dependent extension '${dependencyId}' is not installed.`);
}
} else {
const message = `Cannot activate the '${id}' extension because it depends on the '${dependencyId}' extension, which is not installed.`;
this.messageRegistryProxy.$showMessage(MainMessageType.Error, message, {}, []);
console.warn(message);
return false;
}
}
}

let pluginMain = this.host.loadPlugin(plugin);
// see https://github.com/TypeFox/vscode/blob/70b8db24a37fafc77247de7f7cb5bb0195120ed0/src/vs/workbench/api/common/extHostExtensionService.ts#L372-L376
pluginMain = pluginMain || {};
return this.startPlugin(plugin, configStorage, pluginMain);
let pluginMain = this.host.loadPlugin(plugin);
// see https://github.com/TypeFox/vscode/blob/70b8db24a37fafc77247de7f7cb5bb0195120ed0/src/vs/workbench/api/common/extHostExtensionService.ts#L372-L376
pluginMain = pluginMain || {};
await this.startPlugin(plugin, configStorage, pluginMain);
return true;
} catch (err) {
if (this.pluginActivationPromises.has(plugin.model.id)) {
this.pluginActivationPromises.get(plugin.model.id)!.reject(err);
}
const message = `Activating extension '${plugin.model.displayName || plugin.model.name}' failed: ${err.message}`;
this.messageRegistryProxy.$showMessage(MainMessageType.Error, message, {}, []);
console.error(message);
return false;
}
})();
}
this.loadedPlugins.set(plugin.model.id, loading);
Expand All @@ -269,7 +273,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager {
}

// tslint:disable-next-line:no-any
private async startPlugin(plugin: Plugin, configStorage: ConfigStorage, pluginMain: any): Promise<boolean> {
private async startPlugin(plugin: Plugin, configStorage: ConfigStorage, pluginMain: any): Promise<void> {
const subscriptions: theia.Disposable[] = [];
const asAbsolutePath = (relativePath: string): string => join(plugin.pluginFolder, relativePath);
const logPath = join(configStorage.hostLogPath, plugin.model.id); // todo check format
Expand Down Expand Up @@ -298,29 +302,19 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager {
}
const id = plugin.model.displayName || plugin.model.id;
if (typeof pluginMain[plugin.lifecycle.startMethod] === 'function') {
try {
const pluginExport = await pluginMain[plugin.lifecycle.startMethod].apply(getGlobal(), [pluginContext]);
this.activatedPlugins.set(plugin.model.id, new ActivatedPlugin(pluginContext, pluginExport, stopFn));

// resolve activation promise
if (this.pluginActivationPromises.has(plugin.model.id)) {
this.pluginActivationPromises.get(plugin.model.id)!.resolve();
this.pluginActivationPromises.delete(plugin.model.id);
}
} catch (err) {
if (this.pluginActivationPromises.has(plugin.model.id)) {
this.pluginActivationPromises.get(plugin.model.id)!.reject(err);
}
this.messageRegistryProxy.$showMessage(MainMessageType.Error, `Activating extension ${id} failed: ${err.message}.`, {}, []);
console.error(`Error on activation of ${plugin.model.name}`, err);
return false;
const pluginExport = await pluginMain[plugin.lifecycle.startMethod].apply(getGlobal(), [pluginContext]);
this.activatedPlugins.set(plugin.model.id, new ActivatedPlugin(pluginContext, pluginExport, stopFn));

// resolve activation promise
if (this.pluginActivationPromises.has(plugin.model.id)) {
this.pluginActivationPromises.get(plugin.model.id)!.resolve();
this.pluginActivationPromises.delete(plugin.model.id);
}
} else {
// https://github.com/TypeFox/vscode/blob/70b8db24a37fafc77247de7f7cb5bb0195120ed0/src/vs/workbench/api/common/extHostExtensionService.ts#L400-L401
console.log(`plugin ${id}, ${plugin.lifecycle.startMethod} method is undefined so the module is the extension's exports`);
this.activatedPlugins.set(plugin.model.id, new ActivatedPlugin(pluginContext, pluginMain));
}
return true;
}

getAllPlugins(): Plugin[] {
Expand Down

0 comments on commit ed0a065

Please sign in to comment.