@@ -63,6 +63,10 @@ import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerCh
63
63
import { CipherNameAndProtocol , TLSSocket } from 'tls' ;
64
64
import { getErrorCode , getErrorMessage } from './error' ;
65
65
66
+ const UNLIMITED_CONNECTION_AGE_MS = ~ ( 1 << 31 ) ;
67
+ const KEEPALIVE_MAX_TIME_MS = ~ ( 1 << 31 ) ;
68
+ const KEEPALIVE_TIMEOUT_MS = 20000 ;
69
+
66
70
const {
67
71
HTTP2_HEADER_PATH
68
72
} = http2 . constants
@@ -161,6 +165,12 @@ export class Server {
161
165
private listenerChildrenTracker = new ChannelzChildrenTracker ( ) ;
162
166
private sessionChildrenTracker = new ChannelzChildrenTracker ( ) ;
163
167
168
+ private readonly maxConnectionAgeMs : number ;
169
+ private readonly maxConnectionAgeGraceMs : number ;
170
+
171
+ private readonly keepaliveTimeMs : number ;
172
+ private readonly keepaliveTimeoutMs : number ;
173
+
164
174
constructor ( options ?: ChannelOptions ) {
165
175
this . options = options ?? { } ;
166
176
if ( this . options [ 'grpc.enable_channelz' ] === 0 ) {
@@ -170,7 +180,10 @@ export class Server {
170
180
if ( this . channelzEnabled ) {
171
181
this . channelzTrace . addTrace ( 'CT_INFO' , 'Server created' ) ;
172
182
}
173
-
183
+ this . maxConnectionAgeMs = this . options [ 'grpc.max_connection_age_ms' ] ?? UNLIMITED_CONNECTION_AGE_MS ;
184
+ this . maxConnectionAgeGraceMs = this . options [ 'grpc.max_connection_age_grace_ms' ] ?? UNLIMITED_CONNECTION_AGE_MS ;
185
+ this . keepaliveTimeMs = this . options [ 'grpc.keepalive_time_ms' ] ?? KEEPALIVE_MAX_TIME_MS ;
186
+ this . keepaliveTimeoutMs = this . options [ 'grpc.keepalive_timeout_ms' ] ?? KEEPALIVE_TIMEOUT_MS ;
174
187
this . trace ( 'Server constructed' ) ;
175
188
}
176
189
@@ -970,12 +983,69 @@ export class Server {
970
983
this . channelzTrace . addTrace ( 'CT_INFO' , 'Connection established by client ' + clientAddress ) ;
971
984
this . sessionChildrenTracker . refChild ( channelzRef ) ;
972
985
}
986
+ let connectionAgeTimer : NodeJS . Timer | null = null ;
987
+ let connectionAgeGraceTimer : NodeJS . Timer | null = null ;
988
+ let sessionClosedByServer = false ;
989
+ if ( this . maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS ) {
990
+ // Apply a random jitter within a +/-10% range
991
+ const jitterMagnitude = this . maxConnectionAgeMs / 10 ;
992
+ const jitter = Math . random ( ) * jitterMagnitude * 2 - jitterMagnitude ;
993
+ connectionAgeTimer = setTimeout ( ( ) => {
994
+ sessionClosedByServer = true ;
995
+ if ( this . channelzEnabled ) {
996
+ this . channelzTrace . addTrace ( 'CT_INFO' , 'Connection dropped by max connection age from ' + clientAddress ) ;
997
+ }
998
+ try {
999
+ session . goaway ( http2 . constants . NGHTTP2_NO_ERROR , ~ ( 1 << 31 ) , Buffer . from ( 'max_age' ) ) ;
1000
+ } catch ( e ) {
1001
+ // The goaway can't be sent because the session is already closed
1002
+ session . destroy ( ) ;
1003
+ return ;
1004
+ }
1005
+ session . close ( ) ;
1006
+ /* Allow a grace period after sending the GOAWAY before forcibly
1007
+ * closing the connection. */
1008
+ if ( this . maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS ) {
1009
+ connectionAgeGraceTimer = setTimeout ( ( ) => {
1010
+ session . destroy ( ) ;
1011
+ } , this . maxConnectionAgeGraceMs ) . unref ?.( ) ;
1012
+ }
1013
+ } , this . maxConnectionAgeMs + jitter ) . unref ?.( ) ;
1014
+ }
1015
+ const keeapliveTimeTimer : NodeJS . Timer | null = setInterval ( ( ) => {
1016
+ const timeoutTImer = setTimeout ( ( ) => {
1017
+ sessionClosedByServer = true ;
1018
+ if ( this . channelzEnabled ) {
1019
+ this . channelzTrace . addTrace ( 'CT_INFO' , 'Connection dropped by keepalive timeout from ' + clientAddress ) ;
1020
+ }
1021
+ session . close ( ) ;
1022
+ } , this . keepaliveTimeoutMs ) . unref ?.( ) ;
1023
+ try {
1024
+ session . ping ( ( err : Error | null , duration : number , payload : Buffer ) => {
1025
+ clearTimeout ( timeoutTImer ) ;
1026
+ } ) ;
1027
+ } catch ( e ) {
1028
+ // The ping can't be sent because the session is already closed
1029
+ session . destroy ( ) ;
1030
+ }
1031
+ } , this . keepaliveTimeMs ) . unref ?.( ) ;
973
1032
session . on ( 'close' , ( ) => {
974
1033
if ( this . channelzEnabled ) {
975
- this . channelzTrace . addTrace ( 'CT_INFO' , 'Connection dropped by client ' + clientAddress ) ;
1034
+ if ( ! sessionClosedByServer ) {
1035
+ this . channelzTrace . addTrace ( 'CT_INFO' , 'Connection dropped by client ' + clientAddress ) ;
1036
+ }
976
1037
this . sessionChildrenTracker . unrefChild ( channelzRef ) ;
977
1038
unregisterChannelzRef ( channelzRef ) ;
978
1039
}
1040
+ if ( connectionAgeTimer ) {
1041
+ clearTimeout ( connectionAgeTimer ) ;
1042
+ }
1043
+ if ( connectionAgeGraceTimer ) {
1044
+ clearTimeout ( connectionAgeGraceTimer ) ;
1045
+ }
1046
+ if ( keeapliveTimeTimer ) {
1047
+ clearTimeout ( keeapliveTimeTimer ) ;
1048
+ }
979
1049
this . sessions . delete ( session ) ;
980
1050
} ) ;
981
1051
} ) ;
0 commit comments