@@ -125,10 +125,13 @@ class ChannelSubchannelWrapper
125125 ) => {
126126 channel . throttleKeepalive ( keepaliveTime ) ;
127127 } ;
128- childSubchannel . addConnectivityStateListener ( this . subchannelStateListener ) ;
129128 }
130129
131130 ref ( ) : void {
131+ if ( this . refCount === 0 ) {
132+ this . child . addConnectivityStateListener ( this . subchannelStateListener ) ;
133+ this . channel . addWrappedSubchannel ( this ) ;
134+ }
132135 this . child . ref ( ) ;
133136 this . refCount += 1 ;
134137 }
@@ -159,6 +162,26 @@ class ShutdownPicker implements Picker {
159162 }
160163}
161164
165+ class ChannelzInfoTracker {
166+ readonly trace = new ChannelzTrace ( ) ;
167+ readonly callTracker = new ChannelzCallTracker ( ) ;
168+ readonly childrenTracker = new ChannelzChildrenTracker ( ) ;
169+ state : ConnectivityState = ConnectivityState . IDLE ;
170+ constructor ( private target : string ) { }
171+
172+ getChannelzInfoCallback ( ) : ( ) => ChannelInfo {
173+ return ( ) => {
174+ return {
175+ target : this . target ,
176+ state : this . state ,
177+ trace : this . trace ,
178+ callTracker : this . callTracker ,
179+ children : this . childrenTracker . getChildLists ( )
180+ } ;
181+ } ;
182+ }
183+ }
184+
162185export class InternalChannel {
163186 private readonly resolvingLoadBalancer : ResolvingLoadBalancer ;
164187 private readonly subchannelPool : SubchannelPool ;
@@ -179,9 +202,10 @@ export class InternalChannel {
179202 * event loop open while there are any pending calls for the channel that
180203 * have not yet been assigned to specific subchannels. In other words,
181204 * the invariant is that callRefTimer is reffed if and only if pickQueue
182- * is non-empty.
205+ * is non-empty. In addition, the timer is null while the state is IDLE or
206+ * SHUTDOWN and there are no pending calls.
183207 */
184- private readonly callRefTimer : NodeJS . Timeout ;
208+ private callRefTimer : NodeJS . Timeout | null = null ;
185209 private configSelector : ConfigSelector | null = null ;
186210 /**
187211 * This is the error from the name resolver if it failed most recently. It
@@ -203,11 +227,8 @@ export class InternalChannel {
203227
204228 // Channelz info
205229 private readonly channelzEnabled : boolean = true ;
206- private readonly originalTarget : string ;
207230 private readonly channelzRef : ChannelRef ;
208- private readonly channelzTrace : ChannelzTrace ;
209- private readonly callTracker = new ChannelzCallTracker ( ) ;
210- private readonly childrenTracker = new ChannelzChildrenTracker ( ) ;
231+ private readonly channelzInfoTracker : ChannelzInfoTracker ;
211232
212233 /**
213234 * Randomly generated ID to be passed to the config selector, for use by
@@ -236,7 +257,7 @@ export class InternalChannel {
236257 throw new TypeError ( 'Channel options must be an object' ) ;
237258 }
238259 }
239- this . originalTarget = target ;
260+ this . channelzInfoTracker = new ChannelzInfoTracker ( target ) ;
240261 const originalTargetUri = parseUri ( target ) ;
241262 if ( originalTargetUri === null ) {
242263 throw new Error ( `Could not parse target name "${ target } "` ) ;
@@ -250,21 +271,17 @@ export class InternalChannel {
250271 ) ;
251272 }
252273
253- this . callRefTimer = setInterval ( ( ) => { } , MAX_TIMEOUT_TIME ) ;
254- this . callRefTimer . unref ?.( ) ;
255-
256274 if ( this . options [ 'grpc.enable_channelz' ] === 0 ) {
257275 this . channelzEnabled = false ;
258276 }
259277
260- this . channelzTrace = new ChannelzTrace ( ) ;
261278 this . channelzRef = registerChannelzChannel (
262279 target ,
263- ( ) => this . getChannelzInfo ( ) ,
280+ this . channelzInfoTracker . getChannelzInfoCallback ( ) ,
264281 this . channelzEnabled
265282 ) ;
266283 if ( this . channelzEnabled ) {
267- this . channelzTrace . addTrace ( 'CT_INFO' , 'Channel created' ) ;
284+ this . channelzInfoTracker . trace . addTrace ( 'CT_INFO' , 'Channel created' ) ;
268285 }
269286
270287 if ( this . options [ 'grpc.default_authority' ] ) {
@@ -305,7 +322,7 @@ export class InternalChannel {
305322 ) ;
306323 subchannel . throttleKeepalive ( this . keepaliveTime ) ;
307324 if ( this . channelzEnabled ) {
308- this . channelzTrace . addTrace (
325+ this . channelzInfoTracker . trace . addTrace (
309326 'CT_INFO' ,
310327 'Created subchannel or used existing subchannel' ,
311328 subchannel . getChannelzRef ( )
@@ -315,7 +332,6 @@ export class InternalChannel {
315332 subchannel ,
316333 this
317334 ) ;
318- this . wrappedSubchannels . add ( wrappedSubchannel ) ;
319335 return wrappedSubchannel ;
320336 } ,
321337 updateState : ( connectivityState : ConnectivityState , picker : Picker ) => {
@@ -338,12 +354,12 @@ export class InternalChannel {
338354 } ,
339355 addChannelzChild : ( child : ChannelRef | SubchannelRef ) => {
340356 if ( this . channelzEnabled ) {
341- this . childrenTracker . refChild ( child ) ;
357+ this . channelzInfoTracker . childrenTracker . refChild ( child ) ;
342358 }
343359 } ,
344360 removeChannelzChild : ( child : ChannelRef | SubchannelRef ) => {
345361 if ( this . channelzEnabled ) {
346- this . childrenTracker . unrefChild ( child ) ;
362+ this . channelzInfoTracker . childrenTracker . unrefChild ( child ) ;
347363 }
348364 } ,
349365 } ;
@@ -366,7 +382,7 @@ export class InternalChannel {
366382 RETRY_THROTTLER_MAP . delete ( this . getTarget ( ) ) ;
367383 }
368384 if ( this . channelzEnabled ) {
369- this . channelzTrace . addTrace (
385+ this . channelzInfoTracker . trace . addTrace (
370386 'CT_INFO' ,
371387 'Address resolution succeeded'
372388 ) ;
@@ -388,7 +404,7 @@ export class InternalChannel {
388404 } ,
389405 status => {
390406 if ( this . channelzEnabled ) {
391- this . channelzTrace . addTrace (
407+ this . channelzInfoTracker . trace . addTrace (
392408 'CT_WARNING' ,
393409 'Address resolution failed with code ' +
394410 status . code +
@@ -440,16 +456,6 @@ export class InternalChannel {
440456 this . lastActivityTimestamp = new Date ( ) ;
441457 }
442458
443- private getChannelzInfo ( ) : ChannelInfo {
444- return {
445- target : this . originalTarget ,
446- state : this . connectivityState ,
447- trace : this . channelzTrace ,
448- callTracker : this . callTracker ,
449- children : this . childrenTracker . getChildLists ( ) ,
450- } ;
451- }
452-
453459 private trace ( text : string , verbosityOverride ?: LogVerbosity ) {
454460 trace (
455461 verbosityOverride ?? LogVerbosity . DEBUG ,
@@ -459,6 +465,9 @@ export class InternalChannel {
459465 }
460466
461467 private callRefTimerRef ( ) {
468+ if ( ! this . callRefTimer ) {
469+ this . callRefTimer = setInterval ( ( ) => { } , MAX_TIMEOUT_TIME )
470+ }
462471 // If the hasRef function does not exist, always run the code
463472 if ( ! this . callRefTimer . hasRef ?.( ) ) {
464473 this . trace (
@@ -472,15 +481,15 @@ export class InternalChannel {
472481 }
473482
474483 private callRefTimerUnref ( ) {
475- // If the hasRef function does not exist, always run the code
476- if ( ! this . callRefTimer . hasRef || this . callRefTimer . hasRef ( ) ) {
484+ // If the timer or the hasRef function does not exist, always run the code
485+ if ( ! this . callRefTimer ? .hasRef || this . callRefTimer . hasRef ( ) ) {
477486 this . trace (
478487 'callRefTimer.unref | configSelectionQueue.length=' +
479488 this . configSelectionQueue . length +
480489 ' pickQueue.length=' +
481490 this . pickQueue . length
482491 ) ;
483- this . callRefTimer . unref ?.( ) ;
492+ this . callRefTimer ? .unref ?.( ) ;
484493 }
485494 }
486495
@@ -509,12 +518,13 @@ export class InternalChannel {
509518 ConnectivityState [ newState ]
510519 ) ;
511520 if ( this . channelzEnabled ) {
512- this . channelzTrace . addTrace (
521+ this . channelzInfoTracker . trace . addTrace (
513522 'CT_INFO' ,
514523 'Connectivity state change to ' + ConnectivityState [ newState ]
515524 ) ;
516525 }
517526 this . connectivityState = newState ;
527+ this . channelzInfoTracker . state = newState ;
518528 const watchersCopy = this . connectivityStateWatchers . slice ( ) ;
519529 for ( const watcherObject of watchersCopy ) {
520530 if ( newState !== watcherObject . currentState ) {
@@ -539,6 +549,10 @@ export class InternalChannel {
539549 }
540550 }
541551
552+ addWrappedSubchannel ( wrappedSubchannel : ChannelSubchannelWrapper ) {
553+ this . wrappedSubchannels . add ( wrappedSubchannel ) ;
554+ }
555+
542556 removeWrappedSubchannel ( wrappedSubchannel : ChannelSubchannelWrapper ) {
543557 this . wrappedSubchannels . delete ( wrappedSubchannel ) ;
544558 }
@@ -591,6 +605,10 @@ export class InternalChannel {
591605 clearTimeout ( this . idleTimer ) ;
592606 this . idleTimer = null ;
593607 }
608+ if ( this . callRefTimer ) {
609+ clearInterval ( this . callRefTimer ) ;
610+ this . callRefTimer = null ;
611+ }
594612 }
595613
596614 private startIdleTimeout ( timeoutMs : number ) {
@@ -634,17 +652,17 @@ export class InternalChannel {
634652
635653 private onCallStart ( ) {
636654 if ( this . channelzEnabled ) {
637- this . callTracker . addCallStarted ( ) ;
655+ this . channelzInfoTracker . callTracker . addCallStarted ( ) ;
638656 }
639657 this . callCount += 1 ;
640658 }
641659
642660 private onCallEnd ( status : StatusObject ) {
643661 if ( this . channelzEnabled ) {
644662 if ( status . code === Status . OK ) {
645- this . callTracker . addCallSucceeded ( ) ;
663+ this . channelzInfoTracker . callTracker . addCallSucceeded ( ) ;
646664 } else {
647- this . callTracker . addCallFailed ( ) ;
665+ this . channelzInfoTracker . callTracker . addCallFailed ( ) ;
648666 }
649667 }
650668 this . callCount -= 1 ;
@@ -776,7 +794,9 @@ export class InternalChannel {
776794 call . cancelWithStatus ( Status . UNAVAILABLE , 'Channel closed before call started' ) ;
777795 }
778796 this . pickQueue = [ ] ;
779- clearInterval ( this . callRefTimer ) ;
797+ if ( this . callRefTimer ) {
798+ clearInterval ( this . callRefTimer ) ;
799+ }
780800 if ( this . idleTimer ) {
781801 clearTimeout ( this . idleTimer ) ;
782802 }
0 commit comments