Change:
a traditional . , listen for debugging the extension, open a file picker, then close it. session token's never gets triggered when the session is done (aka when the file picker is closed) :bug: This contradicts
regression from

Discussion:
import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider, hasSiblingFn, excludeToGlobPattern, DEFAULT_MAX_SEARCH_RESULTS } from 'vs/workbench/services/search/common/search'; import { FileSearchProviderFolderOptions, FileSearchProviderNew, FileSearchProviderOptions } from 'vs/workbench/services/search/common/searchExtTypes'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; <ins> import { Disposable } from 'vs/base/common/lifecycle'; import { OldFileSearchProviderConverter } from 'vs/workbench/services/search/common/searchExtConversionTypes'; </ins> interface IInternalFileMatch { base: URI;

private globalExcludePattern?: glob.ParsedExpression; <del> constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionToken?: unknown) { </del> <ins> constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionLifecycle?: SessionLifecycle) { </ins> this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || undefined;

private async doSearch(fqs: IFolderQuery<URI>[], onResult: (match: IInternalFileMatch) => void): Promise<IFileSearchProviderStats | null> { const cancellation = new CancellationTokenSource(); const folderOptions = fqs.map(fq => this.getSearchOptionsForFolder(fq)); <ins> const session = this.provider instanceof OldFileSearchProviderConverter ? this.sessionLifecycle?.tokenSource.token : this.sessionLifecycle?.obj; </ins> const options: FileSearchProviderOptions = { folderOptions, maxResults: this.config.maxResults ?? DEFAULT_MAX_SEARCH_RESULTS, <del> session: this.sessionToken </del> <ins> session </ins> };

stats?: IFileSearchProviderStats; } <ins> /** * For backwards compatibility, store both a cancellation token and a session object. The session object is the new implementation, where */ class SessionLifecycle extends Disposable { public readonly obj: object; public readonly tokenSource: CancellationTokenSource; constructor() { super(); this.obj = new Object(); this.tokenSource = new CancellationTokenSource(); } public override dispose(): void { this.tokenSource.cancel(); super.dispose(); } } </ins> export class FileSearchManager { private static readonly BATCH_SIZE = 512; <del> private readonly sessions = new Map<string, unknown>(); </del> <ins> private readonly sessions = new Map<string, SessionLifecycle>(); </ins> fileSearch(config: IFileQuery, provider: FileSearchProviderNew, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise<ISearchCompleteStats> { const sessionTokenSource = this.getSessionTokenSource(config.cacheKey);

} clearCache(cacheKey: string): void { <ins> // cancel the token this.sessions.get(cacheKey)?.dispose(); </ins> // with no reference to this, it will be removed from WeakMaps this.sessions.delete(cacheKey); } <del> private getSessionTokenSource(cacheKey: string | undefined): unknown { </del> <ins> private getSessionTokenSource(cacheKey: string | undefined): SessionLifecycle | undefined { </ins> if (!cacheKey) { return undefined; } if (!this.sessions.has(cacheKey)) { <del> this.sessions.set(cacheKey, new Object()); </del> <ins> this.sessions.set(cacheKey, new SessionLifecycle()); </ins> } return this.sessions.get(cacheKey);

* and searches with the same session object can search the same cache. When the object is garbage-collected, the session is complete and the cache can be cleared. * Please do not store any references to the session object, except via a weak reference (e.g. `WeakRef` or `WeakMap`). */ <del> session: unknown; </del> <ins> session: object; </ins> /** * The maximum number of results to be returned.