Skip to content

Commit 72b99a1

Browse files
authored
Merge pull request #2363 from murgatroid99/grpc-js_channel_keepalive_throttling
grpc-js: Propagate keepalive throttling throughout channel
2 parents ba08267 + 2ed8e71 commit 72b99a1

File tree

6 files changed

+81
-18
lines changed

6 files changed

+81
-18
lines changed

packages/grpc-js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@grpc/grpc-js",
3-
"version": "1.8.8",
3+
"version": "1.8.9",
44
"description": "gRPC Library for Node - pure JS implementation",
55
"homepage": "https://grpc.io/",
66
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",

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

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { ResolvingCall } from './resolving-call';
5151
import { getNextCallNumber } from './call-number';
5252
import { restrictControlPlaneStatusCode } from './control-plane-status';
5353
import { MessageBufferTracker, RetryingCall, RetryThrottler } from './retrying-call';
54+
import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from './subchannel-interface';
5455

5556
/**
5657
* See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args
@@ -84,6 +85,33 @@ const RETRY_THROTTLER_MAP: Map<string, RetryThrottler> = new Map();
8485
const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1<<24; // 16 MB
8586
const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1<<20; // 1 MB
8687

88+
class ChannelSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface {
89+
private stateListeners: ConnectivityStateListener[] = [];
90+
private refCount = 0;
91+
constructor(childSubchannel: SubchannelInterface, private channel: InternalChannel) {
92+
super(childSubchannel);
93+
childSubchannel.addConnectivityStateListener((subchannel, previousState, newState, keepaliveTime) => {
94+
channel.throttleKeepalive(keepaliveTime);
95+
for (const listener of this.stateListeners) {
96+
listener(this, previousState, newState, keepaliveTime);
97+
}
98+
});
99+
}
100+
101+
ref(): void {
102+
this.child.ref();
103+
this.refCount += 1;
104+
}
105+
106+
unref(): void {
107+
this.child.unref();
108+
this.refCount -= 1;
109+
if (this.refCount <= 0) {
110+
this.channel.removeWrappedSubchannel(this);
111+
}
112+
}
113+
}
114+
87115
export class InternalChannel {
88116

89117
private resolvingLoadBalancer: ResolvingLoadBalancer;
@@ -116,8 +144,10 @@ export class InternalChannel {
116144
* configSelector becomes set or the channel state becomes anything other
117145
* than TRANSIENT_FAILURE.
118146
*/
119-
private currentResolutionError: StatusObject | null = null;
120-
private retryBufferTracker: MessageBufferTracker;
147+
private currentResolutionError: StatusObject | null = null;
148+
private retryBufferTracker: MessageBufferTracker;
149+
private keepaliveTime: number;
150+
private wrappedSubchannels: Set<ChannelSubchannelWrapper> = new Set();
121151

122152
// Channelz info
123153
private readonly channelzEnabled: boolean = true;
@@ -190,6 +220,7 @@ export class InternalChannel {
190220
options['grpc.retry_buffer_size'] ?? DEFAULT_RETRY_BUFFER_SIZE_BYTES,
191221
options['grpc.per_rpc_retry_buffer_size'] ?? DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES
192222
);
223+
this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1;
193224
const channelControlHelper: ChannelControlHelper = {
194225
createSubchannel: (
195226
subchannelAddress: SubchannelAddress,
@@ -201,10 +232,13 @@ export class InternalChannel {
201232
Object.assign({}, this.options, subchannelArgs),
202233
this.credentials
203234
);
235+
subchannel.throttleKeepalive(this.keepaliveTime);
204236
if (this.channelzEnabled) {
205237
this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef());
206238
}
207-
return subchannel;
239+
const wrappedSubchannel = new ChannelSubchannelWrapper(subchannel, this);
240+
this.wrappedSubchannels.add(wrappedSubchannel);
241+
return wrappedSubchannel;
208242
},
209243
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
210244
this.currentPicker = picker;
@@ -369,6 +403,19 @@ export class InternalChannel {
369403
}
370404
}
371405

406+
throttleKeepalive(newKeepaliveTime: number) {
407+
if (newKeepaliveTime > this.keepaliveTime) {
408+
this.keepaliveTime = newKeepaliveTime;
409+
for (const wrappedSubchannel of this.wrappedSubchannels) {
410+
wrappedSubchannel.throttleKeepalive(newKeepaliveTime);
411+
}
412+
}
413+
}
414+
415+
removeWrappedSubchannel(wrappedSubchannel: ChannelSubchannelWrapper) {
416+
this.wrappedSubchannels.delete(wrappedSubchannel);
417+
}
418+
372419
doPick(metadata: Metadata, extraPickInfo: {[key: string]: string}) {
373420
return this.currentPicker.pick({metadata: metadata, extraPickInfo: extraPickInfo});
374421
}

