stacklok / stacklok/toolhive

Streamable proxy drops/fails requests under high concurrent load (fixed 100-buffer channels, no backpressure)

Open
#5,952 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
2.2k
Forks
300
Avg merge
1d 15h
Merged PRs (30d)
184

Description

Summary

The streamable HTTP→stdio proxy forwards through two fixed-size (buffered=100) channels with non-blocking, drop-on-full sends, drained by single-consumer goroutines. Under high concurrent in-flight load (>~100 simultaneous requests) it degrades ungracefully — hard-failing or dropping — rather than applying backpressure. This is especially relevant to the Modern (2026-07-28) stateless path, which invites high concurrency (no session pinning).

Detail

pkg/transport/proxy/streamable/streamable_proxy.go:

  • messageCh/responseCh are buffered at 100 (hardcoded ~L175-176; no Option to configure).
  • SendMessageToDestination (~L295) and ForwardResponseToClients (~L305): select { case ch <- msg: return nil; default: return err } — non-blocking; on a full channel they fail/drop immediately.
  • dispatchResponses drains responseCh in a single goroutine.

If either consumer is briefly descheduled while >100 requests are in flight:

  • messageCh full → SendMessageToDestination errors → doRequest errors → HTTP 500.
  • responseCh full → response dropped → the waiting client blocks until requestTimeout (60s default) → HTTP 504.
Evidence

Observed while building the Modern no-accumulation resilience test (#5837): a 1000-request concurrent burst intermittently produced a 504, root-caused by an MoE review to this backpressure gap (worse under -race scheduling). The test now throttles its own concurrency to sidestep it, but the production behavior remains.

Suggested direction

Make the buffer sizes configurable and/or apply real backpressure (blocking or context-bounded send) instead of drop-on-full, so burst load degrades in latency rather than dropped/failed requests. At minimum, document the ~100 concurrent-in-flight ceiling.

Context

Enhancement / resilience, surfaced by #5837 Tier-3 testing. Not a regression; pre-existing design limitation.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read pkg/transport/proxy/streamable/streamable_proxy.go, focusing on the messageCh and responseCh setup, SendMessageToDestination, ForwardResponseToClients, and dispatchResponses. Reproduce the burst behavior described from the Modern resilience test in #5837, then verify that concurrent requests no longer fail or time out because either channel is full while preserving request cancellation and timeout behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.