@@ -20,7 +20,12 @@ import { ChannelOptions } from './channel-options';
20
20
import { ResolvingLoadBalancer } from './resolving-load-balancer' ;
21
21
import { SubchannelPool , getSubchannelPool } from './subchannel-pool' ;
22
22
import { ChannelControlHelper } from './load-balancer' ;
23
- import { UnavailablePicker , Picker , PickResultType } from './picker' ;
23
+ import {
24
+ UnavailablePicker ,
25
+ Picker ,
26
+ PickResultType ,
27
+ QueuePicker ,
28
+ } from './picker' ;
24
29
import { Metadata } from './metadata' ;
25
30
import { Status , LogVerbosity , Propagate } from './constants' ;
26
31
import { FilterStackFactory } from './filter-stack' ;
@@ -85,6 +90,11 @@ import {
85
90
*/
86
91
const MAX_TIMEOUT_TIME = 2147483647 ;
87
92
93
+ const MIN_IDLE_TIMEOUT_MS = 1000 ;
94
+
95
+ // 30 minutes
96
+ const DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000 ;
97
+
88
98
interface ConnectivityStateWatcher {
89
99
currentState : ConnectivityState ;
90
100
timer : NodeJS . Timeout | null ;
@@ -153,8 +163,8 @@ class ChannelSubchannelWrapper
153
163
}
154
164
155
165
export class InternalChannel {
156
- private resolvingLoadBalancer : ResolvingLoadBalancer ;
157
- private subchannelPool : SubchannelPool ;
166
+ private readonly resolvingLoadBalancer : ResolvingLoadBalancer ;
167
+ private readonly subchannelPool : SubchannelPool ;
158
168
private connectivityState : ConnectivityState = ConnectivityState . IDLE ;
159
169
private currentPicker : Picker = new UnavailablePicker ( ) ;
160
170
/**
@@ -164,17 +174,17 @@ export class InternalChannel {
164
174
private configSelectionQueue : ResolvingCall [ ] = [ ] ;
165
175
private pickQueue : LoadBalancingCall [ ] = [ ] ;
166
176
private connectivityStateWatchers : ConnectivityStateWatcher [ ] = [ ] ;
167
- private defaultAuthority : string ;
168
- private filterStackFactory : FilterStackFactory ;
169
- private target : GrpcUri ;
177
+ private readonly defaultAuthority : string ;
178
+ private readonly filterStackFactory : FilterStackFactory ;
179
+ private readonly target : GrpcUri ;
170
180
/**
171
181
* This timer does not do anything on its own. Its purpose is to hold the
172
182
* event loop open while there are any pending calls for the channel that
173
183
* have not yet been assigned to specific subchannels. In other words,
174
184
* the invariant is that callRefTimer is reffed if and only if pickQueue
175
185
* is non-empty.
176
186
*/
177
- private callRefTimer : NodeJS . Timer ;
187
+ private readonly callRefTimer : NodeJS . Timer ;
178
188
private configSelector : ConfigSelector | null = null ;
179
189
/**
180
190
* This is the error from the name resolver if it failed most recently. It
@@ -184,17 +194,22 @@ export class InternalChannel {
184
194
* than TRANSIENT_FAILURE.
185
195
*/
186
196
private currentResolutionError : StatusObject | null = null ;
187
- private retryBufferTracker : MessageBufferTracker ;
197
+ private readonly retryBufferTracker : MessageBufferTracker ;
188
198
private keepaliveTime : number ;
189
- private wrappedSubchannels : Set < ChannelSubchannelWrapper > = new Set ( ) ;
199
+ private readonly wrappedSubchannels : Set < ChannelSubchannelWrapper > =
200
+ new Set ( ) ;
201
+
202
+ private callCount = 0 ;
203
+ private idleTimer : NodeJS . Timer | null = null ;
204
+ private readonly idleTimeoutMs : number ;
190
205
191
206
// Channelz info
192
207
private readonly channelzEnabled : boolean = true ;
193
- private originalTarget : string ;
194
- private channelzRef : ChannelRef ;
195
- private channelzTrace : ChannelzTrace ;
196
- private callTracker = new ChannelzCallTracker ( ) ;
197
- private childrenTracker = new ChannelzChildrenTracker ( ) ;
208
+ private readonly originalTarget : string ;
209
+ private readonly channelzRef : ChannelRef ;
210
+ private readonly channelzTrace : ChannelzTrace ;
211
+ private readonly callTracker = new ChannelzCallTracker ( ) ;
212
+ private readonly childrenTracker = new ChannelzChildrenTracker ( ) ;
198
213
199
214
constructor (
200
215
target : string ,
@@ -265,6 +280,10 @@ export class InternalChannel {
265
280
DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES
266
281
) ;
267
282
this . keepaliveTime = options [ 'grpc.keepalive_time_ms' ] ?? - 1 ;
283
+ this . idleTimeoutMs = Math . max (
284
+ options [ 'grpc.client_idle_timeout_ms' ] ?? DEFAULT_IDLE_TIMEOUT_MS ,
285
+ MIN_IDLE_TIMEOUT_MS
286
+ ) ;
268
287
const channelControlHelper : ChannelControlHelper = {
269
288
createSubchannel : (
270
289
subchannelAddress : SubchannelAddress ,
@@ -548,6 +567,49 @@ export class InternalChannel {
548
567
this . callRefTimerRef ( ) ;
549
568
}
550
569
570
+ private enterIdle ( ) {
571
+ this . resolvingLoadBalancer . destroy ( ) ;
572
+ this . updateState ( ConnectivityState . IDLE ) ;
573
+ this . currentPicker = new QueuePicker ( this . resolvingLoadBalancer ) ;
574
+ }
575
+
576
+ private maybeStartIdleTimer ( ) {
577
+ if ( this . callCount === 0 ) {
578
+ this . idleTimer = setTimeout ( ( ) => {
579
+ this . trace (
580
+ 'Idle timer triggered after ' +
581
+ this . idleTimeoutMs +
582
+ 'ms of inactivity'
583
+ ) ;
584
+ this . enterIdle ( ) ;
585
+ } , this . idleTimeoutMs ) ;
586
+ this . idleTimer . unref ?.( ) ;
587
+ }
588
+ }
589
+
590
+ private onCallStart ( ) {
591
+ if ( this . channelzEnabled ) {
592
+ this . callTracker . addCallStarted ( ) ;
593
+ }
594
+ this . callCount += 1 ;
595
+ if ( this . idleTimer ) {
596
+ clearTimeout ( this . idleTimer ) ;
597
+ this . idleTimer = null ;
598
+ }
599
+ }
600
+
601
+ private onCallEnd ( status : StatusObject ) {
602
+ if ( this . channelzEnabled ) {
603
+ if ( status . code === Status . OK ) {
604
+ this . callTracker . addCallSucceeded ( ) ;
605
+ } else {
606
+ this . callTracker . addCallFailed ( ) ;
607
+ }
608
+ }
609
+ this . callCount -= 1 ;
610
+ this . maybeStartIdleTimer ( ) ;
611
+ }
612
+
551
613
createLoadBalancingCall (
552
614
callConfig : CallConfig ,
553
615
method : string ,
@@ -653,16 +715,10 @@ export class InternalChannel {
653
715
callNumber
654
716
) ;
655
717
656
- if ( this . channelzEnabled ) {
657
- this . callTracker . addCallStarted ( ) ;
658
- call . addStatusWatcher ( status => {
659
- if ( status . code === Status . OK ) {
660
- this . callTracker . addCallSucceeded ( ) ;
661
- } else {
662
- this . callTracker . addCallFailed ( ) ;
663
- }
664
- } ) ;
665
- }
718
+ this . onCallStart ( ) ;
719
+ call . addStatusWatcher ( status => {
720
+ this . onCallEnd ( status ) ;
721
+ } ) ;
666
722
return call ;
667
723
}
668
724
@@ -685,6 +741,7 @@ export class InternalChannel {
685
741
const connectivityState = this . connectivityState ;
686
742
if ( tryToConnect ) {
687
743
this . resolvingLoadBalancer . exitIdle ( ) ;
744
+ this . maybeStartIdleTimer ( ) ;
688
745
}
689
746
return connectivityState ;
690
747
}
0 commit comments