diff --git a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts index 6c52cbc9378..d7af8caf14a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts @@ -9,8 +9,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { isThenable } from 'vs/base/common/async'; -import { isNullOrUndefined } from 'util'; +import { GCSignal } from 'gc-signals'; export const IHeapService = createDecorator('heapService'); @@ -20,10 +19,9 @@ export interface IHeapService { readonly onGarbageCollection: Event; /** - * Track gc-collection for all new objects that - * have the $ident-value set. + * Track gc-collection for the given object */ - trackRecursive(obj: T | Promise): Promise; + trackObject(obj: ObjectIdentifier): void; } export class HeapService implements IHeapService { @@ -35,7 +33,10 @@ export class HeapService implements IHeapService { private _activeSignals = new WeakMap(); private _activeIds = new Set(); + private _consumeHandle: any; + private _ctor: { new(id: number): GCSignal }; + private _ctorInit: Promise; constructor() { // @@ -45,73 +46,41 @@ export class HeapService implements IHeapService { clearInterval(this._consumeHandle); } - trackRecursive(obj: T | Promise): Promise { - if (isThenable(obj)) { - return obj.then(result => this.trackRecursive(result)); + trackObject(obj: ObjectIdentifier | undefined | null): void { + if (!obj || typeof obj.$ident !== 'number') { + return; + } + + if (this._ctor) { + // track and leave + this._activeIds.add(obj.$ident); + this._activeSignals.set(obj, new this._ctor(obj.$ident)); + } else { - return this._doTrackRecursive(obj); - } - } + // make sure to load gc-signals, then track and leave + if (!this._ctorInit) { + this._ctorInit = import('gc-signals').then(({ GCSignal, consumeSignals }) => { + this._ctor = GCSignal; + this._consumeHandle = setInterval(() => { + const ids = consumeSignals(); - private _doTrackRecursive(obj: any): Promise { - - if (isNullOrUndefined(obj)) { - return Promise.resolve(obj); - } - - return import('gc-signals').then(({ GCSignal, consumeSignals }) => { - - if (this._consumeHandle === undefined) { - // ensure that there is one consumer of signals - this._consumeHandle = setInterval(() => { - const ids = consumeSignals(); - - if (ids.length > 0) { - // local book-keeping - for (const id of ids) { - this._activeIds.delete(id); + if (ids.length > 0) { + // local book-keeping + for (const id of ids) { + this._activeIds.delete(id); + } + // fire event + this._onGarbageCollection.fire(ids); } - - // fire event - this._onGarbageCollection.fire(ids); - } - - }, 15 * 1000); + }, 15 * 1000); + }); } - 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; - }); + this._ctorInit.then(() => { + this._activeIds.add(obj.$ident); + this._activeSignals.set(obj, new this._ctor(obj.$ident)); + }); + } } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index 4432efc4373..f3307626193 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -123,10 +123,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha const provider = { provideCodeLenses: (model: ITextModel, token: CancellationToken): modes.ICodeLensSymbol[] | Promise => { - return this._heapService.trackRecursive(this._proxy.$provideCodeLenses(handle, model.uri, token)); + return this._proxy.$provideCodeLenses(handle, model.uri, token).then(dto => { + if (dto) { dto.forEach(obj => this._heapService.trackObject(obj)); } + return dto; + }); }, resolveCodeLens: (model: ITextModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): modes.ICodeLensSymbol | Promise => { - return this._heapService.trackRecursive(this._proxy.$resolveCodeLens(handle, model.uri, codeLens, token)); + return this._proxy.$resolveCodeLens(handle, model.uri, codeLens, token).then(obj => { + this._heapService.trackObject(obj); + return obj; + }); } }; @@ -215,7 +221,10 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void { this._registrations[handle] = modes.CodeActionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { provideCodeActions: (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { - return this._heapService.trackRecursive(this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token)).then(MainThreadLanguageFeatures._reviveCodeActionDto); + return this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token).then(dto => { + if (dto) { dto.forEach(obj => this._heapService.trackObject(obj.command)); } + return MainThreadLanguageFeatures._reviveCodeActionDto(dto); + }); }, providedCodeActionKinds }); @@ -328,10 +337,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void { this._registrations[handle] = modes.LinkProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { provideLinks: (model, token) => { - return this._heapService.trackRecursive(this._proxy.$provideDocumentLinks(handle, model.uri, token)); + return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => { + if (dto) { dto.forEach(obj => this._heapService.trackObject(obj)); } + return dto; + }); }, resolveLink: (link, token) => { - return this._proxy.$resolveDocumentLink(handle, link, token); + return this._proxy.$resolveDocumentLink(handle, link, token).then(obj => { + this._heapService.trackObject(obj); + return obj; + }); } }); } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 790a4c8e2a6..363d37088cf 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -770,7 +770,7 @@ export interface ExtHostFileSystemEventServiceShape { } export interface ObjectIdentifier { - $ident: number; + $ident?: number; } export namespace ObjectIdentifier { @@ -867,19 +867,25 @@ export function reviveWorkspaceEditDto(data: WorkspaceEditDto): modes.WorkspaceE return data; } +export type CommandDto = ObjectIdentifier & modes.Command; + export interface CodeActionDto { title: string; edit?: WorkspaceEditDto; diagnostics?: IMarkerData[]; - command?: modes.Command; + command?: CommandDto; kind?: string; isPreferred?: boolean; } +export type LinkDto = ObjectIdentifier & modes.ILink; + +export type CodeLensDto = ObjectIdentifier & modes.ICodeLensSymbol; + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol, token: CancellationToken): Promise; + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveCodeLens(handle: number, resource: UriComponents, symbol: CodeLensDto, token: CancellationToken): Promise; $provideDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDeclaration(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; @@ -900,8 +906,8 @@ export interface ExtHostLanguageFeaturesShape { $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, suggestion: modes.CompletionItem, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveDocumentLink(handle: number, link: modes.ILink, token: CancellationToken): Promise; + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveDocumentLink(handle: number, link: LinkDto, token: CancellationToken): Promise; $provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/node/extHostCommands.ts b/src/vs/workbench/api/node/extHostCommands.ts index 4e91ca97d00..8b73f059351 100644 --- a/src/vs/workbench/api/node/extHostCommands.ts +++ b/src/vs/workbench/api/node/extHostCommands.ts @@ -8,7 +8,7 @@ import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands 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, ObjectIdentifier, IMainContext } from './extHost.protocol'; +import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, IMainContext, CommandDto } from './extHost.protocol'; import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import * as modes from 'vs/editor/common/modes'; @@ -207,15 +207,16 @@ export class CommandsConverter { this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } - toInternal(command: vscode.Command): modes.Command { + toInternal(command: vscode.Command): CommandDto { if (!command) { return undefined; } - const result: modes.Command = { + const result: CommandDto = { + $ident: undefined, id: command.command, - title: command.title + title: command.title, }; if (command.command && isNonEmptyArray(command.arguments)) { @@ -223,7 +224,7 @@ export class CommandsConverter { // means we don't want to send the arguments around const id = this._heap.keep(command); - ObjectIdentifier.mixin(result, id); + result.$ident = id; result.id = this._delegatingCommandId; result.arguments = [id]; diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index df50261a1e6..8cbf6ad2d1e 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { asPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata } from './extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto } from './extHost.protocol'; import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; @@ -104,24 +104,25 @@ class CodeLensAdapter { private readonly _provider: vscode.CodeLensProvider ) { } - provideCodeLenses(resource: URI, token: CancellationToken): Promise { + provideCodeLenses(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocumentData(resource).document; return asPromise(() => this._provider.provideCodeLenses(doc, token)).then(lenses => { - if (Array.isArray(lenses)) { - return lenses.map(lens => { + let result: CodeLensDto[] = []; + if (isNonEmptyArray(lenses)) { + for (const lens of lenses) { const id = this._heapService.keep(lens); - return ObjectIdentifier.mixin({ + result.push(ObjectIdentifier.mixin({ range: typeConvert.Range.from(lens.range), command: this._commands.toInternal(lens.command) - }, id); - }); + }, id)); + } } - return undefined; + return result; }); } - resolveCodeLens(resource: URI, symbol: modes.ICodeLensSymbol, token: CancellationToken): Promise { + resolveCodeLens(resource: URI, symbol: CodeLensDto, token: CancellationToken): Promise { const lens = this._heapService.get(ObjectIdentifier.of(symbol)); if (!lens) { @@ -778,25 +779,24 @@ class LinkProviderAdapter { private readonly _provider: vscode.DocumentLinkProvider ) { } - provideLinks(resource: URI, token: CancellationToken): Promise { + provideLinks(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocumentData(resource).document; return asPromise(() => this._provider.provideDocumentLinks(doc, token)).then(links => { if (!Array.isArray(links)) { return undefined; } - const result: modes.ILink[] = []; + const result: LinkDto[] = []; for (const link of links) { let data = typeConvert.DocumentLink.from(link); let id = this._heapService.keep(link); - ObjectIdentifier.mixin(data, id); - result.push(data); + result.push(ObjectIdentifier.mixin(data, id)); } return result; }); } - resolveLink(link: modes.ILink, token: CancellationToken): Promise { + resolveLink(link: LinkDto, token: CancellationToken): Promise { if (typeof this._provider.resolveDocumentLink !== 'function') { return undefined; }