You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(sdk/go): TCP().Listen sets up tunnels serially in the accept loop #4512
I use OpenShell and build automation against its SDKs. While reviewing sdk/ I hit this behavior and reproduced it with the minimal test below against main.
Problem Statement
tunnelListener.acceptAndBridge calls Forward/SSH().Tunnel (a GetSandbox RPC, stream open and init frame) synchronously before returning to Accept, so each new connection waits for every earlier connection's tunnel setup.
Impact / Why This Matters
One slow or hung tunnel setup blocks all other local clients of the listener; connection latency grows linearly with concurrent connections (browsers, connection pools).
Acceptance Criteria
Concurrent connections to a Listen port establish tunnels in parallel; N connections with ~500ms setup each complete in roughly 500ms, not N×500ms.
A hung setup for one connection does not block others.
Reproduction Steps
Stub SandboxInterface.Get to sleep 500ms; open 4 concurrent connections to a Listen port, each writes 1 byte and reads the echo. Total time: 2.01s.
Thanks for the report and the timing reproduction.
Confirmed on main. tunnelListener.acceptAndBridge (sdk/go/openshell/v1/tcp_client.go) calls Forward or SSH().Tunnel inside the accept loop and only starts the bridge goroutine after that returns. Each connection waits for the previous one's tunnel setup. There is no timeout on setup either, so one hung setup stalls the listener until someone closes it. The TypeScript SDK doesn't have this problem because net.createServer runs each connection's setup asynchronously.
The fix is to move tunnel setup into the per-connection goroutine, add that goroutine to the WaitGroup, and check closing before spawning it.
Setup also needs a deadline, and that part is easy to get wrong. Forward and SSH().Tunnel derive the stream context from the context they're given, so wrapping it in context.WithTimeout would kill established connections once the timeout expires. The deadline should cover only the GetSandbox call, opening the stream, and sending the init frame, for example with a timer that cancels on expiry and is stopped once the init frame is sent.
Separately, setup failures are dropped without a trace today (the loop just continues), which makes problems like this one hard to diagnose. Logging them through the SDK logger would help once #4510 makes that logger do something.
The evidence and acceptance criteria are complete. Next step: maintainer acceptance decision.
Activity
rhuss commentedon Oct 14, 2026
Thanks for the report and the timing reproduction.
Confirmed on
main.tunnelListener.acceptAndBridge(sdk/go/openshell/v1/tcp_client.go) callsForwardorSSH().Tunnelinside the accept loop and only starts thebridgegoroutine after that returns. Each connection waits for the previous one's tunnel setup. There is no timeout on setup either, so one hung setup stalls the listener until someone closes it. The TypeScript SDK doesn't have this problem becausenet.createServerruns each connection's setup asynchronously.The fix is to move tunnel setup into the per-connection goroutine, add that goroutine to the
WaitGroup, and checkclosingbefore spawning it.Setup also needs a deadline, and that part is easy to get wrong.
ForwardandSSH().Tunnelderive the stream context from the context they're given, so wrapping it incontext.WithTimeoutwould kill established connections once the timeout expires. The deadline should cover only theGetSandboxcall, opening the stream, and sending the init frame, for example with a timer that cancels on expiry and is stopped once the init frame is sent.Separately, setup failures are dropped without a trace today (the loop just
continues), which makes problems like this one hard to diagnose. Logging them through the SDK logger would help once #4510 makes that logger do something.The evidence and acceptance criteria are complete. Next step: maintainer acceptance decision.
d0w commentedon Oct 15, 2026
Hey @CloneOfAlex, I can take a look at this one if you aren't already working on a fix for it. Please let me know!
CC: @rhuss