Skip to content

Commit 023c1d0

Browse files
authored
Merge pull request #2791 from murgatroid99/grpc-js_channel_close_pick_fix
grpc-js: Ensure pending calls end after channel close
2 parents fbbc78d + 810e9e6 commit 023c1d0

File tree

2 files changed

+59
-2
lines changed

2 files changed

+59
-2
lines changed

packages/grpc-js/src/internal-channel.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { ChannelOptions } from './channel-options';
2020
import { ResolvingLoadBalancer } from './resolving-load-balancer';
2121
import { SubchannelPool, getSubchannelPool } from './subchannel-pool';
2222
import { ChannelControlHelper } from './load-balancer';
23-
import { UnavailablePicker, Picker, QueuePicker } from './picker';
23+
import { UnavailablePicker, Picker, QueuePicker, PickArgs, PickResult, PickResultType } from './picker';
2424
import { Metadata } from './metadata';
2525
import { Status, LogVerbosity, Propagate } from './constants';
2626
import { FilterStackFactory } from './filter-stack';
@@ -143,6 +143,22 @@ class ChannelSubchannelWrapper
143143
}
144144
}
145145

146+
class ShutdownPicker implements Picker {
147+
pick(pickArgs: PickArgs): PickResult {
148+
return {
149+
pickResultType: PickResultType.DROP,
150+
status: {
151+
code: Status.UNAVAILABLE,
152+
details: 'Channel closed before call started',
153+
metadata: new Metadata()
154+
},
155+
subchannel: null,
156+
onCallStarted: null,
157+
onCallEnded: null
158+
}
159+
}
160+
}
161+
146162
export class InternalChannel {
147163
private readonly resolvingLoadBalancer: ResolvingLoadBalancer;
148164
private readonly subchannelPool: SubchannelPool;
@@ -536,7 +552,9 @@ export class InternalChannel {
536552
}
537553

538554
getConfig(method: string, metadata: Metadata): GetConfigResult {
539-
this.resolvingLoadBalancer.exitIdle();
555+
if (this.connectivityState !== ConnectivityState.SHUTDOWN) {
556+
this.resolvingLoadBalancer.exitIdle();
557+
}
540558
if (this.configSelector) {
541559
return {
542560
type: 'SUCCESS',
@@ -745,6 +763,15 @@ export class InternalChannel {
745763
close() {
746764
this.resolvingLoadBalancer.destroy();
747765
this.updateState(ConnectivityState.SHUTDOWN);
766+
this.currentPicker = new ShutdownPicker();
767+
for (const call of this.configSelectionQueue) {
768+
call.cancelWithStatus(Status.UNAVAILABLE, 'Channel closed before call started');
769+
}
770+
this.configSelectionQueue = [];
771+
for (const call of this.pickQueue) {
772+
call.cancelWithStatus(Status.UNAVAILABLE, 'Channel closed before call started');
773+
}
774+
this.pickQueue = [];
748775
clearInterval(this.callRefTimer);
749776
if (this.idleTimer) {
750777
clearTimeout(this.idleTimer);

packages/grpc-js/test/test-client.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,21 @@ describe('Client without a server', () => {
9797
}
9898
);
9999
});
100+
it('close should force calls to end', done => {
101+
client.makeUnaryRequest(
102+
'/service/method',
103+
x => x,
104+
x => x,
105+
Buffer.from([]),
106+
new grpc.Metadata({waitForReady: true}),
107+
(error, value) => {
108+
assert(error);
109+
assert.strictEqual(error?.code, grpc.status.UNAVAILABLE);
110+
done();
111+
}
112+
);
113+
client.close();
114+
});
100115
});
101116

102117
describe('Client with a nonexistent target domain', () => {
@@ -133,4 +148,19 @@ describe('Client with a nonexistent target domain', () => {
133148
}
134149
);
135150
});
151+
it('close should force calls to end', done => {
152+
client.makeUnaryRequest(
153+
'/service/method',
154+
x => x,
155+
x => x,
156+
Buffer.from([]),
157+
new grpc.Metadata({waitForReady: true}),
158+
(error, value) => {
159+
assert(error);
160+
assert.strictEqual(error?.code, grpc.status.UNAVAILABLE);
161+
done();
162+
}
163+
);
164+
client.close();
165+
});
136166
});

0 commit comments

Comments
 (0)