client: avoid finishing a stream twice when stream creation fails (#9191)
In `clientStream.withRetry`, when `newAttemptLocked` fails before an attempt is created, `withRetry` called `cs.finish` and then returned the error. `newClientStream` also runs `endOfClientStream` from its deferred cleanup on an error return, so the stream was finished twice and every `grpc.OnFinish` callback ran twice for a single RPC, breaking its documented "called only once" contract. One of those callbacks is the idleness manager's `OnCallEnd`. The extra `OnCallEnd` pushes the manager's int32 `activeCallsCount` one below its `-math.MaxInt32` idle sentinel and corrupts the counter. After that the channel can get permanently stuck in IDLE, and later RPCs fail with "context deadline exceeded while waiting for connections to become ready" even though the backend is reachable. It happens when RPCs are canceled around the idle-timeout boundary. On this path no attempt is created, so there is nothing to finish: the context is canceled by `newClientStreamWithParams`'s deferred cleanup, and `newClientStream`'s deferred `endOfClientStream` finishes the stream exactly once. Replace the `cs.finish` call with `cs.commitAttemptLocked` so the stream is still committed, releasing resources held for retries such as the config selector's `OnCommitted` callback, without being finished a second time. This mirrors the `retryLocked` give-up path. Add a regression test that fails before this change and passes after it. RELEASE NOTES: * client: Fix a bug where a ClientConn could get permanently stuck in IDLE after an RPC was canceled while the channel was exiting idle mode
U
Utku Özdemir committed
75f3c0bb28669b4ba74dd57d836a568dd1b250ac
Parent: 7d87045
Committed by GitHub <noreply@github.com>
on 7/17/2026, 5:06:06 PM