From eff232047302adbb5beb4821468dfdfc74a876a1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 19 Nov 2019 12:34:19 +0100 Subject: [PATCH] check timeout on extension host, blame extension when exceeded, #43768 --- src/vs/base/common/event.ts | 4 ++-- .../mainThreadFileSystemEventService.ts | 2 +- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../common/extHostFileSystemEventService.ts | 21 ++++++++++++------- .../api/extHostFileSystemEventService.test.ts | 5 +++-- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index e6c16f43df6a1..f049202623964 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -656,7 +656,7 @@ export class AsyncEmitter extends Emitter { private _asyncDeliveryQueue?: LinkedList<[Listener, Omit]>; - async fireAsync(data: Omit, token: CancellationToken, promiseJoin?: (p: Promise) => Promise): Promise { + async fireAsync(data: Omit, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise { if (!this._listeners) { return; } @@ -681,7 +681,7 @@ export class AsyncEmitter extends Emitter { throw new Error('waitUntil can NOT be called asynchronous'); } if (promiseJoin) { - p = promiseJoin(p); + p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]); } thenables.push(p); } diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index da387c35747f2..38f3b5fdca993 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -88,7 +88,7 @@ export class MainThreadFileSystemEventService { reject(new Error('timeout')); }, timeout); - proxy.$onWillRunFileOperation(e.operation, e.target, e.source, cts.token) + proxy.$onWillRunFileOperation(e.operation, e.target, e.source, timeout, cts.token) .then(resolve, reject) .finally(() => clearTimeout(timeoutHandle)); }); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 93d360383345c..c35de421c574b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -116,7 +116,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); - const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors)); + const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ec42255a44dde..9639c11433811 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -923,7 +923,7 @@ export interface FileSystemEvents { export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; - $onWillRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined, token: CancellationToken): Promise; + $onWillRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise; $onDidRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined): void; } diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index d289fc249fed9..39bc81f6f8c52 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -15,6 +15,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { FileOperation } from 'vs/platform/files/common/files'; import { flatten } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ILogService } from 'vs/platform/log/common/log'; class FileSystemWatcher implements vscode.FileSystemWatcher { @@ -121,6 +122,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ constructor( mainContext: IMainContext, + private readonly _logService: ILogService, private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors) ) { @@ -177,32 +179,37 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ }; } - async $onWillRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined, token: CancellationToken): Promise { + async $onWillRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise { switch (operation) { case FileOperation.MOVE: - await this._fireWillEvent(this._onWillRenameFile, { files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] }, token); + await this._fireWillEvent(this._onWillRenameFile, { files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] }, timeout, token); break; case FileOperation.DELETE: - await this._fireWillEvent(this._onWillDeleteFile, { files: [URI.revive(target)] }, token); + await this._fireWillEvent(this._onWillDeleteFile, { files: [URI.revive(target)] }, timeout, token); break; case FileOperation.CREATE: - await this._fireWillEvent(this._onWillCreateFile, { files: [URI.revive(target)] }, token); + await this._fireWillEvent(this._onWillCreateFile, { files: [URI.revive(target)] }, timeout, token); break; default: //ignore, dont send } } - private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, token: CancellationToken): Promise { + private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, timeout: number, token: CancellationToken): Promise { const edits: WorkspaceEdit[] = []; - await emitter.fireAsync(data, token, async p => { + await emitter.fireAsync(data, token, async (thenable, listener: IExtensionListener) => { // ignore all results except for WorkspaceEdits. Those are stored in an array. - const result = await Promise.resolve(p); + const now = Date.now(); + const result = await Promise.resolve(thenable); if (result instanceof WorkspaceEdit) { edits.push(result); } + + if (Date.now() - now > timeout) { + this._logService.warn('SLOW file-participant', listener.extension?.identifier); + } }); if (token.isCancellationRequested) { diff --git a/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts b/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts index d8b1cbadc9b9d..c20cee41ce166 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostFileSystemEventService', () => { @@ -17,12 +18,12 @@ suite('ExtHostFileSystemEventService', () => { assertRegistered: undefined! }; - const watcher1 = new ExtHostFileSystemEventService(protocol, undefined!).createFileSystemWatcher('**/somethingInteresting', false, false, false); + const watcher1 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingInteresting', false, false, false); assert.equal(watcher1.ignoreChangeEvents, false); assert.equal(watcher1.ignoreCreateEvents, false); assert.equal(watcher1.ignoreDeleteEvents, false); - const watcher2 = new ExtHostFileSystemEventService(protocol, undefined!).createFileSystemWatcher('**/somethingBoring', true, true, true); + const watcher2 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingBoring', true, true, true); assert.equal(watcher2.ignoreChangeEvents, true); assert.equal(watcher2.ignoreCreateEvents, true); assert.equal(watcher2.ignoreDeleteEvents, true);