diff --git a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java index 35ffedbb16f..2a886704cd9 100644 --- a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java +++ b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java @@ -192,7 +192,10 @@ public long getMaxAwaitTimeMS() { public void runMaxTimeMS(final LongConsumer onRemaining) { if (maxTimeSupplier != null) { - runWithFixedTimeout(maxTimeSupplier.get(), onRemaining); + long maxTimeMS = maxTimeSupplier.get(); + if (maxTimeMS > 0) { + runMinTimeout(onRemaining, maxTimeMS); + } return; } if (timeout == null) { @@ -209,6 +212,22 @@ public void runMaxTimeMS(final LongConsumer onRemaining) { } + private void runMinTimeout(final LongConsumer onRemaining, final long fixedMs) { + Timeout timeout = timeoutIncludingRoundTrip(); + if (timeout != null) { + timeout.run(MILLISECONDS, () -> { + onRemaining.accept(fixedMs); + }, + (renamingMs) -> { + onRemaining.accept(Math.min(renamingMs, fixedMs)); + }, () -> { + throwMongoTimeoutException("The operation exceeded the timeout limit."); + }); + } else { + onRemaining.accept(fixedMs); + } + } + private static void runWithFixedTimeout(final long ms, final LongConsumer onRemaining) { if (ms != 0) { onRemaining.accept(ms); @@ -227,10 +246,14 @@ public void resetToDefaultMaxTime() { *

* NOTE: Suitable for static user-defined values only (i.e MaxAwaitTimeMS), * not for running timeouts that adjust dynamically (CSOT). + * + * If remaining CSOT timeout is less than this static timeout, then CSOT timeout will be used. + * */ public void setMaxTimeOverride(final long maxTimeMS) { this.maxTimeSupplier = () -> maxTimeMS; } + /** * Disable the maxTimeMS override. This way the maxTimeMS will not * be appended to the command in the {@link CommandMessage}. diff --git a/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/tailable-awaitData.json b/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/tailable-awaitData.json index d0fe950dd8e..a29e7b3a867 100644 --- a/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/tailable-awaitData.json +++ b/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/tailable-awaitData.json @@ -4,7 +4,8 @@ "schemaVersion": "1.9", "runOnRequirements": [ { - "minServerVersion": "4.4" + "minServerVersion": "4.4", + "serverless": "forbid" } ], "createEntities": [ @@ -419,6 +420,141 @@ ] } ] + }, + { + "description": "apply remaining timeoutMS if less than maxAwaitTimeMS", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "blockConnection": true, + "blockTimeMS": 30 + } + } + } + }, + { + "name": "createFindCursor", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "cursorType": "tailableAwait", + "batchSize": 1, + "maxAwaitTimeMS": 100, + "timeoutMS": 200 + }, + "saveResultAsEntity": "tailableCursor" + }, + { + "name": "iterateOnce", + "object": "tailableCursor" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "tailableCursor", + "expectError": { + "isTimeoutError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "ignoreExtraEvents": true, + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "databaseName": "test" + } + }, + { + "commandStartedEvent": { + "commandName": "getMore", + "databaseName": "test", + "command": { + "maxTimeMS": { + "$$lte": 100 + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "getMore", + "databaseName": "test", + "command": { + "maxTimeMS": { + "$$lte": 70 + } + } + } + } + ] + } + ] + }, + { + "description": "apply maxAwaitTimeMS if less than remaining timeout", + "operations": [ + { + "name": "createFindCursor", + "object": "collection", + "arguments": { + "filter": {}, + "cursorType": "tailableAwait", + "batchSize": 1, + "maxAwaitTimeMS": 100, + "timeoutMS": 200 + }, + "saveResultAsEntity": "tailableCursor" + }, + { + "name": "iterateOnce", + "object": "tailableCursor" + }, + { + "name": "iterateOnce", + "object": "tailableCursor" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "databaseName": "test" + } + }, + { + "commandStartedEvent": { + "commandName": "getMore", + "databaseName": "test", + "command": { + "maxTimeMS": { + "$$lte": 100 + } + } + } + } + ] + } + ] } ] -} +} \ No newline at end of file diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index df9706b8dd7..31b8ebb3bdf 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -65,6 +65,10 @@ public static void applyCustomizations(final TestDef def) { // client-side-operation-timeout (CSOT) + def.skipNoncompliantReactive("No good way to fulfill tryNext() requirement with a Publisher") + .directory("client-side-operation-timeout") + .test("timeoutMS behaves correctly for tailable awaitData cursors", "apply remaining timeoutMS if less than maxAwaitTimeMS"); + // TODO-JAVA-5712 // collection-management