@@ -435,6 +435,14 @@ export class Server {
435435 ) ;
436436 }
437437
438+ private keepaliveTrace ( text : string ) : void {
439+ logging . trace (
440+ LogVerbosity . DEBUG ,
441+ 'keepalive' ,
442+ '(' + this . channelzRef . id + ') ' + text
443+ ) ;
444+ }
445+
438446 addProtoService ( ) : never {
439447 throw new Error ( 'Not implemented. Use addService() instead' ) ;
440448 }
@@ -1376,8 +1384,7 @@ export class Server {
13761384
13771385 let connectionAgeTimer : NodeJS . Timeout | null = null ;
13781386 let connectionAgeGraceTimer : NodeJS . Timeout | null = null ;
1379- let keeapliveTimeTimer : NodeJS . Timeout | null = null ;
1380- let keepaliveTimeoutTimer : NodeJS . Timeout | null = null ;
1387+ let keepaliveTimer : NodeJS . Timeout | null = null ;
13811388 let sessionClosedByServer = false ;
13821389
13831390 const idleTimeoutObj = this . enableIdleTimeout ( session ) ;
@@ -1420,41 +1427,90 @@ export class Server {
14201427 connectionAgeTimer . unref ?.( ) ;
14211428 }
14221429
1423- if ( this . keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS ) {
1424- keeapliveTimeTimer = setInterval ( ( ) => {
1425- keepaliveTimeoutTimer = setTimeout ( ( ) => {
1426- sessionClosedByServer = true ;
1427- session . close ( ) ;
1428- } , this . keepaliveTimeoutMs ) ;
1429- keepaliveTimeoutTimer . unref ?.( ) ;
1430+ const clearKeepaliveTimeout = ( ) => {
1431+ if ( keepaliveTimer ) {
1432+ clearTimeout ( keepaliveTimer ) ;
1433+ keepaliveTimer = null ;
1434+ }
1435+ } ;
14301436
1431- try {
1432- session . ping (
1433- ( err : Error | null , duration : number , payload : Buffer ) => {
1434- if ( keepaliveTimeoutTimer ) {
1435- clearTimeout ( keepaliveTimeoutTimer ) ;
1436- }
1437-
1438- if ( err ) {
1439- sessionClosedByServer = true ;
1440- this . trace (
1441- 'Connection dropped due to error of a ping frame ' +
1442- err . message +
1443- ' return in ' +
1444- duration
1445- ) ;
1446- session . close ( ) ;
1447- }
1437+ const canSendPing = ( ) => {
1438+ return (
1439+ ! session . destroyed &&
1440+ this . keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS &&
1441+ this . keepaliveTimeMs > 0
1442+ ) ;
1443+ } ;
1444+
1445+ /* eslint-disable-next-line prefer-const */
1446+ let sendPing : ( ) => void ; // hoisted for use in maybeStartKeepalivePingTimer
1447+
1448+ const maybeStartKeepalivePingTimer = ( ) => {
1449+ if ( ! canSendPing ( ) ) {
1450+ return ;
1451+ }
1452+ this . keepaliveTrace (
1453+ 'Starting keepalive timer for ' + this . keepaliveTimeMs + 'ms'
1454+ ) ;
1455+ keepaliveTimer = setTimeout ( ( ) => {
1456+ clearKeepaliveTimeout ( ) ;
1457+ sendPing ( ) ;
1458+ } , this . keepaliveTimeMs ) ;
1459+ keepaliveTimer . unref ?.( ) ;
1460+ } ;
1461+
1462+ sendPing = ( ) => {
1463+ if ( ! canSendPing ( ) ) {
1464+ return ;
1465+ }
1466+ this . keepaliveTrace (
1467+ 'Sending ping with timeout ' + this . keepaliveTimeoutMs + 'ms'
1468+ ) ;
1469+ let pingSendError = '' ;
1470+ try {
1471+ const pingSentSuccessfully = session . ping (
1472+ ( err : Error | null , duration : number , payload : Buffer ) => {
1473+ clearKeepaliveTimeout ( ) ;
1474+ if ( err ) {
1475+ this . keepaliveTrace ( 'Ping failed with error: ' + err . message ) ;
1476+ sessionClosedByServer = true ;
1477+ session . close ( ) ;
1478+ } else {
1479+ this . keepaliveTrace ( 'Received ping response' ) ;
1480+ maybeStartKeepalivePingTimer ( ) ;
14481481 }
1449- ) ;
1450- } catch ( e ) {
1451- clearTimeout ( keepaliveTimeoutTimer ) ;
1452- // The ping can't be sent because the session is already closed
1453- session . destroy ( ) ;
1482+ }
1483+ ) ;
1484+ if ( ! pingSentSuccessfully ) {
1485+ pingSendError = 'Ping returned false' ;
14541486 }
1455- } , this . keepaliveTimeMs ) ;
1456- keeapliveTimeTimer . unref ?.( ) ;
1457- }
1487+ } catch ( e ) {
1488+ // grpc/grpc-node#2139
1489+ pingSendError =
1490+ ( e instanceof Error ? e . message : '' ) || 'Unknown error' ;
1491+ }
1492+
1493+ if ( pingSendError ) {
1494+ this . keepaliveTrace ( 'Ping send failed: ' + pingSendError ) ;
1495+ this . trace (
1496+ 'Connection dropped due to ping send error: ' + pingSendError
1497+ ) ;
1498+ sessionClosedByServer = true ;
1499+ session . close ( ) ;
1500+ return ;
1501+ }
1502+
1503+ keepaliveTimer = setTimeout ( ( ) => {
1504+ clearKeepaliveTimeout ( ) ;
1505+ this . keepaliveTrace ( 'Ping timeout passed without response' ) ;
1506+ this . trace ( 'Connection dropped by keepalive timeout' ) ;
1507+ sessionClosedByServer = true ;
1508+ session . close ( ) ;
1509+ } , this . keepaliveTimeoutMs ) ;
1510+ keepaliveTimer . unref ?.( ) ;
1511+ } ;
1512+
1513+ maybeStartKeepalivePingTimer ( ) ;
14581514
14591515 session . on ( 'close' , ( ) => {
14601516 if ( ! sessionClosedByServer ) {
@@ -1471,12 +1527,7 @@ export class Server {
14711527 clearTimeout ( connectionAgeGraceTimer ) ;
14721528 }
14731529
1474- if ( keeapliveTimeTimer ) {
1475- clearInterval ( keeapliveTimeTimer ) ;
1476- if ( keepaliveTimeoutTimer ) {
1477- clearTimeout ( keepaliveTimeoutTimer ) ;
1478- }
1479- }
1530+ clearKeepaliveTimeout ( ) ;
14801531
14811532 if ( idleTimeoutObj !== null ) {
14821533 clearTimeout ( idleTimeoutObj . timeout ) ;
@@ -1521,8 +1572,7 @@ export class Server {
15211572
15221573 let connectionAgeTimer : NodeJS . Timeout | null = null ;
15231574 let connectionAgeGraceTimer : NodeJS . Timeout | null = null ;
1524- let keeapliveTimeTimer : NodeJS . Timeout | null = null ;
1525- let keepaliveTimeoutTimer : NodeJS . Timeout | null = null ;
1575+ let keepaliveTimeout : NodeJS . Timeout | null = null ;
15261576 let sessionClosedByServer = false ;
15271577
15281578 const idleTimeoutObj = this . enableIdleTimeout ( session ) ;
@@ -1564,49 +1614,103 @@ export class Server {
15641614 connectionAgeTimer . unref ?.( ) ;
15651615 }
15661616
1567- if ( this . keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS ) {
1568- keeapliveTimeTimer = setInterval ( ( ) => {
1569- keepaliveTimeoutTimer = setTimeout ( ( ) => {
1570- sessionClosedByServer = true ;
1571- this . channelzTrace . addTrace (
1572- 'CT_INFO' ,
1573- 'Connection dropped by keepalive timeout from ' + clientAddress
1574- ) ;
1617+ const clearKeepaliveTimeout = ( ) => {
1618+ if ( keepaliveTimeout ) {
1619+ clearTimeout ( keepaliveTimeout ) ;
1620+ keepaliveTimeout = null ;
1621+ }
1622+ } ;
1623+
1624+ const canSendPing = ( ) => {
1625+ return (
1626+ ! session . destroyed &&
1627+ this . keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS &&
1628+ this . keepaliveTimeMs > 0
1629+ ) ;
1630+ } ;
15751631
1576- session . close ( ) ;
1577- } , this . keepaliveTimeoutMs ) ;
1578- keepaliveTimeoutTimer . unref ?.( ) ;
1632+ /* eslint-disable-next-line prefer-const */
1633+ let sendPing : ( ) => void ; // hoisted for use in maybeStartKeepalivePingTimer
15791634
1580- try {
1581- session . ping (
1582- ( err : Error | null , duration : number , payload : Buffer ) => {
1583- if ( keepaliveTimeoutTimer ) {
1584- clearTimeout ( keepaliveTimeoutTimer ) ;
1585- }
1586-
1587- if ( err ) {
1588- sessionClosedByServer = true ;
1589- this . channelzTrace . addTrace (
1590- 'CT_INFO' ,
1591- 'Connection dropped due to error of a ping frame ' +
1592- err . message +
1593- ' return in ' +
1594- duration
1595- ) ;
1596-
1597- session . close ( ) ;
1598- }
1635+ const maybeStartKeepalivePingTimer = ( ) => {
1636+ if ( ! canSendPing ( ) ) {
1637+ return ;
1638+ }
1639+ this . keepaliveTrace (
1640+ 'Starting keepalive timer for ' + this . keepaliveTimeMs + 'ms'
1641+ ) ;
1642+ keepaliveTimeout = setTimeout ( ( ) => {
1643+ clearKeepaliveTimeout ( ) ;
1644+ sendPing ( ) ;
1645+ } , this . keepaliveTimeMs ) ;
1646+ keepaliveTimeout . unref ?.( ) ;
1647+ } ;
1648+
1649+ sendPing = ( ) => {
1650+ if ( ! canSendPing ( ) ) {
1651+ return ;
1652+ }
1653+ this . keepaliveTrace (
1654+ 'Sending ping with timeout ' + this . keepaliveTimeoutMs + 'ms'
1655+ ) ;
1656+ let pingSendError = '' ;
1657+ try {
1658+ const pingSentSuccessfully = session . ping (
1659+ ( err : Error | null , duration : number , payload : Buffer ) => {
1660+ clearKeepaliveTimeout ( ) ;
1661+ if ( err ) {
1662+ this . keepaliveTrace ( 'Ping failed with error: ' + err . message ) ;
1663+ this . channelzTrace . addTrace (
1664+ 'CT_INFO' ,
1665+ 'Connection dropped due to error of a ping frame ' +
1666+ err . message +
1667+ ' return in ' +
1668+ duration
1669+ ) ;
1670+ sessionClosedByServer = true ;
1671+ session . close ( ) ;
1672+ } else {
1673+ this . keepaliveTrace ( 'Received ping response' ) ;
1674+ maybeStartKeepalivePingTimer ( ) ;
15991675 }
1600- ) ;
1601- channelzSessionInfo . keepAlivesSent += 1 ;
1602- } catch ( e ) {
1603- clearTimeout ( keepaliveTimeoutTimer ) ;
1604- // The ping can't be sent because the session is already closed
1605- session . destroy ( ) ;
1676+ }
1677+ ) ;
1678+ if ( ! pingSentSuccessfully ) {
1679+ pingSendError = 'Ping returned false' ;
16061680 }
1607- } , this . keepaliveTimeMs ) ;
1608- keeapliveTimeTimer . unref ?.( ) ;
1609- }
1681+ } catch ( e ) {
1682+ // grpc/grpc-node#2139
1683+ pingSendError =
1684+ ( e instanceof Error ? e . message : '' ) || 'Unknown error' ;
1685+ }
1686+
1687+ if ( pingSendError ) {
1688+ this . keepaliveTrace ( 'Ping send failed: ' + pingSendError ) ;
1689+ this . channelzTrace . addTrace (
1690+ 'CT_INFO' ,
1691+ 'Connection dropped due to ping send error: ' + pingSendError
1692+ ) ;
1693+ sessionClosedByServer = true ;
1694+ session . close ( ) ;
1695+ return ;
1696+ }
1697+
1698+ channelzSessionInfo . keepAlivesSent += 1 ;
1699+
1700+ keepaliveTimeout = setTimeout ( ( ) => {
1701+ clearKeepaliveTimeout ( ) ;
1702+ this . keepaliveTrace ( 'Ping timeout passed without response' ) ;
1703+ this . channelzTrace . addTrace (
1704+ 'CT_INFO' ,
1705+ 'Connection dropped by keepalive timeout from ' + clientAddress
1706+ ) ;
1707+ sessionClosedByServer = true ;
1708+ session . close ( ) ;
1709+ } , this . keepaliveTimeoutMs ) ;
1710+ keepaliveTimeout . unref ?.( ) ;
1711+ } ;
1712+
1713+ maybeStartKeepalivePingTimer ( ) ;
16101714
16111715 session . on ( 'close' , ( ) => {
16121716 if ( ! sessionClosedByServer ) {
@@ -1627,12 +1731,7 @@ export class Server {
16271731 clearTimeout ( connectionAgeGraceTimer ) ;
16281732 }
16291733
1630- if ( keeapliveTimeTimer ) {
1631- clearInterval ( keeapliveTimeTimer ) ;
1632- if ( keepaliveTimeoutTimer ) {
1633- clearTimeout ( keepaliveTimeoutTimer ) ;
1634- }
1635- }
1734+ clearKeepaliveTimeout ( ) ;
16361735
16371736 if ( idleTimeoutObj !== null ) {
16381737 clearTimeout ( idleTimeoutObj . timeout ) ;
0 commit comments