debt - no guessing-caching, better use of gc-signals, command converter using gc-signals, main side heap service

This commit is contained in:
Johannes Rieken
2016-10-21 18:53:05 +02:00
parent c57c31abc9
commit fbacd65e2a
13 changed files with 292 additions and 256 deletions

View File

@@ -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<ExtHostHeapService>(new ExtHostHeapService());
const extHostHeapService = col.define(ExtHostContext.ExtHostHeapService).set<ExtHostHeapService>(new ExtHostHeapService());
const extHostDocuments = col.define(ExtHostContext.ExtHostDocuments).set<ExtHostDocuments>(new ExtHostDocuments(threadService));
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));
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 extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set<ExtHostDiagnostics>(new ExtHostDiagnostics(threadService));
const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set<ExtHostLanguageFeatures>(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapMonitor, extHostDiagnostics));
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());
const extHostQuickOpen = col.define(ExtHostContext.ExtHostQuickOpen).set<ExtHostQuickOpen>(new ExtHostQuickOpen(threadService));
const extHostTerminalService = col.define(ExtHostContext.ExtHostTerminalService).set<ExtHostTerminalService>(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

View File

@@ -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);
}
}

View File

@@ -276,12 +276,13 @@ export interface ObjectIdentifier {
}
export namespace ObjectIdentifier {
export const name = '$ident';
export function mixin<T>(obj: T, id: number): T & ObjectIdentifier {
Object.defineProperty(obj, '$ident', { value: id, enumerable: true });
Object.defineProperty(obj, name, { value: id, enumerable: true });
return <T & ObjectIdentifier>obj;
}
export function get(obj: any): number {
return obj['$ident'];
export function of(obj: any): number {
return obj[name];
}
}

View File

@@ -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));
});
}
});

View File

@@ -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: <T>(...args: any[]) => T | Thenable<T>, 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<vscode.Command>(id);
} else {
return {
command: command.id,
title: command.title,
arguments: command.arguments
};
}
}
private _executeConvertedCommand([id]: number[]) {
const actualCmd = this._heap.get<vscode.Command>(id);
return this._commands.executeCommand(actualCmd.command, ...actualCmd.arguments);
}
}

View File

@@ -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);
}
}

View File

@@ -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: '<<MISSING COMMAND>>' };
private _documents: ExtHostDocuments;
private _commands: ExtHostCommands;
private _commands: CommandsConverter;
private _heapService: ExtHostHeapService;
private _provider: vscode.CodeLensProvider;
private _cache: { [uri: string]: { version: number; data: TPromise<CachedCodeLens>; } } = 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<modes.ICodeLensSymbol[]> {
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(<modes.ICodeLensSymbol>{
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<modes.ICodeLensSymbol> {
const entry = this._cache[resource.toString()];
if (!entry) {
const lens = this._heapService.get<vscode.CodeLens>(ObjectIdentifier.of(symbol));
if (!lens) {
return;
}
return entry.data.then(cachedData => {
let resolve: TPromise<vscode.CodeLens>;
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<vscode.CodeLens>;
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: '<<MISSING COMMAND>>',
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 <modes.CodeAction>{
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<vscode.SymbolInformation>(ObjectIdentifier.get(item));
const symbolInfo = this._heapService.get<vscode.SymbolInformation>(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<CompletionItem>(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);
}

View File

@@ -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 || '<missing 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 = <modes.Command>{
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;
}
}
}
}

View File

@@ -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<K, V> {
set(key: K, value?: V): WeakMap<K, V>;
}
declare class Set<E> {
add(e: E): this;
has(e: E): boolean;
delete(e: E): boolean;
}
export const IHeapService = createDecorator<IHeapService>('heapService');
export interface IHeapService {
_serviceBrand: any;
/**
* Track gc-collection for all new objects that
* have the $ident-value set.
*/
trackRecursive<T>(p: TPromise<T>): TPromise<T>;
/**
* Track gc-collection for all new objects that
* have the $ident-value set.
*/
trackRecursive<T>(obj: T): T;
}
export class MainThreadHeapService implements IHeapService {
_serviceBrand: any;
private _activeSignals = new WeakMap<any, GCSignal>();
private _activeIds = new Set<number>();
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();
}
}
trackRecursive<T>(p: TPromise<T>): TPromise<T>;
trackRecursive<T>(obj: T): T;
trackRecursive<T>(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);

View File

@@ -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<any> {
@@ -54,10 +59,10 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape
$registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
this._registrations[handle] = modes.CodeLensProviderRegistry.register(selector, <modes.CodeLensProvider>{
provideCodeLenses: (model: IReadOnlyModel, token: CancellationToken): modes.ICodeLensSymbol[] | Thenable<modes.ICodeLensSymbol[]> => {
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<modes.ICodeLensSymbol> => {
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<any> {
this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, <modes.CodeActionProvider>{
provideCodeActions: (model: IReadOnlyModel, range: EditorRange, token: CancellationToken): Thenable<modes.CodeAction[]> => {
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<any> {
this._registrations[handle] = WorkspaceSymbolProviderRegistry.register(<IWorkspaceSymbolProvider>{
provideWorkspaceSymbols: (search: string): TPromise<IWorkspaceSymbol[]> => {
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<IWorkspaceSymbol> => {
return this._proxy.$resolveWorkspaceSymbol(handle, item);
@@ -188,14 +186,7 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape
this._registrations[handle] = modes.SuggestRegistry.register(selector, <modes.ISuggestSupport>{
triggerCharacters: triggerCharacters,
provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable<modes.ISuggestResult> => {
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<modes.ISuggestion> => {
return wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion));