@@ -3,6 +3,7 @@ import { clearTimeout, setTimeout } from 'timers';
3
3
import { type Document , Long } from '../bson' ;
4
4
import { connect } from '../cmap/connect' ;
5
5
import { Connection , type ConnectionOptions } from '../cmap/connection' ;
6
+ import { getFAASEnv } from '../cmap/handshake/client_metadata' ;
6
7
import { LEGACY_HELLO_COMMAND } from '../constants' ;
7
8
import { MongoError , MongoErrorLabel , MongoNetworkTimeoutError } from '../error' ;
8
9
import { CancellationToken , TypedEventEmitter } from '../mongo_types' ;
@@ -44,6 +45,16 @@ function isInCloseState(monitor: Monitor) {
44
45
return monitor . s . state === STATE_CLOSED || monitor . s . state === STATE_CLOSING ;
45
46
}
46
47
48
+ /** @public */
49
+ export const ServerMonitoringMode = Object . freeze ( {
50
+ auto : 'auto' ,
51
+ poll : 'poll' ,
52
+ stream : 'stream'
53
+ } as const ) ;
54
+
55
+ /** @public */
56
+ export type ServerMonitoringMode = ( typeof ServerMonitoringMode ) [ keyof typeof ServerMonitoringMode ] ;
57
+
47
58
/** @internal */
48
59
export interface MonitorPrivate {
49
60
state : string ;
@@ -55,6 +66,7 @@ export interface MonitorOptions
55
66
connectTimeoutMS : number ;
56
67
heartbeatFrequencyMS : number ;
57
68
minHeartbeatFrequencyMS : number ;
69
+ serverMonitoringMode : ServerMonitoringMode ;
58
70
}
59
71
60
72
/** @public */
@@ -73,9 +85,16 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
73
85
s : MonitorPrivate ;
74
86
address : string ;
75
87
options : Readonly <
76
- Pick < MonitorOptions , 'connectTimeoutMS' | 'heartbeatFrequencyMS' | 'minHeartbeatFrequencyMS' >
88
+ Pick <
89
+ MonitorOptions ,
90
+ | 'connectTimeoutMS'
91
+ | 'heartbeatFrequencyMS'
92
+ | 'minHeartbeatFrequencyMS'
93
+ | 'serverMonitoringMode'
94
+ >
77
95
> ;
78
96
connectOptions : ConnectionOptions ;
97
+ isRunningInFaasEnv : boolean ;
79
98
[ kServer ] : Server ;
80
99
[ kConnection ] ?: Connection ;
81
100
[ kCancellationToken ] : CancellationToken ;
@@ -103,8 +122,10 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
103
122
this . options = Object . freeze ( {
104
123
connectTimeoutMS : options . connectTimeoutMS ?? 10000 ,
105
124
heartbeatFrequencyMS : options . heartbeatFrequencyMS ?? 10000 ,
106
- minHeartbeatFrequencyMS : options . minHeartbeatFrequencyMS ?? 500
125
+ minHeartbeatFrequencyMS : options . minHeartbeatFrequencyMS ?? 500 ,
126
+ serverMonitoringMode : options . serverMonitoringMode
107
127
} ) ;
128
+ this . isRunningInFaasEnv = getFAASEnv ( ) != null ;
108
129
109
130
const cancellationToken = this [ kCancellationToken ] ;
110
131
// TODO: refactor this to pull it directly from the pool, requires new ConnectionPool integration
@@ -207,27 +228,38 @@ function resetMonitorState(monitor: Monitor) {
207
228
monitor [ kConnection ] = undefined ;
208
229
}
209
230
231
+ function useStreamingProtocol ( monitor : Monitor , topologyVersion : TopologyVersion | null ) : boolean {
232
+ // If we have no topology version we always poll no matter
233
+ // what the user provided, since the server does not support
234
+ // the streaming protocol.
235
+ if ( topologyVersion == null ) return false ;
236
+
237
+ const serverMonitoringMode = monitor . options . serverMonitoringMode ;
238
+ if ( serverMonitoringMode === ServerMonitoringMode . poll ) return false ;
239
+ if ( serverMonitoringMode === ServerMonitoringMode . stream ) return true ;
240
+
241
+ // If we are in auto mode, we need to figure out if we're in a FaaS
242
+ // environment or not and choose the appropriate mode.
243
+ if ( monitor . isRunningInFaasEnv ) return false ;
244
+ return true ;
245
+ }
246
+
210
247
function checkServer ( monitor : Monitor , callback : Callback < Document | null > ) {
211
248
let start = now ( ) ;
212
249
const topologyVersion = monitor [ kServer ] . description . topologyVersion ;
213
- const isAwaitable = topologyVersion != null ;
250
+ const isAwaitable = useStreamingProtocol ( monitor , topologyVersion ) ;
214
251
monitor . emit (
215
252
Server . SERVER_HEARTBEAT_STARTED ,
216
253
new ServerHeartbeatStartedEvent ( monitor . address , isAwaitable )
217
254
) ;
218
255
219
- function failureHandler ( err : Error ) {
256
+ function failureHandler ( err : Error , awaited : boolean ) {
220
257
monitor [ kConnection ] ?. destroy ( { force : true } ) ;
221
258
monitor [ kConnection ] = undefined ;
222
259
223
260
monitor . emit (
224
261
Server . SERVER_HEARTBEAT_FAILED ,
225
- new ServerHeartbeatFailedEvent (
226
- monitor . address ,
227
- calculateDurationInMs ( start ) ,
228
- err ,
229
- isAwaitable
230
- )
262
+ new ServerHeartbeatFailedEvent ( monitor . address , calculateDurationInMs ( start ) , err , awaited )
231
263
) ;
232
264
233
265
const error = ! ( err instanceof MongoError )
@@ -274,7 +306,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
274
306
275
307
connection . command ( ns ( 'admin.$cmd' ) , cmd , options , ( err , hello ) => {
276
308
if ( err ) {
277
- return failureHandler ( err ) ;
309
+ return failureHandler ( err , isAwaitable ) ;
278
310
}
279
311
280
312
if ( ! ( 'isWritablePrimary' in hello ) ) {
@@ -286,15 +318,14 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
286
318
const duration =
287
319
isAwaitable && rttPinger ? rttPinger . roundTripTime : calculateDurationInMs ( start ) ;
288
320
289
- const awaited = isAwaitable && hello . topologyVersion != null ;
290
321
monitor . emit (
291
322
Server . SERVER_HEARTBEAT_SUCCEEDED ,
292
- new ServerHeartbeatSucceededEvent ( monitor . address , duration , hello , awaited )
323
+ new ServerHeartbeatSucceededEvent ( monitor . address , duration , hello , isAwaitable )
293
324
) ;
294
325
295
- // if we are using the streaming protocol then we immediately issue another ` started`
296
- // event, otherwise the "check" is complete and return to the main monitor loop
297
- if ( awaited ) {
326
+ // If we are using the streaming protocol then we immediately issue another ' started'
327
+ // event, otherwise the "check" is complete and return to the main monitor loop.
328
+ if ( isAwaitable ) {
298
329
monitor . emit (
299
330
Server . SERVER_HEARTBEAT_STARTED ,
300
331
new ServerHeartbeatStartedEvent ( monitor . address , true )
@@ -316,7 +347,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
316
347
if ( err ) {
317
348
monitor [ kConnection ] = undefined ;
318
349
319
- failureHandler ( err ) ;
350
+ failureHandler ( err , false ) ;
320
351
return ;
321
352
}
322
353
@@ -337,7 +368,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
337
368
monitor . address ,
338
369
calculateDurationInMs ( start ) ,
339
370
conn . hello ,
340
- false
371
+ useStreamingProtocol ( monitor , conn . hello ?. topologyVersion )
341
372
)
342
373
) ;
343
374
@@ -370,7 +401,7 @@ function monitorServer(monitor: Monitor) {
370
401
}
371
402
372
403
// if the check indicates streaming is supported, immediately reschedule monitoring
373
- if ( hello && hello . topologyVersion ) {
404
+ if ( useStreamingProtocol ( monitor , hello ? .topologyVersion ) ) {
374
405
setTimeout ( ( ) => {
375
406
if ( ! isInCloseState ( monitor ) ) {
376
407
monitor [ kMonitorId ] ?. wake ( ) ;
0 commit comments