-
Notifications
You must be signed in to change notification settings - Fork 30.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Terminal Bug: Race condition looses terminal data in shell integration (AsyncIterable consumer with POC) #237208
Comments
To clarify, this is about
Does this actually cause a problem in cline or is it just an unexpected inconsistency? |
yes
It has caused problems in production, see below.
It is definitely an unexpected inconsistency; IMHO, extensions should either always get it or never get it. As implemented in the currently released version of Cline, Given the concern for consistency, it would be nice to guarantee that either extensions always get
Cline's let match1 = output.match(/\x1b\]633;C\x07(.*?)\x1b\]633;D(?:;(\d+))?/s)?.[1]
let match2 = output.match(/.*\x1b\]633;C\x07(.*)$/s)?.[1]
let match = match1 !== undefined ? match1 :
(match2 !== undefined ? match2 : undefined) There are probably more efficient ways to match this, but implementation aside, you can see that we have some special cases implementing workarounds for whether TL;DR: Ultimately I do not mind the special casing, as we are going to need it anyway to be compatible with existing versions of vscode that are already deployed: My biggest concerns are those listed above in the interest of guaranteeing that no command output text is lost. |
I suppose it's better to be consistent. Here are some code pointers if anyone wants to take a stab at fixing it: Where read is created (exthost process): vscode/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts Lines 298 to 324 in decaa08
Where data is send to read (renderer process):
I'm guessing the problem is coming from the |
In the current vscode implementation, does the implementation guarantee that the command output stream is flushed and the only thing that could possibly be lost is the |
@KJ7LNW can't say without investigating further, this is fairly low priority for me |
I was able to confirm that data is lost. You can verify that using this test plugin with the command set to https://github.com/KJ7LNW/vsce-test-terminal-integration Without sleep (fails)Fails with or without the shell integration box checked With 50ms sleep (succeeds)Succeeds with or without the shell integration box checked PROMPT_COMMAND is set here (abbreviated excerpts): const env: { [key: string]: string } = {};
if (promptCommand.trim()) {
env.PROMPT_COMMAND = promptCommand;
}
this.terminal = vscode.window.createTerminal({
name: 'Command Runner',
env
});
// stream is picked up in the first line of onDidStartTerminalShellExecution as:
// const stream = e.execution.read();
// and then output is displayed in the UI with and without parsing
let output = '';
for await (const data of stream) {
output += data;
} |
Maybe this issue should be escalated now that we know it is losing data in some cases. Note that data is lost in both terminal integration via |
Here is more information based on the testing I have done using the test extension linked above. Note that the following test cases demonstrate behaviors that are reliably reproducible on my system, but due to the timing-sensitive nature of these race conditions, actual behavior may vary slightly on different systems or under different load conditions.
|
Terminal Shell Integration Race Condition
Overview
There is a race condition in VSCode's terminal shell integration where the command finished barrier is released before AsyncIterable consumers receive the sequence data. The sequence of events causing this race is:
The key issue is that steps 3-4 happen before step 5-6. This means:
The "633;D found" case explained below (correct behavior) only occurs when the write callback happens to complete before the barrier releases, which is not guaranteed by the current implementation.
Does this issue occur when all extensions are disabled?
This is a bug related to an extension API so at least the bug-test plugin below is needed to inspect. See proof of concept bug test below.
Proposed Solution
The race condition can be fixed by delaying the barrier release:
This ensures consumers will always see the sequence before the barrier releases, making the behavior deterministic rather than depending on timing.
Environment
Steps to Reproduce
Test Results
Running the test extension shows:
Example of a "found" case (GOOD - expected behavior where consumer sees sequence before barrier, contains
\x1B]633;D
):Example of a "not found" case (BUG - race condition where barrier releases too early, does NOT contain
\x1B]633;D
):Expected Behavior
The barrier should only be released after AsyncIterable consumers have received and processed the command finished sequence. The "found" count should increment consistently, indicating consumers reliably see the sequence before the barrier releases. The "not found" count should always be zero in a correct implementation.
Actual Behavior
The barrier is frequently released before consumers receive the sequence data:
Technical Details
The race condition can also be verified by:
This confirms the sequence is parsed and handled before the write completes and reaches consumers.
Alternative Solutions
Event Ordering:
However, the delay barrier release approach is simpler and more robust, especially given the empirical evidence showing the high frequency of the race condition in practice.
See the full technical analysis at: https://github.com/KJ7LNW/vsce-test-terminal-integration
The text was updated successfully, but these errors were encountered: