diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 8e1dab9ef1b..ef4f2b68e69 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; @@ -20,7 +20,7 @@ import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUt import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import type * as vscode from 'vscode'; -export class NativeExtHostSearch extends ExtHostSearch { +export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { protected _pfs: typeof pfs = pfs; // allow extending for tests @@ -29,6 +29,8 @@ export class NativeExtHostSearch extends ExtHostSearch { private _registeredEHSearchProvider = false; + private _disposables = new DisposableStore(); + constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, @@ -38,12 +40,16 @@ export class NativeExtHostSearch extends ExtHostSearch { super(extHostRpc, _uriTransformer, _logService); const outputChannel = new OutputChannel('RipgrepSearchUD', this._logService); - this.registerTextSearchProvider(Schemas.vscodeUserData, new RipgrepSearchProvider(outputChannel)); + this._disposables.add(this.registerTextSearchProvider(Schemas.vscodeUserData, new RipgrepSearchProvider(outputChannel))); if (initData.remote.isRemote && initData.remote.authority) { this._registerEHSearchProviders(); } } + dispose(): void { + this._disposables.dispose(); + } + override $enableExtensionHostSearch(): void { this._registerEHSearchProviders(); } @@ -55,8 +61,8 @@ export class NativeExtHostSearch extends ExtHostSearch { this._registeredEHSearchProvider = true; const outputChannel = new OutputChannel('RipgrepSearchEH', this._logService); - this.registerTextSearchProvider(Schemas.file, new RipgrepSearchProvider(outputChannel)); - this.registerInternalFileSearchProvider(Schemas.file, new SearchService('fileSearchProvider')); + this._disposables.add(this.registerTextSearchProvider(Schemas.file, new RipgrepSearchProvider(outputChannel))); + this._disposables.add(this.registerInternalFileSearchProvider(Schemas.file, new SearchService('fileSearchProvider'))); } private registerInternalFileSearchProvider(scheme: string, provider: SearchService): IDisposable { diff --git a/src/vs/workbench/api/test/node/extHostSearch.test.ts b/src/vs/workbench/api/test/node/extHostSearch.test.ts index 3ed96b49e9d..7942b81654e 100644 --- a/src/vs/workbench/api/test/node/extHostSearch.test.ts +++ b/src/vs/workbench/api/test/node/extHostSearch.test.ts @@ -8,26 +8,25 @@ import { mapArrayOrNot } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isCancellationError } from 'vs/base/common/errors'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; +import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; -import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import type * as vscode from 'vscode'; let rpcProtocol: TestRPCProtocol; let extHostSearch: NativeExtHostSearch; -const disposables = new DisposableStore(); let mockMainThreadSearch: MockMainThreadSearch; class MockMainThreadSearch implements MainThreadSearchShape { @@ -68,6 +67,8 @@ function extensionResultIsMatch(data: vscode.TextSearchResult): data is vscode.T } suite('ExtHostSearch', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + async function registerTestTextSearchProvider(provider: vscode.TextSearchProvider, scheme = 'file'): Promise { disposables.add(extHostSearch.registerTextSearchProvider(scheme, provider)); await rpcProtocol.sync(); @@ -137,7 +138,7 @@ suite('ExtHostSearch', () => { rpcProtocol.set(MainContext.MainThreadSearch, mockMainThreadSearch); mockPFS = {}; - extHostSearch = new class extends NativeExtHostSearch { + extHostSearch = disposables.add(new class extends NativeExtHostSearch { constructor() { super( rpcProtocol, @@ -151,11 +152,10 @@ suite('ExtHostSearch', () => { protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { return new NativeTextSearchManager(query, provider, this._pfs); } - }; + }); }); teardown(() => { - disposables.clear(); return rpcProtocol.sync(); }); @@ -231,7 +231,7 @@ suite('ExtHostSearch', () => { if (token.isCancellationRequested) { onCancel(); } else { - token.onCancellationRequested(() => onCancel()); + disposables.add(token.onCancellationRequested(() => onCancel())); } }); } @@ -512,7 +512,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestFileSearchProvider({ provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { - token.onCancellationRequested(() => wasCanceled = true); + disposables.add(token.onCancellationRequested(() => wasCanceled = true)); return Promise.resolve(reportedResults); } @@ -548,7 +548,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestFileSearchProvider({ provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { - token.onCancellationRequested(() => wasCanceled = true); + disposables.add(token.onCancellationRequested(() => wasCanceled = true)); return Promise.resolve(reportedResults); } @@ -583,7 +583,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestFileSearchProvider({ provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { - token.onCancellationRequested(() => wasCanceled = true); + disposables.add(token.onCancellationRequested(() => wasCanceled = true)); return Promise.resolve(reportedResults); } @@ -613,7 +613,7 @@ suite('ExtHostSearch', () => { let cancels = 0; await registerTestFileSearchProvider({ async provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { - token.onCancellationRequested(() => cancels++); + disposables.add(token.onCancellationRequested(() => cancels++)); // Provice results async so it has a chance to invoke every provider await new Promise(r => process.nextTick(r)); @@ -1083,7 +1083,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestTextSearchProvider({ provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Promise { - token.onCancellationRequested(() => wasCanceled = true); + disposables.add(token.onCancellationRequested(() => wasCanceled = true)); providedResults.forEach(r => progress.report(r)); return Promise.resolve(null!); } @@ -1116,7 +1116,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestTextSearchProvider({ provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Promise { - token.onCancellationRequested(() => wasCanceled = true); + disposables.add(token.onCancellationRequested(() => wasCanceled = true)); providedResults.forEach(r => progress.report(r)); return Promise.resolve(null!); } @@ -1148,7 +1148,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestTextSearchProvider({ provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Promise { - token.onCancellationRequested(() => wasCanceled = true); + disposables.add(token.onCancellationRequested(() => wasCanceled = true)); providedResults.forEach(r => progress.report(r)); return Promise.resolve(null!); } @@ -1205,7 +1205,7 @@ suite('ExtHostSearch', () => { let cancels = 0; await registerTestTextSearchProvider({ async provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Promise { - token.onCancellationRequested(() => cancels++); + disposables.add(token.onCancellationRequested(() => cancels++)); await new Promise(r => process.nextTick(r)); [ 'file1.ts', diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index 3fb1237e178..960c90492fd 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -329,7 +329,7 @@ export class FileSearchManager { } private doSearch(engine: FileSearchEngine, batchSize: number, onResultBatch: (matches: IInternalFileMatch[]) => void, token: CancellationToken): Promise { - token.onCancellationRequested(() => { + const listener = token.onCancellationRequested(() => { engine.cancel(); }); @@ -349,12 +349,14 @@ export class FileSearchManager { onResultBatch(batch); } + listener.dispose(); return result; }, error => { if (batch.length) { onResultBatch(batch); } + listener.dispose(); return Promise.reject(error); }); } diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts index 68dcacea828..9210f766f2c 100644 --- a/src/vs/workbench/services/search/common/textSearchManager.ts +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -31,7 +31,7 @@ export class TextSearchManager { search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { const folderQueries = this.query.folderQueries || []; const tokenSource = new CancellationTokenSource(); - token.onCancellationRequested(() => tokenSource.cancel()); + const listener = token.onCancellationRequested(() => tokenSource.cancel()); return new Promise((resolve, reject) => { this.collector = new TextSearchResultsCollector(onProgress); @@ -65,6 +65,7 @@ export class TextSearchManager { return this.searchInFolder(fq, r => onResult(r, i), tokenSource.token); })).then(results => { tokenSource.dispose(); + listener.dispose(); this.collector!.flush(); const someFolderHitLImit = results.some(result => !!result && !!result.limitHit); @@ -81,6 +82,7 @@ export class TextSearchManager { }); }, (err: Error) => { tokenSource.dispose(); + listener.dispose(); const errMsg = toErrorMessage(err); reject(new Error(errMsg)); });