-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Unknown
-
None
-
Affects Version/s: 7.6.0
-
Component/s: SDAM
-
(copied to CRM)
-
None
-
None
-
None
-
None
-
None
-
None
-
None
Expected Behavior
As a user connected to a MongoDB 4.4+ deployment using the streaming monitoring protocol (the default).
I expect round trip time measurement to continue for the life of the connection, and to recover on its own when the RTT connection is interrupted.
The SDAM spec requires this unconditionally. From Measuring RTT: "clients MUST issue a hello or legacy hello command to each server to measure RTT every heartbeatFrequencyMS". The normative RttMonitor pseudocode places the wait outside the error handler, so a failed ping continues the loop:
while this monitor is not stopped: try: rtt = pingServer() addSample(rtt) except Exception as exc: # Don't call reset() here. The Monitor thread is responsible # for resetting the average RTT. close connection connection = Null helloOk = stableApi != Null # Can be awakened when the client is closed. event.wait(heartbeatFrequencyMS) event.clear()
Actual Behavior and Impact
Only the success path reschedules. RTTPinger.measureAndReschedule() (src/sdam/monitor.ts:527-531) sets the next monitorId, but neither error handler in measureRoundTripTime() does:
- the connect() rejection handler (src/sdam/monitor.ts:545-548) clears this.connection and returns;
- the connection.command() rejection handler (src/sdam/monitor.ts:558-562) destroys the connection, clears it, and returns.
In both cases no timer is armed, so the pinger stops permanently on the first interruption of its dedicated connection. It never reconnects and never takes another sample.
latestRtt is never cleared, so it retains its last good value indefinitely. That stale value is then consumed on every subsequent streaming heartbeat (src/sdam/monitor.ts:301-306):
const duration =
isAwaitable && monitor.rttPinger
? (monitor.rttPinger.latestRtt ?? calculateDurationInMs(start))
: calculateDurationInMs(start);
so it feeds roundTripTime/minRoundTripTime into server selection and localThresholdMS forever. Impact is silent mis-selection: the driver keeps ranking a server by an RTT it measured once, before the interruption.
Recovery only happens if the whole Monitor resets (resetMonitorState, src/sdam/monitor.ts:230-243), which recreates the RTTPinger. A transient error on the dedicated RTT connection does not trigger that, and per spec must not: "Errors encountered when running a hello or legacy hello command MUST NOT update the topology."
Reported externally in HELP-100351, with a reproduction script that polls rttPinger.latestRtt and flags it as frozen. Present since the class was introduced (v4.0.0), so all supported versions are affected.
Dependencies
- None.
Risks/Unknowns
- latestRtt must not be cleared on failure. SDAM requires "If a hello or legacy hello call fails, the RTT is not updated", and the pseudocode's error branch explicitly says not to reset the average. Rescheduling alone is the correct fix; the stale window then closes within one heartbeatFrequencyMS instead of lasting forever.
- Rescheduling must be suppressed once close() has run, or a closed pinger will keep arming timers and leak.
Acceptance Criteria
Implementation Requirements
- A failed RTT measurement must schedule the next measurement, matching the success path's heartbeatFrequencyMS delay.
- Applies to both failure modes: establishing the dedicated connection, and the hello command on an existing one.
- No rescheduling after close().
- latestRtt and the sample set are left untouched on failure.
- No public API change; no topology or error-type change.
Testing Requirements
Unit tests in test/unit/sdam/monitor.test.ts and integration tests in test/integration/connection-monitoring-and-pooling/rtt_pinger.test.ts, each confirmed to fail before the fix:
- failed connect() still arms a new timer;
- failed command() on an established connection still arms a new timer;
- destroying the RTT connection against a live server is followed by re-establishment;
- latestRtt advances past a sentinel value after the interruption.
Note: a failCommand failpoint on hello cannot be used to target this. It matches the monitor's streaming hello as well as the RTT ping, and failing the former triggers resetMonitorState, which recreates the RTTPinger and masks the defect. The tests interrupt only the RTT connection.
Note: when asserting on monitorId, sample it after the handshake completes. The timer legitimately changes once on the first successful measurement, which will mask the bug.
Documentation Requirements
None. RTTPinger is @internal.
Follow Up Requirements
- The SDAM prose test "Test that RTT is continuously updated" (appName=streamingRttTest, tests/README.md) is not implemented in this driver. It would not have caught this bug, since it blocks hello with blockTimeMS rather than erroring it, but the gap is worth its own ticket.
Additional context
- Reported in HELP-100351.