Cancelled ripgrep text searches can remain pending indefinitely waiting for child process close
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
## Type
Bug
## Environment
- VS Code Server commit: `1b6a188127eeaf9194f945eb6eb89a657e93c54c`
- Remote host: Windows VM
- Client: macOS desktop
- Connection: VS Code Remote Tunnel
- Trigger observed through GitHub Copilot Chat invoking workspace text/file searches
## Description
Under concurrent workspace searches in a large multi-root remote workspace, cancelled text searches can remain active below the cancellation boundary. After enough concurrent or long-running searches, the Remote Extension Host becomes unresponsive. Reloading the window temporarily recovers it.
The tunnel transport itself remains alive (`liveness pong` continues), while the RPC layer repeatedly reattaches at approximately 20-second intervals. Immediately before the hang, Copilot text searches time out after 20 seconds. Serializing those searches substantially reduces the frequency of the hang.
## Source-level cancellation gap
`SearchService` returns a cancellation race around the provider promise:
https://github.com/microsoft/vscode/blob/1b6a188127eeaf9194f945eb6eb89a657e93c54c/src/vs/workbench/services/search/common/searchService.ts#L158-L202
`raceCancellationError` rejects the outer promise but does not settle or await the underlying provider promise:
https://github.com/microsoft/vscode/blob/1b6a188127eeaf9194f945eb6eb89a657e93c54c/src/vs/base/common/async.ts#L99-L106
In `RipgrepTextSearchEngine`, cancellation only marks the search done, calls `rgProc.kill()`, and cancels the parser:
https://github.com/microsoft/vscode/blob/1b6a188127eeaf9194f945eb6eb89a657e93c54c/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts#L85-L92
However, the provider promise is settled only from the child process `close` event:
https://github.com/microsoft/vscode/blob/1b6a188127eeaf9194f945eb6eb89a657e93c54c/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts#L119-L140
If `close` is delayed or never observed, the provider promise has no bounded completion path. In a multi-root workspace, each search starts one ripgrep operation per folder concurrently via `Promise.all`, multiplying the impact:
https://github.com/microsoft/vscode/blob/1b6a188127eeaf9194f945eb6eb89a657e93c54c/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts#L24-L41
The same cancellation structure is still present on `main` at the time of filing.
## Expected behavior
Cancelling a text search should deterministically settle the provider operation and clean up the child process and listeners, even when the child process does not emit `close` promptly.
## Actual behavior
The caller observes cancellation, but the underlying provider operation can remain pending while waiting for `close`. Repeated concurrent searches can accumulate and eventually make the Remote Extension Host unresponsive.
## Suggested fix
Make cancellation idempotent and give it a bounded completion path:
1. terminate the ripgrep child process and cancel parsing;
2. settle the provider promise on cancellation without waiting indefinitely for `close`;
3. dispose cancellation/process listeners deterministically;
4. retain a guarded `close` handler so completion happens only once.
I am preparing a focused PR with a regression test.
## Related issues
- #16665 reported the broader class of runaway search processes not stopping after cancellation in 2016, but predates this implementation and was closed.
- #183480 concerns slow remote file search, not cancelled text-search provider promises remaining pending.
Contributor guide
Assessment
This issue has not been assessed yet.