StdioTransport.send() still interleaves concurrent sends under EAGAIN backpressure — #252 was closed without a merged fix
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 58/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- swift
- 領域
- networking
調査の方向性
Start at StdioTransport.send() and inspect the write loop around the EAGAIN backpressure sleep and actor suspension. Reproduce with a slowly drained pipe, a large send, and a concurrent small send; done means concurrent newline-delimited JSON frames remain intact while backpressure is retried.
索引モデルが issue の本文から書いたものです。
説明
Summary
StdioTransport.send() can splice the bytes of one JSON-RPC frame into the
middle of another when two sends run concurrently and the first hits stdout
backpressure. This corrupts the newline-delimited framing for any client
reading the stream.
This is the same defect reported in #252 (2026-07-02). That issue was closed
by its reporter the next day with no linked commit or PR, and the code on
main today is unchanged — the bug is still present. Filing fresh with an
independent, in-the-wild reproduction.
Root cause
StdioTransport is an actor, but send(_:) contains an await inside its
write loop — the EAGAIN backpressure sleep:
} catch let error where MCPError.isResourceTemporarilyUnavailable(error) {
try await Task.sleep(for: .milliseconds(10)) // ← actor reentrancy point
continue
}
Actor isolation guarantees mutual exclusion only between suspension
points. While one send is suspended in that sleep mid-frame, a second
send enters the actor and writes its complete frame into the middle of the
first one.
Observed in the wild
Version 0.12.1, macOS 15 (Darwin 25.5), a stdio MCP server whose
initialize result carries a ~1.9 KB instructions string. A pipelining
client sent initialize + a tools/call back-to-back; the captured stdout
shows the tool response spliced mid-word into the initialize result:
...has not made any bucket visi{"id":2,"jsonrpc":"2.0","result":{...}}
ble to agents yet...
Both frames are unparseable to a line-delimited JSON reader at that point.
Any client that issues parallel tool calls (several do) can trigger this
whenever a response exceeds what the pipe accepts in one write — no
misbehaving client required.
Reproduction sketch
Same shape as #252's: create the transport over a pipe the reader drains
slowly, start a send large enough to hit EAGAIN (hundreds of KB, or a small
pipe), and issue a second small send while the first is suspended. The small
frame lands inside the large one.
Suggested fix
Serialize sends so one completes before the next begins — either drop to a
blocking write for the remainder of a frame, or chain sends FIFO. We shipped
the FIFO as a wrapper in our server and it eliminates the corruption:
private var lastSend: Task<Void, Never>?
func send(_ data: Data) async throws {
let previous = lastSend
let task = Task<Void, Error> { [base] in
await previous?.value
try await base.send(data)
}
lastSend = Task { try? await task.value }
try await task.value
}
The same pattern inlined into StdioTransport.send (chain before the write
loop) would fix it at the source. Happy to turn this into a PR if that's
welcome.
- 主要言語
- Swift
- スター
- 1.5k
- フォーク
- 243
- PR マージ指標
- 30日以内にマージされた PR はありません
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
modelcontextprotocol/swift-sdk のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
-
enhancement
難易度 1/5 1時間未満 初心者へのやさしさ 78/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
modelcontextprotocol/swift-sdk の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
googleapis/google-cloud-swift#1151 ·
-
`--app` focus through the daemon can pick an off-screen window and refuse with `axElementNotFound` オープンclawsweeper:fix-shape-clear clawsweeper:queueable-fix clawsweeper:source-repro impact:other issue-rating: 🦞 diamond lobster no-stale P1
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
-
code health
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
apple/coreai-models#288 ·
-
task
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
vsanthanam/JBird#429 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100