@@ -394,6 +394,74 @@ func TestCursor_tailableAwaitData(t *testing.T) {
394394 assert .LessOrEqual (mt , parseMaxAwaitTime (mt , getMoreStartedEvents [1 ]), int64 (71 ))
395395 })
396396
397+ // Use a 200ms timeout that caps the lifetime of cursor.Next. The underlying
398+ // getMore loop should run at least two times: the first getMore will block
399+ // for 30ms on the getMore and then an additional 100ms for the
400+ // maxAwaitTimeMS. The second getMore will then use the remaining ~70ms
401+ // left on the timeout.
402+ clientMtOpts := mtOpts .ClientOptions (options .Client ().SetTimeout (200 * time .Millisecond ))
403+
404+ mt .RunOpts ("apply remaining client-level timeoutMS if less than maxAwaitTimeMS" , clientMtOpts , func (mt * mtest.T ) {
405+ initCollection (mt , mt .Coll )
406+
407+ // Create a 30ms failpoint for getMore.
408+ mt .SetFailPoint (failpoint.FailPoint {
409+ ConfigureFailPoint : "failCommand" ,
410+ Mode : failpoint.Mode {
411+ Times : 1 ,
412+ },
413+ Data : failpoint.Data {
414+ FailCommands : []string {"getMore" },
415+ BlockConnection : true ,
416+ BlockTimeMS : 30 ,
417+ },
418+ })
419+
420+ // Create a find cursor with a 100ms maxAwaitTimeMS and a tailable awaitData
421+ // cursor type.
422+ opts := options .Find ().
423+ SetMaxAwaitTime (100 * time .Millisecond ).
424+ SetCursorType (options .TailableAwait )
425+
426+ cursor , err := mt .Coll .Find (context .Background (), bson.D {{"x" , 1 }}, opts )
427+ require .NoError (mt , err )
428+
429+ defer cursor .Close (context .Background ())
430+
431+ // Iterate twice to force a getMore
432+ cursor .Next (context .Background ())
433+
434+ // We expect 2 calls to getMore. Since batchSize=1 the first call will
435+ mt .ClearEvents ()
436+ cursor .Next (context .Background ())
437+
438+ m := make (map [string ]any )
439+
440+ err = cursor .Decode (& m )
441+ require .NoError (t , err , "expected to decode a document, got error: %v" , err )
442+
443+ require .Error (mt , cursor .Err (), "expected error from cursor.Next" )
444+ assert .ErrorIs (mt , cursor .Err (), context .DeadlineExceeded , "expected context deadline exceeded error" )
445+
446+ // Collect all started events to find the getMore commands.
447+ startedEvents := mt .GetAllStartedEvents ()
448+
449+ var getMoreStartedEvents []* event.CommandStartedEvent
450+ for _ , evt := range startedEvents {
451+ if evt .CommandName == "getMore" {
452+ getMoreStartedEvents = append (getMoreStartedEvents , evt )
453+ }
454+ }
455+
456+ // The first getMore should have a maxTimeMS of <= 100ms.
457+ assert .LessOrEqual (mt , parseMaxAwaitTime (mt , getMoreStartedEvents [0 ]), int64 (100 ))
458+
459+ // The second getMore should have a maxTimeMS of <=71, indicating that we
460+ // are using the time remaining in the context rather than the
461+ // maxAwaitTimeMS.
462+ assert .LessOrEqual (mt , parseMaxAwaitTime (mt , getMoreStartedEvents [1 ]), int64 (71 ))
463+ })
464+
397465 mtOpts .Topologies (mtest .ReplicaSet , mtest .Sharded , mtest .LoadBalanced , mtest .Single )
398466
399467 mt .RunOpts ("apply maxAwaitTimeMS if less than remaining timeout" , mtOpts , func (mt * mtest.T ) {
0 commit comments