diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 6cf987cb..96c1dda7 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -142,7 +142,9 @@ export class ChannelImplementation implements Channel { ) { /* The global boolean parameter to getSubchannelPool has the inverse meaning to what * the grpc.use_local_subchannel_pool channel option means. */ - this.subchannelPool = getSubchannelPool((options['grpc.use_local_subchannel_pool'] ?? 0) === 0); + this.subchannelPool = getSubchannelPool( + (options['grpc.use_local_subchannel_pool'] ?? 0) === 0 + ); const channelControlHelper: ChannelControlHelper = { createSubchannel: ( subchannelAddress: string, @@ -234,16 +236,25 @@ export class ChannelImplementation implements Channel { callStream ); } catch (error) { - callStream.cancelWithStatus( - Status.UNAVAILABLE, - 'Failed to start call on picked subchannel' - ); + /* An error here indicates thaat something when wrong with + * the picked subchannel's http2 stream right before we + * tried to start the stream. We are handling a promise + * result here, so this asynchronous with respect to the + * original tryPick call, so calling it again is not + * recursive. We call tryPick immediately instead of + * queueing this pick again because handling the queue is + * triggered by state changes, and we want to immediately + * check if the state has already changed since the + * previous tryPick call. We do this instead of cancelling + * the stream because the correct behavior may be + * re-queueing instead, based on the logic in the rest of + * tryPick */ + this.tryPick(callStream, callMetadata); } } else { - callStream.cancelWithStatus( - Status.UNAVAILABLE, - 'Connection dropped while starting call' - ); + /* The logic for doing this here is the same as in the catch + * block above */ + this.tryPick(callStream, callMetadata); } }, (error: Error & { code: number }) => {