From fbacd65e2a1d808d058ee4b7aed599cd7f2b04b7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 21 Oct 2016 18:53:05 +0200 Subject: [PATCH] debt - no guessing-caching, better use of gc-signals, command converter using gc-signals, main side heap service --- src/vs/workbench/api/node/extHost.api.impl.ts | 16 +- .../api/node/extHost.contribution.ts | 5 +- src/vs/workbench/api/node/extHost.protocol.ts | 7 +- .../workbench/api/node/extHostApiCommands.ts | 14 +- src/vs/workbench/api/node/extHostCommands.ts | 80 ++++++++- .../workbench/api/node/extHostHeapService.ts | 8 +- .../api/node/extHostLanguageFeatures.ts | 160 +++++------------- .../api/node/extHostTypeConverters.ts | 71 +------- .../api/node/mainThreadHeapService.ts | 113 +++++++++++-- .../api/node/mainThreadLanguageFeatures.ts | 35 ++-- .../test/node/api/extHostApiCommands.test.ts | 21 ++- .../test/node/api/extHostCommands.test.ts | 4 +- .../node/api/extHostLanguageFeatures.test.ts | 14 +- 13 files changed, 292 insertions(+), 256 deletions(-) diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 85097ec0ad5..060ac66a02f 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -28,8 +28,7 @@ import { ExtHostMessageService } from 'vs/workbench/api/node/extHostMessageServi import { ExtHostEditors } from 'vs/workbench/api/node/extHostEditors'; import { ExtHostLanguages } from 'vs/workbench/api/node/extHostLanguages'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; -import * as ExtHostTypeConverters from 'vs/workbench/api/node/extHostTypeConverters'; -import { registerApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; +import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; import * as extHostTypes from 'vs/workbench/api/node/extHostTypes'; import Modes = require('vs/editor/common/modes'); import URI from 'vs/base/common/uri'; @@ -58,16 +57,17 @@ export interface IExtensionApiFactory { export function createApiFactory(threadService: IThreadService, extensionService: ExtHostExtensionService, contextService: IWorkspaceContextService, telemetryService: ITelemetryService): IExtensionApiFactory { + // Addressable instances const col = new InstanceCollection(); - const extHostHeapMonitor = col.define(ExtHostContext.ExtHostHeapService).set(new ExtHostHeapService()); + const extHostHeapService = col.define(ExtHostContext.ExtHostHeapService).set(new ExtHostHeapService()); const extHostDocuments = col.define(ExtHostContext.ExtHostDocuments).set(new ExtHostDocuments(threadService)); const extHostDocumentSaveParticipant = col.define(ExtHostContext.ExtHostDocumentSaveParticipant).set(new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace))); const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set(new ExtHostEditors(threadService, extHostDocuments)); - const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set(new ExtHostCommands(threadService, extHostEditors)); + const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set(new ExtHostCommands(threadService, extHostEditors, extHostHeapService)); const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration))); const extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set(new ExtHostDiagnostics(threadService)); - const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapMonitor, extHostDiagnostics)); + const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics)); const extHostFileSystemEvent = col.define(ExtHostContext.ExtHostFileSystemEventService).set(new ExtHostFileSystemEventService()); const extHostQuickOpen = col.define(ExtHostContext.ExtHostQuickOpen).set(new ExtHostQuickOpen(threadService)); const extHostTerminalService = col.define(ExtHostContext.ExtHostTerminalService).set(new ExtHostTerminalService(threadService)); @@ -88,10 +88,8 @@ export function createApiFactory(threadService: IThreadService, extensionService mainThreadErrors.onUnexpectedExtHostError(errors.transformErrorForSerialization(err)); }); - // the converter might create delegate commands to avoid sending args - // around all the time - ExtHostTypeConverters.Command.initialize(extHostCommands); - registerApiCommands(extHostCommands); + // Register API-ish commands + ExtHostApiCommands.register(extHostCommands); // TODO@joh,alex - this is lifecycle critical // fetch and store telemetry info diff --git a/src/vs/workbench/api/node/extHost.contribution.ts b/src/vs/workbench/api/node/extHost.contribution.ts index c528de5b895..0c08d17a796 100644 --- a/src/vs/workbench/api/node/extHost.contribution.ts +++ b/src/vs/workbench/api/node/extHost.contribution.ts @@ -32,7 +32,6 @@ import { MainThreadTerminalService } from './mainThreadTerminalService'; import { MainThreadWorkspace } from './mainThreadWorkspace'; import { MainProcessExtensionService } from './mainThreadExtensionService'; import { MainThreadFileSystemEventService } from './mainThreadFileSystemEventService'; -import { MainThreadHeapService } from './mainThreadHeapService'; // --- other interested parties import { MainProcessTextMateSyntax } from 'vs/editor/node/textMate/TMSyntax'; @@ -41,6 +40,9 @@ import { JSONValidationExtensionPoint } from 'vs/platform/jsonschemas/common/jso import { LanguageConfigurationFileHandler } from 'vs/editor/node/languageConfiguration'; import { SaveParticipant } from './mainThreadSaveParticipant'; +// --- registers itself as service +import './mainThreadHeapService'; + export class ExtHostContribution implements IWorkbenchContribution { constructor( @@ -89,7 +91,6 @@ export class ExtHostContribution implements IWorkbenchContribution { create(JSONValidationExtensionPoint); create(LanguageConfigurationFileHandler); create(MainThreadFileSystemEventService); - create(MainThreadHeapService); create(SaveParticipant); } } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 45547c8fba1..8813c2da060 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -276,12 +276,13 @@ export interface ObjectIdentifier { } export namespace ObjectIdentifier { + export const name = '$ident'; export function mixin(obj: T, id: number): T & ObjectIdentifier { - Object.defineProperty(obj, '$ident', { value: id, enumerable: true }); + Object.defineProperty(obj, name, { value: id, enumerable: true }); return obj; } - export function get(obj: any): number { - return obj['$ident']; + export function of(obj: any): number { + return obj[name]; } } diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index d7ba704e899..56796b4f7c0 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -18,16 +18,16 @@ import { IOutline } from 'vs/editor/contrib/quickOpen/common/quickOpen'; import { IWorkspaceSymbolProvider, IWorkspaceSymbol } from 'vs/workbench/parts/search/common/search'; import { ICodeLensData } from 'vs/editor/contrib/codelens/common/codelens'; -export function registerApiCommands(commands: ExtHostCommands) { - new ExtHostApiCommands(commands).registerCommands(); -} +export class ExtHostApiCommands { -class ExtHostApiCommands { + static register(commands: ExtHostCommands) { + return new ExtHostApiCommands(commands).registerCommands(); + } private _commands: ExtHostCommands; private _disposables: IDisposable[] = []; - constructor(commands: ExtHostCommands) { + private constructor(commands: ExtHostCommands) { this._commands = commands; } @@ -369,7 +369,7 @@ class ExtHostApiCommands { if (!Array.isArray(value)) { return; } - return value.map(quickFix => typeConverters.Command.to(quickFix.command)); + return value.map(quickFix => this._commands.converter.fromInternal(quickFix.command)); }); } @@ -380,7 +380,7 @@ class ExtHostApiCommands { return value.map(item => { return new types.CodeLens( typeConverters.toRange(item.symbol.range), - typeConverters.Command.to(item.symbol.command)); + this._commands.converter.fromInternal(item.symbol.command)); }); } }); diff --git a/src/vs/workbench/api/node/extHostCommands.ts b/src/vs/workbench/api/node/extHostCommands.ts index 678a76550f2..daf9841a22b 100644 --- a/src/vs/workbench/api/node/extHostCommands.ts +++ b/src/vs/workbench/api/node/extHostCommands.ts @@ -12,7 +12,11 @@ import { ExtHostEditors } from 'vs/workbench/api/node/extHostEditors'; import * as extHostTypes from 'vs/workbench/api/node/extHostTypes'; import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters'; import { cloneAndChange } from 'vs/base/common/objects'; -import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape } from './extHost.protocol'; +import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier } from './extHost.protocol'; +import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; +import { isFalsyOrEmpty } from 'vs/base/common/arrays'; +import * as modes from 'vs/editor/common/modes'; +import * as vscode from 'vscode'; interface CommandHandler { callback: Function; @@ -25,14 +29,21 @@ export class ExtHostCommands extends ExtHostCommandsShape { private _commands: { [n: string]: CommandHandler } = Object.create(null); private _proxy: MainThreadCommandsShape; private _extHostEditors: ExtHostEditors; + private _converter: CommandsConverter; constructor( threadService: IThreadService, - extHostEditors: ExtHostEditors + extHostEditors: ExtHostEditors, + heapService: ExtHostHeapService ) { super(); this._extHostEditors = extHostEditors; this._proxy = threadService.get(MainContext.MainThreadCommands); + this._converter = new CommandsConverter(this, heapService); + } + + get converter(): CommandsConverter { + return this._converter; } registerCommand(id: string, callback: (...args: any[]) => T | Thenable, thisArg?: any, description?: ICommandHandlerDescription): extHostTypes.Disposable { @@ -137,3 +148,68 @@ export class ExtHostCommands extends ExtHostCommandsShape { return TPromise.as(result); } } + + +export class CommandsConverter { + + private _commands: ExtHostCommands; + private _heap: ExtHostHeapService; + + // --- conversion between internal and api commands + constructor(commands: ExtHostCommands, heap: ExtHostHeapService) { + + this._commands = commands; + this._heap = heap; + this._commands.registerCommand('_internal_command_delegation', this._executeConvertedCommand, this); + } + + toInternal(command: vscode.Command): modes.Command { + + if (!command) { + return; + } + + const result: modes.Command = { + id: command.command, + title: command.title + }; + + if (!isFalsyOrEmpty(command.arguments)) { + // we have a contributed command with arguments. that + // means we don't want to send the arguments around + + const id = this._heap.keep(command); + ObjectIdentifier.mixin(result, id); + + result.id = '_internal_command_delegation'; + result.arguments = [id]; + } + + return result; + } + + fromInternal(command: modes.Command): vscode.Command { + + if (!command) { + return; + } + + const id = ObjectIdentifier.of(command); + if (typeof id === 'number') { + return this._heap.get(id); + + } else { + return { + command: command.id, + title: command.title, + arguments: command.arguments + }; + } + } + + private _executeConvertedCommand([id]: number[]) { + const actualCmd = this._heap.get(id); + return this._commands.executeCommand(actualCmd.command, ...actualCmd.arguments); + } + +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostHeapService.ts b/src/vs/workbench/api/node/extHostHeapService.ts index 98d85816f2c..c993ae4ecba 100644 --- a/src/vs/workbench/api/node/extHostHeapService.ts +++ b/src/vs/workbench/api/node/extHostHeapService.ts @@ -11,19 +11,14 @@ export class ExtHostHeapService extends ExtHostHeapServiceShape { private static _idPool = 0; private _data: { [n: number]: any } = Object.create(null); - private _callbacks: { [n: number]: Function } = Object.create(null); - keep(obj: any, callback?: () => any): number { + keep(obj: any): number { const id = ExtHostHeapService._idPool++; this._data[id] = obj; - if (typeof callback === 'function') { - this._callbacks[id] = callback; - } return id; } delete(id: number): boolean { - delete this._callbacks[id]; return this._data[id]; } @@ -33,7 +28,6 @@ export class ExtHostHeapService extends ExtHostHeapServiceShape { $onGarbageCollection(ids: number[]): void { for (const id of ids) { - setTimeout(this._callbacks[id]); this.delete(id); } } diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index c6b2b3316b1..c1e112f1123 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -6,7 +6,6 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import * as vscode from 'vscode'; import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters'; @@ -15,10 +14,10 @@ import { IPosition, IRange, ISingleEditOperation } from 'vs/editor/common/editor import * as modes from 'vs/editor/common/modes'; import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; -import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; +import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { IWorkspaceSymbolProvider, IWorkspaceSymbol } from 'vs/workbench/parts/search/common/search'; -import { asWinJsPromise, ShallowCancelThenPromise } from 'vs/base/common/async'; +import { asWinJsPromise } from 'vs/base/common/async'; import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier } from './extHost.protocol'; import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; @@ -44,112 +43,57 @@ class OutlineAdapter { } } -interface CachedCodeLens { - symbols: modes.ICodeLensSymbol[]; - lenses: vscode.CodeLens[]; - disposables: IDisposable[]; -} - class CodeLensAdapter { + private static _badCmd: vscode.Command = { command: 'missing', title: '<>' }; + private _documents: ExtHostDocuments; - private _commands: ExtHostCommands; + private _commands: CommandsConverter; + private _heapService: ExtHostHeapService; private _provider: vscode.CodeLensProvider; - private _cache: { [uri: string]: { version: number; data: TPromise; } } = Object.create(null); - - constructor(documents: ExtHostDocuments, commands: ExtHostCommands, provider: vscode.CodeLensProvider) { + constructor(documents: ExtHostDocuments, commands: CommandsConverter, heapService: ExtHostHeapService, provider: vscode.CodeLensProvider) { this._documents = documents; this._commands = commands; + this._heapService = heapService; this._provider = provider; } provideCodeLenses(resource: URI): TPromise { const doc = this._documents.getDocumentData(resource).document; - const version = doc.version; - const key = resource.toString(); - // from cache - let entry = this._cache[key]; - if (entry && entry.version === version) { - return new ShallowCancelThenPromise(entry.data.then(cached => cached.symbols)); - } + return asWinJsPromise(token => this._provider.provideCodeLenses(doc, token)).then(lenses => { - const newCodeLensData = asWinJsPromise(token => this._provider.provideCodeLenses(doc, token)).then(lenses => { - if (!Array.isArray(lenses)) { - return; - } - - const data: CachedCodeLens = { - lenses, - symbols: [], - disposables: [], - }; - - lenses.forEach((lens, i) => { - data.symbols.push({ - id: String(i), - range: TypeConverters.fromRange(lens.range), - command: TypeConverters.Command.from(lens.command, data.disposables) + if (Array.isArray(lenses)) { + return lenses.map(lens => { + const id = this._heapService.keep(lens); + return ObjectIdentifier.mixin({ + range: TypeConverters.fromRange(lens.range), + command: this._commands.toInternal(lens.command) + }, id); }); - }); - - return data; - }); - - this._cache[key] = { - version, - data: newCodeLensData - }; - - return new ShallowCancelThenPromise(newCodeLensData.then(newCached => { - if (entry) { - // only now dispose old commands et al - entry.data.then(oldCached => dispose(oldCached.disposables)); } - return newCached && newCached.symbols; - })); - + }); } resolveCodeLens(resource: URI, symbol: modes.ICodeLensSymbol): TPromise { - const entry = this._cache[resource.toString()]; - if (!entry) { + const lens = this._heapService.get(ObjectIdentifier.of(symbol)); + if (!lens) { return; } - return entry.data.then(cachedData => { + let resolve: TPromise; + if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) { + resolve = TPromise.as(lens); + } else { + resolve = asWinJsPromise(token => this._provider.resolveCodeLens(lens, token)); + } - if (!cachedData) { - return; - } - - let lens = cachedData.lenses[Number(symbol.id)]; - if (!lens) { - return; - } - - let resolve: TPromise; - if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) { - resolve = TPromise.as(lens); - } else { - resolve = asWinJsPromise(token => this._provider.resolveCodeLens(lens, token)); - } - - return resolve.then(newLens => { - lens = newLens || lens; - let command = lens.command; - if (!command) { - command = { - title: '<>', - command: 'missing', - }; - } - - symbol.command = TypeConverters.Command.from(command, cachedData.disposables); - return symbol; - }); + return resolve.then(newLens => { + newLens = newLens || lens; + symbol.command = this._commands.toInternal(newLens.command || CodeLensAdapter._badCmd); + return symbol; }); } } @@ -280,13 +224,11 @@ class ReferenceAdapter { class QuickFixAdapter { private _documents: ExtHostDocuments; - private _commands: ExtHostCommands; + private _commands: CommandsConverter; private _diagnostics: ExtHostDiagnostics; private _provider: vscode.CodeActionProvider; - private _cachedCommands: IDisposable[][] = []; - - constructor(documents: ExtHostDocuments, commands: ExtHostCommands, diagnostics: ExtHostDiagnostics, provider: vscode.CodeActionProvider) { + constructor(documents: ExtHostDocuments, commands: CommandsConverter, diagnostics: ExtHostDiagnostics, heapService: ExtHostHeapService, provider: vscode.CodeActionProvider) { this._documents = documents; this._commands = commands; this._diagnostics = diagnostics; @@ -309,23 +251,13 @@ class QuickFixAdapter { } }); - // we cache the last 10 commands that might have been - // created during type conversion. when as have more - // than 10 we drop the first three - const cachedCommands: IDisposable[] = []; - if (this._cachedCommands.push(cachedCommands) > 10) { - dispose(...this._cachedCommands.shift()); - dispose(...this._cachedCommands.shift()); - dispose(...this._cachedCommands.shift()); - } - return asWinJsPromise(token => this._provider.provideCodeActions(doc, ran, { diagnostics: allDiagnostics }, token)).then(commands => { if (!Array.isArray(commands)) { return; } return commands.map((command, i) => { return { - command: TypeConverters.Command.from(command, cachedCommands), + command: this._commands.toInternal(command), score: i }; }); @@ -433,7 +365,7 @@ class NavigateTypeAdapter implements IWorkspaceSymbolProvider { return TPromise.as(item); } - const symbolInfo = this._heapService.get(ObjectIdentifier.get(item)); + const symbolInfo = this._heapService.get(ObjectIdentifier.of(item)); if (symbolInfo) { return asWinJsPromise(token => this._provider.resolveWorkspaceSymbol(symbolInfo, token)).then(value => { return value && TypeConverters.fromSymbolInformation(value); @@ -494,12 +426,13 @@ class RenameAdapter { class SuggestAdapter { private _documents: ExtHostDocuments; + private _commands: CommandsConverter; private _heapService: ExtHostHeapService; private _provider: vscode.CompletionItemProvider; - private _disposables: { [id: number]: IDisposable[] } = []; - constructor(documents: ExtHostDocuments, heapService: ExtHostHeapService, provider: vscode.CompletionItemProvider) { + constructor(documents: ExtHostDocuments, commands: CommandsConverter, heapService: ExtHostHeapService, provider: vscode.CompletionItemProvider) { this._documents = documents; + this._commands = commands; this._heapService = heapService; this._provider = provider; } @@ -535,11 +468,9 @@ class SuggestAdapter { for (let i = 0; i < list.items.length; i++) { const item = list.items[i]; - const disposables: IDisposable[] = []; - const suggestion = TypeConverters.Suggest.from(item, disposables); - const id = this._heapService.keep(item, () => dispose(this._disposables[id])); - this._disposables[id] = disposables; - ObjectIdentifier.mixin(suggestion, id); + const suggestion = TypeConverters.Suggest.from(item); + suggestion.command = this._commands.toInternal(item.command); + ObjectIdentifier.mixin(suggestion, this._heapService.keep(item)); if (item.textEdit) { @@ -577,13 +508,16 @@ class SuggestAdapter { return TPromise.as(suggestion); } - const id = ObjectIdentifier.get(suggestion); + const id = ObjectIdentifier.of(suggestion); const item = this._heapService.get(id); if (!item) { return TPromise.as(suggestion); } return asWinJsPromise(token => this._provider.resolveCompletionItem(item, token)).then(resolvedItem => { - return TypeConverters.Suggest.from(resolvedItem || item, this._disposables[id]); + resolvedItem = resolvedItem || item; + const suggestion = TypeConverters.Suggest.from(resolvedItem); + suggestion.command = this._commands.toInternal(resolvedItem.command); + return suggestion; }); } } @@ -709,7 +643,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new CodeLensAdapter(this._documents, this._commands, provider); + this._adapter[handle] = new CodeLensAdapter(this._documents, this._commands.converter, this._heapService, provider); this._proxy.$registerCodeLensSupport(handle, selector); return this._createDisposable(handle); } @@ -778,7 +712,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new QuickFixAdapter(this._documents, this._commands, this._diagnostics, provider); + this._adapter[handle] = new QuickFixAdapter(this._documents, this._commands.converter, this._diagnostics, this._heapService, provider); this._proxy.$registerQuickFixSupport(handle, selector); return this._createDisposable(handle); } @@ -856,7 +790,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new SuggestAdapter(this._documents, this._heapService, provider); + this._adapter[handle] = new SuggestAdapter(this._documents, this._commands.converter, this._heapService, provider); this._proxy.$registerSuggestSupport(handle, selector, triggerCharacters); return this._createDisposable(handle); } diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 28683634501..344ab09f3eb 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -4,10 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import Severity from 'vs/base/common/severity'; -import { isFalsyOrEmpty } from 'vs/base/common/arrays'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { stringDiff } from 'vs/base/common/diff/diff'; import * as modes from 'vs/editor/common/modes'; import * as types from './extHostTypes'; @@ -314,7 +311,7 @@ export const CompletionItemKind = { export const Suggest = { - from(item: vscode.CompletionItem, disposables: IDisposable[]): modes.ISuggestion { + from(item: vscode.CompletionItem): modes.ISuggestion { const suggestion: modes.ISuggestion = { label: item.label || '', insertText: item.insertText || item.label, @@ -323,7 +320,6 @@ export const Suggest = { documentation: item.documentation, sortText: item.sortText, filterText: item.filterText, - command: Command.from(item.command, disposables), additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(TextEdit.from) }; return suggestion; @@ -375,69 +371,6 @@ export namespace DocumentLink { } } -export namespace Command { - - const _delegateId = '_internal_delegate_command'; - const _cache: { [id: string]: vscode.Command } = Object.create(null); - let _idPool = 1; - - export function initialize(commands: ExtHostCommands) { - return commands.registerCommand(_delegateId, (id: string) => { - const command = _cache[id]; - if (!command) { - // handle already disposed delegations graceful - return; - } - return commands.executeCommand(command.command, ...command.arguments); - }); - } - - export function from(command: vscode.Command, disposables: IDisposable[]): modes.Command { - - if (!command) { - return; - } - - const result = { - id: command.command, - title: command.title - }; - - if (!isFalsyOrEmpty(command.arguments)) { - - // redirect to delegate command and store actual command - const id = `delegate/${_idPool++}/for/${command.command}`; - - result.id = _delegateId; - result.arguments = [id]; - _cache[id] = command; - - disposables.push({ - dispose() { - delete _cache[id]; - } - }); - } - - return result; - } - - export function to(command: modes.Command): vscode.Command { - let result: vscode.Command; - if (command.id === _delegateId) { - let [key] = command.arguments; - result = _cache[key]; - } - if (!result) { - result = { - command: command.id, - title: command.title - }; - } - return result; - } -} - export namespace TextDocumentSaveReason { export function to(reason: SaveReason): vscode.TextDocumentSaveReason { @@ -451,4 +384,4 @@ export namespace TextDocumentSaveReason { return types.TextDocumentSaveReason.FocusOut; } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/mainThreadHeapService.ts b/src/vs/workbench/api/node/mainThreadHeapService.ts index 48855067077..d19a269a945 100644 --- a/src/vs/workbench/api/node/mainThreadHeapService.ts +++ b/src/vs/workbench/api/node/mainThreadHeapService.ts @@ -5,28 +5,119 @@ 'use strict'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { TPromise } from 'vs/base/common/winjs.base'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { ExtHostContext } from './extHost.protocol'; -import { onDidGarbageCollectSignals, consumeSignals } from 'gc-signals'; +import { ExtHostContext, ObjectIdentifier } from './extHost.protocol'; +import { consumeSignals, GCSignal } from 'gc-signals'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -export class MainThreadHeapService { - private _subscription: IDisposable; +declare class WeakMap { + set(key: K, value?: V): WeakMap; +} + +declare class Set { + add(e: E): this; + has(e: E): boolean; + delete(e: E): boolean; +} + +export const IHeapService = createDecorator('heapService'); + +export interface IHeapService { + _serviceBrand: any; + + /** + * Track gc-collection for all new objects that + * have the $ident-value set. + */ + trackRecursive(p: TPromise): TPromise; + + /** + * Track gc-collection for all new objects that + * have the $ident-value set. + */ + trackRecursive(obj: T): T; +} + + +export class MainThreadHeapService implements IHeapService { + + _serviceBrand: any; + + private _activeSignals = new WeakMap(); + private _activeIds = new Set(); private _consumeHandle: number; constructor( @IThreadService threadService: IThreadService) { const proxy = threadService.get(ExtHostContext.ExtHostHeapService); - this._subscription = onDidGarbageCollectSignals(ids => { - proxy.$onGarbageCollection(ids); - }); + this._consumeHandle = setInterval(() => { + const ids = consumeSignals(); - this._consumeHandle = setInterval(consumeSignals, 15 * 1000); + if (ids.length > 0) { + // local book-keeping + for (const id of ids) { + this._activeIds.delete(id); + } + + // send to ext host + proxy.$onGarbageCollection(ids); + } + + }, 15 * 1000); } dispose() { clearInterval(this._consumeHandle); - this._subscription.dispose(); } -} \ No newline at end of file + + trackRecursive(p: TPromise): TPromise; + trackRecursive(obj: T): T; + trackRecursive(obj: any): any { + if (TPromise.is(obj)) { + return obj.then(result => this.trackRecursive(result)); + } else { + return this._doTrackRecursive(obj); + } + } + + private _doTrackRecursive(obj: any): any { + + const stack = [obj]; + while (stack.length > 0) { + + // remove first element + let obj = stack.shift(); + + if (!obj || typeof obj !== 'object') { + continue; + } + + for (let key in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, key)) { + continue; + } + + const value = obj[key]; + // recurse -> object/array + if (typeof value === 'object') { + stack.push(value); + + } else if (key === ObjectIdentifier.name) { + // track new $ident-objects + + if (typeof value === 'number' && !this._activeIds.has(value)) { + this._activeIds.add(value); + this._activeSignals.set(obj, new GCSignal(value)); + } + } + } + } + + return obj; + } +} + +registerSingleton(IHeapService, MainThreadHeapService); diff --git a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts index f06b4d66b68..cf924f2f9fe 100644 --- a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts @@ -15,18 +15,23 @@ import { wireCancellationToken } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier } from './extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape } from './extHost.protocol'; import { LanguageConfigurationRegistry, LanguageConfiguration } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { trackGarbageCollection } from 'gc-signals'; +import { IHeapService } from './mainThreadHeapService'; export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape { private _proxy: ExtHostLanguageFeaturesShape; + private _heapService: IHeapService; private _registrations: { [handle: number]: IDisposable; } = Object.create(null); - constructor( @IThreadService threadService: IThreadService) { + constructor( + @IThreadService threadService: IThreadService, + @IHeapService heapService: IHeapService + ) { super(); this._proxy = threadService.get(ExtHostContext.ExtHostLanguageFeatures); + this._heapService = heapService; } $unregister(handle: number): TPromise { @@ -54,10 +59,10 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise { this._registrations[handle] = modes.CodeLensProviderRegistry.register(selector, { provideCodeLenses: (model: IReadOnlyModel, token: CancellationToken): modes.ICodeLensSymbol[] | Thenable => { - return wireCancellationToken(token, this._proxy.$provideCodeLenses(handle, model.uri)); + return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeLenses(handle, model.uri))); }, resolveCodeLens: (model: IReadOnlyModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): modes.ICodeLensSymbol | Thenable => { - return wireCancellationToken(token, this._proxy.$resolveCodeLens(handle, model.uri, codeLens)); + return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$resolveCodeLens(handle, model.uri, codeLens))); } }); return undefined; @@ -112,7 +117,7 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape $registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise { this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, { provideCodeActions: (model: IReadOnlyModel, range: EditorRange, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range)); + return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range))); } }); return undefined; @@ -155,14 +160,7 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape $registerNavigateTypeSupport(handle: number): TPromise { this._registrations[handle] = WorkspaceSymbolProviderRegistry.register({ provideWorkspaceSymbols: (search: string): TPromise => { - return this._proxy.$provideWorkspaceSymbols(handle, search).then(result => { - if (result) { - for (const item of result) { - trackGarbageCollection(item, ObjectIdentifier.get(item)); - } - } - return result; - }); + return this._heapService.trackRecursive(this._proxy.$provideWorkspaceSymbols(handle, search)); }, resolveWorkspaceSymbol: (item: IWorkspaceSymbol): TPromise => { return this._proxy.$resolveWorkspaceSymbol(handle, item); @@ -188,14 +186,7 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape this._registrations[handle] = modes.SuggestRegistry.register(selector, { triggerCharacters: triggerCharacters, provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)).then(result => { - if (result && result.suggestions) { - for (const suggestion of result.suggestions) { - trackGarbageCollection(suggestion, ObjectIdentifier.get(suggestion)); - } - } - return result; - }); + return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position))); }, resolveCompletionItem: (model: IReadOnlyModel, position: EditorPosition, suggestion: modes.ISuggestion, token: CancellationToken): Thenable => { return wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion)); diff --git a/src/vs/workbench/test/node/api/extHostApiCommands.test.ts b/src/vs/workbench/test/node/api/extHostApiCommands.test.ts index e852800aadc..6dccdf6ea07 100644 --- a/src/vs/workbench/test/node/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/node/api/extHostApiCommands.test.ts @@ -21,12 +21,12 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c import { IModelService } from 'vs/editor/common/services/modelService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; import { MainThreadLanguageFeatures } from 'vs/workbench/api/node/mainThreadLanguageFeatures'; -import { registerApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; +import { IHeapService } from 'vs/workbench/api/node/mainThreadHeapService'; +import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; import { MainThreadCommands } from 'vs/workbench/api/node/mainThreadCommands'; import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; -import * as ExtHostTypeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { MainContext, ExtHostContext } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import * as vscode from 'vscode'; @@ -58,7 +58,13 @@ suite('ExtHostLanguageFeatureCommands', function () { let instantiationService = new TestInstantiationService(); threadService = new TestThreadService(); - + instantiationService.stub(IHeapService, { + _serviceBrand: undefined, + trackRecursive(args) { + // nothing + return args; + } + }); instantiationService.stub(ICommandService, { _serviceBrand: undefined, executeCommand(id, args): any { @@ -102,16 +108,17 @@ suite('ExtHostLanguageFeatureCommands', function () { }, }); - commands = new ExtHostCommands(threadService, null); + const heapService = new ExtHostHeapService(); + + commands = new ExtHostCommands(threadService, null, heapService); threadService.set(ExtHostContext.ExtHostCommands, commands); - ExtHostTypeConverters.Command.initialize(commands); threadService.setTestInstance(MainContext.MainThreadCommands, instantiationService.createInstance(MainThreadCommands)); - registerApiCommands(commands); + ExtHostApiCommands.register(commands); const diagnostics = new ExtHostDiagnostics(threadService); threadService.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, new ExtHostHeapService(), diagnostics); + extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, heapService, diagnostics); threadService.set(ExtHostContext.ExtHostLanguageFeatures, extHost); mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, instantiationService.createInstance(MainThreadLanguageFeatures)); diff --git a/src/vs/workbench/test/node/api/extHostCommands.test.ts b/src/vs/workbench/test/node/api/extHostCommands.test.ts index efd619cbc17..66a25730819 100644 --- a/src/vs/workbench/test/node/api/extHostCommands.test.ts +++ b/src/vs/workbench/test/node/api/extHostCommands.test.ts @@ -28,7 +28,7 @@ suite('ExtHostCommands', function () { } }; - const commands = new ExtHostCommands(OneGetThreadService(shape), undefined); + const commands = new ExtHostCommands(OneGetThreadService(shape), undefined, undefined); commands.registerCommand('foo', () => { }).dispose(); assert.equal(lastUnregister, 'foo'); assert.equal(CommandsRegistry.getCommand('foo'), undefined); @@ -49,7 +49,7 @@ suite('ExtHostCommands', function () { } }; - const commands = new ExtHostCommands(OneGetThreadService(shape), undefined); + const commands = new ExtHostCommands(OneGetThreadService(shape), undefined, undefined); const reg = commands.registerCommand('foo', () => { }); reg.dispose(); reg.dispose(); diff --git a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts index f63f4b4d6d7..a625ad54faa 100644 --- a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts @@ -21,6 +21,7 @@ import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFe import { MainThreadLanguageFeatures } from 'vs/workbench/api/node/mainThreadLanguageFeatures'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { MainThreadCommands } from 'vs/workbench/api/node/mainThreadCommands'; +import { IHeapService } from 'vs/workbench/api/node/mainThreadHeapService'; import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/common/quickOpen'; import { DocumentSymbolProviderRegistry, DocumentHighlightKind } from 'vs/editor/common/modes'; @@ -67,6 +68,13 @@ suite('ExtHostLanguageFeatures', function () { let instantiationService = new TestInstantiationService(); instantiationService.stub(IThreadService, threadService); instantiationService.stub(IMarkerService); + instantiationService.stub(IHeapService, { + _serviceBrand: undefined, + trackRecursive(args) { + // nothing + return args; + } + }); originalErrorHandler = errorHandler.getUnexpectedErrorHandler(); setUnexpectedErrorHandler(() => { }); @@ -92,14 +100,16 @@ suite('ExtHostLanguageFeatures', function () { }, }); - const commands = new ExtHostCommands(threadService, null); + const heapService = new ExtHostHeapService(); + + const commands = new ExtHostCommands(threadService, null, heapService); threadService.set(ExtHostContext.ExtHostCommands, commands); threadService.setTestInstance(MainContext.MainThreadCommands, instantiationService.createInstance(MainThreadCommands)); const diagnostics = new ExtHostDiagnostics(threadService); threadService.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, new ExtHostHeapService(), diagnostics); + extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, heapService, diagnostics); threadService.set(ExtHostContext.ExtHostLanguageFeatures, extHost); mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, instantiationService.createInstance(MainThreadLanguageFeatures));