Skip to content

Commit 5b730ca

Browse files
committed
feat(NODE-5197): add server monitoring mode
1 parent 32b7176 commit 5b730ca

File tree

19 files changed

+939
-2188
lines changed

19 files changed

+939
-2188
lines changed

src/connection_string.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from './mongo_logger';
3030
import { ReadConcern, type ReadConcernLevel } from './read_concern';
3131
import { ReadPreference, type ReadPreferenceMode } from './read_preference';
32+
import { ServerMonitoringModes } from './sdam/monitor';
3233
import type { TagSet } from './sdam/server_description';
3334
import {
3435
DEFAULT_PK_FACTORY,
@@ -1052,6 +1053,17 @@ export const OPTIONS = {
10521053
serializeFunctions: {
10531054
type: 'boolean'
10541055
},
1056+
serverMonitoringMode: {
1057+
default: 'auto',
1058+
transform({ values: [value] }) {
1059+
if (!ServerMonitoringModes.includes(value as string)) {
1060+
throw new MongoParseError(
1061+
'serverMonitoringMode must be one of `auto`, `poll`, or `stream`'
1062+
);
1063+
}
1064+
return value;
1065+
}
1066+
},
10551067
serverSelectionTimeoutMS: {
10561068
default: 30000,
10571069
type: 'uint'

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,9 @@ export type {
483483
MonitorOptions,
484484
MonitorPrivate,
485485
RTTPinger,
486-
RTTPingerOptions
486+
RTTPingerOptions,
487+
ServerMonitoringMode,
488+
ServerMonitoringModes
487489
} from './sdam/monitor';
488490
export type { Server, ServerEvents, ServerOptions, ServerPrivate } from './sdam/server';
489491
export type {

src/mongo_client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { executeOperation } from './operations/execute_operation';
2727
import { RunAdminCommandOperation } from './operations/run_command';
2828
import type { ReadConcern, ReadConcernLevel, ReadConcernLike } from './read_concern';
2929
import { ReadPreference, type ReadPreferenceMode } from './read_preference';
30+
import type { ServerMonitoringMode } from './sdam/monitor';
3031
import type { TagSet } from './sdam/server_description';
3132
import { readPreferenceServerSelector } from './sdam/server_selection';
3233
import type { SrvPoller } from './sdam/srv_polling';
@@ -247,6 +248,8 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC
247248
proxyUsername?: string;
248249
/** Configures a Socks5 proxy password when the proxy in proxyHost requires username/password authentication. */
249250
proxyPassword?: string;
251+
/** Instructs the driver monitors to use a specific monitoring mode */
252+
serverMonitoringMode?: ServerMonitoringMode;
250253

251254
/** @internal */
252255
srvPoller?: SrvPoller;
@@ -771,6 +774,7 @@ export interface MongoOptions
771774
proxyPort?: number;
772775
proxyUsername?: string;
773776
proxyPassword?: string;
777+
serverMonitoringMode: ServerMonitoringMode;
774778

775779
/** @internal */
776780
connectionType?: typeof Connection;

src/sdam/monitor.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { clearTimeout, setTimeout } from 'timers';
33
import { type Document, Long } from '../bson';
44
import { connect } from '../cmap/connect';
55
import { Connection, type ConnectionOptions } from '../cmap/connection';
6+
import { getFAASEnv } from '../cmap/handshake/client_metadata';
67
import { LEGACY_HELLO_COMMAND } from '../constants';
78
import { MongoError, MongoErrorLabel, MongoNetworkTimeoutError } from '../error';
89
import { CancellationToken, TypedEventEmitter } from '../mongo_types';
@@ -44,6 +45,11 @@ function isInCloseState(monitor: Monitor) {
4445
return monitor.s.state === STATE_CLOSED || monitor.s.state === STATE_CLOSING;
4546
}
4647

48+
/** @public */
49+
export const ServerMonitoringModes = ['auto', 'poll', 'stream'];
50+
/** @public */
51+
export type ServerMonitoringMode = (typeof ServerMonitoringModes)[number];
52+
4753
/** @internal */
4854
export interface MonitorPrivate {
4955
state: string;
@@ -55,6 +61,7 @@ export interface MonitorOptions
5561
connectTimeoutMS: number;
5662
heartbeatFrequencyMS: number;
5763
minHeartbeatFrequencyMS: number;
64+
serverMonitoringMode: ServerMonitoringMode;
5865
}
5966

6067
/** @public */
@@ -73,9 +80,16 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
7380
s: MonitorPrivate;
7481
address: string;
7582
options: Readonly<
76-
Pick<MonitorOptions, 'connectTimeoutMS' | 'heartbeatFrequencyMS' | 'minHeartbeatFrequencyMS'>
83+
Pick<
84+
MonitorOptions,
85+
| 'connectTimeoutMS'
86+
| 'heartbeatFrequencyMS'
87+
| 'minHeartbeatFrequencyMS'
88+
| 'serverMonitoringMode'
89+
>
7790
>;
7891
connectOptions: ConnectionOptions;
92+
isRunningInFaasEnv: boolean;
7993
[kServer]: Server;
8094
[kConnection]?: Connection;
8195
[kCancellationToken]: CancellationToken;
@@ -103,8 +117,11 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
103117
this.options = Object.freeze({
104118
connectTimeoutMS: options.connectTimeoutMS ?? 10000,
105119
heartbeatFrequencyMS: options.heartbeatFrequencyMS ?? 10000,
106-
minHeartbeatFrequencyMS: options.minHeartbeatFrequencyMS ?? 500
120+
minHeartbeatFrequencyMS: options.minHeartbeatFrequencyMS ?? 500,
121+
serverMonitoringMode: options.serverMonitoringMode
107122
});
123+
console.log(getFAASEnv());
124+
this.isRunningInFaasEnv = getFAASEnv() != null;
108125

109126
const cancellationToken = this[kCancellationToken];
110127
// TODO: refactor this to pull it directly from the pool, requires new ConnectionPool integration
@@ -207,10 +224,26 @@ function resetMonitorState(monitor: Monitor) {
207224
monitor[kConnection] = undefined;
208225
}
209226

227+
function useStreamingProtocol(monitor: Monitor, topologyVersion: TopologyVersion | null): boolean {
228+
// If we have no topology version we always poll no matter
229+
// what the user provided.
230+
if (topologyVersion == null) return false;
231+
232+
const serverMonitoringMode = monitor.options.serverMonitoringMode;
233+
if (serverMonitoringMode === 'poll') return false;
234+
if (serverMonitoringMode === 'stream') return true;
235+
236+
// If we are in auto mode, we need to figure out if we're in a FaaS
237+
// environment or not and choose the appropriate mode.
238+
if (monitor.isRunningInFaasEnv) return false;
239+
return true;
240+
}
241+
210242
function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
211243
let start = now();
212244
const topologyVersion = monitor[kServer].description.topologyVersion;
213-
const isAwaitable = topologyVersion != null;
245+
console.log('checkServer', topologyVersion);
246+
const isAwaitable = useStreamingProtocol(monitor, topologyVersion);
214247
monitor.emit(
215248
Server.SERVER_HEARTBEAT_STARTED,
216249
new ServerHeartbeatStartedEvent(monitor.address, isAwaitable)
@@ -286,7 +319,8 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
286319
const duration =
287320
isAwaitable && rttPinger ? rttPinger.roundTripTime : calculateDurationInMs(start);
288321

289-
const awaited = isAwaitable && hello.topologyVersion != null;
322+
console.log('command', topologyVersion, hello.topologyVersion, hello);
323+
const awaited = useStreamingProtocol(monitor, hello.topologyVersion);
290324
monitor.emit(
291325
Server.SERVER_HEARTBEAT_SUCCEEDED,
292326
new ServerHeartbeatSucceededEvent(monitor.address, duration, hello, awaited)
@@ -370,7 +404,8 @@ function monitorServer(monitor: Monitor) {
370404
}
371405

372406
// if the check indicates streaming is supported, immediately reschedule monitoring
373-
if (hello && hello.topologyVersion) {
407+
console.log('checkServerCallback', hello?.topologyVersion);
408+
if (useStreamingProtocol(monitor, hello?.topologyVersion)) {
374409
setTimeout(() => {
375410
if (!isInCloseState(monitor)) {
376411
monitor[kMonitorId]?.wake();

src/sdam/topology.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import {
6565
TopologyDescriptionChangedEvent,
6666
TopologyOpeningEvent
6767
} from './events';
68+
import type { ServerMonitoringMode } from './monitor';
6869
import { Server, type ServerEvents, type ServerOptions } from './server';
6970
import { compareTopologyVersion, ServerDescription } from './server_description';
7071
import { readPreferenceServerSelector, type ServerSelector } from './server_selection';
@@ -143,6 +144,7 @@ export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
143144
directConnection: boolean;
144145
loadBalanced: boolean;
145146
metadata: ClientMetadata;
147+
serverMonitoringMode: ServerMonitoringMode;
146148
/** MongoDB server API version */
147149
serverApi?: ServerApi;
148150
[featureFlag: symbol]: any;

test/integration/change-streams/change_stream.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1823,7 +1823,7 @@ describe('Change Streams', function () {
18231823
});
18241824
});
18251825

1826-
describe('ChangeStream resumability', function () {
1826+
describe.only('ChangeStream resumability', function () {
18271827
let client: MongoClient;
18281828
let collection: Collection;
18291829
let changeStream: ChangeStream;

test/lambda/mongodb/app.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as assert from 'node:assert/strict';
2+
13
import { MongoClient } from 'mongodb';
24

35
// Creates the client that is cached for all requests, subscribes to
@@ -30,18 +32,21 @@ mongoClient.on('commandFailed', (event) => {
3032

3133
mongoClient.on('serverHeartbeatStarted', (event) => {
3234
console.log('serverHeartbeatStarted', event);
35+
assert.strictEqual(event.awaited, false);
3336
});
3437

3538
mongoClient.on('serverHeartbeatSucceeded', (event) => {
3639
heartbeatCount++;
3740
totalHeartbeatDuration += event.duration;
3841
console.log('serverHeartbeatSucceeded', event);
42+
assert.strictEqual(event.awaited, false);
3943
});
4044

4145
mongoClient.on('serverHeartbeatFailed', (event) => {
4246
heartbeatCount++;
4347
totalHeartbeatDuration += event.duration;
4448
console.log('serverHeartbeatFailed', event);
49+
assert.strictEqual(event.awaited, false);
4550
});
4651

4752
mongoClient.on('connectionCreated', (event) => {

0 commit comments

Comments
 (0)