packages/grpc-js/src/load-balancer-outlier-detection.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,11 @@ class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements
205205
constructor(childSubchannel: SubchannelInterface, private mapEntry?: MapEntry) {
206206
super(childSubchannel);
207207
this.childSubchannelState = childSubchannel.getConnectivityState();
208-
childSubchannel.addConnectivityStateListener((subchannel, previousState, newState) => {
208+
childSubchannel.addConnectivityStateListener((subchannel, previousState, newState, keepaliveTime) => {
209209
this.childSubchannelState = newState;
210210
if (!this.ejected) {
211211
for (const listener of this.stateListeners) {
212-
listener(this, previousState, newState);
212+
listener(this, previousState, newState, keepaliveTime);
213213
}
214214
}
215215
});
@@ -265,14 +265,14 @@ class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements
265265
eject() {
266266
this.ejected = true;
267267
for (const listener of this.stateListeners) {
268-
listener(this, this.childSubchannelState, ConnectivityState.TRANSIENT_FAILURE);
268+
listener(this, this.childSubchannelState, ConnectivityState.TRANSIENT_FAILURE, -1);
269269
}
270270
}
271271

272272
uneject() {
273273
this.ejected = false;
274274
for (const listener of this.stateListeners) {
275-
listener(this, ConnectivityState.TRANSIENT_FAILURE, this.childSubchannelState);
275+
listener(this, ConnectivityState.TRANSIENT_FAILURE, this.childSubchannelState, -1);
276276
}
277277
}
278278

packages/grpc-js/src/subchannel-interface.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import { Subchannel } from "./subchannel";
2222
export type ConnectivityStateListener = (
2323
subchannel: SubchannelInterface,
2424
previousState: ConnectivityState,
25-
newState: ConnectivityState
25+
newState: ConnectivityState,
26+
keepaliveTime: number
2627
) => void;
2728

2829
/**
@@ -40,6 +41,7 @@ export interface SubchannelInterface {
4041
removeConnectivityStateListener(listener: ConnectivityStateListener): void;
4142
startConnecting(): void;
4243
getAddress(): string;
44+
throttleKeepalive(newKeepaliveTime: number): void;
4345
ref(): void;
4446
unref(): void;
4547
getChannelzRef(): SubchannelRef;
@@ -67,6 +69,9 @@ export abstract class BaseSubchannelWrapper implements SubchannelInterface {
6769
getAddress(): string {
6870
return this.child.getAddress();
6971
}
72+
throttleKeepalive(newKeepaliveTime: number): void {
73+
this.child.throttleKeepalive(newKeepaliveTime);
74+
}
7075
ref(): void {
7176
this.child.ref();
7277
}

packages/grpc-js/src/subchannel.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class Subchannel {
6464

6565
private backoffTimeout: BackoffTimeout;
6666

67-
private keepaliveTimeMultiplier = 1;
67+
private keepaliveTime: number;
6868
/**
6969
* Tracks channels and subchannel pools with references to this subchannel
7070
*/
@@ -111,6 +111,8 @@ export class Subchannel {
111111
}, backoffOptions);
112112
this.subchannelAddressString = subchannelAddressToString(subchannelAddress);
113113

114+
this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1;
115+
114116
if (options['grpc.enable_channelz'] === 0) {
115117
this.channelzEnabled = false;
116118
}
@@ -169,7 +171,7 @@ export class Subchannel {
169171
private startConnectingInternal() {
170172
let options = this.options;
171173
if (options['grpc.keepalive_time_ms']) {
172-
const adjustedKeepaliveTime = Math.min(options['grpc.keepalive_time_ms'] * this.keepaliveTimeMultiplier, KEEPALIVE_MAX_TIME_MS);
174+
const adjustedKeepaliveTime = Math.min(this.keepaliveTime, KEEPALIVE_MAX_TIME_MS);
173175
options = {...options, 'grpc.keepalive_time_ms': adjustedKeepaliveTime};
174176
}
175177
this.connector.connect(this.subchannelAddress, this.credentials, options).then(
@@ -181,14 +183,14 @@ export class Subchannel {
181183
}
182184
transport.addDisconnectListener((tooManyPings) => {
183185
this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE);
184-
if (tooManyPings) {
185-
this.keepaliveTimeMultiplier *= 2;
186+
if (tooManyPings && this.keepaliveTime > 0) {
187+
this.keepaliveTime *= 2;
186188
logging.log(
187189
LogVerbosity.ERROR,
188190
`Connection to ${uriToString(this.channelTarget)} at ${
189191
this.subchannelAddressString
190-
} rejected by server because of excess pings. Increasing ping interval multiplier to ${
191-
this.keepaliveTimeMultiplier
192+
} rejected by server because of excess pings. Increasing ping interval to ${
193+
this.keepaliveTime
192194
} ms`
193195
);
194196
}
@@ -262,7 +264,7 @@ export class Subchannel {
262264
/* We use a shallow copy of the stateListeners array in case a listener
263265
* is removed during this iteration */
264266
for (const listener of [...this.stateListeners]) {
265-
listener(this, previousState, newState);
267+
listener(this, previousState, newState, this.keepaliveTime);
266268
}
267269
return true;
268270
}
@@ -403,4 +405,10 @@ export class Subchannel {
403405
getRealSubchannel(): this {
404406
return this;
405407
}
408+
409+
throttleKeepalive(newKeepaliveTime: number) {
410+
if (newKeepaliveTime > this.keepaliveTime) {
411+
this.keepaliveTime = newKeepaliveTime;
412+
}
413+
}
406414
}

packages/grpc-js/src/transport.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class Http2Transport implements Transport {
7777
/**
7878
* The amount of time in between sending pings
7979
*/
80-
private keepaliveTimeMs: number = KEEPALIVE_MAX_TIME_MS;
80+
private keepaliveTimeMs: number = -1;
8181
/**
8282
* The amount of time to wait for an acknowledgement after sending a ping
8383
*/
@@ -133,7 +133,7 @@ class Http2Transport implements Transport {
133133
]
134134
.filter((e) => e)
135135
.join(' '); // remove falsey values first
136-
136+
137137
if ('grpc.keepalive_time_ms' in options) {
138138
this.keepaliveTimeMs = options['grpc.keepalive_time_ms']!;
139139
}
@@ -334,6 +334,9 @@ class Http2Transport implements Transport {
334334
}
335335

336336
private startKeepalivePings() {
337+
if (this.keepaliveTimeMs < 0) {
338+
return;
339+
}
337340
this.keepaliveIntervalId = setInterval(() => {
338341
this.sendPing();
339342
}, this.keepaliveTimeMs);

0 commit comments

Comments
 (0)