diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 61cb690f2ab24..4266bd833e960 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -152,6 +152,11 @@ "from": "bpasero/fsevents#vscode", "resolved": "git://github.com/bpasero/fsevents.git#fe2aaccaaffbd69a23374cf46a8c6bafe8e51b01" }, + "gc-signals": { + "version": "0.0.1", + "from": "gc-signals@0.0.1", + "resolved": "https://registry.npmjs.org/gc-signals/-/gc-signals-0.0.1.tgz" + }, "getmac": { "version": "1.0.7", "from": "getmac@1.0.7", diff --git a/package.json b/package.json index 3435297932ad8..bc346f0683dcd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "applicationinsights": "0.15.6", "chokidar": "bpasero/chokidar#vscode", "emmet": "1.3.1", + "gc-signals": "^0.0.1", "getmac": "1.0.7", "graceful-fs": "4.1.2", "http-proxy-agent": "0.2.7", diff --git a/src/typings/gc-signals.d.ts b/src/typings/gc-signals.d.ts new file mode 100644 index 0000000000000..cc982d1260c91 --- /dev/null +++ b/src/typings/gc-signals.d.ts @@ -0,0 +1,19 @@ +declare module 'gc-signals' { + export interface GCSignal { + } + /** + * Create a new GC signal. When being garbage collected the passed + * value is stored for later consumption. + */ + export const GCSignal: { + new (id: number): GCSignal; + }; + /** + * Consume ids of garbage collected signals. + */ + export function consumeSignals(): number[]; + export function onDidGarbageCollectSignals(callback: (ids: number[]) => any): { + dispose(): void; + }; + export function trackGarbageCollection(obj: any, id: number): number; +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index f4c1b144ae07f..60c3d773de3b1 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -17,6 +17,7 @@ import {ExtHostConfiguration} from 'vs/workbench/api/node/extHostConfiguration'; import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; import {ExtHostWorkspace} from 'vs/workbench/api/node/extHostWorkspace'; import {ExtHostQuickOpen} from 'vs/workbench/api/node/extHostQuickOpen'; +import {ExtHostHeapService} from 'vs/workbench/api/node/extHostHeapService'; import {ExtHostStatusBar} from 'vs/workbench/api/node/extHostStatusBar'; import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; import {ExtHostOutputService} from 'vs/workbench/api/node/extHostOutputService'; @@ -99,12 +100,13 @@ export class ExtHostAPIImplementation { // Addressable instances const col = new InstanceCollection(); + const extHostHeapMonitor = col.define(ExtHostContext.ExtHostHeapService).set(new ExtHostHeapService()); const extHostDocuments = col.define(ExtHostContext.ExtHostDocuments).set(new ExtHostDocuments(threadService)); const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set(new ExtHostEditors(threadService, extHostDocuments)); const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set(new ExtHostCommands(threadService, extHostEditors)); 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, extHostDiagnostics)); + const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapMonitor, extHostDiagnostics)); const extHostFileSystemEvent = col.define(ExtHostContext.ExtHostFileSystemEventService).set(new ExtHostFileSystemEventService()); const extHostQuickOpen = col.define(ExtHostContext.ExtHostQuickOpen).set(new ExtHostQuickOpen(threadService)); col.define(ExtHostContext.ExtHostExtensionService).set(extensionService); diff --git a/src/vs/workbench/api/node/extHost.contribution.ts b/src/vs/workbench/api/node/extHost.contribution.ts index 639227e8ee1f4..56753b050109c 100644 --- a/src/vs/workbench/api/node/extHost.contribution.ts +++ b/src/vs/workbench/api/node/extHost.contribution.ts @@ -32,6 +32,7 @@ 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'; @@ -87,6 +88,7 @@ export class ExtHostContribution implements IWorkbenchContribution { create(JSONValidationExtensionPoint); create(LanguageConfigurationFileHandler); create(MainThreadFileSystemEventService); + create(MainThreadHeapService); } } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 06a1389dc30de..a2031bbce9aee 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -265,6 +265,24 @@ export abstract class ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents) { throw ni(); } } +export interface ObjectIdentifier { + $ident: number; +} + +export namespace ObjectIdentifier { + export function mixin(obj: T, id: number): T & ObjectIdentifier { + Object.defineProperty(obj, '$ident', { value: id, enumerable: true }); + return obj; + } + export function get(obj: any): number { + return obj['$ident']; + } +} + +export abstract class ExtHostHeapServiceShape { + $onGarbageCollection(ids: number[]): void { throw ni(); } +} + export abstract class ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: URI): TPromise { throw ni(); } $provideCodeLenses(handle: number, resource: URI): TPromise { throw ni(); } @@ -321,6 +339,7 @@ export const ExtHostContext = { ExtHostDocuments: createExtId('ExtHostDocuments', ExtHostDocumentsShape), ExtHostEditors: createExtId('ExtHostEditors', ExtHostEditorsShape), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService', ExtHostFileSystemEventServiceShape), + ExtHostHeapService: createExtId('ExtHostHeapMonitor', ExtHostHeapServiceShape), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures', ExtHostLanguageFeaturesShape), ExtHostQuickOpen: createExtId('ExtHostQuickOpen', ExtHostQuickOpenShape), ExtHostExtensionService: createExtId('ExtHostExtensionService', ExtHostExtensionServiceShape), diff --git a/src/vs/workbench/api/node/extHostHeapService.ts b/src/vs/workbench/api/node/extHostHeapService.ts new file mode 100644 index 0000000000000..47a3d8acdbcbc --- /dev/null +++ b/src/vs/workbench/api/node/extHostHeapService.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {ExtHostHeapServiceShape} from './extHost.protocol'; + +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 { + 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]; + } + + get(id: number): T { + return this._data[id]; + } + + $onGarbageCollection(ids: number[]): void { + for (const id of ids) { + setTimeout(this._callbacks[id]); + this.delete(id); + } + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index e0e5db8ee61a2..02b6c218188bf 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -10,15 +10,16 @@ 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'; -import {Range, Disposable, CompletionList} from 'vs/workbench/api/node/extHostTypes'; +import {Range, Disposable, CompletionList, CompletionItem} from 'vs/workbench/api/node/extHostTypes'; import {IPosition, IRange, ISingleEditOperation} from 'vs/editor/common/editorCommon'; 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 {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 {MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape} from './extHost.protocol'; +import {MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier} from './extHost.protocol'; import {regExpLeadsToEndlessLoop} from 'vs/base/common/strings'; // --- adapter @@ -496,18 +497,17 @@ class RenameAdapter { } } -interface ISuggestion2 extends modes.ISuggestion { - id: string; -} class SuggestAdapter { private _documents: ExtHostDocuments; + private _heapService: ExtHostHeapService; private _provider: vscode.CompletionItemProvider; - private _cache: { [key: string]: { list: CompletionList; disposables: IDisposable[]; } } = Object.create(null); + private _disposables: { [id: number]: IDisposable[] } = []; - constructor(documents: ExtHostDocuments, provider: vscode.CompletionItemProvider) { + constructor(documents: ExtHostDocuments, heapMonitor: ExtHostHeapService, provider: vscode.CompletionItemProvider) { this._documents = documents; + this._heapService = heapMonitor; this._provider = provider; } @@ -516,12 +516,6 @@ class SuggestAdapter { const doc = this._documents.getDocumentData(resource).document; const pos = TypeConverters.toPosition(position); - const key = resource.toString(); - if (this._cache[key]) { - dispose(this._cache[key].disposables); - delete this._cache[key]; - } - return asWinJsPromise(token => this._provider.provideCompletionItems(doc, pos, token)).then(value => { const result: modes.ISuggestResult = { @@ -533,7 +527,6 @@ class SuggestAdapter { const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) || new Range(pos, pos)) .with({ end: pos }); - const disposables: IDisposable[] = []; let list: CompletionList; if (!value) { // undefined and null are valid results @@ -550,7 +543,11 @@ class SuggestAdapter { for (let i = 0; i < list.items.length; i++) { const item = list.items[i]; - const suggestion = TypeConverters.Suggest.from(item, disposables); + 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); if (item.textEdit) { @@ -574,32 +571,27 @@ class SuggestAdapter { suggestion.overwriteAfter = 0; } - // assign identifier to suggestion - suggestion.id = String(i); - // store suggestion result.suggestions.push(suggestion); } - // cache for details call - this._cache[key] = { list, disposables }; - return result; }); } resolveCompletionItem(resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise { - if (typeof this._provider.resolveCompletionItem !== 'function' || !this._cache[resource.toString()]) { + + if (typeof this._provider.resolveCompletionItem !== 'function') { return TPromise.as(suggestion); } - const {list, disposables} = this._cache[resource.toString()]; - const item = list.items[Number(( suggestion).id)]; + const id = ObjectIdentifier.get(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, disposables); + return TypeConverters.Suggest.from(resolvedItem || item, this._disposables[id]); }); } } @@ -670,6 +662,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { private _proxy: MainThreadLanguageFeaturesShape; private _documents: ExtHostDocuments; private _commands: ExtHostCommands; + private _heapMonitor: ExtHostHeapService; private _diagnostics: ExtHostDiagnostics; private _adapter: { [handle: number]: Adapter } = Object.create(null); @@ -677,12 +670,14 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { threadService: IThreadService, documents: ExtHostDocuments, commands: ExtHostCommands, + heapMonitor: ExtHostHeapService, diagnostics: ExtHostDiagnostics ) { super(); this._proxy = threadService.get(MainContext.MainThreadLanguageFeatures); this._documents = documents; this._commands = commands; + this._heapMonitor = heapMonitor; this._diagnostics = diagnostics; } @@ -869,7 +864,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, provider); + this._adapter[handle] = new SuggestAdapter(this._documents, this._heapMonitor, provider); this._proxy.$registerSuggestSupport(handle, selector, triggerCharacters); return this._createDisposable(handle); } diff --git a/src/vs/workbench/api/node/mainThreadHeapService.ts b/src/vs/workbench/api/node/mainThreadHeapService.ts new file mode 100644 index 0000000000000..4f3f6f53a26d2 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadHeapService.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import {IDisposable} from 'vs/base/common/lifecycle'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import {ExtHostContext} from './extHost.protocol'; +import {onDidGarbageCollectSignals, consumeSignals} from 'gc-signals'; + +export class MainThreadHeapService { + + private _subscription: IDisposable; + private _consumeHandle: number; + + constructor( @IThreadService threadService: IThreadService) { + const proxy = threadService.get(ExtHostContext.ExtHostHeapService); + + this._subscription = onDidGarbageCollectSignals(ids => { + proxy.$onGarbageCollection(ids); + }); + + this._consumeHandle = setInterval(consumeSignals, 15 * 1000); + } + + dispose() { + clearInterval(this._consumeHandle); + this._subscription.dispose(); + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts index 4df3d0f6dc989..ca3e536223a6f 100644 --- a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts @@ -15,8 +15,9 @@ 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} from './extHost.protocol'; +import {ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier} from './extHost.protocol'; import {LanguageConfigurationRegistry, LanguageConfiguration} from 'vs/editor/common/modes/languageConfigurationRegistry'; +import {trackGarbageCollection} from 'gc-signals'; export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape { @@ -180,7 +181,12 @@ 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)); + return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)).then(result => { + for (const suggestion of result.suggestions) { + trackGarbageCollection(suggestion, ObjectIdentifier.get(suggestion)); + } + return result; + }); }, 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 168c41180da23..c4b0d1dbaf3f9 100644 --- a/src/vs/workbench/test/node/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/node/api/extHostApiCommands.test.ts @@ -23,6 +23,7 @@ import {ExtHostLanguageFeatures} from 'vs/workbench/api/node/extHostLanguageFeat import {MainThreadLanguageFeatures} from 'vs/workbench/api/node/mainThreadLanguageFeatures'; import {registerApiCommands} 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'; @@ -108,7 +109,7 @@ suite('ExtHostLanguageFeatureCommands', function() { const diagnostics = new ExtHostDiagnostics(threadService); threadService.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, diagnostics); + extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, new ExtHostHeapService(), diagnostics); threadService.set(ExtHostContext.ExtHostLanguageFeatures, extHost); mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, instantiationService.createInstance(MainThreadLanguageFeatures)); diff --git a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts index f4e2808920fca..e22a94358752f 100644 --- a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts @@ -39,6 +39,7 @@ import {getLinks} from 'vs/editor/contrib/links/common/links'; import {asWinJsPromise} from 'vs/base/common/async'; import {MainContext, ExtHostContext} from 'vs/workbench/api/node/extHost.protocol'; import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; +import {ExtHostHeapService} from 'vs/workbench/api/node/extHostHeapService'; const defaultSelector = { scheme: 'far' }; const model: EditorCommon.IModel = EditorModel.createFromString( @@ -97,7 +98,7 @@ suite('ExtHostLanguageFeatures', function() { const diagnostics = new ExtHostDiagnostics(threadService); threadService.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, diagnostics); + extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, new ExtHostHeapService(), diagnostics); threadService.set(ExtHostContext.ExtHostLanguageFeatures, extHost); mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, instantiationService.createInstance(MainThreadLanguageFeatures));