Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

`Client.connect` busy-loops at 100% CPU when the transport reaches EOF before `initialize` completes

オープン
#256 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 7 日以内に返信

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
静か
技術スタック
swift

調査の方向性

Start in Sources/MCP/Client/Client.swift at the message-handling task created by Client.connect and inspect how connection.receive() behaves when its AsyncThrowingStream finishes. Run the described /usr/bin/true StdioTransport reproduction first. Done means EOF stops further receive polling and connect/_initialize throws a connection-closed error instead of hanging.

索引モデルが issue の本文から書いたものです。

説明

If a spawned MCP server dies (or closes its stdout) during startup — before the client's initialize handshake finishes — Client.connect(transport:) never returns and one core sits pinned at 100%.

What I see: a stdio server that exits immediately on launch. ps shows the host process in state R, and sample <pid> shows the spin inside closure #1 in Client.connect → AsyncThrowingStream.Iterator.next().

The message-handling loop in Sources/MCP/Client/Client.swift is (trimmed):

task = Task {
    guard let connection = self.connection else { return }
    repeat {
        if Task.isCancelled { break }
        do {
            let stream = await connection.receive()
            for try await data in stream {
                if Task.isCancelled { break }
                // decode + dispatch
            }
        } catch { break }
    } while true
}
// ...
return try await _initialize()

When the transport hits EOF, the stream from receive() finishes, the for try await completes without throwing, and while true immediately loops back and calls receive() again, which returns another already-finished stream. Nothing sleeps or exits on end-of-stream, so it spins. Meanwhile connect has moved on to await _initialize(), whose response never arrives because the server is gone, so connect is parked forever on a continuation nothing resumes.

Minimal repro: spawn /usr/bin/true via Process with two Pipes, wrap the pipe fds in a StdioTransport(input:output:), build a Client, and try await client.connect(transport:). Just re-ran this against v0.12.1: connect has neither returned nor thrown 8 seconds in, and ps samples the process at ~100% CPU for the whole wait (96.6 / 100.0 / 100.0 / 100.0 over four 1.5s samples).

Two changes would fix it:

  • The loop should stop (or back off) when the underlying stream finishes rather than re-polling a finished stream — a finished transport stream means the connection is gone.
  • connect / _initialize should then fail with a connection-closed error instead of hanging.

For what it's worth, my workaround is to race connect against a timeout and call client.disconnect() on expiry — disconnect() cancels that task and resumes the pending initialize with an error, so connect throws instead of hanging. Separately I had to set signal(SIGPIPE, SIG_IGN), because writing to the dead server's stdin was raising SIGPIPE and taking down the whole host process. I'd be happy to turn the loop-termination part into a PR if that's welcome.

主要言語
Swift
スター
1.5k
フォーク
252
PR マージ指標
30日以内にマージされた PR はありません

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

modelcontextprotocol/swift-sdk のほかの issue

modelcontextprotocol/swift-sdk の issue をすべて見る

似ている issue

Swift の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。