提交 497ea886 编写于 作者: R Rob Lourens

#56137 - remove more TPromise#cancel from search

上级 5f513e74
......@@ -13,6 +13,7 @@ import { URI as uri, UriComponents } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IFilesConfiguration } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { CancellationToken } from 'vs/base/common/cancellation';
export const VIEW_ID = 'workbench.view.search';
......@@ -24,7 +25,7 @@ export const ISearchService = createDecorator<ISearchService>('searchService');
*/
export interface ISearchService {
_serviceBrand: any;
search(query: ISearchQuery, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete>;
search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete>;
extendQuery(query: ISearchQuery): void;
clearCache(cacheKey: string): TPromise<void>;
registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable;
......@@ -55,7 +56,7 @@ export enum SearchProviderType {
}
export interface ISearchResultProvider {
search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete>;
search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete>;
clearCache(cacheKey: string): TPromise<void>;
}
......
......@@ -13,6 +13,7 @@ import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISea
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../node/extHost.protocol';
import { CancellationToken } from 'vs/base/common/cancellation';
@extHostNamedCustomer(MainContext.MainThreadSearch)
export class MainThreadSearch implements MainThreadSearchShape {
......@@ -106,34 +107,28 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
dispose(this._registrations);
}
search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete> {
search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete> {
if (isFalsyOrEmpty(query.folderQueries)) {
return TPromise.as(undefined);
}
let outer: TPromise;
return new TPromise((resolve, reject) => {
const search = new SearchOperation(onProgress);
this._searches.set(search.id, search);
outer = query.type === QueryType.File
const searchP = query.type === QueryType.File
? this._proxy.$provideFileSearchResults(this._handle, search.id, query)
: this._proxy.$provideTextSearchResults(this._handle, search.id, query.contentPattern, query);
outer.then((result: ISearchCompleteStats) => {
if (token) {
token.onCancellationRequested(() => searchP.cancel());
}
return searchP.then((result: ISearchCompleteStats) => {
this._searches.delete(search.id);
resolve(({ results: values(search.matches), stats: result.stats, limitHit: result.limitHit }));
return { results: values(search.matches), stats: result.stats, limitHit: result.limitHit };
}, err => {
this._searches.delete(search.id);
reject(err);
});
}, () => {
if (outer) {
outer.cancel();
}
return TPromise.wrapError(err);
});
}
......
......@@ -24,12 +24,13 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
private readonly _toDispose: IDisposable[] = [];
private readonly _activeSearches: { [id: number]: TPromise<any> } = Object.create(null);
private readonly _activeCancelTokens: { [id: number]: CancellationTokenSource } = Object.create(null);
private readonly _proxy: ExtHostWorkspaceShape;
constructor(
......@@ -51,9 +52,9 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
dispose(): void {
dispose(this._toDispose);
for (let requestId in this._activeSearches) {
const search = this._activeSearches[requestId];
search.cancel();
for (let requestId in this._activeCancelTokens) {
const tokenSource = this._activeCancelTokens[requestId];
tokenSource.cancel();
}
}
......@@ -157,7 +158,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
this._searchService.extendQuery(query);
const search = this._searchService.search(query).then(result => {
const tokenSource = new CancellationTokenSource();
const search = this._searchService.search(query, tokenSource.token).then(result => {
return result.results.map(m => m.resource);
}, err => {
if (!isPromiseCanceledError(err)) {
......@@ -166,8 +168,11 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return undefined;
});
this._activeSearches[requestId] = search;
const onDone = () => delete this._activeSearches[requestId];
this._activeCancelTokens[requestId] = tokenSource;
const onDone = () => {
tokenSource.dispose();
delete this._activeCancelTokens[requestId];
};
search.then(onDone, onDone);
return search;
......@@ -186,13 +191,12 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
}
};
const search = this._searchService.search(query, onProgress).then(
const tokenSource = new CancellationTokenSource();
const search = this._searchService.search(query, tokenSource.token, onProgress).then(
() => {
delete this._activeSearches[requestId];
return null;
},
err => {
delete this._activeSearches[requestId];
if (!isPromiseCanceledError(err)) {
return TPromise.wrapError(err);
}
......@@ -200,20 +204,27 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return undefined;
});
this._activeSearches[requestId] = search;
this._activeCancelTokens[requestId] = tokenSource;
const onDone = () => {
tokenSource.dispose();
delete this._activeCancelTokens[requestId];
};
search.then(onDone, onDone);
return search;
}
$checkExists(query: ISearchQuery, requestId: number): TPromise<boolean> {
query.exists = true;
const search = this._searchService.search(query).then(
const tokenSource = new CancellationTokenSource();
const search = this._searchService.search(query, tokenSource.token).then(
result => {
delete this._activeSearches[requestId];
delete this._activeCancelTokens[requestId];
return result.limitHit;
},
err => {
delete this._activeSearches[requestId];
delete this._activeCancelTokens[requestId];
if (!isPromiseCanceledError(err)) {
return TPromise.wrapError(err);
}
......@@ -221,16 +232,16 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return undefined;
});
this._activeSearches[requestId] = search;
this._activeCancelTokens[requestId] = tokenSource;
return search;
}
$cancelSearch(requestId: number): Thenable<boolean> {
const search = this._activeSearches[requestId];
if (search) {
delete this._activeSearches[requestId];
search.cancel();
const tokenSource = this._activeCancelTokens[requestId];
if (tokenSource) {
delete this._activeCancelTokens[requestId];
tokenSource.cancel();
return TPromise.as(true);
}
return undefined;
......
......@@ -98,7 +98,6 @@ export class ExtHostSearch implements ExtHostSearchShape {
});
} else {
const indexProvider = this._fileIndexProvider.get(handle);
if (indexProvider) {
return this._fileIndexSearchManager.fileSearch(query, indexProvider, batch => {
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
}).then(null, err => {
......@@ -108,9 +107,6 @@ export class ExtHostSearch implements ExtHostSearchShape {
return null;
});
} else {
throw new Error('something went wrong');
}
}
}
......@@ -300,36 +296,31 @@ class BatchedCollector<T> {
class TextSearchEngine {
private activeCancellationTokens = new Set<CancellationTokenSource>();
private collector: TextSearchResultsCollector;
private isLimitHit: boolean;
private resultCount = 0;
private isCanceled: boolean;
constructor(private pattern: IPatternInfo, private config: ISearchQuery, private provider: vscode.TextSearchProvider, private _extfs: typeof extfs) {
}
public cancel(): void {
this.isCanceled = true;
this.activeCancellationTokens.forEach(t => t.cancel());
this.activeCancellationTokens = new Set();
}
public search(onProgress: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> {
const folderQueries = this.config.folderQueries;
const tokenSource = new CancellationTokenSource();
return new TPromise<ISearchCompleteStats>((resolve, reject) => {
this.collector = new TextSearchResultsCollector(onProgress);
let isCanceled = false;
const onResult = (match: vscode.TextSearchResult, folderIdx: number) => {
if (this.isCanceled) {
if (isCanceled) {
return;
}
if (this.resultCount >= this.config.maxResults) {
this.isLimitHit = true;
this.cancel();
isCanceled = true;
tokenSource.cancel();
}
if (!this.isLimitHit) {
......@@ -340,8 +331,9 @@ class TextSearchEngine {
// For each root folder
TPromise.join(folderQueries.map((fq, i) => {
return this.searchInFolder(fq, r => onResult(r, i));
return this.searchInFolder(fq, r => onResult(r, i), tokenSource.token);
})).then(() => {
tokenSource.dispose();
this.collector.flush();
resolve({
limitHit: this.isLimitHit,
......@@ -350,17 +342,20 @@ class TextSearchEngine {
}
});
}, (errs: Error[]) => {
tokenSource.dispose();
const errMsg = errs
.map(err => toErrorMessage(err))
.filter(msg => !!msg)[0];
reject(new Error(errMsg));
});
}, () => {
// From IPC
tokenSource.cancel();
});
}
private searchInFolder(folderQuery: IFolderQuery<URI>, onResult: (result: vscode.TextSearchResult) => void): TPromise<void> {
let cancellation = new CancellationTokenSource();
private searchInFolder(folderQuery: IFolderQuery<URI>, onResult: (result: vscode.TextSearchResult) => void, token: CancellationToken): TPromise<void> {
return new TPromise((resolve, reject) => {
const queryTester = new QueryGlobTester(this.config, folderQuery);
......@@ -385,22 +380,14 @@ class TextSearchEngine {
const searchOptions = this.getSearchOptionsForFolder(folderQuery);
new TPromise(resolve => process.nextTick(resolve))
.then(() => {
this.activeCancellationTokens.add(cancellation);
return this.provider.provideTextSearchResults(patternInfoToQuery(this.pattern), searchOptions, progress, cancellation.token);
return this.provider.provideTextSearchResults(patternInfoToQuery(this.pattern), searchOptions, progress, token);
})
.then(() => {
this.activeCancellationTokens.delete(cancellation);
return TPromise.join(testingPs);
})
.then(
() => {
cancellation.dispose();
resolve(null);
},
err => {
cancellation.dispose();
reject(err);
});
() => resolve(null),
reject);
});
}
......
......@@ -172,8 +172,7 @@ export class OpenFileHandler extends QuickOpenHandler {
return TPromise.wrap(<ISearchComplete>{ results: [{ resource: result }] });
}
// TODO@Rob support cancellation
return this.searchService.search(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions));
return this.searchService.search(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token);
}).then(complete => {
const results: QuickOpenEntry[] = [];
for (let i = 0; i < complete.results.length; i++) {
......
......@@ -24,6 +24,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
export class Match {
......@@ -733,7 +734,7 @@ export class SearchModel extends Disposable {
private readonly _onReplaceTermChanged: Emitter<void> = this._register(new Emitter<void>());
public readonly onReplaceTermChanged: Event<void> = this._onReplaceTermChanged.event;
private currentRequest: TPromise<ISearchComplete>;
private currentCancelTokenSource: CancellationTokenSource;
constructor(@ISearchService private searchService: ISearchService, @ITelemetryService private telemetryService: ITelemetryService, @IInstantiationService private instantiationService: IInstantiationService) {
super();
......@@ -778,7 +779,8 @@ export class SearchModel extends Disposable {
const progressEmitter = new Emitter<void>();
this._replacePattern = new ReplacePattern(this._replaceString, this._searchQuery.contentPattern);
this.currentRequest = this.searchService.search(this._searchQuery, p => {
const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource();
const currentRequest = this.searchService.search(this._searchQuery, this.currentCancelTokenSource.token, p => {
progressEmitter.fire();
this.onSearchProgress(p);
......@@ -787,7 +789,10 @@ export class SearchModel extends Disposable {
}
});
const onDone = fromPromise(this.currentRequest);
const dispose = () => tokenSource.dispose();
currentRequest.then(dispose, dispose);
const onDone = fromPromise(currentRequest);
const onFirstRender = anyEvent<any>(onDone, progressEmitter.event);
const onFirstRenderStopwatch = stopwatch(onFirstRender);
/* __GDPR__
......@@ -807,18 +812,14 @@ export class SearchModel extends Disposable {
*/
onDoneStopwatch(duration => this.telemetryService.publicLog('searchResultsFinished', { duration }));
const currentRequest = this.currentRequest;
this.currentRequest.then(
currentRequest.then(
value => this.onSearchCompleted(value, Date.now() - start),
e => this.onSearchError(e, Date.now() - start));
// this.currentRequest may be completed (and nulled) immediately
return currentRequest;
}
private onSearchCompleted(completed: ISearchComplete, duration: number): ISearchComplete {
this.currentRequest = null;
const options: IPatternInfo = objects.assign({}, this._searchQuery.contentPattern);
delete options.pattern;
......@@ -866,9 +867,8 @@ export class SearchModel extends Disposable {
}
public cancelSearch(): boolean {
if (this.currentRequest) {
this.currentRequest.cancel();
this.currentRequest = null;
if (this.currentCancelTokenSource) {
this.currentCancelTokenSource.cancel();
return true;
}
return false;
......
......@@ -20,6 +20,7 @@ import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchPro
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { SearchModel } from 'vs/workbench/parts/search/common/searchModel';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
const nullEvent = new class {
......@@ -83,7 +84,7 @@ suite('SearchModel', () => {
function searchServiceWithResults(results: IFileMatch[], complete: ISearchComplete = null): ISearchService {
return <ISearchService>{
search(query: ISearchQuery, onProgress: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> {
search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> {
return new TPromise(resolve => {
process.nextTick(() => {
results.forEach(onProgress);
......@@ -96,7 +97,7 @@ suite('SearchModel', () => {
function searchServiceWithError(error: Error): ISearchService {
return <ISearchService>{
search(query: ISearchQuery, onProgress: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> {
search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> {
return new TPromise((resolve, reject) => {
reject(error);
});
......@@ -104,6 +105,22 @@ suite('SearchModel', () => {
};
}
function canceleableSearchService(tokenSource: CancellationTokenSource): ISearchService {
return <ISearchService>{
search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> {
if (token) {
token.onCancellationRequested(() => tokenSource.cancel());
}
return new TPromise(resolve => {
process.nextTick(() => {
resolve(<any>{});
});
});
}
};
}
test('Search Model: Search adds to results', async () => {
let results = [
aRawMatch('file://c:/1',
......@@ -257,15 +274,15 @@ suite('SearchModel', () => {
});
test('Search Model: Previous search is cancelled when new search is called', async () => {
let target = sinon.spy();
instantiationService.stub(ISearchService, 'search', new DeferredTPromise(target));
let testObject: SearchModel = instantiationService.createInstance(SearchModel);
const tokenSource = new CancellationTokenSource();
instantiationService.stub(ISearchService, canceleableSearchService(tokenSource));
const testObject: SearchModel = instantiationService.createInstance(SearchModel);
testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries });
instantiationService.stub(ISearchService, searchServiceWithResults([]));
testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries });
assert.ok(target.calledOnce);
assert.ok(tokenSource.token.isCancellationRequested);
});
test('getReplaceString returns proper replace string for regExpressions', async () => {
......
......@@ -30,6 +30,8 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { IRawSearch, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from './search';
import { ISearchChannel, SearchChannelClient } from './searchIpc';
import { Range } from 'vs/editor/common/core/range';
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
export class SearchService extends Disposable implements ISearchService {
public _serviceBrand: any;
......@@ -92,11 +94,7 @@ export class SearchService extends Disposable implements ISearchService {
}
}
public search(query: ISearchQuery, onProgress?: (item: ISearchProgressItem) => void): TPromise<ISearchComplete> {
let combinedPromise: TPromise<void>;
return new TPromise<ISearchComplete>((onComplete, onError) => {
public search(query: ISearchQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): TPromise<ISearchComplete> {
// Get local results from dirty/untitled
const localResults = this.getLocalResults(query);
......@@ -129,7 +127,7 @@ export class SearchService extends Disposable implements ISearchService {
const providerPromise = TPromise.join(providerActivations)
.then(() => this.extensionService.whenInstalledExtensionsRegistered())
.then(() => this.searchWithProviders(query, onProviderProgress))
.then(() => this.searchWithProviders(query, onProviderProgress, token))
.then(completes => {
completes = completes.filter(c => !!c);
if (!completes.length) {
......@@ -150,7 +148,8 @@ export class SearchService extends Disposable implements ISearchService {
return TPromise.wrapError(errs[0]);
});
combinedPromise = providerPromise.then(value => {
return new TPromise((c, e) => {
providerPromise.then(value => {
const values = [value];
const result: ISearchComplete = {
......@@ -177,10 +176,11 @@ export class SearchService extends Disposable implements ISearchService {
}
return result;
}).then(c, e);
}, () => {
// Need the OpenAnythingHandler to stop trying to cancel this promise, https://github.com/Microsoft/vscode/issues/56137
});
}).then(onComplete, onError);
}, () => combinedPromise && combinedPromise.cancel());
}
private getSchemesInQuery(query: ISearchQuery): Set<string> {
......@@ -196,7 +196,7 @@ export class SearchService extends Disposable implements ISearchService {
return schemes;
}
private searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void) {
private searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) {
const e2eSW = StopWatch.create(false);
const diskSearchQueries: IFolderQuery[] = [];
......@@ -219,7 +219,7 @@ export class SearchService extends Disposable implements ISearchService {
}
};
searchPs.push(provider.search(oneFolderQuery, onProviderProgress));
searchPs.push(provider.search(oneFolderQuery, onProviderProgress, token));
}
});
......@@ -234,7 +234,7 @@ export class SearchService extends Disposable implements ISearchService {
extraFileResources: diskSearchExtraFileResources
};
searchPs.push(this.diskSearch.search(diskSearchQuery, onProviderProgress));
searchPs.push(this.diskSearch.search(diskSearchQuery, onProviderProgress, token));
}
return TPromise.join(searchPs).then(completes => {
......@@ -447,7 +447,7 @@ export class DiskSearch implements ISearchResultProvider {
this.raw = new SearchChannelClient(channel);
}
public search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete> {
public search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete> {
const folderQueries = query.folderQueries || [];
return TPromise.join(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath)))
.then(exists => {
......@@ -461,7 +461,7 @@ export class DiskSearch implements ISearchResultProvider {
event = this.raw.textSearch(rawSearch);
}
return DiskSearch.collectResultsFromEvent(event, onProgress);
return DiskSearch.collectResultsFromEvent(event, onProgress, token);
});
}
......@@ -507,11 +507,21 @@ export class DiskSearch implements ISearchResultProvider {
return rawSearch;
}
public static collectResultsFromEvent(event: Event<ISerializedSearchProgressItem | ISerializedSearchComplete>, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete> {
public static collectResultsFromEvent(event: Event<ISerializedSearchProgressItem | ISerializedSearchComplete>, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete> {
let result: IFileMatch[] = [];
let listener: IDisposable;
return new TPromise<ISearchComplete>((c, e) => {
if (token) {
token.onCancellationRequested(() => {
if (listener) {
listener.dispose();
}
e(canceled());
});
}
listener = event(ev => {
if (isSerializedSearchComplete(ev)) {
if (isSerializedSearchSuccess(ev)) {
......@@ -551,8 +561,7 @@ export class DiskSearch implements ISearchResultProvider {
}
}
});
},
() => listener && listener.dispose());
});
}
private static createFileMatch(data: ISerializedFileMatch): FileMatch {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册