Call extension deactivate(), dispose subscriptions on extension host shutdown

This commit is contained in:
Alex Dima 2015-12-09 13:21:29 +01:00
parent 9f7989d36c
commit 98473e0e88
9 changed files with 109 additions and 7 deletions

View file

@ -272,4 +272,8 @@ export class SimplePluginService extends AbstractPluginService {
console.log(msg);
}
}
public deactivate(pluginId:string): void {
// nothing to do
}
}

View file

@ -73,6 +73,10 @@ class WorkerPluginService extends AbstractPluginService {
}
}
public deactivate(pluginId:string): void {
// nothing to do
}
}
export class EditorWorkerServer {

View file

@ -123,6 +123,10 @@ class MockPluginService extends AbstractPluginService {
console.log(msg);
}
}
public deactivate(pluginId:string): void {
// nothing to do
}
}
class MockModelService extends ModelServiceImpl {}

View file

@ -21,8 +21,8 @@ export interface IPluginExports {
}
export interface IPluginModule {
activate(subscriptions: IDisposable[]): WinJS.TPromise<IPluginExports>;
deactivate(callback:(err:any, success:boolean)=>void): void;
activate(ctx: IPluginContext): WinJS.TPromise<IPluginExports>;
deactivate(): void;
}
export interface IPluginContext {
@ -83,6 +83,7 @@ export abstract class AbstractPluginService implements IPluginService {
this.activatedPlugins = {};
}
public abstract deactivate(pluginId:string): void;
protected abstract _showMessage(severity:Severity, message:string): void;
protected showMessage(severity:Severity, source:string, message:string): void {

View file

@ -15,6 +15,7 @@ import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {PluginHostStorage} from 'vs/platform/storage/common/remotable.storage';
import * as paths from 'vs/base/common/paths';
import {IWorkspaceContextService, IConfiguration} from 'vs/platform/workspace/common/workspace';
import {disposeAll} from 'vs/base/common/lifecycle';
class PluginMemento implements IPluginMemento {
@ -168,6 +169,10 @@ export class MainProcessPluginService extends AbstractPluginService {
return this._pluginsStatus;
}
public deactivate(pluginId:string): void {
this._proxy.deactivate(pluginId);
}
// -- overwriting AbstractPluginService
protected _actualActivatePlugin(pluginDescription: IPluginDescription): WinJS.TPromise<ActivatedPlugin> {
@ -232,6 +237,28 @@ export class PluginHostPluginService extends AbstractPluginService {
}
}
public deactivate(pluginId:string): void {
let plugin = this.activatedPlugins[pluginId];
if (!plugin) {
return;
}
// call deactivate if available
try {
if (typeof plugin.module.deactivate === 'function') {
plugin.module.deactivate();
}
} catch(err) {
// TODO: Do something with err if this is not the shutdown case
}
// clean up subscriptions
try {
disposeAll(plugin.subscriptions)
} catch(err) {
// TODO: Do something with err if this is not the shutdown case
}
}
// -- overwriting AbstractPluginService

View file

@ -46,15 +46,30 @@ export interface IPluginStatus {
export interface IPluginService {
serviceId: ServiceIdentifier<any>;
activateByEvent(activationEvent:string): TPromise<any>;
activateAndGet(pluginId:string): TPromise<any>;
activateAndGet<T>(pluginId:string): TPromise<T>;
isActivated(pluginId:string): boolean;
/**
* This method should be called only on shutdown!
* More work is needed for this to be called any time!
*/
deactivate(pluginId:string): void;
/**
* To be used only by the platform once on startup.
*/
registrationDone(errors?:IMessage[]): void;
registerOneTimeActivationEventListener(activationEvent: string, listener: IActivationEventListener): void;
get(pluginId:string): any;
/**
* Block on this signal any interactions with extensions.
*/
onReady(): TPromise<boolean>;
getPluginsStatus(): { [id: string]: IPluginStatus };
}

View file

@ -104,16 +104,45 @@ interface ITestRunner {
export class PluginHostMain {
private _isTerminating: boolean;
constructor(
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IPluginService private pluginService: IPluginService,
@IInstantiationService instantiationService: IInstantiationService
) {}
) {
this._isTerminating = false;
}
public start(): TPromise<void> {
return this.readPlugins();
}
public terminate(): void {
if (this._isTerminating) {
// we are already shutting down...
return;
}
this._isTerminating = true;
try {
let allExtensions = PluginsRegistry.getAllPluginDescriptions();
let allExtensionsIds = allExtensions.map(ext => ext.id);
let activatedExtensions = allExtensionsIds.filter(id => this.pluginService.isActivated(id));
activatedExtensions.forEach((extensionId) => {
this.pluginService.deactivate(extensionId);
});
} catch(err) {
// TODO: write to log once we have one
}
// Give extensions 1 second to wrap up any async dispose, then exit
setTimeout(() => {
exit()
}, 1000);
}
private readPlugins(): TPromise<void> {
let collector = new PluginsMessageCollector();
let env = this.contextService.getConfiguration().env;

View file

@ -16,8 +16,14 @@ interface IRendererConnection {
initData: IInitData;
}
// This calls exit directly in case the initialization is not finished and we need to exit
// Otherwise, if initialization completed we go to pluginHostMain.terminate()
var onTerminate = function() {
exit();
};
function connectToRenderer(): TPromise<IRendererConnection> {
return new TPromise((c, e) => {
return new TPromise<IRendererConnection>((c, e) => {
const stats: number[] = [];
// Listen init data message
@ -28,7 +34,13 @@ function connectToRenderer(): TPromise<IRendererConnection> {
});
// Listen to all other messages
process.on('message', msg => remoteCom.handle(msg));
process.on('message', (msg) => {
if (msg.type === '__$terminate') {
onTerminate();
return;
}
remoteCom.handle(msg);
});
// Print a console message when rejection isn't handled. For details
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
@ -50,7 +62,7 @@ function connectToRenderer(): TPromise<IRendererConnection> {
try {
process.kill(msg.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
} catch (e) {
exit();
onTerminate();
}
}, 5000);
@ -86,6 +98,10 @@ TPromise.join<any>([connectToRenderer(), connectToSharedProcess()])
const instantiationService = createServices(renderer.remoteCom, renderer.initData, sharedProcessClient);
const pluginHostMain = instantiationService.createInstance(PluginHostMain);
onTerminate = () => {
pluginHostMain.terminate();
};
pluginHostMain.start()
.done(null, err => console.error(err));
});

View file

@ -305,7 +305,9 @@ class PluginHostProcessManager {
this.terminating = true;
if (this.pluginHostProcessHandle) {
this.pluginHostProcessHandle.kill();
this.pluginHostProcessHandle.send({
type: '__$terminate'
});
}
}
}