Skip to content

Commit 24b5318

Browse files
DRIVERS-2868 Adjust getMore maxTimeMS Calculation for tailable awaitData Cursors (#1749)
Co-authored-by: Viacheslav Babanin <frest0512@gmail.com>
1 parent 89e9152 commit 24b5318

3 files changed

Lines changed: 222 additions & 7 deletions

File tree

source/client-side-operations-timeout/client-side-operations-timeout.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,10 @@ resulting cursor but MUST NOT append a `maxTimeMS` field to any commands.
334334
##### Tailable awaitData Cursors
335335

336336
If `timeoutMS` is set, drivers MUST apply it to the original operation. Drivers MUST also apply the original `timeoutMS`
337-
value to each `next` call on the resulting cursor but MUST NOT use it to derive a `maxTimeMS` value for `getMore`
338-
commands. Helpers for operations that create tailable awaitData cursors MUST also support the `maxAwaitTimeMS` option.
339-
Drivers MUST error if this option is set, `timeoutMS` is set to a non-zero value, and `maxAwaitTimeMS` is greater than
340-
or equal to `timeoutMS`. If this option is set, drivers MUST use it as the `maxTimeMS` field on `getMore` commands.
337+
value to each `next` call on the resulting cursor. Helpers for operations that create tailable awaitData cursors MUST
338+
also support the `maxAwaitTimeMS` option. Drivers MUST error if this option is set, `timeoutMS` is set to a non-zero
339+
value, and `maxAwaitTimeMS` is greater than or equal to `timeoutMS`. If this option is set, drivers MUST use
340+
`min(maxAwaitTimeMS, remaining timeoutMS - minRoundTripTime)` as the `maxTimeMS` field on `getMore` commands.
341341

342342
See [Tailable cursor behavior](#tailable-cursor-behavior) for rationale regarding both non-awaitData and awaitData
343343
cursors.
@@ -349,8 +349,8 @@ Driver `watch` helpers MUST support both `timeoutMS` and `maxAwaitTimeMS` option
349349
`timeoutMS`. These helpers MUST NOT support the `timeoutMode` option as change streams are an abstraction around
350350
tailable-awaitData cursors, so they implicitly use `ITERATION` mode. If set, drivers MUST apply the `timeoutMS` option
351351
to the initial `aggregate` operation. Drivers MUST also apply the original `timeoutMS` value to each `next` call on the
352-
change stream but MUST NOT use it to derive a `maxTimeMS` field for `getMore` commands. If the `maxAwaitTimeMS` option
353-
is set, drivers MUST use it as the `maxTimeMS` field on `getMore` commands.
352+
change stream. If this option is set, drivers MUST use `min(maxAwaitTimeMS, remaining timeoutMS - minRoundTripTime)` as
353+
the `maxTimeMS` field on `getMore` commands.
354354

355355
If a `next` call fails with a timeout error, drivers MUST NOT invalidate the change stream. The subsequent `next` call
356356
MUST perform a resume attempt to establish a new change stream on the server. Any errors from the `aggregate` operation
@@ -600,6 +600,9 @@ distinct meanings, so supporting both yields a more robust, albeit verbose, API.
600600
greater than or equal to `timeoutMS` because in that case, `getMore` requests would not succeed if the batch was empty:
601601
the server would wait for `maxAwaitTimeMS`, but the driver would close the socket after `timeoutMS`.
602602

603+
For tailable awaitData cursors we use the `min(maxAwaitTimeMS, remaining timeoutMS - minRoundTripTime)` to allow the
604+
server more opportunities to respond with an empty batch before a client-side timeout
605+
603606
### Change stream behavior
604607

605608
Change streams internally behave as tailable awaitData cursors, so the behavior of the `timeoutMS` option is the same
@@ -665,6 +668,7 @@ timeout for each database operation. This would mimic using `timeoutMode=ITERATI
665668

666669
## Changelog
667670

671+
- 2024-01-29: Adjust getMore maxTimeMS calculation for tailable awaitData cursors.
668672
- 2024-09-12: Specify that explain helpers support support timeoutMS.
669673
- 2023-12-07: Migrated from reStructuredText to Markdown.
670674
- 2022-11-17: Use minimum RTT for maxTimeMS calculation instead of 90th percentile RTT.

source/client-side-operations-timeout/tests/tailable-awaitData.json

Lines changed: 137 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/client-side-operations-timeout/tests/tailable-awaitData.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ schemaVersion: "1.9"
44

55
runOnRequirements:
66
- minServerVersion: "4.4"
7+
serverless: forbid # Capped collections are not allowed for serverless.
78

89
createEntities:
910
- client:
@@ -245,3 +246,77 @@ tests:
245246
command:
246247
getMore: { $$type: ["int", "long"] }
247248
collection: *collectionName
249+
250+
- description: "apply remaining timeoutMS if less than maxAwaitTimeMS"
251+
operations:
252+
- name: failPoint
253+
object: testRunner
254+
arguments:
255+
client: *failPointClient
256+
failPoint:
257+
configureFailPoint: failCommand
258+
mode: { times: 1 }
259+
data:
260+
failCommands: ["getMore"]
261+
blockConnection: true
262+
blockTimeMS: 30
263+
- name: createFindCursor
264+
object: *collection
265+
arguments:
266+
filter: { _id: 1 }
267+
cursorType: tailableAwait
268+
batchSize: 1
269+
maxAwaitTimeMS: 100
270+
timeoutMS: 200
271+
saveResultAsEntity: &tailableCursor tailableCursor
272+
- name: iterateOnce
273+
object: *tailableCursor
274+
- name: iterateUntilDocumentOrError
275+
object: *tailableCursor
276+
expectError:
277+
isTimeoutError: true
278+
expectEvents:
279+
- client: *client
280+
ignoreExtraEvents: true
281+
events:
282+
- commandStartedEvent:
283+
commandName: find
284+
databaseName: *databaseName
285+
- commandStartedEvent:
286+
commandName: getMore
287+
databaseName: *databaseName
288+
command:
289+
maxTimeMS: { $$lte: 100 }
290+
- commandStartedEvent:
291+
commandName: getMore
292+
databaseName: *databaseName
293+
command:
294+
maxTimeMS: { $$lte: 70 }
295+
296+
- description: "apply maxAwaitTimeMS if less than remaining timeout"
297+
operations:
298+
- name: createFindCursor
299+
object: *collection
300+
arguments:
301+
filter: {}
302+
cursorType: tailableAwait
303+
batchSize: 1
304+
maxAwaitTimeMS: 100
305+
timeoutMS: 200
306+
saveResultAsEntity: &tailableCursor tailableCursor
307+
# Iterate twice to force a getMore.
308+
- name: iterateOnce
309+
object: *tailableCursor
310+
- name: iterateOnce
311+
object: *tailableCursor
312+
expectEvents:
313+
- client: *client
314+
events:
315+
- commandStartedEvent:
316+
commandName: find
317+
databaseName: *databaseName
318+
- commandStartedEvent:
319+
commandName: getMore
320+
databaseName: *databaseName
321+
command:
322+
maxTimeMS: { $$lte: 100 }

0 commit comments

Comments
 (0)