From 06839fa380c696a74abc0d5850e0310ca0a5a6d2 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sat, 17 Aug 2024 11:58:26 -0400 Subject: [PATCH 1/2] fix(connection): avoid returning `readyState = connected` if connection state is stale Fix #14727 --- lib/connection.js | 9 +++++++++ lib/drivers/node-mongodb-native/connection.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/connection.js b/lib/connection.js index 6e52d6ca4a0..b4787d6ed3a 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -103,6 +103,15 @@ Object.setPrototypeOf(Connection.prototype, EventEmitter.prototype); Object.defineProperty(Connection.prototype, 'readyState', { get: function() { + // If connection thinks it is connected, but we haven't received a heartbeat in 2 heartbeat intervals, + // that likely means the connection is stale (potentially due to frozen AWS Lambda container) + if ( + this._readyState === STATES.connected && + this._lastHeartbeatAt != null && + typeof this.client?.topology?.s?.description?.heartbeatFrequencyMS === 'number' && + Date.now() - this._lastHeartbeatAt >= this.client.topology.s.description.heartbeatFrequencyMS * 2) { + return STATES.disconnected; + } return this._readyState; }, set: function(val) { diff --git a/lib/drivers/node-mongodb-native/connection.js b/lib/drivers/node-mongodb-native/connection.js index 6a164bca8b3..9286429c022 100644 --- a/lib/drivers/node-mongodb-native/connection.js +++ b/lib/drivers/node-mongodb-native/connection.js @@ -23,6 +23,11 @@ const utils = require('../../utils'); function NativeConnection() { MongooseConnection.apply(this, arguments); this._listening = false; + // Tracks the last time (as unix timestamp) the connection received a + // serverHeartbeatSucceeded or serverHeartbeatFailed event from the underlying MongoClient. + // If we haven't received one in a while (like due to a frozen AWS Lambda container) then + // `readyState` is likely stale. + this._lastHeartbeatAt = null; } /** @@ -106,6 +111,7 @@ NativeConnection.prototype.useDb = function(name, options) { _opts.noListener = options.noListener; } newConn.db = _this.client.db(name, _opts); + newConn._lastHeartbeatAt = _this._lastHeartbeatAt; newConn.onOpen(); } @@ -409,6 +415,12 @@ function _setClient(conn, client, options, dbName) { } }); } + client.on('serverHeartbeatSucceeded', () => { + conn._lastHeartbeatAt = Date.now(); + }); + client.on('serverHeartbeatFailed', () => { + conn._lastHeartbeatAt = Date.now(); + }); if (options.monitorCommands) { client.on('commandStarted', (data) => conn.emit('commandStarted', data)); @@ -417,6 +429,9 @@ function _setClient(conn, client, options, dbName) { } conn.onOpen(); + if (client.topology?.s?.state === 'connected') { + conn._lastHeartbeatAt = Date.now(); + } for (const i in conn.collections) { if (utils.object.hasOwnProperty(conn.collections, i)) { From 86fea9341f824d3c72303c51c3b091f6ef4f9c3c Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 17 Sep 2024 12:39:05 -0400 Subject: [PATCH 2/2] fix: dont treat failed heartbeat as heartbeat --- lib/drivers/node-mongodb-native/connection.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/drivers/node-mongodb-native/connection.js b/lib/drivers/node-mongodb-native/connection.js index 9286429c022..0b4e7c33852 100644 --- a/lib/drivers/node-mongodb-native/connection.js +++ b/lib/drivers/node-mongodb-native/connection.js @@ -418,9 +418,6 @@ function _setClient(conn, client, options, dbName) { client.on('serverHeartbeatSucceeded', () => { conn._lastHeartbeatAt = Date.now(); }); - client.on('serverHeartbeatFailed', () => { - conn._lastHeartbeatAt = Date.now(); - }); if (options.monitorCommands) { client.on('commandStarted', (data) => conn.emit('commandStarted', data));