Fix: correct terminal command handling logic#124
Merged
asdek merged 1 commit intovxcontrol:feature/project_improvementsfrom Feb 22, 2026
Merged
Fix: correct terminal command handling logic#124asdek merged 1 commit intovxcontrol:feature/project_improvementsfrom
asdek merged 1 commit intovxcontrol:feature/project_improvementsfrom
Conversation
Contributor
|
thanks for this PR! 🙏 |
This was referenced Feb 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix: Goroutine and Resource Leak in Terminal Exec Timeout Handling
Description of the Change
Problem
getExecResult()inbackend/pkg/tools/terminal.gocontained a goroutine leak triggered every time a command execution timed out.When
ctx.Done()fired, the function returned early but left a background goroutine permanently blocked onio.Copy(&dst, resp.Reader). The goroutine had no way to be interrupted and was never joined, so it persisted in memory until process termination.Two additional bugs compounded the issue:
errvariable while the function's main goroutine could be reading it, violating the Go memory model.runtime.NumGoroutine()counts, and eventual Docker connection pool exhaustion.Solution
Replaced the
donechannel pattern with a bufferederrChanand explicit synchronization on timeout:copyErrvariable inside the goroutine eliminates the data race on the outererr.resp.Close()on timeout unblocks the underlyingnet.Connread insideio.Copyimmediately.<-errChanafter close synchronously waits for the goroutine to fully exit before the function returns — guaranteed on every code path.Closes #
Type of Change
Areas Affected
Testing and Verification
Test Configuration
Test Steps
getExecResult()usingnet.Pipe()as a real in-process connection, soresp.Close()genuinely unblocks theRead()call insideio.Copy.go test ./pkg/tools/ -run TestGetExecResult -v -count=1.runtime.NumGoroutine()) were measured before and after timeout scenarios to assert no leak.Test Results
TestGetExecResult_SuccessTestGetExecResult_Timeout_NoGoroutineLeakruntime.NumGoroutine()returns to baseline after a single timeout — core regression testTestGetExecResult_Timeout_PartialOutputTestGetExecResult_AttachErrorContainerExecAttachfailure propagates with correct error wrappingTestGetExecResult_MultipleTimeoutsSecurity Considerations
No security model changes. The fix eliminates unclosed Docker readers accumulating in memory, which reduces the attack surface for resource exhaustion under sustained load.
Performance Impact
Positive. The fix prevents goroutine and memory accumulation over time. In the timeout path, the only addition is one
resp.Close()call (which was already deferred) and a single channel receive that resolves as soon as the goroutine unblocks — negligible overhead.Documentation Updates
Deployment Notes
No environment variable, configuration, schema, or migration changes required. Drop-in fix — fully backward compatible.
Checklist
Code Quality
go fmtandgo vet(for Go code)npm run lint(for TypeScript/JavaScript code)Security
Compatibility
Documentation
Additional Notes
The root pattern — spinning a goroutine around
io.Copywithout a guaranteed interruption mechanism — is a common mistake when combining context cancellation with blocking I/O in Go. The fix follows the established correct pattern: close the underlying connection to unblock the read, then synchronously wait on a buffered channel to confirm the goroutine has exited before returning.The
defer resp.Close()that already existed in the function is retained as a safe no-op double-close guard for the success path —HijackedResponse.Close()wrapsnet.Conn.Close()which is safe to call multiple times.