Fixes #14500: wait for extension points

* Use the extension descriptions available in the UI thread
* Don't wait for handling extension points until extension host is running
* Create the extension host process only after extension points have been handled
* Send the initial configuration as part of the initData to the extension host
This commit is contained in:
Alex Dima 2016-10-26 22:02:47 +02:00
parent d801542057
commit 6dd7a04bbd
11 changed files with 39 additions and 52 deletions

View file

@ -78,6 +78,12 @@ export abstract class AbstractExtensionService<T extends ActivatedExtension> imp
});
}
public readExtensions(): TPromise<IExtensionDescription[]> {
return this.onReady().then(() => {
return this._registry.getAllExtensionDescriptions();
});
}
public getExtensionsStatus(): { [id: string]: IExtensionsStatus } {
return null;
}

View file

@ -43,7 +43,7 @@ import * as vscode from 'vscode';
import * as paths from 'vs/base/common/paths';
import { realpathSync } from 'fs';
import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
import { MainContext, ExtHostContext, InstanceCollection } from './extHost.protocol';
import { MainContext, ExtHostContext, InstanceCollection, IInitConfiguration } from './extHost.protocol';
export interface IExtensionApiFactory {
@ -53,7 +53,7 @@ export interface IExtensionApiFactory {
/**
* This method instantiates and returns the extension API surface
*/
export function createApiFactory(threadService: IThreadService, extensionService: ExtHostExtensionService, contextService: IWorkspaceContextService, telemetryService: ITelemetryService): IExtensionApiFactory {
export function createApiFactory(initDataConfiguration: IInitConfiguration, threadService: IThreadService, extensionService: ExtHostExtensionService, contextService: IWorkspaceContextService, telemetryService: ITelemetryService): IExtensionApiFactory {
@ -64,7 +64,7 @@ export function createApiFactory(threadService: IThreadService, extensionService
const extHostDocumentSaveParticipant = col.define(ExtHostContext.ExtHostDocumentSaveParticipant).set<ExtHostDocumentSaveParticipant>(new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace)));
const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set<ExtHostEditors>(new ExtHostEditors(threadService, extHostDocuments));
const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set<ExtHostCommands>(new ExtHostCommands(threadService, extHostEditors, extHostHeapService));
const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set<ExtHostConfiguration>(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration)));
const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set<ExtHostConfiguration>(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), initDataConfiguration));
const extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set<ExtHostDiagnostics>(new ExtHostDiagnostics(threadService));
const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set<ExtHostLanguageFeatures>(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics));
const extHostFileSystemEvent = col.define(ExtHostContext.ExtHostFileSystemEventService).set<ExtHostFileSystemEventService>(new ExtHostFileSystemEventService());

View file

@ -43,6 +43,10 @@ export interface IEnvironment {
extensionTestsPath: string;
}
export interface IInitConfiguration {
_initConfigurationBrand: void;
}
export interface IInitData {
parentPid: number;
environment: IEnvironment;
@ -50,6 +54,7 @@ export interface IInitData {
workspace: IWorkspace;
};
extensions: IExtensionDescription[];
configuration: IInitConfiguration;
}
export interface InstanceSetter<T> {
@ -213,7 +218,6 @@ export abstract class MainThreadWorkspaceShape {
}
export abstract class MainProcessExtensionServiceShape {
$onExtensionHostReady(extensionDescriptions: IExtensionDescription[]): TPromise<void> { throw ni(); }
$localShowMessage(severity: Severity, msg: string): void { throw ni(); }
$onExtensionActivated(extensionId: string): void { throw ni(); }
$onExtensionActivationFailed(extensionId: string): void { throw ni(); }

View file

@ -5,7 +5,6 @@
'use strict';
import { mixin } from 'vs/base/common/objects';
import { illegalState } from 'vs/base/common/errors';
import Event, { Emitter } from 'vs/base/common/event';
import { WorkspaceConfiguration } from 'vscode';
import { ExtHostConfigurationShape, MainThreadConfigurationShape } from './extHost.protocol';
@ -14,13 +13,13 @@ import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/
export class ExtHostConfiguration extends ExtHostConfigurationShape {
private _proxy: MainThreadConfigurationShape;
private _hasConfig: boolean;
private _config: any;
private _onDidChangeConfiguration = new Emitter<void>();
constructor(proxy: MainThreadConfigurationShape) {
constructor(proxy: MainThreadConfigurationShape, configuration: any) {
super();
this._proxy = proxy;
this._config = configuration;
}
get onDidChangeConfiguration(): Event<void> {
@ -29,14 +28,10 @@ export class ExtHostConfiguration extends ExtHostConfigurationShape {
public $acceptConfigurationChanged(config: any) {
this._config = config;
this._hasConfig = true;
this._onDidChangeConfiguration.fire(undefined);
}
public getConfiguration(section?: string): WorkspaceConfiguration {
if (!this._hasConfig) {
throw illegalState('missing config');
}
const config = section
? ExtHostConfiguration._lookUp(section, this._config)

View file

@ -119,7 +119,7 @@ export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExt
* This class is constructed manually because it is a service, so it doesn't use any ctor injection
*/
constructor(availableExtensions: IExtensionDescription[], threadService: IThreadService, telemetryService: ITelemetryService, args: { _serviceBrand: any; workspaceStoragePath: string; }) {
super(false);
super(true);
this._registry.registerExtensions(availableExtensions);
this._threadService = threadService;
this._storage = new ExtHostStorage(threadService);
@ -186,14 +186,6 @@ export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExt
return result;
}
public registrationDone(): void {
this._proxy.$onExtensionHostReady(this._registry.getAllExtensionDescriptions()).then(() => {
// Wait for the main process to acknowledge its receival of the extensions descriptions
// before allowing extensions to be activated
this._triggerOnReady();
});
}
// -- overwriting AbstractExtensionService
protected _showMessage(severity: Severity, msg: string): void {

View file

@ -25,7 +25,6 @@ export class MainThreadConfiguration extends MainThreadConfigurationShape {
this._configurationEditingService = configurationEditingService;
const proxy = threadService.get(ExtHostContext.ExtHostConfiguration);
this._toDispose = configurationService.onDidUpdateConfiguration(event => proxy.$acceptConfigurationChanged(event.config));
proxy.$acceptConfigurationChanged(configurationService.getConfiguration());
}
public dispose(): void {

View file

@ -7,7 +7,7 @@
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { AbstractExtensionService, ActivatedExtension } from 'vs/platform/extensions/common/abstractExtensionService';
import { IMessage, IExtensionDescription, IExtensionsStatus } from 'vs/platform/extensions/common/extensions';
import { IExtensionsRuntimeService, IMessage, IExtensionDescription, IExtensionsStatus } from 'vs/platform/extensions/common/extensions';
import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry';
import { IMessageService } from 'vs/platform/message/common/message';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
@ -53,7 +53,8 @@ export class MainProcessExtensionService extends AbstractExtensionService<Activa
constructor(
@IThreadService threadService: IThreadService,
@IMessageService messageService: IMessageService,
@IEnvironmentService private environmentService: IEnvironmentService
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionsRuntimeService extensionsRuntimeService: IExtensionsRuntimeService
) {
super(false);
this._isDev = !environmentService.isBuilt || !!environmentService.extensionDevelopmentPath;
@ -62,6 +63,8 @@ export class MainProcessExtensionService extends AbstractExtensionService<Activa
this._threadService = threadService;
this._proxy = this._threadService.get(ExtHostContext.ExtHostExtensionService);
this._extensionsStatus = {};
extensionsRuntimeService.getExtensions().then((extensionDescriptions) => this._onExtensionDescriptions(extensionDescriptions));
}
private _handleMessage(msg: IMessage) {
@ -124,7 +127,7 @@ export class MainProcessExtensionService extends AbstractExtensionService<Activa
// -- called by extension host
public $onExtensionHostReady(extensionDescriptions: IExtensionDescription[]): TPromise<void> {
private _onExtensionDescriptions(extensionDescriptions: IExtensionDescription[]): void {
this._registry.registerExtensions(extensionDescriptions);
let availableExtensions = this._registry.getAllExtensionDescriptions();
@ -135,7 +138,6 @@ export class MainProcessExtensionService extends AbstractExtensionService<Activa
}
this._triggerOnReady();
return;
}
private _handleExtensionPoint<T>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[]): void {

View file

@ -296,12 +296,13 @@ export class WorkbenchShell {
serviceCollection.set(IExtensionsRuntimeService, extensionsRuntimeService);
disposables.add(extensionsRuntimeService);
const extensionHostProcessWorker = this.startExtensionHost(instantiationService);
const extensionHostProcessWorker = instantiationService.createInstance(ExtensionHostProcessWorker);
this.threadService = instantiationService.createInstance(MainThreadService, extensionHostProcessWorker.messagingProtocol);
serviceCollection.set(IThreadService, this.threadService);
const extensionService = instantiationService.createInstance(MainProcessExtensionService);
serviceCollection.set(IExtensionService, extensionService);
extensionHostProcessWorker.start(extensionService);
serviceCollection.set(ICommandService, new CommandService(instantiationService, extensionService));
@ -454,12 +455,6 @@ export class WorkbenchShell {
this.workbench.layout();
}
private startExtensionHost(instantiationService: InstantiationService): ExtensionHostProcessWorker {
const extensionHostProcessWorker: ExtensionHostProcessWorker = <ExtensionHostProcessWorker>instantiationService.createInstance(ExtensionHostProcessWorker);
extensionHostProcessWorker.start();
return extensionHostProcessWorker;
}
public joinCreation(): TPromise<boolean> {
return this.workbench.joinCreation();
}

View file

@ -55,7 +55,7 @@ export class ExtensionHostMain {
this._extensionService = new ExtHostExtensionService(initData.extensions, threadService, telemetryService, { _serviceBrand: 'optionalArgs', workspaceStoragePath });
// Create the ext host API
const factory = createApiFactory(threadService, this._extensionService, this._contextService, telemetryService);
const factory = createApiFactory(initData.configuration, threadService, this._extensionService, this._contextService, telemetryService);
defineAPI(factory, this._extensionService);
}
@ -107,7 +107,7 @@ export class ExtensionHostMain {
}
public start(): TPromise<void> {
return this.registerExtensions();
return this.handleEagerExtensions().then(() => this.handleExtensionTests());
}
public terminate(): void {
@ -142,11 +142,6 @@ export class ExtensionHostMain {
}, 1000);
}
private registerExtensions(): TPromise<void> {
this._extensionService.registrationDone();
return this.handleEagerExtensions().then(() => this.handleExtensionTests());
}
// Handle "eager" activation extensions
private handleEagerExtensions(): TPromise<void> {
this._extensionService.activateByEvent('*').then(null, (err) => {

View file

@ -22,11 +22,12 @@ import { ipcRenderer as ipc } from 'electron';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionsRuntimeService } from 'vs/platform/extensions/common/extensions';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import Event, { Emitter } from 'vs/base/common/event';
import { createQueuedSender, IQueuedSender } from 'vs/base/node/processes';
import { IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { IInitData, IInitConfiguration } from 'vs/workbench/api/node/extHost.protocol';
import { MainProcessExtensionService } from 'vs/workbench/api/node/mainThreadExtensionService';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
export const EXTENSION_LOG_BROADCAST_CHANNEL = 'vscode:extensionLog';
export const EXTENSION_ATTACH_BROADCAST_CHANNEL = 'vscode:extensionAttach';
@ -58,6 +59,8 @@ export class ExtensionHostProcessWorker {
return this._onMessage.event;
}
private extensionService: MainProcessExtensionService;
constructor(
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IMessageService private messageService: IMessageService,
@ -65,7 +68,7 @@ export class ExtensionHostProcessWorker {
@ILifecycleService lifecycleService: ILifecycleService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionsRuntimeService private extensionsRuntimeService: IExtensionsRuntimeService
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService
) {
// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
this.isExtensionDevelopmentHost = !!environmentService.extensionDevelopmentPath;
@ -78,7 +81,8 @@ export class ExtensionHostProcessWorker {
lifecycleService.onShutdown(() => this.terminate());
}
public start(): void {
public start(extensionService: MainProcessExtensionService): void {
this.extensionService = extensionService;
let opts: any = {
env: objects.mixin(objects.clone(process.env), {
AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess',
@ -200,7 +204,7 @@ export class ExtensionHostProcessWorker {
if (this.initializeTimer) {
window.clearTimeout(this.initializeTimer);
}
this.extensionsRuntimeService.getExtensions().then(extensionDescriptors => {
this.extensionService.readExtensions().then((extensionDescriptions) => {
let initData: IInitData = {
parentPid: process.pid,
environment: {
@ -213,7 +217,8 @@ export class ExtensionHostProcessWorker {
contextService: {
workspace: this.contextService.getWorkspace()
},
extensions: extensionDescriptors
extensions: extensionDescriptions,
configuration: this.configurationService.getConfiguration<IInitConfiguration>()
};
this.extensionHostProcessQueuedSender.send(stringify(initData));
});

View file

@ -25,15 +25,9 @@ suite('ExtHostConfiguration', function () {
if (!shape) {
shape = new class extends MainThreadConfigurationShape { };
}
const result = new ExtHostConfiguration(shape);
result.$acceptConfigurationChanged(data);
return result;
return new ExtHostConfiguration(shape, data);
}
test('check illegal state', function () {
assert.throws(() => new ExtHostConfiguration(new class extends MainThreadConfigurationShape { }).getConfiguration('foo'));
});
test('udate / section to key', function () {
const shape = new RecordingShape();