SIGN IN SIGN UP

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