Skip to content

Stop gotoReady() spinning on closed or settled states - #1849

Merged
ajeetdsouza merged 2 commits into
connectrpc:mainfrom
princejain756:fix-session-manager-gotoready-spin
Oct 27, 2026
Merged

ajeetdsouza merged 2 commits into
connectrpc:mainfrom
princejain756:fix-session-manager-gotoready-spin

Conversation

@princejain756

Copy link
Copy Markdown
Contributor

Summary

Http2SessionManager.gotoReady() can spin forever. Its wait loop only handled the "error" and "connecting" states:

while (this.s.t !== "ready") {
  if (this.s.t === "error") {
    throw this.s.reason;
  }
  if (this.s.t === "connecting") {
    await this.s.conn;
  }
}

Two states can reach that loop without a wait condition:

  1. "closed" - reachable while gotoReady() is awaiting verification: a graceful close (idle timeout, GOAWAY, server close) transitions the manager to "closed" underneath the await, and the loop then has nothing to wait for and no branch to take.
  2. "connecting" with an already-resolved promise - this is the 100% CPU freeze reported in Http2SessionManager.gotoReady spins at 100% CPU when the state is closed or stuck in connecting #1841. If the http2 session dies between connecting and becoming ready, ready() throws ConnectError: [internal] expected open session inside the state.conn.then(...) handler in setState(). The exception is swallowed by the promise machinery, the manager stays "connecting" with a settled conn, and await this.s.conn returns instantly on every iteration - a pinned core with the only stack frame inside gotoReady().

Changes

  • The wait loop reconnects when it sees "closed", matching the method's entry branch.
  • The "connecting" promise handler wraps setState(ready(...)) in try/catch and falls back to setState(closedOrError(e)), so a dead session lands the manager in "error" and pending connect()/request() callers reject instead of spinning.

Test plan

Two regression tests in http2-session-manager.spec.ts:

  • should open a new connection if the existing one closes after a successful verification PING - destroys the server session in a ping listener so the PING acknowledgement and teardown travel in the same turn; verification resolves while the manager transitions to "closed". Without the fix the request never settles.
  • should reject if the connection dies before it becomes ready - injects a "connecting" state whose promise resolves to an already-closed session, deterministically reproducing the ready() throw. Without the fix the whole spec file hangs (verified: timeout kills the suite at the reported freeze); with the fix the request rejects with a ConnectError.

All 114 tests in packages/connect-node pass, tsc --noEmit and biome lint/format are clean.

Fixes #1841

The while loop in gotoReady() only handled "error" and "connecting".
A "closed" state had no wait condition, so the loop spun forever - and
a "connecting" state whose promise had already resolved spun at 100%
CPU whenever ready() threw inside the state transition, e.g. when the
session died between connecting and becoming ready.

The loop now reconnects from "closed", matching the entry branch, and
the "connecting" promise handler catches a throwing ready() so the
manager lands in "error" and pending callers reject instead of
spinning.

Fixes connectrpc#1841

Signed-off-by: Prince Jain <[email protected]>
@princejain756
princejain756 force-pushed the fix-session-manager-gotoready-spin branch from eae77fa to 3f4f58b Compare October 23, 2026 02:23
Signed-off-by: Ajeet D'Souza <[email protected]>
@ajeetdsouza ajeetdsouza changed the title fix(connect-node): stop gotoReady() spinning on closed or settled states Stop gotoReady() spinning on closed or settled states Oct 27, 2026
@ajeetdsouza

Copy link
Copy Markdown
Contributor

Thanks, this looks great! :)

@ajeetdsouza
ajeetdsouza merged commit 73b8d9f into connectrpc:main Oct 27, 2026
30 of 31 checks passed
princejain756 added a commit to princejain756/princejain756 that referenced this pull request Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Http2SessionManager.gotoReady spins at 100% CPU when the state is closed or stuck in connecting

2 participants