From 21abfe27e6d371027a01eb62374913b176e028f0 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 11 Dec 2025 15:35:23 +0000 Subject: [PATCH 01/26] Fixes JAVA-5949 prevent connection churn on backpressure errors when establishing connections --- .../DefaultSdamServerDescriptionManager.java | 26 ++++++++-- .../InternalStreamConnectionInitializer.java | 2 + .../SdamServerDescriptionManager.java | 51 +++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java index af4acd8c031..b6154a00cf2 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.MongoException; import com.mongodb.annotations.ThreadSafe; import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.connection.ServerDescription; @@ -137,9 +138,28 @@ private void handleException(final SdamIssue sdamIssue, final boolean beforeHand serverMonitor.connect(); } else if (sdamIssue.relatedToNetworkNotTimeout() || (beforeHandshake && (sdamIssue.relatedToNetworkTimeout() || sdamIssue.relatedToAuth()))) { - updateDescription(sdamIssue.serverDescription()); - connectionPool.invalidate(sdamIssue.exception().orElse(null)); - serverMonitor.cancelCurrentCheck(); + // Backpressure spec: Don't clear pool or mark server unknown for connection establishment failures + // (network errors or timeouts during handshake). Authentication errors after handshake should still + // clear the pool as they're not related to overload. + // TLS configuration errors (certificate validation, protocol mismatches) should also clear the pool + // as they indicate configuration issues, not server overload. + if (beforeHandshake && (sdamIssue.relatedToNetworkNotTimeout() || sdamIssue.relatedToNetworkTimeout()) + && !sdamIssue.relatedToAuth() && !sdamIssue.relatedToTlsConfigurationError()) { + // Don't update server description to Unknown + // Don't invalidate the connection pool + // Apply error labels for backpressure + sdamIssue.exception().ifPresent(exception -> { + if (exception instanceof MongoException) { + MongoException mongoException = (MongoException) exception; + mongoException.addLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL); + mongoException.addLabel(MongoException.RETRYABLE_ERROR_LABEL); + } + }); + } else { + updateDescription(sdamIssue.serverDescription()); + connectionPool.invalidate(sdamIssue.exception().orElse(null)); + serverMonitor.cancelCurrentCheck(); + } } else if (sdamIssue.relatedToWriteConcern() || sdamIssue.relatedToStalePrimary()) { updateDescription(sdamIssue.serverDescription()); serverMonitor.connect(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java index 36f6688cb0e..004e9e8e35f 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java @@ -199,6 +199,8 @@ private BsonDocument createHelloCommand(final Authenticator authenticator, final helloCommandDocument.append("speculativeAuthenticate", speculativeAuthenticateDocument); } } + // Add backpressure support indication + helloCommandDocument.append("backpressure", BsonBoolean.TRUE); return helloCommandDocument; } diff --git a/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java b/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java index 7f014d7ede6..a2135085535 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java +++ b/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java @@ -30,6 +30,10 @@ import com.mongodb.connection.TopologyVersion; import com.mongodb.lang.Nullable; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateException; import java.util.Optional; import static com.mongodb.assertions.Assertions.assertNotNull; @@ -162,6 +166,53 @@ boolean relatedToWriteConcern() { return exception instanceof MongoWriteConcernWithResponseException; } + /** + * Checks if the exception is related to TLS configuration errors that are NOT due to server overload. + * These include certificate validation failures, protocol mismatches, etc. + * + * @return true if this is a TLS configuration error (not network-related) + */ + boolean relatedToTlsConfigurationError() { + if (!(exception instanceof MongoSocketException)) { + return false; + } + Throwable cause = exception.getCause(); + while (cause != null) { + // Check for various certificate validation and TLS configuration errors + if (cause instanceof CertificateException + || cause instanceof CertPathValidatorException + || cause instanceof SSLPeerUnverifiedException) { + return true; // Certificate/peer validation failure + } + + // Check for SunCertPathBuilderException by class name to avoid compile-time dependency on internal classes + String className = cause.getClass().getName(); + if (className.equals("sun.security.provider.certpath.SunCertPathBuilderException")) { + return true; // Certificate path building failure + } + + // SSLHandshakeException can be either network or config, so we check the message + if (cause instanceof SSLHandshakeException) { + String message = cause.getMessage(); + if (message != null) { + String lowerMessage = message.toLowerCase(); + // These indicate configuration issues, not network issues + if (lowerMessage.contains("certificate") + || lowerMessage.contains("verify") + || lowerMessage.contains("trust") + || lowerMessage.contains("hostname") + || lowerMessage.contains("protocol") + || lowerMessage.contains("cipher") + || lowerMessage.contains("handshake_failure")) { + return true; + } + } + } + cause = cause.getCause(); + } + return false; + } + private static boolean stale(@Nullable final Throwable t, final ServerDescription currentServerDescription) { return TopologyVersionHelper.topologyVersion(t) .map(candidateTopologyVersion -> TopologyVersionHelper.newerOrEqual( From 339297b1e03e505a1513a11d9ac3b45e68a6cacc Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 15 Dec 2025 16:08:49 +0000 Subject: [PATCH 02/26] Remove handshake and update submodule including new tests --- .../connection/InternalStreamConnectionInitializer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java index 004e9e8e35f..36f6688cb0e 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java @@ -199,8 +199,6 @@ private BsonDocument createHelloCommand(final Authenticator authenticator, final helloCommandDocument.append("speculativeAuthenticate", speculativeAuthenticateDocument); } } - // Add backpressure support indication - helloCommandDocument.append("backpressure", BsonBoolean.TRUE); return helloCommandDocument; } From 2e4407298fc17e29424b0daa23b28e8c44813424 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 20 Jan 2026 13:02:32 +0000 Subject: [PATCH 03/26] Update spec test; fix test runner --- .../connection/DefaultServerSpecification.groovy | 16 +++++++++------- .../com/mongodb/client/unified/EventMatcher.java | 11 +++++++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy index 3910da575f0..a9fb84d4ba5 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy @@ -234,10 +234,12 @@ class DefaultServerSpecification extends Specification { ] } - def 'failed open should invalidate the server'() { + def 'network error should not invalidate the pool'() { given: def connectionPool = Mock(ConnectionPool) - connectionPool.get(_) >> { throw exceptionToThrow } + connectionPool.get(_) >> { + throw exceptionToThrow + } def serverMonitor = Mock(ServerMonitor) def server = defaultServer(connectionPool, serverMonitor) @@ -247,8 +249,8 @@ class DefaultServerSpecification extends Specification { then: def e = thrown(MongoException) e.is(exceptionToThrow) - 1 * connectionPool.invalidate(exceptionToThrow) - 1 * serverMonitor.cancelCurrentCheck() + 0 * connectionPool.invalidate(_) + 0 * serverMonitor.cancelCurrentCheck() where: exceptionToThrow << [ @@ -281,7 +283,7 @@ class DefaultServerSpecification extends Specification { ] } - def 'failed open should invalidate the server asynchronously'() { + def 'failed open should not invalidate the pool asynchronously'() { given: def connectionPool = Mock(ConnectionPool) connectionPool.getAsync(_, _) >> { it.last().onResult(null, exceptionToThrow) } @@ -301,8 +303,8 @@ class DefaultServerSpecification extends Specification { then: !receivedConnection receivedThrowable.is(exceptionToThrow) - 1 * connectionPool.invalidate(exceptionToThrow) - 1 * serverMonitor.cancelCurrentCheck() + 0 * connectionPool.invalidate(exceptionToThrow) + 0 * serverMonitor.cancelCurrentCheck() where: diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java b/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java index b2718b4b2d7..7c069b8d95b 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java @@ -436,9 +436,16 @@ private static boolean serverDescriptionChangedEventMatches(final BsonDocument e switch (newType) { case "Unknown": return event.getNewDescription().getType() == ServerType.UNKNOWN; - case "LoadBalancer": { + case "LoadBalancer": return event.getNewDescription().getType() == ServerType.LOAD_BALANCER; - } + case "Mongos": + return event.getNewDescription().getType() == ServerType.SHARD_ROUTER; + case "Standalone": + return event.getNewDescription().getType() == ServerType.STANDALONE; + case "RSPrimary": + return event.getNewDescription().getType() == ServerType.REPLICA_SET_PRIMARY; + case "RSSecondary": + return event.getNewDescription().getType() == ServerType.REPLICA_SET_SECONDARY; default: throw new UnsupportedOperationException(); } From 4cff1b27bf90cbef37c43abc067b1ee7c564df2a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 26 Jan 2026 16:53:21 +0000 Subject: [PATCH 04/26] Add prose test --- ...erverDiscoveryAndMonitoringProseTests.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index 18b3b3f4fc5..943ee78de31 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -18,6 +18,7 @@ import com.mongodb.ClusterFixture; import com.mongodb.MongoClientSettings; +import com.mongodb.event.ConnectionCheckOutFailedEvent; import com.mongodb.event.ConnectionPoolClearedEvent; import com.mongodb.event.ConnectionPoolListener; import com.mongodb.event.ConnectionPoolReadyEvent; @@ -47,7 +48,10 @@ import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; import static com.mongodb.ClusterFixture.configureFailPoint; import static com.mongodb.ClusterFixture.disableFailPoint; @@ -268,6 +272,80 @@ public void shouldEmitHeartbeatStartedBeforeSocketIsConnected() { // As it requires mocking and package access to `com.mongodb.internal.connection` } + /** + * See + * Connection Pool Backpressure. + */ + @Test + public void testConnectionPoolBackpressure() throws InterruptedException { + assumeTrue(serverVersionAtLeast(7, 0)); + + AtomicInteger connectionCheckOutFailedEventCount = new AtomicInteger(0); + AtomicInteger poolClearedEventCount = new AtomicInteger(0); + + ConnectionPoolListener connectionPoolListener = new ConnectionPoolListener() { + @Override + public void connectionCheckOutFailed(final ConnectionCheckOutFailedEvent event) { + connectionCheckOutFailedEventCount.incrementAndGet(); + } + + @Override + public void connectionPoolCleared(final ConnectionPoolClearedEvent event) { + poolClearedEventCount.incrementAndGet(); + } + }; + + MongoClientSettings clientSettings = getMongoClientSettingsBuilder() + .applyToConnectionPoolSettings(builder -> builder + .maxConnecting(100) + .addConnectionPoolListener(connectionPoolListener)) + .build(); + + try (MongoClient adminClient = MongoClients.create(getMongoClientSettingsBuilder().build()); + MongoClient client = MongoClients.create(clientSettings)) { + + MongoDatabase adminDatabase = adminClient.getDatabase("admin"); + MongoDatabase database = client.getDatabase(getDefaultDatabaseName()); + MongoCollection collection = database.getCollection("testCollection"); + + // Configure rate limiter using admin commands + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRateLimiterEnabled", true)); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRatePerSec", 20)); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentBurstCapacitySecs", 1)); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentMaxQueueDepth", 1)); + + // Add a document to the collection + collection.insertOne(Document.parse("{}"));// change + + // Run 100 parallel find operations with 2-seconds sleep + ExecutorService executor = Executors.newFixedThreadPool(100); + for (int i = 0; i < 100; i++) { + executor.submit(() -> collection.find(new Document("$where", "function() { sleep(2000); return true; }")).first()); + } + + // Wait for all operations to complete (max 10 seconds) + executor.shutdown(); + boolean terminated = executor.awaitTermination(10, SECONDS); + assertTrue("Executor did not terminate within timeout", terminated); + + // Assert at least 10 ConnectionCheckOutFailedEvents occurred + assertTrue("Expected at least 10 ConnectionCheckOutFailedEvents, but got " + connectionCheckOutFailedEventCount.get(), + connectionCheckOutFailedEventCount.get() >= 10); + + // Assert 0 PoolClearedEvents occurred + assertEquals("Expected 0 PoolClearedEvents", 0, poolClearedEventCount.get()); + + // Teardown: sleep 1 second and reset rate limiter + Thread.sleep(1000); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRateLimiterEnabled", false)); + } + } + private static void assertPoll(final BlockingQueue queue, @Nullable final Class allowed, final Set> required) throws InterruptedException { assertPoll(queue, allowed, required, Timeout.expiresIn(TEST_WAIT_TIMEOUT_MILLIS, MILLISECONDS, ZERO_DURATION_MEANS_EXPIRED)); From 0a93dd74d8dfeb4feafc27a769680321660b9e92 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 27 Jan 2026 17:20:38 +0000 Subject: [PATCH 05/26] Increasing the timeout termination --- .../mongodb/client/ServerDiscoveryAndMonitoringProseTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index 943ee78de31..389f66ee935 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -329,7 +329,7 @@ public void connectionPoolCleared(final ConnectionPoolClearedEvent event) { // Wait for all operations to complete (max 10 seconds) executor.shutdown(); - boolean terminated = executor.awaitTermination(10, SECONDS); + boolean terminated = executor.awaitTermination(20, SECONDS); assertTrue("Executor did not terminate within timeout", terminated); // Assert at least 10 ConnectionCheckOutFailedEvents occurred From b1f4ba092c37f37ab28ba0b34c66efa7537cbce7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 27 Jan 2026 18:10:06 +0000 Subject: [PATCH 06/26] - Revert spec timeout - Fixing static check analysis --- .../client/ServerDiscoveryAndMonitoringProseTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index 389f66ee935..ff83ddd3d93 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -319,7 +319,7 @@ public void connectionPoolCleared(final ConnectionPoolClearedEvent event) { .append("ingressConnectionEstablishmentMaxQueueDepth", 1)); // Add a document to the collection - collection.insertOne(Document.parse("{}"));// change + collection.insertOne(Document.parse("{}")); // Run 100 parallel find operations with 2-seconds sleep ExecutorService executor = Executors.newFixedThreadPool(100); @@ -329,7 +329,7 @@ public void connectionPoolCleared(final ConnectionPoolClearedEvent event) { // Wait for all operations to complete (max 10 seconds) executor.shutdown(); - boolean terminated = executor.awaitTermination(20, SECONDS); + boolean terminated = executor.awaitTermination(10, SECONDS); assertTrue("Executor did not terminate within timeout", terminated); // Assert at least 10 ConnectionCheckOutFailedEvents occurred From 1b1558d5eb9d38e06f7c418d7d2e0f223b819ba2 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 27 Jan 2026 18:57:37 +0000 Subject: [PATCH 07/26] Update exception checks --- .../internal/connection/SdamServerDescriptionManager.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java b/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java index a2135085535..304f56830ec 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java +++ b/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java @@ -32,6 +32,8 @@ import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; +import java.security.cert.CertPathBuilderException; import java.security.cert.CertPathValidatorException; import java.security.cert.CertificateException; import java.util.Optional; @@ -180,9 +182,11 @@ boolean relatedToTlsConfigurationError() { while (cause != null) { // Check for various certificate validation and TLS configuration errors if (cause instanceof CertificateException + || cause instanceof CertPathBuilderException || cause instanceof CertPathValidatorException - || cause instanceof SSLPeerUnverifiedException) { - return true; // Certificate/peer validation failure + || cause instanceof SSLPeerUnverifiedException + || cause instanceof SSLProtocolException) { + return true; // Certificate/peer validation failure or protocol error } // Check for SunCertPathBuilderException by class name to avoid compile-time dependency on internal classes From fcdfa231831ef57da8d0e5bd753c4ef292cf67d2 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 27 Jan 2026 23:59:46 +0000 Subject: [PATCH 08/26] Increase timeout for operations to complete --- .../client/ServerDiscoveryAndMonitoringProseTests.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index ff83ddd3d93..74e0e08d365 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -318,7 +318,6 @@ public void connectionPoolCleared(final ConnectionPoolClearedEvent event) { adminDatabase.runCommand(new Document("setParameter", 1) .append("ingressConnectionEstablishmentMaxQueueDepth", 1)); - // Add a document to the collection collection.insertOne(Document.parse("{}")); // Run 100 parallel find operations with 2-seconds sleep @@ -327,9 +326,9 @@ public void connectionPoolCleared(final ConnectionPoolClearedEvent event) { executor.submit(() -> collection.find(new Document("$where", "function() { sleep(2000); return true; }")).first()); } - // Wait for all operations to complete (max 10 seconds) + // Wait for all operations to complete executor.shutdown(); - boolean terminated = executor.awaitTermination(10, SECONDS); + boolean terminated = executor.awaitTermination(20, SECONDS); assertTrue("Executor did not terminate within timeout", terminated); // Assert at least 10 ConnectionCheckOutFailedEvents occurred From d4230937bc44783d55013edbc8a1f43e0a7cc3b7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 28 Jan 2026 01:29:10 +0000 Subject: [PATCH 09/26] Simplifying conditions check --- .../connection/DefaultSdamServerDescriptionManager.java | 3 +-- .../internal/connection/SdamServerDescriptionManager.java | 8 +------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java index b6154a00cf2..67dc436e7b6 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java @@ -143,8 +143,7 @@ private void handleException(final SdamIssue sdamIssue, final boolean beforeHand // clear the pool as they're not related to overload. // TLS configuration errors (certificate validation, protocol mismatches) should also clear the pool // as they indicate configuration issues, not server overload. - if (beforeHandshake && (sdamIssue.relatedToNetworkNotTimeout() || sdamIssue.relatedToNetworkTimeout()) - && !sdamIssue.relatedToAuth() && !sdamIssue.relatedToTlsConfigurationError()) { + if (beforeHandshake && !sdamIssue.relatedToAuth() && !sdamIssue.relatedToTlsConfigurationError()) { // Don't update server description to Unknown // Don't invalidate the connection pool // Apply error labels for backpressure diff --git a/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java b/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java index 304f56830ec..e5c4ce035fd 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java +++ b/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java @@ -186,13 +186,7 @@ boolean relatedToTlsConfigurationError() { || cause instanceof CertPathValidatorException || cause instanceof SSLPeerUnverifiedException || cause instanceof SSLProtocolException) { - return true; // Certificate/peer validation failure or protocol error - } - - // Check for SunCertPathBuilderException by class name to avoid compile-time dependency on internal classes - String className = cause.getClass().getName(); - if (className.equals("sun.security.provider.certpath.SunCertPathBuilderException")) { - return true; // Certificate path building failure + return true; } // SSLHandshakeException can be either network or config, so we check the message From bce97d488f538aa7f11e56a2ddc4216facf1b2c2 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 22 Apr 2026 18:15:00 +0100 Subject: [PATCH 10/26] Apply backpressure labels in CMAP layer; align SDAM with spec --- .../connection/BackpressureErrorLabeler.java | 112 ++++++++++++++++++ .../DefaultSdamServerDescriptionManager.java | 26 +--- .../connection/InternalStreamConnection.java | 8 ++ .../SdamServerDescriptionManager.java | 51 +------- .../DefaultServerSpecification.groovy | 16 ++- ...erverDiscoveryAndMonitoringProseTests.java | 83 ++++++------- 6 files changed, 174 insertions(+), 122 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java diff --git a/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java b/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java new file mode 100644 index 00000000000..bdfa8d47ba9 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java @@ -0,0 +1,112 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.connection; + +import com.mongodb.MongoException; +import com.mongodb.MongoSecurityException; +import com.mongodb.MongoSocketException; + +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; +import java.net.UnknownHostException; +import java.security.cert.CertPathBuilderException; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateException; +import java.util.Locale; + +/** + * Attaches {@link MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} and + * {@link MongoException#RETRYABLE_ERROR_LABEL} to network errors encountered during connection + * establishment or the hello message, per the CMAP specification. + * + *

This is topology-agnostic: it must be invoked from the connection-establishment path so that + * both default SDAM and load-balanced modes are covered. + */ +final class BackpressureErrorLabeler { + + private BackpressureErrorLabeler() { + } + + static void applyLabelsIfEligible(final Throwable t) { + if (!(t instanceof MongoException)) { + return; + } + if (t instanceof MongoSecurityException) { + return; + } + if (!(t instanceof MongoSocketException)) { + return; + } + if (isDnsLookupFailure(t)) { + return; + } + if (isTlsConfigurationError(t)) { + return; + } + // TODO-BACKPRESSURE Nabil - SOCKS5 Revisit alongside JAVA-5205 (SOCKS5 in async) so both sync and + // async proxy error surfaces can be handled together — likely via a dedicated internal + // exception thrown from the proxy code path. + MongoException mongoException = (MongoException) t; + mongoException.addLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL); + mongoException.addLabel(MongoException.RETRYABLE_ERROR_LABEL); + } + + static boolean isDnsLookupFailure(final Throwable t) { + Throwable cause = t.getCause(); + while (cause != null) { + if (cause instanceof UnknownHostException) { + return true; + } + cause = cause.getCause(); + } + return false; + } + + static boolean isTlsConfigurationError(final Throwable t) { + if (!(t instanceof MongoSocketException)) { + return false; + } + Throwable cause = t.getCause(); + while (cause != null) { + if (cause instanceof CertificateException + || cause instanceof CertPathBuilderException + || cause instanceof CertPathValidatorException + || cause instanceof SSLPeerUnverifiedException + || cause instanceof SSLProtocolException) { + return true; + } + if (cause instanceof SSLHandshakeException) { + String message = cause.getMessage(); + if (message != null) { + String lowerMessage = message.toLowerCase(Locale.ROOT); + if (lowerMessage.contains("certificate") + || lowerMessage.contains("verify") + || lowerMessage.contains("trust") + || lowerMessage.contains("hostname") + || lowerMessage.contains("protocol") + || lowerMessage.contains("cipher") + || lowerMessage.contains("handshake_failure")) { + return true; + } + } + } + cause = cause.getCause(); + } + return false; + } +} diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java index 67dc436e7b6..b0b456e97fb 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java @@ -16,7 +16,6 @@ package com.mongodb.internal.connection; -import com.mongodb.MongoException; import com.mongodb.annotations.ThreadSafe; import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.connection.ServerDescription; @@ -138,27 +137,12 @@ private void handleException(final SdamIssue sdamIssue, final boolean beforeHand serverMonitor.connect(); } else if (sdamIssue.relatedToNetworkNotTimeout() || (beforeHandshake && (sdamIssue.relatedToNetworkTimeout() || sdamIssue.relatedToAuth()))) { - // Backpressure spec: Don't clear pool or mark server unknown for connection establishment failures - // (network errors or timeouts during handshake). Authentication errors after handshake should still - // clear the pool as they're not related to overload. - // TLS configuration errors (certificate validation, protocol mismatches) should also clear the pool - // as they indicate configuration issues, not server overload. - if (beforeHandshake && !sdamIssue.relatedToAuth() && !sdamIssue.relatedToTlsConfigurationError()) { - // Don't update server description to Unknown - // Don't invalidate the connection pool - // Apply error labels for backpressure - sdamIssue.exception().ifPresent(exception -> { - if (exception instanceof MongoException) { - MongoException mongoException = (MongoException) exception; - mongoException.addLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL); - mongoException.addLabel(MongoException.RETRYABLE_ERROR_LABEL); - } - }); - } else { - updateDescription(sdamIssue.serverDescription()); - connectionPool.invalidate(sdamIssue.exception().orElse(null)); - serverMonitor.cancelCurrentCheck(); + if (sdamIssue.hasBackpressureLabel()) { + return; } + updateDescription(sdamIssue.serverDescription()); + connectionPool.invalidate(sdamIssue.exception().orElse(null)); + serverMonitor.cancelCurrentCheck(); } else if (sdamIssue.relatedToWriteConcern() || sdamIssue.relatedToStalePrimary()) { updateDescription(sdamIssue.serverDescription()); serverMonitor.connect(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java index 6b20c467191..09e28d7c845 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java @@ -230,9 +230,11 @@ public void open(final OperationContext originalOperationContext) { isTrue("Open already called", stream == null); stream = streamFactory.create(serverId.getAddress()); OperationContext operationContext = originalOperationContext; + boolean beforeHandshake = true; try { stream.open(operationContext); InternalConnectionInitializationDescription initializationDescription = connectionInitializer.startHandshake(this, operationContext); + beforeHandshake = false; operationContext = operationContext.withOverride(TimeoutContext::withNewlyStartedMaintenanceTimeout); initAfterHandshakeStart(initializationDescription); @@ -241,6 +243,9 @@ public void open(final OperationContext originalOperationContext) { initAfterHandshakeFinish(initializationDescription); } catch (Throwable t) { close(); + if (beforeHandshake) { + BackpressureErrorLabeler.applyLabelsIfEligible(t); + } if (t instanceof MongoException) { throw (MongoException) t; } else { @@ -263,6 +268,7 @@ public void completed(@Nullable final Void aVoid) { (initialResult, initialException) -> { if (initialException != null) { close(); + BackpressureErrorLabeler.applyLabelsIfEligible(initialException); callback.onResult(null, initialException); } else { assertNotNull(initialResult); @@ -278,11 +284,13 @@ public void completed(@Nullable final Void aVoid) { @Override public void failed(final Throwable t) { close(); + BackpressureErrorLabeler.applyLabelsIfEligible(t); callback.onResult(null, t); } }); } catch (Throwable t) { close(); + BackpressureErrorLabeler.applyLabelsIfEligible(t); callback.onResult(null, t); } } diff --git a/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java b/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java index e5c4ce035fd..23c163a9801 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java +++ b/driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java @@ -17,6 +17,7 @@ package com.mongodb.internal.connection; import com.mongodb.MongoCommandException; +import com.mongodb.MongoException; import com.mongodb.MongoNodeIsRecoveringException; import com.mongodb.MongoNotPrimaryException; import com.mongodb.MongoSecurityException; @@ -30,12 +31,6 @@ import com.mongodb.connection.TopologyVersion; import com.mongodb.lang.Nullable; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLProtocolException; -import java.security.cert.CertPathBuilderException; -import java.security.cert.CertPathValidatorException; -import java.security.cert.CertificateException; import java.util.Optional; import static com.mongodb.assertions.Assertions.assertNotNull; @@ -168,47 +163,9 @@ boolean relatedToWriteConcern() { return exception instanceof MongoWriteConcernWithResponseException; } - /** - * Checks if the exception is related to TLS configuration errors that are NOT due to server overload. - * These include certificate validation failures, protocol mismatches, etc. - * - * @return true if this is a TLS configuration error (not network-related) - */ - boolean relatedToTlsConfigurationError() { - if (!(exception instanceof MongoSocketException)) { - return false; - } - Throwable cause = exception.getCause(); - while (cause != null) { - // Check for various certificate validation and TLS configuration errors - if (cause instanceof CertificateException - || cause instanceof CertPathBuilderException - || cause instanceof CertPathValidatorException - || cause instanceof SSLPeerUnverifiedException - || cause instanceof SSLProtocolException) { - return true; - } - - // SSLHandshakeException can be either network or config, so we check the message - if (cause instanceof SSLHandshakeException) { - String message = cause.getMessage(); - if (message != null) { - String lowerMessage = message.toLowerCase(); - // These indicate configuration issues, not network issues - if (lowerMessage.contains("certificate") - || lowerMessage.contains("verify") - || lowerMessage.contains("trust") - || lowerMessage.contains("hostname") - || lowerMessage.contains("protocol") - || lowerMessage.contains("cipher") - || lowerMessage.contains("handshake_failure")) { - return true; - } - } - } - cause = cause.getCause(); - } - return false; + boolean hasBackpressureLabel() { + return exception instanceof MongoException + && ((MongoException) exception).hasErrorLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL); } private static boolean stale(@Nullable final Throwable t, final ServerDescription currentServerDescription) { diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy index a9fb84d4ba5..3910da575f0 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy @@ -234,12 +234,10 @@ class DefaultServerSpecification extends Specification { ] } - def 'network error should not invalidate the pool'() { + def 'failed open should invalidate the server'() { given: def connectionPool = Mock(ConnectionPool) - connectionPool.get(_) >> { - throw exceptionToThrow - } + connectionPool.get(_) >> { throw exceptionToThrow } def serverMonitor = Mock(ServerMonitor) def server = defaultServer(connectionPool, serverMonitor) @@ -249,8 +247,8 @@ class DefaultServerSpecification extends Specification { then: def e = thrown(MongoException) e.is(exceptionToThrow) - 0 * connectionPool.invalidate(_) - 0 * serverMonitor.cancelCurrentCheck() + 1 * connectionPool.invalidate(exceptionToThrow) + 1 * serverMonitor.cancelCurrentCheck() where: exceptionToThrow << [ @@ -283,7 +281,7 @@ class DefaultServerSpecification extends Specification { ] } - def 'failed open should not invalidate the pool asynchronously'() { + def 'failed open should invalidate the server asynchronously'() { given: def connectionPool = Mock(ConnectionPool) connectionPool.getAsync(_, _) >> { it.last().onResult(null, exceptionToThrow) } @@ -303,8 +301,8 @@ class DefaultServerSpecification extends Specification { then: !receivedConnection receivedThrowable.is(exceptionToThrow) - 0 * connectionPool.invalidate(exceptionToThrow) - 0 * serverMonitor.cancelCurrentCheck() + 1 * connectionPool.invalidate(exceptionToThrow) + 1 * serverMonitor.cancelCurrentCheck() where: diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index 74e0e08d365..a48cf2c2610 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -27,6 +27,7 @@ import com.mongodb.event.ServerHeartbeatSucceededEvent; import com.mongodb.event.ServerListener; import com.mongodb.event.ServerMonitorListener; +import com.mongodb.internal.connection.TestConnectionPoolListener; import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; import com.mongodb.internal.time.TimePointTest; @@ -51,7 +52,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicInteger; import static com.mongodb.ClusterFixture.configureFailPoint; import static com.mongodb.ClusterFixture.disableFailPoint; @@ -280,20 +280,7 @@ public void shouldEmitHeartbeatStartedBeforeSocketIsConnected() { public void testConnectionPoolBackpressure() throws InterruptedException { assumeTrue(serverVersionAtLeast(7, 0)); - AtomicInteger connectionCheckOutFailedEventCount = new AtomicInteger(0); - AtomicInteger poolClearedEventCount = new AtomicInteger(0); - - ConnectionPoolListener connectionPoolListener = new ConnectionPoolListener() { - @Override - public void connectionCheckOutFailed(final ConnectionCheckOutFailedEvent event) { - connectionCheckOutFailedEventCount.incrementAndGet(); - } - - @Override - public void connectionPoolCleared(final ConnectionPoolClearedEvent event) { - poolClearedEventCount.incrementAndGet(); - } - }; + TestConnectionPoolListener connectionPoolListener = new TestConnectionPoolListener(); MongoClientSettings clientSettings = getMongoClientSettingsBuilder() .applyToConnectionPoolSettings(builder -> builder @@ -308,40 +295,46 @@ public void connectionPoolCleared(final ConnectionPoolClearedEvent event) { MongoDatabase database = client.getDatabase(getDefaultDatabaseName()); MongoCollection collection = database.getCollection("testCollection"); - // Configure rate limiter using admin commands adminDatabase.runCommand(new Document("setParameter", 1) .append("ingressConnectionEstablishmentRateLimiterEnabled", true)); - adminDatabase.runCommand(new Document("setParameter", 1) - .append("ingressConnectionEstablishmentRatePerSec", 20)); - adminDatabase.runCommand(new Document("setParameter", 1) - .append("ingressConnectionEstablishmentBurstCapacitySecs", 1)); - adminDatabase.runCommand(new Document("setParameter", 1) - .append("ingressConnectionEstablishmentMaxQueueDepth", 1)); - - collection.insertOne(Document.parse("{}")); + try { + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRatePerSec", 20)); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentBurstCapacitySecs", 1)); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentMaxQueueDepth", 1)); + + collection.insertOne(Document.parse("{}")); + + ExecutorService executor = Executors.newFixedThreadPool(100); + try { + for (int i = 0; i < 100; i++) { + executor.submit(() -> + collection.find(new Document("$where", "function() { sleep(2000); return true; }")).first()); + } + executor.shutdown(); + assertTrue("Executor did not terminate within timeout", + executor.awaitTermination(20, SECONDS)); + } finally { + if (!executor.isTerminated()) { + executor.shutdownNow(); + } + } - // Run 100 parallel find operations with 2-seconds sleep - ExecutorService executor = Executors.newFixedThreadPool(100); - for (int i = 0; i < 100; i++) { - executor.submit(() -> collection.find(new Document("$where", "function() { sleep(2000); return true; }")).first()); + long connectionErrorCount = connectionPoolListener.getEvents().stream() + .filter(e -> e instanceof ConnectionCheckOutFailedEvent) + .map(e -> ((ConnectionCheckOutFailedEvent) e).getReason()) + .filter(r -> r == ConnectionCheckOutFailedEvent.Reason.CONNECTION_ERROR) + .count(); + assertTrue("Expected at least 10 ConnectionCheckOutFailedEvents with CONNECTION_ERROR, but got " + + connectionErrorCount, + connectionErrorCount >= 10); + assertEquals(0, connectionPoolListener.countEvents(ConnectionPoolClearedEvent.class)); + } finally { + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRateLimiterEnabled", false)); } - - // Wait for all operations to complete - executor.shutdown(); - boolean terminated = executor.awaitTermination(20, SECONDS); - assertTrue("Executor did not terminate within timeout", terminated); - - // Assert at least 10 ConnectionCheckOutFailedEvents occurred - assertTrue("Expected at least 10 ConnectionCheckOutFailedEvents, but got " + connectionCheckOutFailedEventCount.get(), - connectionCheckOutFailedEventCount.get() >= 10); - - // Assert 0 PoolClearedEvents occurred - assertEquals("Expected 0 PoolClearedEvents", 0, poolClearedEventCount.get()); - - // Teardown: sleep 1 second and reset rate limiter - Thread.sleep(1000); - adminDatabase.runCommand(new Document("setParameter", 1) - .append("ingressConnectionEstablishmentRateLimiterEnabled", false)); } } From 41b2773385900a2571d94c1a4e73a73c45f27ff9 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 23 Apr 2026 11:06:32 +0100 Subject: [PATCH 11/26] Align Prose Test with Spec --- .../ServerDiscoveryAndMonitoringProseTests.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index a48cf2c2610..8e13782531a 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -322,16 +322,12 @@ public void testConnectionPoolBackpressure() throws InterruptedException { } } - long connectionErrorCount = connectionPoolListener.getEvents().stream() - .filter(e -> e instanceof ConnectionCheckOutFailedEvent) - .map(e -> ((ConnectionCheckOutFailedEvent) e).getReason()) - .filter(r -> r == ConnectionCheckOutFailedEvent.Reason.CONNECTION_ERROR) - .count(); - assertTrue("Expected at least 10 ConnectionCheckOutFailedEvents with CONNECTION_ERROR, but got " - + connectionErrorCount, - connectionErrorCount >= 10); + int failedCheckOutCount = connectionPoolListener.countEvents(ConnectionCheckOutFailedEvent.class); + assertTrue("Expected at least 10 ConnectionCheckOutFailedEvents, but got " + failedCheckOutCount, + failedCheckOutCount >= 10); assertEquals(0, connectionPoolListener.countEvents(ConnectionPoolClearedEvent.class)); } finally { + Thread.sleep(1000); adminDatabase.runCommand(new Document("setParameter", 1) .append("ingressConnectionEstablishmentRateLimiterEnabled", false)); } From 508d7419652af58f18ad21c9fe2e0c178f0b04ce Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 23 Apr 2026 12:51:21 +0100 Subject: [PATCH 12/26] Test runner needs to add the SystemOverloadedError label for the pre-handshake network timeout --- .../AbstractServerDiscoveryAndMonitoringTest.java | 1 + .../connection/ServerDiscoveryAndMonitoringTest.java | 3 --- .../client/unified/UnifiedTestModifications.java | 10 ---------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java index e187e94da7b..6e63f9c586a 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java @@ -112,6 +112,7 @@ protected void applyApplicationError(final BsonDocument applicationError) { switch (when) { case "beforeHandshakeCompletes": + BackpressureErrorLabeler.applyLabelsIfEligible(exception); server.sdamServerDescriptionManager().handleExceptionBeforeHandshake( SdamIssue.of(exception, new SdamIssue.Context(server.serverId(), errorGeneration, maxWireVersion))); break; diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java index dc81e5071e1..2a70deaf90d 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java @@ -54,9 +54,6 @@ public class ServerDiscoveryAndMonitoringTest extends AbstractServerDiscoveryAnd public ServerDiscoveryAndMonitoringTest(final String description, final BsonDocument definition) { super(definition); - assumeFalse("https://jira.mongodb.org/browse/JAVA-5949", - description.equals("error_handling_handshake.json: Network timeouts before and after the handshake completes")); - this.description = description; init(serverAddress -> NO_OP_SERVER_LISTENER, NO_OP_CLUSTER_LISTENER); } 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 328c8298b6c..0dd4218035a 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 @@ -439,16 +439,6 @@ public static void applyCustomizations(final TestDef def) { .file("server-discovery-and-monitoring", "pool-clear-on-error-checkout"); def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") .file("server-discovery-and-monitoring", "pool-cleared-on-min-pool-size-population-error"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-error-fail-single"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-timeout-error-single"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-error-fail-replicaset"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-timeout-error-replicaset"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-server-description-unchanged-on-min-pool-size-population-error"); // session tests def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") From c5e68fc385e9819e7805dd158a87b65e41d319a4 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 23 Apr 2026 14:02:13 +0100 Subject: [PATCH 13/26] - Re-enable JAVA-5949-skipped SDAM/backpressure tests - Fixed https://jira.mongodb.org/browse/JAVA-5664 (https://jira.mongodb.org/browse/DRIVERS-1785) --- .../com/mongodb/client/unified/EventMatcher.java | 7 +++++++ .../functional/com/mongodb/client/unified/UnifiedTest.java | 2 ++ .../mongodb/client/unified/UnifiedTestModifications.java | 6 ------ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java b/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java index 7c069b8d95b..e1abe106c1d 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java @@ -26,6 +26,7 @@ import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandSucceededEvent; import com.mongodb.event.ConnectionCheckOutFailedEvent; +import com.mongodb.event.ConnectionCheckedInEvent; import com.mongodb.event.ConnectionClosedEvent; import com.mongodb.event.ConnectionCreatedEvent; import com.mongodb.event.ConnectionPoolClearedEvent; @@ -208,6 +209,12 @@ public void waitForConnectionPoolEvents(final String client, final BsonDocument case "connectionReadyEvent": eventClass = ConnectionReadyEvent.class; break; + case "connectionClosedEvent": + eventClass = ConnectionClosedEvent.class; + break; + case "connectionCheckedInEvent": + eventClass = ConnectionCheckedInEvent.class; + break; default: throw new UnsupportedOperationException("Unsupported event: " + event.getFirstKey()); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java index cf003078f04..a6f14688b45 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java @@ -800,6 +800,8 @@ private OperationResult executeWaitForEvent(final UnifiedTestContext context, fi case "poolReadyEvent": case "connectionCreatedEvent": case "connectionReadyEvent": + case "connectionClosedEvent": + case "connectionCheckedInEvent": context.getEventMatcher().waitForConnectionPoolEvents(clientId, event, count, entities.getConnectionPoolListener(clientId)); break; case "serverHeartbeatStartedEvent": 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 0dd4218035a..040b90e8018 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 @@ -433,12 +433,6 @@ public static void applyCustomizations(final TestDef def) { .test("server-discovery-and-monitoring", "serverMonitoringMode", "poll waits after successful heartbeat"); def.skipJira("https://jira.mongodb.org/browse/JAVA-4536") .file("server-discovery-and-monitoring", "interruptInUse"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") - .file("server-discovery-and-monitoring", "pool-clear-application-error"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") - .file("server-discovery-and-monitoring", "pool-clear-on-error-checkout"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") - .file("server-discovery-and-monitoring", "pool-cleared-on-min-pool-size-population-error"); // session tests def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") From 71c91f0ecd3a816dda564701644dcde919f13fd1 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 23 Apr 2026 15:11:46 +0100 Subject: [PATCH 14/26] Deferred fixes to https://jira.mongodb.org/browse/JAVA-5664 to another PR --- .../mongodb/client/unified/UnifiedTestModifications.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 040b90e8018..01bd85d1dce 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 @@ -433,6 +433,14 @@ public static void applyCustomizations(final TestDef def) { .test("server-discovery-and-monitoring", "serverMonitoringMode", "poll waits after successful heartbeat"); def.skipJira("https://jira.mongodb.org/browse/JAVA-4536") .file("server-discovery-and-monitoring", "interruptInUse"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") + .file("server-discovery-and-monitoring", "pool-clear-application-error"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") + .file("server-discovery-and-monitoring", "pool-clear-on-error-checkout"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") + .file("server-discovery-and-monitoring", "pool-cleared-on-min-pool-size-population-error"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-6174") + .file("server-discovery-and-monitoring", "backpressure-server-description-unchanged-on-min-pool-size-population-error"); // session tests def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") From 6afa1a823db39a1e56c2e97d540f0d420230e9d8 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 23 Apr 2026 18:16:51 +0100 Subject: [PATCH 15/26] Add DNS-lookup regression test for backpressure pool invalidation --- .../DefaultServerSpecification.groovy | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy index 3910da575f0..8fe409ab76c 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy @@ -259,6 +259,28 @@ class DefaultServerSpecification extends Specification { ] } + def 'DNS lookup failure should invalidate the pool'() { + given: + def exceptionToThrow = new MongoSocketException('DNS lookup failed', new ServerAddress(), + new UnknownHostException('no such host')) + BackpressureErrorLabeler.applyLabelsIfEligible(exceptionToThrow) + assert !exceptionToThrow.hasErrorLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL) + + def connectionPool = Mock(ConnectionPool) + connectionPool.get(_) >> { throw exceptionToThrow } + def serverMonitor = Mock(ServerMonitor) + def server = defaultServer(connectionPool, serverMonitor) + + when: + server.getConnection(OPERATION_CONTEXT) + + then: + def e = thrown(MongoException) + e.is(exceptionToThrow) + 1 * connectionPool.invalidate(exceptionToThrow) + 1 * serverMonitor.cancelCurrentCheck() + } + def 'failed authentication should invalidate the connection pool'() { given: def connectionPool = Mock(ConnectionPool) From 4938439da72ee4cce112ce8a8e863bafef65ef6e Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 27 Apr 2026 14:48:45 +0100 Subject: [PATCH 16/26] Addressing Claude-Code review feedback --- .../connection/BackpressureErrorLabeler.java | 14 ++------------ .../connection/DefaultServerSpecification.groovy | 2 +- .../ServerDiscoveryAndMonitoringProseTests.java | 6 +++++- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java b/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java index bdfa8d47ba9..85037b5bdb2 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java +++ b/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java @@ -17,7 +17,6 @@ package com.mongodb.internal.connection; import com.mongodb.MongoException; -import com.mongodb.MongoSecurityException; import com.mongodb.MongoSocketException; import javax.net.ssl.SSLHandshakeException; @@ -43,12 +42,6 @@ private BackpressureErrorLabeler() { } static void applyLabelsIfEligible(final Throwable t) { - if (!(t instanceof MongoException)) { - return; - } - if (t instanceof MongoSecurityException) { - return; - } if (!(t instanceof MongoSocketException)) { return; } @@ -66,7 +59,7 @@ static void applyLabelsIfEligible(final Throwable t) { mongoException.addLabel(MongoException.RETRYABLE_ERROR_LABEL); } - static boolean isDnsLookupFailure(final Throwable t) { + private static boolean isDnsLookupFailure(final Throwable t) { Throwable cause = t.getCause(); while (cause != null) { if (cause instanceof UnknownHostException) { @@ -77,10 +70,7 @@ static boolean isDnsLookupFailure(final Throwable t) { return false; } - static boolean isTlsConfigurationError(final Throwable t) { - if (!(t instanceof MongoSocketException)) { - return false; - } + private static boolean isTlsConfigurationError(final Throwable t) { Throwable cause = t.getCause(); while (cause != null) { if (cause instanceof CertificateException diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy index 8fe409ab76c..394ae48b1d0 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy @@ -259,7 +259,7 @@ class DefaultServerSpecification extends Specification { ] } - def 'DNS lookup failure should invalidate the pool'() { + def 'DNS lookup failure should not be labeled as backpressure and should invalidate the pool'() { given: def exceptionToThrow = new MongoSocketException('DNS lookup failed', new ServerAddress(), new UnknownHostException('no such host')) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index 8e13782531a..fe544c19343 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -327,7 +327,11 @@ public void testConnectionPoolBackpressure() throws InterruptedException { failedCheckOutCount >= 10); assertEquals(0, connectionPoolListener.countEvents(ConnectionPoolClearedEvent.class)); } finally { - Thread.sleep(1000); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } adminDatabase.runCommand(new Document("setParameter", 1) .append("ingressConnectionEstablishmentRateLimiterEnabled", false)); } From 98add5468012c044ced29fd57871e534bb276be0 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 27 Apr 2026 15:42:53 +0100 Subject: [PATCH 17/26] Update driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../client/ServerDiscoveryAndMonitoringProseTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index fe544c19343..8e20fdf2118 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -295,9 +295,9 @@ public void testConnectionPoolBackpressure() throws InterruptedException { MongoDatabase database = client.getDatabase(getDefaultDatabaseName()); MongoCollection collection = database.getCollection("testCollection"); - adminDatabase.runCommand(new Document("setParameter", 1) - .append("ingressConnectionEstablishmentRateLimiterEnabled", true)); try { + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRateLimiterEnabled", true)); adminDatabase.runCommand(new Document("setParameter", 1) .append("ingressConnectionEstablishmentRatePerSec", 20)); adminDatabase.runCommand(new Document("setParameter", 1) From 9ce8dde60cd768fd998e46c72a9be36e30ebcc0e Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Mon, 20 Apr 2026 11:12:27 -0600 Subject: [PATCH 18/26] Add `maxAdaptiveRetries` API (#1944) JAVA-6141 --- .../main/com/mongodb/ConnectionString.java | 52 +++++-- .../main/com/mongodb/MongoClientSettings.java | 145 +++++++++++++++++- .../src/main/com/mongodb/MongoException.java | 24 ++- .../mongodb/AbstractConnectionStringTest.java | 3 + .../com/mongodb/ConnectionStringUnitTest.java | 56 ++++--- .../MongoClientSettingsSpecification.groovy | 28 +++- .../kotlin/client/coroutine/ClientSession.kt | 6 +- .../mongodb/kotlin/client/ClientSession.kt | 9 +- .../main/com/mongodb/MongoClientOptions.java | 56 ++++++- .../src/main/com/mongodb/MongoClientURI.java | 21 +-- .../MongoClientOptionsSpecification.groovy | 23 +++ .../MongoClientURISpecification.groovy | 35 ++++- .../reactivestreams/client/ClientSession.java | 6 +- .../scala/ClientSessionImplicits.scala | 2 +- .../scala/org/mongodb/scala/package.scala | 30 ++++ .../com/mongodb/client/ClientSession.java | 10 +- 16 files changed, 433 insertions(+), 73 deletions(-) diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index 659e8fd02aa..36ab59d469f 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -17,6 +17,7 @@ package com.mongodb; import com.mongodb.annotations.Alpha; +import com.mongodb.annotations.Beta; import com.mongodb.annotations.Reason; import com.mongodb.connection.ClusterSettings; import com.mongodb.connection.ConnectionPoolSettings; @@ -264,14 +265,17 @@ *

SRV configuration:

*
    *
  • {@code srvServiceName=string}: The SRV service name. See {@link ClusterSettings#getSrvServiceName()} for details.
  • - *
  • {@code srvMaxHosts=number}: The maximum number of hosts from the SRV record to connect to.
  • + *
  • {@code srvMaxHosts=n}: The maximum number of hosts from the SRV record to connect to.
  • *
*

General configuration:

*
    - *
  • {@code retryWrites=true|false}. If true the driver will retry supported write operations if they fail due to a network error. - * Defaults to true.
  • - *
  • {@code retryReads=true|false}. If true the driver will retry supported read operations if they fail due to a network error. - * Defaults to true.
  • + *
  • {@code retryWrites=true|false}: Whether attempts to execute write commands should be retried if they fail due to a retryable error. + * Defaults to true. See also {@code maxAdaptiveRetries}.
  • + *
  • {@code retryReads=true|false}: Whether attempts to execute read commands should be retried if they fail due to a retryable error. + * Defaults to true. See also {@code maxAdaptiveRetries}.
  • + *
  • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. + * The maximum number of retry attempts when encountering a retryable overload error. + * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
  • *
  • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
  • @@ -308,6 +312,7 @@ public class ConnectionString { private WriteConcern writeConcern; private Boolean retryWrites; private Boolean retryReads; + private Integer maxAdaptiveRetries; private ReadConcern readConcern; private Integer minConnectionPoolSize; @@ -558,6 +563,7 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient GENERAL_OPTIONS_KEYS.add("servermonitoringmode"); GENERAL_OPTIONS_KEYS.add("retrywrites"); GENERAL_OPTIONS_KEYS.add("retryreads"); + GENERAL_OPTIONS_KEYS.add("maxadaptiveretries"); GENERAL_OPTIONS_KEYS.add("appname"); @@ -706,6 +712,12 @@ private void translateOptions(final Map> optionsMap) { case "retryreads": retryReads = parseBoolean(value, "retryreads"); break; + case "maxadaptiveretries": + maxAdaptiveRetries = parseInteger(value, "maxadaptiveretries"); + if (maxAdaptiveRetries < 0) { + throw new IllegalArgumentException("maxAdaptiveRetries must be >= 0"); + } + break; case "uuidrepresentation": uuidRepresentation = createUuidRepresentation(value); break; @@ -1455,13 +1467,15 @@ public WriteConcern getWriteConcern() { } /** - *

    Gets whether writes should be retried if they fail due to a network error

    - * + * Gets whether attempts to execute write commands should be retried if they fail due to a retryable error. + * See {@link MongoClientSettings.Builder#retryWrites(boolean)} for more information. + *

    * The name of this method differs from others in this class so as not to conflict with the now removed * getRetryWrites() method, which returned a primitive {@code boolean} value, and didn't allow callers to differentiate * between a false value and an unset value. * - * @return the retryWrites value, or null if unset + * @return the {@code retryWrites} value, or {@code null} if unset + * @see #getMaxAdaptiveRetries() * @since 3.9 * @mongodb.server.release 3.6 */ @@ -1471,9 +1485,11 @@ public Boolean getRetryWritesValue() { } /** - *

    Gets whether reads should be retried if they fail due to a network error

    + * Gets whether attempts to execute read commands should be retried if they fail due to a retryable error. + * See {@link MongoClientSettings.Builder#retryReads(boolean)} for more information. * - * @return the retryWrites value + * @return the {@code retryReads} value, or {@code null} if unset + * @see #getMaxAdaptiveRetries() * @since 3.11 * @mongodb.server.release 3.6 */ @@ -1482,6 +1498,19 @@ public Boolean getRetryReads() { return retryReads; } + /** + * Gets the maximum number of retry attempts when encountering a retryable overload error. + * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information. + * + * @return The {@code maxAdaptiveRetries} value, or {@code null} if unset. + * @since 5.7 + */ + @Beta(Reason.CLIENT) + @Nullable + public Integer getMaxAdaptiveRetries() { + return maxAdaptiveRetries; + } + /** * Gets the minimum connection pool size specified in the connection string. * @return the minimum connection pool size @@ -1795,6 +1824,7 @@ public boolean equals(final Object o) { && Objects.equals(writeConcern, that.writeConcern) && Objects.equals(retryWrites, that.retryWrites) && Objects.equals(retryReads, that.retryReads) + && Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries) && Objects.equals(readConcern, that.readConcern) && Objects.equals(minConnectionPoolSize, that.minConnectionPoolSize) && Objects.equals(maxConnectionPoolSize, that.maxConnectionPoolSize) @@ -1826,7 +1856,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { return Objects.hash(credential, isSrvProtocol, hosts, database, collection, directConnection, readPreference, - writeConcern, retryWrites, retryReads, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime, + writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime, maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, timeout, socketTimeout, sslEnabled, sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency, serverMonitoringMode, applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts, proxyHost, diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index 41c5f73a1d7..c1b3c4a069a 100644 --- a/driver-core/src/main/com/mongodb/MongoClientSettings.java +++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java @@ -17,6 +17,7 @@ package com.mongodb; import com.mongodb.annotations.Alpha; +import com.mongodb.annotations.Beta; import com.mongodb.annotations.Immutable; import com.mongodb.annotations.NotThreadSafe; import com.mongodb.annotations.Reason; @@ -93,6 +94,8 @@ public final class MongoClientSettings { private final WriteConcern writeConcern; private final boolean retryWrites; private final boolean retryReads; + @Nullable + private final Integer maxAdaptiveRetries; private final ReadConcern readConcern; private final MongoCredential credential; private final TransportSettings transportSettings; @@ -214,6 +217,8 @@ public static final class Builder { private WriteConcern writeConcern = WriteConcern.ACKNOWLEDGED; private boolean retryWrites = true; private boolean retryReads = true; + @Nullable + private Integer maxAdaptiveRetries; private ReadConcern readConcern = ReadConcern.DEFAULT; private CodecRegistry codecRegistry = MongoClientSettings.getDefaultCodecRegistry(); private TransportSettings transportSettings; @@ -255,6 +260,7 @@ private Builder(final MongoClientSettings settings) { writeConcern = settings.getWriteConcern(); retryWrites = settings.getRetryWrites(); retryReads = settings.getRetryReads(); + maxAdaptiveRetries = settings.getMaxAdaptiveRetries(); readConcern = settings.getReadConcern(); credential = settings.getCredential(); uuidRepresentation = settings.getUuidRepresentation(); @@ -314,6 +320,9 @@ public Builder applyConnectionString(final ConnectionString connectionString) { if (retryReadsValue != null) { retryReads = retryReadsValue; } + if (connectionString.getMaxAdaptiveRetries() != null) { + maxAdaptiveRetries = connectionString.getMaxAdaptiveRetries(); + } if (connectionString.getUuidRepresentation() != null) { uuidRepresentation = connectionString.getUuidRepresentation(); } @@ -428,13 +437,24 @@ public Builder writeConcern(final WriteConcern writeConcern) { } /** - * Sets whether writes should be retried if they fail due to a network error. + * Sets whether attempts to execute write commands should be retried if they fail due to a retryable error. + *

    + * The errors {@linkplain MongoException#hasErrorLabel(String) having} + * the {@value MongoException#RETRYABLE_ERROR_LABEL} label are not the only ones considered retryable here: + * unlike applications, which may retry operations, the driver retries commands, which gives it more control + * and allows it to safely retry attempts failed due to a broader set of errors + * than what applications may {@linkplain MongoException#RETRYABLE_ERROR_LABEL safely retry}. + *

    + * For more information on how transactions affect retries, + * see the documentation of the {@value MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL}, + * {@value MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL} error labels. * *

    Starting with the 3.11.0 release, the default value is true

    * - * @param retryWrites sets if writes should be retried if they fail due to a network error. + * @param retryWrites sets if write commands should be retried if they fail due to a retryable error. * @return this * @see #getRetryWrites() + * @see #maxAdaptiveRetries(Integer) * @mongodb.server.release 3.6 */ public Builder retryWrites(final boolean retryWrites) { @@ -443,11 +463,24 @@ public Builder retryWrites(final boolean retryWrites) { } /** - * Sets whether reads should be retried if they fail due to a network error. + * Sets whether attempts to execute read commands should be retried if they fail due to a retryable error. + *

    + * The errors {@linkplain MongoException#hasErrorLabel(String) having} + * the {@value MongoException#RETRYABLE_ERROR_LABEL} label are not the only ones considered retryable here: + * unlike applications, which may retry operations, the driver retries commands, which gives it more control + * and allows it to safely retry attempts failed due to a broader set of errors + * than what applications may {@linkplain MongoException#RETRYABLE_ERROR_LABEL safely retry}. + *

    + * For more information on how transactions affect retries, + * see the documentation of the {@value MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL}, + * {@value MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL} error labels. + *

    + * Default is {@code true}. * - * @param retryReads sets if reads should be retried if they fail due to a network error. + * @param retryReads sets if read commands should be retried if they fail due to a retryable error. * @return this * @see #getRetryReads() + * @see #maxAdaptiveRetries(Integer) * @since 3.11 * @mongodb.server.release 3.6 */ @@ -456,6 +489,76 @@ public Builder retryReads(final boolean retryReads) { return this; } + /** + * Sets the maximum number of retry attempts when executing a command and encountering + * an error {@linkplain MongoException#hasErrorLabel(String) having} + * the {@value MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} and {@value MongoException#RETRYABLE_ERROR_LABEL} labels. + * Such errors are referred to as retryable overload errors. + *

    + * Default is {@code null}, implies the value 2 and the above retry behavior. The implied value and behavior may change in + * the future in a minor version. + * This means, there is no guarantee that not setting a value is equivalent to setting the value 2. + * The value 0 results in not retrying the attempts failed due to retryable overload errors. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Interactions with {@link #retryWrites(boolean)}/{@link #retryReads(boolean)}
    Command kindInteraction
    write + * The attempts failed due to retryable overload errors are retried only if + * {@link #retryWrites(boolean)} is {@code true}. + *
    read + * The attempts failed due to retryable overload errors are retried only if + * {@link #retryReads(boolean)} is {@code true}. + *

    + * Executing a write operation, for example, {@code MongoCluster.bulkWrite}, + * may involve executing not only write commands, but also read commands. In such a situation, + * just like in other situations, the behavior related to retries depends on + * the known kind of command, not on the kind of operation. + *

    unknown + * The attempts failed due to retryable overload errors are retried only if + * {@link #retryWrites(boolean)} is {@code true} and {@link #retryReads(boolean)} is {@code true}. + *

    + * The command kind is unknown when a command is executed via the {@code MongoDatabase.runCommand} operation. + *

    + * + * @param maxAdaptiveRetries Sets the maximum number of retry attempts when encountering a retryable overload error. + * + * @return {@code this}. + * @see #getMaxAdaptiveRetries() + * @mongodb.driver.manual reference/parameters/#mongodb-parameter-param.overloadAwareServerSelectionEnabled + * overloadAwareServerSelectionEnabled: the server-side counterpart, which is configured independently + * and affects the server behavior as opposed to the client behavior. + * @since 5.7 + */ + // TODO-BACKPRESSURE Valentin Document commands that we do not retry now, but should retry according to the spec. + @Beta(Reason.CLIENT) + public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) { + if (maxAdaptiveRetries != null) { + isTrueArgument("maxAdaptiveRetries >= 0", maxAdaptiveRetries >= 0); + } + this.maxAdaptiveRetries = maxAdaptiveRetries; + return this; + } + /** * Sets the read concern. * @@ -785,11 +888,14 @@ public WriteConcern getWriteConcern() { } /** - * Returns true if writes should be retried if they fail due to a network error or other retryable error. + * Returns whether attempts to execute write commands should be retried if they fail due to a retryable error. + * See {@link Builder#retryWrites(boolean)} for more information. * *

    Starting with the 3.11.0 release, the default value is true

    * * @return the retryWrites value + * @see Builder#retryWrites(boolean) + * @see #getMaxAdaptiveRetries() * @mongodb.server.release 3.6 */ public boolean getRetryWrites() { @@ -797,9 +903,14 @@ public boolean getRetryWrites() { } /** - * Returns true if reads should be retried if they fail due to a network error or other retryable error. The default value is true. + * Returns whether attempts to execute read commands should be retried if they fail due to a retryable error. + * See {@link Builder#retryReads(boolean)} for more information. + *

    + * Default is {@code true}. * * @return the retryReads value + * @see Builder#retryReads(boolean) + * @see #getMaxAdaptiveRetries() * @since 3.11 * @mongodb.server.release 3.6 */ @@ -807,6 +918,21 @@ public boolean getRetryReads() { return retryReads; } + /** + * Returns the maximum number of retry attempts when encountering a retryable overload error. + * See {@link Builder#maxAdaptiveRetries(Integer)} for more information. + * + * @return The maximum number of retry attempts when encountering a retryable overload error. + * @see Builder#maxAdaptiveRetries(Integer) + * @since 5.7 + */ + @Beta(Reason.CLIENT) + @Nullable + // TODO-BACKPRESSURE Valentin Use the `maxAdaptiveRetries` setting when retrying. + public Integer getMaxAdaptiveRetries() { + return maxAdaptiveRetries; + } + /** * The read concern to use. * @@ -819,7 +945,7 @@ public ReadConcern getReadConcern() { } /** - * The codec registry to use, or null if not set. + * The codec registry to use. * * @return the codec registry */ @@ -1080,6 +1206,7 @@ public boolean equals(final Object o) { MongoClientSettings that = (MongoClientSettings) o; return retryWrites == that.retryWrites && retryReads == that.retryReads + && Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries) && heartbeatSocketTimeoutSetExplicitly == that.heartbeatSocketTimeoutSetExplicitly && heartbeatConnectTimeoutSetExplicitly == that.heartbeatConnectTimeoutSetExplicitly && Objects.equals(readPreference, that.readPreference) @@ -1109,7 +1236,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { - return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, readConcern, credential, transportSettings, + return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, credential, transportSettings, commandListeners, codecRegistry, loggerSettings, clusterSettings, socketSettings, heartbeatSocketSettings, connectionPoolSettings, serverSettings, sslSettings, applicationName, compressorList, uuidRepresentation, serverApi, autoEncryptionSettings, heartbeatSocketTimeoutSetExplicitly, @@ -1124,6 +1251,7 @@ public String toString() { + ", writeConcern=" + writeConcern + ", retryWrites=" + retryWrites + ", retryReads=" + retryReads + + ", maxAdaptiveRetries=" + maxAdaptiveRetries + ", readConcern=" + readConcern + ", credential=" + credential + ", transportSettings=" + transportSettings @@ -1153,6 +1281,7 @@ private MongoClientSettings(final Builder builder) { readPreference = builder.readPreference; writeConcern = builder.writeConcern; retryWrites = builder.retryWrites; + maxAdaptiveRetries = builder.maxAdaptiveRetries; retryReads = builder.retryReads; readConcern = builder.readConcern; credential = builder.credential; diff --git a/driver-core/src/main/com/mongodb/MongoException.java b/driver-core/src/main/com/mongodb/MongoException.java index 2c585c93cf4..b15023b2374 100644 --- a/driver-core/src/main/com/mongodb/MongoException.java +++ b/driver-core/src/main/com/mongodb/MongoException.java @@ -36,42 +36,52 @@ public class MongoException extends RuntimeException { /** * An error label indicating that the exception can be treated as a transient transaction error. + * See the documentation linked below for more information. * * @see #hasErrorLabel(String) + * @mongodb.driver.manual core/transactions-in-applications/#std-label-transient-transaction-error TransientTransactionError * @since 3.8 - * @mongodb.driver.manual core/transactions-in-applications/#std-label-transient-transaction-error */ public static final String TRANSIENT_TRANSACTION_ERROR_LABEL = "TransientTransactionError"; /** * An error label indicating that the exception can be treated as an unknown transaction commit result. + * See the documentation linked below for more information. * * @see #hasErrorLabel(String) + * @mongodb.driver.manual core/transactions-in-applications/#std-label-unknown-transaction-commit-result UnknownTransactionCommitResult * @since 3.8 - * @mongodb.driver.manual core/transactions-in-applications/#std-label-unknown-transaction-commit-result */ public static final String UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL = "UnknownTransactionCommitResult"; /** * Server is overloaded and shedding load. - * If you retry, use exponential backoff because the server has indicated overload. - * This label on its own does not mean that the operation can be safely retried. + * If an application retries explicitly, it should use exponential backoff because the server has indicated overload. + * This label on its own does not mean that the operation can be {@linkplain #RETRYABLE_ERROR_LABEL safely retried}. * * @see #hasErrorLabel(String) + * @see MongoClientSettings.Builder#maxAdaptiveRetries(Integer) + * @mongodb.atlas.manual overload-errors/ Overload errors * @since 5.7 * @mongodb.server.release 8.3 */ - // TODO-BACKPRESSURE Valentin Add a @mongodb.driver.manual link or something similar, see `content/atlas/source/overload-errors.txt` in https://github.com/10gen/docs-mongodb-internal/pull/17281 public static final String SYSTEM_OVERLOADED_ERROR_LABEL = "SystemOverloadedError"; /** - * The operation was not executed and is safe to retry. + * The operation is safe to retry, that is, + * retry without rereading the relevant data or considering the semantics of the operation. + *

    + * For more information on how transactions affect retries, + * see the documentation of the {@value #TRANSIENT_TRANSACTION_ERROR_LABEL}, {@value #UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL} + * error labels. * * @see #hasErrorLabel(String) + * @see MongoClientSettings.Builder#retryWrites(boolean) + * @see MongoClientSettings.Builder#retryReads(boolean) + * @mongodb.atlas.manual overload-errors/ Overload errors * @since 5.7 * @mongodb.server.release 8.3 */ - // TODO-BACKPRESSURE Valentin Add a @mongodb.driver.manual link or something similar, see `content/atlas/source/overload-errors.txt` in https://github.com/10gen/docs-mongodb-internal/pull/17281 public static final String RETRYABLE_ERROR_LABEL = "RetryableError"; private static final long serialVersionUID = -4415279469780082174L; diff --git a/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java b/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java index d511d2750eb..8aa0b7d5a9e 100644 --- a/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java +++ b/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java @@ -122,6 +122,9 @@ protected void testValidOptions() { } else if (option.getKey().equalsIgnoreCase("retrywrites")) { boolean expected = option.getValue().asBoolean().getValue(); assertEquals(expected, connectionString.getRetryWritesValue().booleanValue()); + } else if (option.getKey().equalsIgnoreCase("maxadaptiveretries")) { + int expected = option.getValue().asInt32().getValue(); + assertEquals(expected, connectionString.getMaxAdaptiveRetries().intValue()); } else if (option.getKey().equalsIgnoreCase("replicaset")) { String expected = option.getValue().asString().getValue(); assertEquals(expected, connectionString.getRequiredReplicaSetName()); diff --git a/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java b/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java index 0b3dd1a0814..c40ea5a0ab6 100644 --- a/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java +++ b/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java @@ -37,7 +37,30 @@ final class ConnectionStringUnitTest { @Test void defaults() { ConnectionString connectionStringDefault = new ConnectionString(DEFAULT_OPTIONS); - assertAll(() -> assertNull(connectionStringDefault.getServerMonitoringMode())); + assertAll( + () -> assertNull(connectionStringDefault.getServerMonitoringMode()), + () -> assertNull(connectionStringDefault.getMaxAdaptiveRetries()) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "serverMonitoringMode=stream", + "maxAdaptiveRetries=42" + }) + void equalAndHashCode(final String connectionStringOptions) { + ConnectionString default1 = new ConnectionString(DEFAULT_OPTIONS); + ConnectionString default2 = new ConnectionString(DEFAULT_OPTIONS); + String connectionString = DEFAULT_OPTIONS + connectionStringOptions; + ConnectionString actual1 = new ConnectionString(connectionString); + ConnectionString actual2 = new ConnectionString(connectionString); + assertAll( + () -> assertEquals(default1, default2), + () -> assertEquals(default1.hashCode(), default2.hashCode()), + () -> assertEquals(actual1, actual2), + () -> assertEquals(actual1.hashCode(), actual2.hashCode()), + () -> assertNotEquals(default1, actual1) + ); } @Test @@ -68,22 +91,6 @@ private static String encode(final String string) { } } - @ParameterizedTest - @ValueSource(strings = {DEFAULT_OPTIONS + "serverMonitoringMode=stream"}) - void equalAndHashCode(final String connectionString) { - ConnectionString default1 = new ConnectionString(DEFAULT_OPTIONS); - ConnectionString default2 = new ConnectionString(DEFAULT_OPTIONS); - ConnectionString actual1 = new ConnectionString(connectionString); - ConnectionString actual2 = new ConnectionString(connectionString); - assertAll( - () -> assertEquals(default1, default2), - () -> assertEquals(default1.hashCode(), default2.hashCode()), - () -> assertEquals(actual1, actual2), - () -> assertEquals(actual1.hashCode(), actual2.hashCode()), - () -> assertNotEquals(default1, actual1) - ); - } - @Test void serverMonitoringMode() { assertAll( @@ -94,7 +101,6 @@ void serverMonitoringMode() { ); } - @ParameterizedTest @ValueSource(strings = {"mongodb://foo:bar/@hostname/java?", "mongodb://foo:bar?@hostname/java/", "mongodb+srv://foo:bar/@hostname/java?", "mongodb+srv://foo:bar?@hostname/java/", @@ -109,4 +115,18 @@ void unescapedPasswordsShouldNotBeLeakedInExceptionMessages(final String input) assertFalse(exception.getMessage().contains("bar")); assertFalse(exception.getMessage().contains("12345678")); } + + @Test + void maxAdaptiveRetries() { + assertAll( + () -> assertEquals(42, + new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=42").getMaxAdaptiveRetries()), + () -> assertEquals(0, + new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=0").getMaxAdaptiveRetries()), + () -> assertThrows(IllegalArgumentException.class, + () -> new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=-1")), + () -> assertThrows(IllegalArgumentException.class, + () -> new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=invalid")) + ); + } } diff --git a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy index c8910751552..d4b59f0cb52 100644 --- a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy @@ -46,6 +46,7 @@ class MongoClientSettingsSpecification extends Specification { settings.getWriteConcern() == WriteConcern.ACKNOWLEDGED settings.getRetryWrites() settings.getRetryReads() + settings.getMaxAdaptiveRetries() == null settings.getReadConcern() == ReadConcern.DEFAULT settings.getReadPreference() == ReadPreference.primary() settings.getCommandListeners().isEmpty() @@ -82,6 +83,11 @@ class MongoClientSettingsSpecification extends Specification { then: thrown(IllegalArgumentException) + when: + builder.maxAdaptiveRetries(-1) + then: + thrown(IllegalArgumentException) + when: builder.credential(null) then: @@ -135,6 +141,7 @@ class MongoClientSettingsSpecification extends Specification { .writeConcern(WriteConcern.JOURNALED) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) .readConcern(ReadConcern.LOCAL) .applicationName('app1') .addCommandListener(commandListener) @@ -160,6 +167,7 @@ class MongoClientSettingsSpecification extends Specification { settings.getWriteConcern() == WriteConcern.JOURNALED settings.getRetryWrites() settings.getRetryReads() + settings.getMaxAdaptiveRetries() == 42 settings.getReadConcern() == ReadConcern.LOCAL settings.getApplicationName() == 'app1' settings.getSocketSettings() == SocketSettings.builder().build() @@ -200,6 +208,7 @@ class MongoClientSettingsSpecification extends Specification { .writeConcern(WriteConcern.JOURNALED) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) .readConcern(ReadConcern.LOCAL) .applicationName('app1') .addCommandListener(commandListener) @@ -330,6 +339,7 @@ class MongoClientSettingsSpecification extends Specification { + '&replicaSet=test' + '&retryWrites=true' + '&retryReads=true' + + '&maxAdaptiveRetries=42' + '&ssl=true&sslInvalidHostNameAllowed=true' + '&w=majority&wTimeoutMS=2500' + '&readPreference=secondary' @@ -398,6 +408,7 @@ class MongoClientSettingsSpecification extends Specification { .compressorList([MongoCompressor.createZlibCompressor().withProperty(MongoCompressor.LEVEL, 5)]) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) .uuidRepresentation(UuidRepresentation.STANDARD) .timeout(10000, TimeUnit.MILLISECONDS) .build() @@ -462,6 +473,7 @@ class MongoClientSettingsSpecification extends Specification { .compressorList([MongoCompressor.createZlibCompressor().withProperty(MongoCompressor.LEVEL, 5)]) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(null) def expectedSettings = builder.build() def settingsWithDefaultConnectionStringApplied = builder @@ -546,6 +558,18 @@ class MongoClientSettingsSpecification extends Specification { .build() } + def 'should allow null, 0 maxAdaptiveRetries'() { + when: + def settings = MongoClientSettings.builder().maxAdaptiveRetries(null).build() + then: + settings.getMaxAdaptiveRetries() == null + + when: + settings = MongoClientSettings.builder().maxAdaptiveRetries(0).build() + then: + settings.getMaxAdaptiveRetries() == 0 + } + def 'should only have the following fields in the builder'() { when: // A regression test so that if anymore fields are added then the builder(final MongoClientSettings settings) should be updated @@ -553,7 +577,7 @@ class MongoClientSettingsSpecification extends Specification { def expected = ['applicationName', 'autoEncryptionSettings', 'clusterSettingsBuilder', 'codecRegistry', 'commandListeners', 'compressorList', 'connectionPoolSettingsBuilder', 'contextProvider', 'credential', 'dnsClient', 'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'loggerSettingsBuilder', - 'observabilitySettings', + 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern', 'readPreference', 'retryReads', 'retryWrites', 'serverApi', 'serverSettingsBuilder', 'socketSettingsBuilder', 'sslSettingsBuilder', 'timeoutMS', 'transportSettings', 'uuidRepresentation', @@ -572,7 +596,7 @@ class MongoClientSettingsSpecification extends Specification { 'applyToSslSettings', 'autoEncryptionSettings', 'build', 'codecRegistry', 'commandListenerList', 'compressorList', 'contextProvider', 'credential', 'dnsClient', 'heartbeatConnectTimeoutMS', - 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'observabilitySettings', 'readConcern', + 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern', 'readPreference', 'retryReads', 'retryWrites', 'serverApi', 'timeout', 'transportSettings', diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt index 6c53a1faf47..a1192285da2 100644 --- a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt @@ -184,6 +184,8 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS /** * Start a transaction in the context of this session with default transaction options. A transaction can not be * started if there is already an active transaction on this session. + * + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL */ public fun startTransaction(): Unit = wrapped.startTransaction() @@ -192,15 +194,17 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * started if there is already an active transaction on this session. * * @param transactionOptions the options to apply to the transaction + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL */ public fun startTransaction(transactionOptions: TransactionOptions): Unit = wrapped.startTransaction(transactionOptions) /** - * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been + * Commit a transaction in the context of this session. A transaction can only be committed if one has first been * started. * * @return an empty publisher that indicates when the operation has completed + * @see com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL */ public suspend fun commitTransaction() { wrapped.commitTransaction().awaitFirstOrNull() diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt index 5656feb4523..0f49d667995 100644 --- a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt @@ -50,6 +50,8 @@ public class ClientSession(public val wrapped: JClientSession) : Closeable { /** * Start a transaction in the context of this session with default transaction options. A transaction can not be * started if there is already an active transaction on this session. + * + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL */ public fun startTransaction(): Unit = wrapped.startTransaction() @@ -58,13 +60,16 @@ public class ClientSession(public val wrapped: JClientSession) : Closeable { * started if there is already an active transaction on this session. * * @param transactionOptions the options to apply to the transaction + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL */ public fun startTransaction(transactionOptions: TransactionOptions): Unit = wrapped.startTransaction(transactionOptions) /** - * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been + * Commit a transaction in the context of this session. A transaction can only be committed if one has first been * started. + * + * @see com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL */ public fun commitTransaction(): Unit = wrapped.commitTransaction() @@ -82,6 +87,8 @@ public class ClientSession(public val wrapped: JClientSession) : Closeable { * @param transactionBody the body of the transaction * @param options the transaction options * @return the return value of the transaction body + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL + * @see com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL */ public fun withTransaction( transactionBody: () -> T, diff --git a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java index 1f19fba3484..fe7b827d362 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java @@ -17,6 +17,7 @@ package com.mongodb; import com.mongodb.annotations.Alpha; +import com.mongodb.annotations.Beta; import com.mongodb.annotations.Immutable; import com.mongodb.annotations.NotThreadSafe; import com.mongodb.annotations.Reason; @@ -442,11 +443,14 @@ public WriteConcern getWriteConcern() { } /** - * Returns true if writes should be retried if they fail due to a network error or other retryable error. + * Returns whether attempts to execute write commands should be retried if they fail due to a retryable error. + * See {@link MongoClientSettings.Builder#retryWrites(boolean)} for more information. * *

    Starting with the 3.11.0 release, the default value is true

    * * @return the retryWrites value + * @see Builder#retryWrites(boolean) + * @see #getMaxAdaptiveRetries() * @mongodb.server.release 3.6 * @since 3.6 */ @@ -455,9 +459,14 @@ public boolean getRetryWrites() { } /** - * Returns true if reads should be retried if they fail due to a network error or other retryable error. + * Returns whether attempts to execute read commands should be retried if they fail due to a retryable error. + * See {@link MongoClientSettings.Builder#retryReads(boolean)} for more information. + *

    + * Default is {@code true}. * * @return the retryReads value + * @see Builder#retryReads(boolean) + * @see #getMaxAdaptiveRetries() * @mongodb.server.release 3.6 * @since 3.11 */ @@ -465,6 +474,20 @@ public boolean getRetryReads() { return wrapped.getRetryReads(); } + /** + * Returns the maximum number of retry attempts when encountering a retryable overload error. + * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information. + * + * @return The maximum number of retry attempts when encountering a retryable overload error. + * @see Builder#maxAdaptiveRetries(Integer) + * @since 5.7 + */ + @Beta(Reason.CLIENT) + @Nullable + public Integer getMaxAdaptiveRetries() { + return wrapped.getMaxAdaptiveRetries(); + } + /** *

    The read concern to use.

    * @@ -1020,14 +1043,16 @@ public Builder writeConcern(final WriteConcern writeConcern) { } /** - * Sets whether writes should be retried if they fail due to a network error. + * Sets whether attempts to execute write commands should be retried if they fail due to a retryable error. + * See {@link MongoClientSettings.Builder#retryWrites(boolean)} for more information. * *

    Starting with the 3.11.0 release, the default value is true

    * - * @param retryWrites sets if writes should be retried if they fail due to a network error. + * @param retryWrites sets if write commands should be retried if they fail due to a retryable error. * @return {@code this} * @mongodb.server.release 3.6 * @see #getRetryWrites() + * @see #maxAdaptiveRetries(Integer) * @since 3.6 */ public Builder retryWrites(final boolean retryWrites) { @@ -1036,12 +1061,16 @@ public Builder retryWrites(final boolean retryWrites) { } /** - * Sets whether reads should be retried if they fail due to a network error. + * Sets whether attempts to execute read commands should be retried if they fail due to a retryable error. + * See {@link MongoClientSettings.Builder#retryReads(boolean)} for more information. + *

    + * Default is {@code true}. * - * @param retryReads sets if reads should be retried if they fail due to a network error. + * @param retryReads sets if read commands should be retried if they fail due to a retryable error. * @return {@code this} * @mongodb.server.release 3.6 * @see #getRetryReads() + * @see #maxAdaptiveRetries(Integer) * @since 3.11 */ public Builder retryReads(final boolean retryReads) { @@ -1049,6 +1078,21 @@ public Builder retryReads(final boolean retryReads) { return this; } + /** + * Sets the maximum number of retry attempts when encountering a retryable overload error. + * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information. + * + * @param maxAdaptiveRetries Sets the maximum number of retry attempts when encountering a retryable overload error. + * @return {@code this}. + * @see #getMaxAdaptiveRetries() + * @since 5.7 + */ + @Beta(Reason.CLIENT) + public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) { + wrapped.maxAdaptiveRetries(maxAdaptiveRetries); + return this; + } + /** * Sets the read concern. * diff --git a/driver-legacy/src/main/com/mongodb/MongoClientURI.java b/driver-legacy/src/main/com/mongodb/MongoClientURI.java index e471bbf1686..5d129bbd07f 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientURI.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientURI.java @@ -16,6 +16,7 @@ package com.mongodb; +import com.mongodb.annotations.Beta; import com.mongodb.lang.Nullable; import org.bson.UuidRepresentation; @@ -147,10 +148,6 @@ *

  • Used in combination with {@code w}
  • *
* - *
  • {@code retryWrites=true|false}. If true the driver will retry supported write operations if they fail due to a network error. - * Defaults to false.
  • - *
  • {@code retryReads=true|false}. If true the driver will retry supported read operations if they fail due to a network error. - * Defaults to false.
  • * * * @@ -214,10 +211,13 @@ * *

    General configuration:

    *
      - *
    • {@code retryWrites=true|false}. If true the driver will retry supported write operations if they fail due to a network error. - * Defaults to true.
    • - *
    • {@code retryReads=true|false}. If true the driver will retry supported read operations if they fail due to a network error. - * Defaults to true.
    • + *
    • {@code retryWrites=true|false}: Whether attempts to execute write commands should be retried if they fail due to a retryable error. + * Defaults to true. See also {@code maxAdaptiveRetries}.
    • + *
    • {@code retryReads=true|false}: Whether attempts to execute read commands should be retried if they fail due to a retryable error. + * Defaults to true. See also {@code maxAdaptiveRetries}.
    • + *
    • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. + * The maximum number of retry attempts when encountering a retryable overload error. + * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
    • *
    • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientOptions#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
    • @@ -381,11 +381,14 @@ public MongoClientOptions getOptions() { if (retryWritesValue != null) { builder.retryWrites(retryWritesValue); } - Boolean retryReads = proxied.getRetryReads(); if (retryReads != null) { builder.retryReads(retryReads); } + Integer maxAdaptiveRetries = proxied.getMaxAdaptiveRetries(); + if (maxAdaptiveRetries != null) { + builder.maxAdaptiveRetries(maxAdaptiveRetries); + } Integer maxConnectionPoolSize = proxied.getMaxConnectionPoolSize(); if (maxConnectionPoolSize != null) { diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy index ae1d332674c..723dddcc280 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy @@ -46,6 +46,7 @@ class MongoClientOptionsSpecification extends Specification { options.getWriteConcern() == WriteConcern.ACKNOWLEDGED options.getRetryWrites() options.getRetryReads() + options.getMaxAdaptiveRetries() == null options.getCodecRegistry() == MongoClientSettings.defaultCodecRegistry options.getUuidRepresentation() == UuidRepresentation.UNSPECIFIED options.getMinConnectionsPerHost() == 0 @@ -84,6 +85,11 @@ class MongoClientOptionsSpecification extends Specification { given: def builder = new MongoClientOptions.Builder() + when: + builder.maxAdaptiveRetries(-1) + then: + thrown(IllegalArgumentException) + when: builder.dbDecoderFactory(null) then: @@ -116,6 +122,7 @@ class MongoClientOptionsSpecification extends Specification { .readPreference(ReadPreference.secondary()) .retryWrites(true) .retryReads(false) + .maxAdaptiveRetries(42) .writeConcern(WriteConcern.JOURNALED) .readConcern(ReadConcern.MAJORITY) .minConnectionsPerHost(30) @@ -162,6 +169,7 @@ class MongoClientOptionsSpecification extends Specification { options.getServerSelector() == serverSelector options.getRetryWrites() !options.getRetryReads() + options.getMaxAdaptiveRetries() == 42 options.getServerSelectionTimeout() == 150 options.getTimeout() == 10_000 options.getMaxWaitTime() == 200 @@ -207,6 +215,7 @@ class MongoClientOptionsSpecification extends Specification { settings.writeConcern == WriteConcern.JOURNALED settings.retryWrites !settings.retryReads + settings.getMaxAdaptiveRetries() == 42 settings.autoEncryptionSettings == autoEncryptionSettings settings.codecRegistry == codecRegistry settings.commandListeners == [commandListener] @@ -227,6 +236,7 @@ class MongoClientOptionsSpecification extends Specification { optionsFromSettings.getServerSelector() == serverSelector optionsFromSettings.getRetryWrites() !optionsFromSettings.getRetryReads() + optionsFromSettings.getMaxAdaptiveRetries() == 42 optionsFromSettings.getServerSelectionTimeout() == 150 optionsFromSettings.getServerSelectionTimeout() == 150 optionsFromSettings.getMaxWaitTime() == 200 @@ -619,6 +629,7 @@ class MongoClientOptionsSpecification extends Specification { .writeConcern(WriteConcern.JOURNALED) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) .uuidRepresentation(UuidRepresentation.STANDARD) .minConnectionsPerHost(30) .connectionsPerHost(500) @@ -663,6 +674,18 @@ class MongoClientOptionsSpecification extends Specification { MongoClientOptions.builder().connectionsPerHost(0).build().getConnectionsPerHost() == 0 } + def 'should allow null, 0 maxAdaptiveRetries'() { + when: + def options = MongoClientOptions.builder().maxAdaptiveRetries(null).build() + then: + options.getMaxAdaptiveRetries() == null + + when: + options = MongoClientOptions.builder().maxAdaptiveRetries(0).build() + then: + options.getMaxAdaptiveRetries() == 0 + } + private static class MyDBEncoderFactory implements DBEncoderFactory { @Override DBEncoder create() { diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy index 241ac958c8a..fb2509554a4 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy @@ -131,6 +131,7 @@ class MongoClientURISpecification extends Specification { + 'heartbeatFrequencyMS=20000&' + 'retryWrites=true&' + 'retryReads=true&' + + 'maxAdaptiveRetries=42&' + 'uuidRepresentation=csharpLegacy&' + 'appName=app1&' + 'timeoutMS=10000') @@ -158,6 +159,7 @@ class MongoClientURISpecification extends Specification { options.getHeartbeatFrequency() == 20000 options.getRetryWrites() options.getRetryReads() + options.getMaxAdaptiveRetries() == 42 options.getUuidRepresentation() == UuidRepresentation.C_SHARP_LEGACY options.getApplicationName() == 'app1' } @@ -178,6 +180,7 @@ class MongoClientURISpecification extends Specification { !options.isSslEnabled() options.getRetryWrites() options.getRetryReads() + options.getMaxAdaptiveRetries() == null options.getUuidRepresentation() == UuidRepresentation.UNSPECIFIED } @@ -188,6 +191,7 @@ class MongoClientURISpecification extends Specification { .readPreference(ReadPreference.secondary()) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) .writeConcern(WriteConcern.JOURNALED) .minConnectionsPerHost(30) .connectionsPerHost(500) @@ -220,6 +224,7 @@ class MongoClientURISpecification extends Specification { options.getWriteConcern() == WriteConcern.JOURNALED options.getRetryWrites() options.getRetryReads() + options.getMaxAdaptiveRetries() == 42 options.getTimeout() == 10_000 options.getServerSelectionTimeout() == 150 options.getMaxWaitTime() == 200 @@ -314,24 +319,33 @@ class MongoClientURISpecification extends Specification { def 'should respect MongoClientOptions builder'() { given: - def uri = new MongoClientURI('mongodb://localhost/', MongoClientOptions.builder().connectionsPerHost(200)) + def uri = new MongoClientURI('mongodb://localhost/', MongoClientOptions.builder() + .connectionsPerHost(200) + .maxAdaptiveRetries(42)) when: def options = uri.getOptions() then: options.getConnectionsPerHost() == 200 + options.getMaxAdaptiveRetries() == 42 } def 'should override MongoClientOptions builder'() { given: - def uri = new MongoClientURI('mongodb://localhost/?maxPoolSize=250', MongoClientOptions.builder().connectionsPerHost(200)) + def uri = new MongoClientURI('mongodb://localhost/?' + + 'maxPoolSize=250' + + '&maxAdaptiveRetries=43', + MongoClientOptions.builder(). + connectionsPerHost(200) + .maxAdaptiveRetries(42)) when: def options = uri.getOptions() then: options.getConnectionsPerHost() == 250 + options.getMaxAdaptiveRetries() == 43 } def 'should be equal to another MongoClientURI with the same string values'() { @@ -371,7 +385,9 @@ class MongoClientURISpecification extends Specification { + 'socketTimeoutMS=5500;' + 'safe=false;w=1;wtimeout=2500;' + 'fsync=true;readPreference=primary;' - + 'ssl=true') | new MongoClientURI('mongodb://localhost/db.coll?minPoolSize=5;' + + 'ssl=true;' + + 'maxAdaptiveRetries=42') | new MongoClientURI('mongodb://localhost/db.coll?' + + 'minPoolSize=5;' + 'maxPoolSize=10;' + 'waitQueueTimeoutMS=150;' + 'maxIdleTimeMS=200&maxLifeTimeMS=300;' @@ -379,7 +395,8 @@ class MongoClientURISpecification extends Specification { + '&replicaSet=test;connectTimeoutMS=2500;' + 'socketTimeoutMS=5500&safe=false&w=1;' + 'wtimeout=2500;fsync=true' - + '&readPreference=primary;ssl=true') + + '&readPreference=primary;ssl=true;' + + 'maxAdaptiveRetries=42') } def 'should be not equal to another MongoClientURI with the different string values'() { @@ -401,12 +418,14 @@ class MongoClientURISpecification extends Specification { + '&readPreferenceTags=dc:ny,rack:1' + '&readPreferenceTags=dc:ny' + '&readPreferenceTags=' - + '&maxConnecting=1') | new MongoClientURI('mongodb://localhost/' + + '&maxConnecting=1' + + '&maxAdaptiveRetries=42') | new MongoClientURI('mongodb://localhost/' + '?readPreference=secondaryPreferred' + '&readPreferenceTags=dc:ny' + '&readPreferenceTags=dc:ny, rack:1' + '&readPreferenceTags=' - + '&maxConnecting=2') + + '&maxConnecting=2' + + '&maxAdaptiveRetries=43') new MongoClientURI('mongodb://ross:123@localhost/?' + 'authMechanism=SCRAM-SHA-1') | new MongoClientURI('mongodb://ross:123@localhost/?' + 'authMechanism=GSSAPI') @@ -419,7 +438,8 @@ class MongoClientURISpecification extends Specification { + 'minPoolSize=7;maxIdleTimeMS=1000;maxLifeTimeMS=2000;maxConnecting=1;' + 'replicaSet=test;' + 'connectTimeoutMS=2500;socketTimeoutMS=5500;autoConnectRetry=true;' - + 'readPreference=secondaryPreferred;safe=false;w=1;wtimeout=2600') + + 'readPreference=secondaryPreferred;safe=false;w=1;wtimeout=2600;' + + 'maxAdaptiveRetries=42') MongoClientOptions.Builder builder = MongoClientOptions.builder() .connectionsPerHost(10) @@ -433,6 +453,7 @@ class MongoClientURISpecification extends Specification { .socketTimeout(5500) .readPreference(secondaryPreferred()) .writeConcern(new WriteConcern(1, 2600)) + .maxAdaptiveRetries(42) MongoClientOptions options = builder.build() diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ClientSession.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ClientSession.java index 3d9354e9ae9..c7301807910 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ClientSession.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ClientSession.java @@ -17,6 +17,7 @@ package com.mongodb.reactivestreams.client; +import com.mongodb.MongoException; import com.mongodb.TransactionOptions; import org.reactivestreams.Publisher; @@ -65,6 +66,7 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * Start a transaction in the context of this session with default transaction options. A transaction can not be started if there is * already an active transaction on this session. * + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL * @mongodb.server.release 4.0 */ void startTransaction(); @@ -75,14 +77,16 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * * @param transactionOptions the options to apply to the transaction * + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL * @mongodb.server.release 4.0 */ void startTransaction(TransactionOptions transactionOptions); /** - * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been started. + * Commit a transaction in the context of this session. A transaction can only be committed if one has first been started. * * @return an empty publisher that indicates when the operation has completed + * @see MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL * @mongodb.server.release 4.0 */ Publisher commitTransaction(); diff --git a/driver-scala/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala b/driver-scala/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala index 9718b01c1a8..b8824dc31f5 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala @@ -33,7 +33,7 @@ trait ClientSessionImplicits { /** * Commit a transaction in the context of this session. * - * A transaction can only be commmited if one has first been started. + * A transaction can only be committed if one has first been started. */ def commitTransaction(): SingleObservable[Unit] = clientSession.commitTransaction() diff --git a/driver-scala/src/main/scala/org/mongodb/scala/package.scala b/driver-scala/src/main/scala/org/mongodb/scala/package.scala index 1cdc2d0a564..487987a6fe9 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/package.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/package.scala @@ -215,18 +215,48 @@ package object scala extends ClientSessionImplicits with ObservableImplicits wit /** * An error label indicating that the exception can be treated as a transient transaction error. + * See the documentation linked below for more information. * + * @see [[https://www.mongodb.com/docs/manual/core/transactions-in-applications/#std-label-transient-transaction-error TransientTransactionError]] * @since 2.4 */ val TRANSIENT_TRANSACTION_ERROR_LABEL: String = com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL /** * An error label indicating that the exception can be treated as an unknown transaction commit result. + * See the documentation linked below for more information. * + * @see [[https://www.mongodb.com/docs/manual/core/transactions-in-applications/#std-label-unknown-transaction-commit-result UnknownTransactionCommitResult]] * @since 2.4 */ val UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL: String = com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL + + /** + * Server is overloaded and shedding load. + * If an application retries explicitly, it should use exponential backoff because the server has indicated overload. + * This label on its own does not mean that the operation can be [[MongoException.RETRYABLE_ERROR_LABEL safely retried]]. + * + * @see [[https://www.mongodb.com/docs/atlas/overload-errors/ Overload errors]] + * @since 5.7 + * @note Requires MongoDB 8.3 or greater + */ + val SYSTEM_OVERLOADED_ERROR_LABEL: String = com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL + + /** + * The operation is safe to retry, that is, + * retry without rereading the relevant data or considering the semantics of the operation. + * + * For more information on how transactions affect retries, + * see the documentation of the + * [[MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL "TransientTransactionError"]], + * [[MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL "UnknownTransactionCommitResult"]] error labels. + * + * @see [[https://www.mongodb.com/docs/atlas/overload-errors/ Overload errors]] + * @since 5.7 + * @note Requires MongoDB 8.3 or greater + */ + val RETRYABLE_ERROR_LABEL: String = com.mongodb.MongoException.RETRYABLE_ERROR_LABEL } /** diff --git a/driver-sync/src/main/com/mongodb/client/ClientSession.java b/driver-sync/src/main/com/mongodb/client/ClientSession.java index 00ba5eba23c..d35ee570900 100644 --- a/driver-sync/src/main/com/mongodb/client/ClientSession.java +++ b/driver-sync/src/main/com/mongodb/client/ClientSession.java @@ -16,6 +16,7 @@ package com.mongodb.client; +import com.mongodb.MongoException; import com.mongodb.ServerAddress; import com.mongodb.TransactionOptions; import com.mongodb.internal.observability.micrometer.TransactionSpan; @@ -76,6 +77,7 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * Start a transaction in the context of this session with default transaction options. A transaction can not be started if there is * already an active transaction on this session. * + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL * @mongodb.server.release 4.0 */ void startTransaction(); @@ -86,13 +88,15 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * * @param transactionOptions the options to apply to the transaction * + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL * @mongodb.server.release 4.0 */ void startTransaction(TransactionOptions transactionOptions); /** - * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been started. + * Commit a transaction in the context of this session. A transaction can only be committed if one has first been started. * + * @see MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL * @mongodb.server.release 4.0 */ void commitTransaction(); @@ -110,6 +114,8 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * @param the return type of the transaction body * @param transactionBody the body of the transaction * @return the return value of the transaction body + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL + * @see MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL * @mongodb.server.release 4.0 * @since 3.11 */ @@ -122,6 +128,8 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * @param transactionBody the body of the transaction * @param options the transaction options * @return the return value of the transaction body + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL + * @see MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL * @mongodb.server.release 4.0 * @since 3.11 */ From a2b0e3caef773732dbc0022c5ccb05d3b8dc9716 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 20 Apr 2026 17:22:14 -0700 Subject: [PATCH 19/26] Add support for server selection's deprioritized servers (#1860) - Deprioritize sharded clusters on any error, all other topologies only on SystemOverloadedError. - Pass ClusterType to updateCandidate so onAttemptFailure can distinguish topology types. - Add retryable reads prose tests 3.1 and 3.2. - Change ServerSelectionSelectionTest to use BaseCluster server selection chain. JAVA-6105 JAVA-6021 JAVA-6074 --------- Co-authored-by: Valentin Kovalenko Co-authored-by: Ross Lawley --- .../internal/connection/BaseCluster.java | 13 +- .../internal/connection/OperationContext.java | 100 ++++---- .../AsyncChangeStreamBatchCursor.java | 4 +- .../operation/ChangeStreamBatchCursor.java | 4 +- .../com/mongodb/ClusterFixture.java | 64 ++--- .../OperationFunctionalSpecification.groovy | 12 +- .../mongodb/client/test/CollectionHelper.java | 56 ++--- .../connection/ConnectionSpecification.groovy | 19 +- .../netty/NettyStreamSpecification.groovy | 8 +- .../AsyncSessionBindingSpecification.groovy | 13 +- ...yncSocketChannelStreamSpecification.groovy | 8 +- .../AsyncStreamTimeoutsSpecification.groovy | 6 +- .../AwsAuthenticationSpecification.groovy | 12 +- .../CommandHelperSpecification.groovy | 8 +- .../connection/DefaultConnectionPoolTest.java | 26 +- .../GSSAPIAuthenticationSpecification.groovy | 16 +- .../GSSAPIAuthenticatorSpecification.groovy | 4 +- .../PlainAuthenticationSpecification.groovy | 12 +- .../connection/PlainAuthenticatorTest.java | 6 +- ...amSha256AuthenticationSpecification.groovy | 18 +- .../internal/connection/ServerHelper.java | 5 +- .../connection/SingleServerClusterTest.java | 6 +- .../SocketStreamHelperSpecification.groovy | 15 +- .../StreamSocketAddressSpecification.groovy | 6 +- .../TlsChannelStreamFunctionalTest.java | 4 +- .../AggregateOperationSpecification.groovy | 19 +- ...eToCollectionOperationSpecification.groovy | 4 +- ...AsyncCommandBatchCursorFunctionalTest.java | 42 ++-- .../ChangeStreamOperationSpecification.groovy | 6 +- .../CommandBatchCursorFunctionalTest.java | 61 ++--- ...ountDocumentsOperationSpecification.groovy | 8 +- ...ateCollectionOperationSpecification.groovy | 10 +- ...CreateIndexesOperationSpecification.groovy | 2 +- .../CreateViewOperationSpecification.groovy | 4 +- .../DistinctOperationSpecification.groovy | 6 +- ...ropCollectionOperationSpecification.groovy | 8 +- .../DropDatabaseOperationSpecification.groovy | 7 +- .../DropIndexOperationSpecification.groovy | 2 +- .../FindOperationSpecification.groovy | 17 +- ...stCollectionsOperationSpecification.groovy | 49 ++-- ...ListDatabasesOperationSpecification.groovy | 6 +- .../ListIndexesOperationSpecification.groovy | 24 +- ...eToCollectionOperationSpecification.groovy | 2 +- ...InlineResultsOperationSpecification.groovy | 6 +- ...ameCollectionOperationSpecification.groovy | 8 +- .../operation/TestOperationHelper.java | 7 +- .../SingleServerBindingSpecification.groovy | 6 +- .../AbstractConnectionPoolTest.java | 3 +- ...tractServerDiscoveryAndMonitoringTest.java | 7 +- .../BaseClusterSpecification.groovy | 7 +- .../internal/connection/BaseClusterTest.java | 2 +- .../DefaultConnectionPoolSpecification.groovy | 61 ++--- ...efaultServerConnectionSpecification.groovy | 11 +- .../DefaultServerSpecification.groovy | 23 +- ...ternalStreamConnectionSpecification.groovy | 120 ++++----- ...ConnectionPoolListenerSpecification.groovy | 6 +- .../connection/LoadBalancedClusterTest.java | 14 +- ...gingCommandEventSenderSpecification.groovy | 10 +- .../MultiServerClusterSpecification.groovy | 9 +- .../PlainAuthenticatorUnitTest.java | 6 +- .../ServerDeprioritizationTest.java | 232 +++++++++++++++--- .../ServerDiscoveryAndMonitoringTest.java | 7 +- .../ServerSelectionSelectionTest.java | 175 ++++++++++--- ...erverSelectionWithinLatencyWindowTest.java | 2 +- .../SingleServerClusterSpecification.groovy | 16 +- ...sageTrackingConnectionSpecification.groovy | 34 +-- .../X509AuthenticatorNoUserNameTest.java | 6 +- .../connection/X509AuthenticatorUnitTest.java | 12 +- .../InsufficientStubbingDetectorDemoTest.java | 12 +- .../AsyncOperationHelperSpecification.groovy | 8 +- .../ClientBulkWriteOperationTest.java | 4 +- ...ansactionOperationUnitSpecification.groovy | 6 +- .../operation/CursorResourceManagerTest.java | 8 +- .../ListCollectionsOperationTest.java | 4 +- .../OperationHelperSpecification.groovy | 6 +- .../OperationUnitSpecification.groovy | 6 +- .../SyncOperationHelperSpecification.groovy | 8 +- .../session/BaseClientSessionImplTest.java | 6 +- .../ServerSessionPoolSpecification.groovy | 10 +- .../test/functional/com/mongodb/DBTest.java | 3 +- ...ixedBulkWriteOperationSpecification.groovy | 2 +- .../client/RetryableReadsProseTest.java | 57 +---- .../ClientSessionBindingSpecification.groovy | 17 +- .../AbstractRetryableReadsProseTest.java | 232 ++++++++++++++++++ .../client/RetryableReadsProseTest.java | 50 +--- .../ClientSessionBindingSpecification.groovy | 18 +- .../CryptConnectionSpecification.groovy | 6 +- 87 files changed, 1217 insertions(+), 758 deletions(-) rename driver-core/src/test/unit/com/mongodb/{ => internal}/connection/ServerSelectionSelectionTest.java (56%) create mode 100644 driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java diff --git a/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java b/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java index 4146d06c22e..2f0fcaa6379 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java @@ -113,9 +113,9 @@ abstract class BaseCluster implements Cluster { private volatile ClusterDescription description; BaseCluster(final ClusterId clusterId, - final ClusterSettings settings, - final ClusterableServerFactory serverFactory, - final ClientMetadata clientMetadata) { + final ClusterSettings settings, + final ClusterableServerFactory serverFactory, + final ClientMetadata clientMetadata) { this.clusterId = notNull("clusterId", clusterId); this.settings = notNull("settings", settings); this.serverFactory = notNull("serverFactory", serverFactory); @@ -159,7 +159,7 @@ public ServerTuple selectServer(final ServerSelector serverSelector, final Opera if (serverTuple != null) { ServerAddress serverAddress = serverTuple.getServerDescription().getAddress(); logServerSelectionSucceeded(operationContext, clusterId, serverAddress, serverSelector, currentDescription); - serverDeprioritization.updateCandidate(serverAddress); + serverDeprioritization.updateCandidate(serverAddress, currentDescription.getType()); return serverTuple; } computedServerSelectionTimeout.onExpired(() -> @@ -302,7 +302,7 @@ private boolean handleServerSelectionRequest( if (serverTuple != null) { ServerAddress serverAddress = serverTuple.getServerDescription().getAddress(); logServerSelectionSucceeded(operationContext, clusterId, serverAddress, request.originalSelector, description); - serverDeprioritization.updateCandidate(serverAddress); + serverDeprioritization.updateCandidate(serverAddress, description.getType()); request.onResult(serverTuple, null); return true; } @@ -361,8 +361,7 @@ private static ServerSelector getCompleteServerSelector( final ClusterSettings settings) { List selectors = Stream.of( getRaceConditionPreFilteringSelector(serversSnapshot), - serverSelector, - serverDeprioritization.getServerSelector(), + serverDeprioritization.apply(serverSelector), settings.getServerSelector(), // may be null new LatencyMinimizingServerSelector(settings.getLocalThreshold(MILLISECONDS), MILLISECONDS), AtMostTwoRandomServerSelector.instance(), diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java index f23d5e5226b..71352abee60 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java +++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java @@ -17,6 +17,7 @@ import com.mongodb.Function; import com.mongodb.MongoConnectionPoolClearedException; +import com.mongodb.MongoException; import com.mongodb.ReadConcern; import com.mongodb.RequestContext; import com.mongodb.ServerAddress; @@ -27,7 +28,6 @@ import com.mongodb.internal.IgnorableRequestContext; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.TimeoutSettings; -import com.mongodb.internal.VisibleForTesting; import com.mongodb.internal.observability.micrometer.Span; import com.mongodb.internal.observability.micrometer.TracingManager; import com.mongodb.internal.session.SessionContext; @@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL; import static java.util.stream.Collectors.toList; /** @@ -76,7 +77,7 @@ public OperationContext(final RequestContext requestContext, final SessionContex null); } - public static OperationContext simpleOperationContext( + static OperationContext simpleOperationContext( final TimeoutSettings timeoutSettings, @Nullable final ServerApi serverApi) { return new OperationContext( IgnorableRequestContext.INSTANCE, @@ -113,6 +114,15 @@ public OperationContext withOperationName(final String operationName) { operationName, tracingSpan); } + /** + * TODO-JAVA-6058: This method enables overriding the ServerDeprioritization state. + * It is a temporary solution to handle cases where deprioritization state persists across operations. + */ + public OperationContext withNewServerDeprioritization() { + return new OperationContext(id, requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), tracingManager, serverApi, + operationName, tracingSpan); + } + public long getId() { return id; } @@ -152,8 +162,7 @@ public void setTracingSpan(final Span tracingSpan) { this.tracingSpan = tracingSpan; } - @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) - public OperationContext(final long id, + private OperationContext(final long id, final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, @@ -174,26 +183,6 @@ public OperationContext(final long id, this.tracingSpan = tracingSpan; } - @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) - public OperationContext(final long id, - final RequestContext requestContext, - final SessionContext sessionContext, - final TimeoutContext timeoutContext, - final TracingManager tracingManager, - @Nullable final ServerApi serverApi, - @Nullable final String operationName) { - this.id = id; - this.serverDeprioritization = new ServerDeprioritization(); - this.requestContext = requestContext; - this.sessionContext = sessionContext; - this.timeoutContext = timeoutContext; - this.tracingManager = tracingManager; - this.serverApi = serverApi; - this.operationName = operationName; - this.tracingSpan = null; - } - - /** * @return The same {@link ServerDeprioritization} if called on the same {@link OperationContext}. */ @@ -227,26 +216,32 @@ public OperationContext withOverride(final TimeoutContextOverride timeoutContext public static final class ServerDeprioritization { @Nullable private ServerAddress candidate; + @Nullable + private ClusterType clusterType; private final Set deprioritized; - private final DeprioritizingSelector selector; private ServerDeprioritization() { candidate = null; deprioritized = new HashSet<>(); - selector = new DeprioritizingSelector(); + clusterType = null; } /** - * The returned {@link ServerSelector} tries to {@linkplain ServerSelector#select(ClusterDescription) select} - * only the {@link ServerDescription}s that do not have deprioritized {@link ServerAddress}es. - * If no such {@link ServerDescription} can be selected, then it selects {@link ClusterDescription#getServerDescriptions()}. + * The returned {@link ServerSelector} wraps the provided selector and attempts + * {@linkplain ServerSelector#select(ClusterDescription) server selection} in two passes: + *
        + *
      1. First pass: selects using the wrapped selector with only non-deprioritized {@link ServerDescription}s.
      2. + *
      3. Second pass: if the first pass selects no {@link ServerDescription}s, + * selects using the wrapped selector again with all {@link ServerDescription}s, including deprioritized ones.
      4. + *
      */ - ServerSelector getServerSelector() { - return selector; + ServerSelector apply(final ServerSelector wrappedSelector) { + return new DeprioritizingSelector(wrappedSelector); } - void updateCandidate(final ServerAddress serverAddress) { - candidate = serverAddress; + void updateCandidate(final ServerAddress serverAddress, final ClusterType clusterType) { + this.candidate = serverAddress; + this.clusterType = clusterType; } public void onAttemptFailure(final Throwable failure) { @@ -254,7 +249,13 @@ public void onAttemptFailure(final Throwable failure) { candidate = null; return; } - deprioritized.add(candidate); + + // As per spec: sharded clusters deprioritize on any error, other topologies only on overload + boolean isSystemOverloadedError = failure instanceof MongoException + && ((MongoException) failure).hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL); + if (clusterType == ClusterType.SHARDED || isSystemOverloadedError) { + deprioritized.add(candidate); + } } /** @@ -263,24 +264,41 @@ public void onAttemptFailure(final Throwable failure) { * which indeed may be used concurrently. {@link DeprioritizingSelector} does not need to be thread-safe. */ private final class DeprioritizingSelector implements ServerSelector { - private DeprioritizingSelector() { + private final ServerSelector wrappedSelector; + + private DeprioritizingSelector(final ServerSelector wrappedSelector) { + this.wrappedSelector = wrappedSelector; } @Override public List select(final ClusterDescription clusterDescription) { List serverDescriptions = clusterDescription.getServerDescriptions(); - if (!isEnabled(clusterDescription.getType())) { - return serverDescriptions; + + // TODO-JAVA-5908: Evaluate whether using the early-return optimization has a meaningful performance impact on server selection. + if (serverDescriptions.size() == 1 || deprioritized.isEmpty()) { + return wrappedSelector.select(clusterDescription); } + + // TODO-JAVA-5908: Evaluate whether using a loop instead of Stream has a meaningful performance impact on server selection. List nonDeprioritizedServerDescriptions = serverDescriptions .stream() .filter(serverDescription -> !deprioritized.contains(serverDescription.getAddress())) .collect(toList()); - return nonDeprioritizedServerDescriptions.isEmpty() ? serverDescriptions : nonDeprioritizedServerDescriptions; - } - private boolean isEnabled(final ClusterType clusterType) { - return clusterType == ClusterType.SHARDED; + // TODO-JAVA-5908: Evaluate whether using the early-return optimization has a meaningful performance impact on server selection. + if (nonDeprioritizedServerDescriptions.isEmpty()) { + return wrappedSelector.select(clusterDescription); + } + + List selected = wrappedSelector.select( + new ClusterDescription( + clusterDescription.getConnectionMode(), + clusterDescription.getType(), + clusterDescription.getSrvResolutionException(), + nonDeprioritizedServerDescriptions, + clusterDescription.getClusterSettings(), + clusterDescription.getServerSettings())); + return selected.isEmpty() ? wrappedSelector.select(clusterDescription) : selected; } } } diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java index ce7127e0dc3..7c32dc41404 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java @@ -73,7 +73,9 @@ final class AsyncChangeStreamBatchCursor implements AsyncAggregateResponseBat this.wrapped = new AtomicReference<>(assertNotNull(wrapped)); this.binding = binding; binding.retain(); - this.initialOperationContext = operationContext.withOverride(TimeoutContext::withMaxTimeAsMaxAwaitTimeOverride); + this.initialOperationContext = operationContext + .withOverride(TimeoutContext::withMaxTimeAsMaxAwaitTimeOverride) + .withNewServerDeprioritization(); this.resumeToken = resumeToken; this.maxWireVersion = maxWireVersion; isClosed = new AtomicBoolean(); diff --git a/driver-core/src/main/com/mongodb/internal/operation/ChangeStreamBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/ChangeStreamBatchCursor.java index cf9f1dcf6c4..226deafa9fa 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ChangeStreamBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ChangeStreamBatchCursor.java @@ -85,7 +85,9 @@ final class ChangeStreamBatchCursor implements AggregateResponseBatchCursor("admin", new BsonDocument("buildInfo", new BsonInt32(1)), new BsonDocumentCodec()) - .execute(new ClusterBinding(getCluster(), ReadPreference.nearest()), OPERATION_CONTEXT)); + .execute(new ClusterBinding(getCluster(), ReadPreference.nearest()), createOperationContext())); } return serverVersion; } - public static final OperationContext OPERATION_CONTEXT = new OperationContext( - IgnorableRequestContext.INSTANCE, - new ReadConcernAwareNoOpSessionContext(ReadConcern.DEFAULT), - new TimeoutContext(TIMEOUT_SETTINGS), - getServerApi()); + public static OperationContext createOperationContext() { + return new OperationContext( + IgnorableRequestContext.INSTANCE, + new ReadConcernAwareNoOpSessionContext(ReadConcern.DEFAULT), + new TimeoutContext(TIMEOUT_SETTINGS), + getServerApi()); + } public static final InternalOperationContextFactory OPERATION_CONTEXT_FACTORY = new InternalOperationContextFactory(TIMEOUT_SETTINGS, getServerApi()); @@ -255,7 +257,7 @@ public static boolean hasEncryptionTestsEnabled() { public static Document getServerStatus() { return new CommandReadOperation<>("admin", new BsonDocument("serverStatus", new BsonInt32(1)), new DocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), createOperationContext()); } public static boolean supportsFsync() { @@ -270,7 +272,7 @@ static class ShutdownHook extends Thread { public void run() { if (cluster != null) { try { - new DropDatabaseOperation(getDefaultDatabaseName(), WriteConcern.ACKNOWLEDGED).execute(getBinding(), OPERATION_CONTEXT); + new DropDatabaseOperation(getDefaultDatabaseName(), WriteConcern.ACKNOWLEDGED).execute(getBinding(), createOperationContext()); } catch (MongoCommandException e) { // if we do not have permission to drop the database, assume it is cleaned up in some other way if (!e.getMessage().contains("Command dropDatabase requires authentication")) { @@ -322,7 +324,7 @@ public static synchronized ConnectionString getConnectionString() { try { BsonDocument helloResult = new CommandReadOperation<>("admin", new BsonDocument(LEGACY_HELLO, new BsonInt32(1)), new BsonDocumentCodec()) - .execute(new ClusterBinding(cluster, ReadPreference.nearest()), OPERATION_CONTEXT); + .execute(new ClusterBinding(cluster, ReadPreference.nearest()), createOperationContext()); if (helloResult.containsKey("setName")) { connectionString = new ConnectionString(DEFAULT_URI + "/?replicaSet=" + helloResult.getString("setName").getValue()); @@ -374,7 +376,9 @@ public static ReadWriteBinding getBinding(final Cluster cluster) { } public static ReadWriteBinding getBinding(final TimeoutSettings timeoutSettings) { - return getBinding(getCluster(), ReadPreference.primary(), createNewOperationContext(timeoutSettings)); + return getBinding(getCluster(), + ReadPreference.primary(), + createOperationContext().withTimeoutContext(new TimeoutContext(timeoutSettings))); } public static ReadWriteBinding getBinding(final OperationContext operationContext) { @@ -382,11 +386,7 @@ public static ReadWriteBinding getBinding(final OperationContext operationContex } public static ReadWriteBinding getBinding(final ReadPreference readPreference) { - return getBinding(getCluster(), readPreference, OPERATION_CONTEXT); - } - - public static OperationContext createNewOperationContext(final TimeoutSettings timeoutSettings) { - return OPERATION_CONTEXT.withTimeoutContext(new TimeoutContext(timeoutSettings)); + return getBinding(getCluster(), readPreference, createOperationContext()); } private static ReadWriteBinding getBinding(final Cluster cluster, @@ -403,7 +403,7 @@ private static ReadWriteBinding getBinding(final Cluster cluster, } public static SingleConnectionBinding getSingleConnectionBinding() { - return new SingleConnectionBinding(getCluster(), ReadPreference.primary(), OPERATION_CONTEXT); + return new SingleConnectionBinding(getCluster(), ReadPreference.primary(), createOperationContext()); } public static AsyncSingleConnectionBinding getAsyncSingleConnectionBinding() { @@ -411,7 +411,7 @@ public static AsyncSingleConnectionBinding getAsyncSingleConnectionBinding() { } public static AsyncSingleConnectionBinding getAsyncSingleConnectionBinding(final Cluster cluster) { - return new AsyncSingleConnectionBinding(cluster, ReadPreference.primary(), OPERATION_CONTEXT); + return new AsyncSingleConnectionBinding(cluster, ReadPreference.primary(), createOperationContext()); } public static AsyncReadWriteBinding getAsyncBinding(final Cluster cluster) { @@ -419,11 +419,11 @@ public static AsyncReadWriteBinding getAsyncBinding(final Cluster cluster) { } public static AsyncReadWriteBinding getAsyncBinding() { - return getAsyncBinding(getAsyncCluster(), ReadPreference.primary(), OPERATION_CONTEXT); + return getAsyncBinding(getAsyncCluster(), ReadPreference.primary(), createOperationContext()); } public static AsyncReadWriteBinding getAsyncBinding(final TimeoutSettings timeoutSettings) { - return getAsyncBinding(createNewOperationContext(timeoutSettings)); + return getAsyncBinding(createOperationContext().withTimeoutContext(new TimeoutContext(timeoutSettings))); } public static AsyncReadWriteBinding getAsyncBinding(final OperationContext operationContext) { @@ -431,7 +431,7 @@ public static AsyncReadWriteBinding getAsyncBinding(final OperationContext opera } public static AsyncReadWriteBinding getAsyncBinding(final ReadPreference readPreference) { - return getAsyncBinding(getAsyncCluster(), readPreference, OPERATION_CONTEXT); + return getAsyncBinding(getAsyncCluster(), readPreference, createOperationContext()); } public static AsyncReadWriteBinding getAsyncBinding( @@ -605,7 +605,7 @@ public static BsonDocument getServerParameters() { if (serverParameters == null) { serverParameters = new CommandReadOperation<>("admin", new BsonDocument("getParameter", new BsonString("*")), new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), createOperationContext()); } return serverParameters; } @@ -673,7 +673,7 @@ public static void configureFailPoint(final BsonDocument failPointDocument) { if (!isSharded()) { try { new CommandReadOperation<>("admin", failPointDocument, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), createOperationContext()); } catch (MongoCommandException e) { if (e.getErrorCode() == COMMAND_NOT_FOUND_ERROR_CODE) { failsPointsSupported = false; @@ -689,7 +689,7 @@ public static void disableFailPoint(final String failPoint) { .append("mode", new BsonString("off")); try { new CommandReadOperation<>("admin", failPointDocument, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), createOperationContext()); } catch (MongoCommandException e) { // ignore } @@ -703,7 +703,7 @@ public static T executeSync(final WriteOperation op) { @SuppressWarnings("overloads") public static T executeSync(final WriteOperation op, final ReadWriteBinding binding) { - return op.execute(binding, applySessionContext(OPERATION_CONTEXT, binding.getReadPreference())); + return op.execute(binding, applySessionContext(createOperationContext(), binding.getReadPreference())); } @SuppressWarnings("overloads") @@ -713,7 +713,7 @@ public static T executeSync(final ReadOperation op) { @SuppressWarnings("overloads") public static T executeSync(final ReadOperation op, final ReadWriteBinding binding) { - return op.execute(binding, OPERATION_CONTEXT); + return op.execute(binding, createOperationContext()); } @SuppressWarnings("overloads") @@ -729,7 +729,7 @@ public static T executeAsync(final WriteOperation op) throws Throwable { @SuppressWarnings("overloads") public static T executeAsync(final WriteOperation op, final AsyncReadWriteBinding binding) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - op.executeAsync(binding, applySessionContext(OPERATION_CONTEXT, binding.getReadPreference()), futureResultCallback); + op.executeAsync(binding, applySessionContext(createOperationContext(), binding.getReadPreference()), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } @@ -741,7 +741,7 @@ public static T executeAsync(final ReadOperation op) throws Throwable @SuppressWarnings("overloads") public static T executeAsync(final ReadOperation op, final AsyncReadBinding binding) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - op.executeAsync(binding, OPERATION_CONTEXT, futureResultCallback); + op.executeAsync(binding, createOperationContext(), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } @@ -811,19 +811,19 @@ public static List collectCursorResults(final BatchCursor batchCursor) public static AsyncConnectionSource getWriteConnectionSource(final AsyncReadWriteBinding binding) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - binding.getWriteConnectionSource(OPERATION_CONTEXT, futureResultCallback); + binding.getWriteConnectionSource(createOperationContext(), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } public static AsyncConnectionSource getReadConnectionSource(final AsyncReadWriteBinding binding) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - binding.getReadConnectionSource(OPERATION_CONTEXT, futureResultCallback); + binding.getReadConnectionSource(createOperationContext(), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } public static AsyncConnection getConnection(final AsyncConnectionSource source) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - source.getConnection(OPERATION_CONTEXT, futureResultCallback); + source.getConnection(createOperationContext(), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } @@ -866,7 +866,7 @@ private static OperationContext applySessionContext(final OperationContext opera return operationContext.withSessionContext(simpleSessionContext); } - public static OperationContext getOperationContext(final ReadPreference readPreference) { - return applySessionContext(OPERATION_CONTEXT, readPreference); + public static OperationContext createOperationContext(final ReadPreference readPreference) { + return applySessionContext(createOperationContext(), readPreference); } } diff --git a/driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy b/driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy index 6648edc50c7..a0dd685d4b3 100644 --- a/driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy @@ -61,7 +61,7 @@ import spock.lang.Specification import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.TIMEOUT import static com.mongodb.ClusterFixture.checkReferenceCountReachesTarget import static com.mongodb.ClusterFixture.executeAsync @@ -108,7 +108,7 @@ class OperationFunctionalSpecification extends Specification { void acknowledgeWrite(final SingleConnectionBinding binding) { new MixedBulkWriteOperation(getNamespace(), [new InsertRequest(new BsonDocument())], true, - ACKNOWLEDGED, false).execute(binding, OPERATION_CONTEXT) + ACKNOWLEDGED, false).execute(binding, createOperationContext()) binding.release() } @@ -279,7 +279,7 @@ class OperationFunctionalSpecification extends Specification { BsonDocument expectedCommand=null, Boolean checkSecondaryOk=false, ReadPreference readPreference=ReadPreference.primary(), Boolean retryable = false, ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false) { - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasActiveTransaction() >> activeTransaction getReadConcern() >> readConcern @@ -353,7 +353,7 @@ class OperationFunctionalSpecification extends Specification { Boolean checkCommand = true, BsonDocument expectedCommand = null, Boolean checkSecondaryOk = false, ReadPreference readPreference = ReadPreference.primary(), Boolean retryable = false, ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false) { - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasActiveTransaction() >> activeTransaction getReadConcern() >> readConcern @@ -447,7 +447,7 @@ class OperationFunctionalSpecification extends Specification { } } - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { hasSession() >> true hasActiveTransaction() >> false @@ -488,7 +488,7 @@ class OperationFunctionalSpecification extends Specification { } } - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { hasSession() >> true hasActiveTransaction() >> false diff --git a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java index 935c2979fc4..e6c28d9d5bc 100644 --- a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java +++ b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java @@ -16,6 +16,7 @@ package com.mongodb.client.test; +import com.mongodb.ClusterFixture; import com.mongodb.MongoClientSettings; import com.mongodb.MongoCommandException; import com.mongodb.MongoNamespace; @@ -72,7 +73,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.executeAsync; import static com.mongodb.ClusterFixture.getBinding; import static java.lang.String.format; @@ -93,7 +93,7 @@ public CollectionHelper(final Codec codec, final MongoNamespace namespace) { public T hello() { return new CommandReadOperation<>("admin", BsonDocument.parse("{isMaster: 1}"), codec) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public static void drop(final MongoNamespace namespace) { @@ -106,7 +106,7 @@ public static void drop(final MongoNamespace namespace, final WriteConcern write boolean success = false; while (!success) { try { - new DropCollectionOperation(namespace, writeConcern).execute(getBinding(), OPERATION_CONTEXT); + new DropCollectionOperation(namespace, writeConcern).execute(getBinding(), ClusterFixture.createOperationContext()); success = true; } catch (MongoWriteConcernException e) { LOGGER.info("Retrying drop collection after a write concern error: " + e); @@ -131,7 +131,7 @@ public static void dropDatabase(final String name, final WriteConcern writeConce return; } try { - new DropDatabaseOperation(name, writeConcern).execute(getBinding(), OPERATION_CONTEXT); + new DropDatabaseOperation(name, writeConcern).execute(getBinding(), ClusterFixture.createOperationContext()); } catch (MongoCommandException e) { if (!e.getErrorMessage().contains("ns not found")) { throw e; @@ -141,7 +141,7 @@ public static void dropDatabase(final String name, final WriteConcern writeConce public static BsonDocument getCurrentClusterTime() { return new CommandReadOperation("admin", new BsonDocument("ping", new BsonInt32(1)), new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT).getDocument("$clusterTime", null); + .execute(getBinding(), ClusterFixture.createOperationContext()).getDocument("$clusterTime", null); } public MongoNamespace getNamespace() { @@ -235,7 +235,7 @@ public void create(final String collectionName, final CreateCollectionOptions op boolean success = false; while (!success) { try { - operation.execute(getBinding(), OPERATION_CONTEXT); + operation.execute(getBinding(), ClusterFixture.createOperationContext()); success = true; } catch (MongoCommandException e) { if ("Interrupted".equals(e.getErrorCodeName())) { @@ -254,7 +254,7 @@ public void killCursor(final MongoNamespace namespace, final ServerCursor server .append("cursors", new BsonArray(singletonList(new BsonInt64(serverCursor.getId())))); try { new CommandReadOperation<>(namespace.getDatabaseName(), command, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } catch (Exception e) { // Ignore any exceptions killing old cursors } @@ -286,7 +286,7 @@ public void insertDocuments(final List documents, final WriteConce for (BsonDocument document : documents) { insertRequests.add(new InsertRequest(document)); } - new MixedBulkWriteOperation(namespace, insertRequests, true, writeConcern, false).execute(binding, OPERATION_CONTEXT); + new MixedBulkWriteOperation(namespace, insertRequests, true, writeConcern, false).execute(binding, ClusterFixture.createOperationContext()); } public void insertDocuments(final Document... documents) { @@ -329,7 +329,7 @@ public List find() { public Optional listSearchIndex(final String indexName) { ListSearchIndexesOperation listSearchIndexesOperation = new ListSearchIndexesOperation<>(namespace, codec, indexName, null, null, null, null, true); - BatchCursor cursor = listSearchIndexesOperation.execute(getBinding(), OPERATION_CONTEXT); + BatchCursor cursor = listSearchIndexesOperation.execute(getBinding(), ClusterFixture.createOperationContext()); List results = new ArrayList<>(); while (cursor.hasNext()) { @@ -342,13 +342,13 @@ public Optional listSearchIndex(final String indexName) { public void createSearchIndex(final SearchIndexRequest searchIndexModel) { CreateSearchIndexesOperation searchIndexesOperation = new CreateSearchIndexesOperation(namespace, singletonList(searchIndexModel)); - searchIndexesOperation.execute(getBinding(), OPERATION_CONTEXT); + searchIndexesOperation.execute(getBinding(), ClusterFixture.createOperationContext()); } public List find(final Codec codec) { BatchCursor cursor = new FindOperation<>(namespace, codec) .sort(new BsonDocument("_id", new BsonInt32(1))) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); List results = new ArrayList<>(); while (cursor.hasNext()) { results.addAll(cursor.next()); @@ -367,7 +367,7 @@ public void updateOne(final Bson filter, final Bson update, final boolean isUpse WriteRequest.Type.UPDATE) .upsert(isUpsert)), true, WriteConcern.ACKNOWLEDGED, false) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void replaceOne(final Bson filter, final Bson update, final boolean isUpsert) { @@ -377,7 +377,7 @@ public void replaceOne(final Bson filter, final Bson update, final boolean isUps WriteRequest.Type.REPLACE) .upsert(isUpsert)), true, WriteConcern.ACKNOWLEDGED, false) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void deleteOne(final Bson filter) { @@ -392,7 +392,7 @@ private void delete(final Bson filter, final boolean multi) { new MixedBulkWriteOperation(namespace, singletonList(new DeleteRequest(filter.toBsonDocument(Document.class, registry)).multi(multi)), true, WriteConcern.ACKNOWLEDGED, false) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public List find(final Bson filter) { @@ -417,7 +417,7 @@ private List aggregate(final List pipeline, final Decoder decode bsonDocumentPipeline.add(cur.toBsonDocument(Document.class, registry)); } BatchCursor cursor = new AggregateOperation<>(namespace, bsonDocumentPipeline, decoder, level) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); List results = new ArrayList<>(); while (cursor.hasNext()) { results.addAll(cursor.next()); @@ -452,7 +452,7 @@ public List find(final BsonDocument filter, final BsonDocument sort, fina public List find(final BsonDocument filter, final BsonDocument sort, final BsonDocument projection, final Decoder decoder) { BatchCursor cursor = new FindOperation<>(namespace, decoder).filter(filter).sort(sort) - .projection(projection).execute(getBinding(), OPERATION_CONTEXT); + .projection(projection).execute(getBinding(), ClusterFixture.createOperationContext()); List results = new ArrayList<>(); while (cursor.hasNext()) { results.addAll(cursor.next()); @@ -465,7 +465,7 @@ public long count() { } public long count(final ReadBinding binding) { - return new CountDocumentsOperation(namespace).execute(binding, OPERATION_CONTEXT); + return new CountDocumentsOperation(namespace).execute(binding, ClusterFixture.createOperationContext()); } public long count(final AsyncReadWriteBinding binding) throws Throwable { @@ -474,7 +474,7 @@ public long count(final AsyncReadWriteBinding binding) throws Throwable { public long count(final Bson filter) { return new CountDocumentsOperation(namespace) - .filter(toBsonDocument(filter)).execute(getBinding(), OPERATION_CONTEXT); + .filter(toBsonDocument(filter)).execute(getBinding(), ClusterFixture.createOperationContext()); } public BsonDocument wrap(final Document document) { @@ -487,36 +487,36 @@ public BsonDocument toBsonDocument(final Bson document) { public void createIndex(final BsonDocument key) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(key)), WriteConcern.ACKNOWLEDGED) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void createIndex(final Document key) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(wrap(key))), WriteConcern.ACKNOWLEDGED) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void createUniqueIndex(final Document key) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(wrap(key)).unique(true)), WriteConcern.ACKNOWLEDGED) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void createIndex(final Document key, final String defaultLanguage) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(wrap(key)).defaultLanguage(defaultLanguage)), WriteConcern.ACKNOWLEDGED).execute( - getBinding(), OPERATION_CONTEXT); + getBinding(), ClusterFixture.createOperationContext()); } public void createIndex(final Bson key) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(key.toBsonDocument(Document.class, registry))), WriteConcern.ACKNOWLEDGED).execute( - getBinding(), OPERATION_CONTEXT); + getBinding(), ClusterFixture.createOperationContext()); } public List listIndexes(){ List indexes = new ArrayList<>(); BatchCursor cursor = new ListIndexesOperation<>(namespace, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); while (cursor.hasNext()) { indexes.addAll(cursor.next()); } @@ -526,7 +526,7 @@ public List listIndexes(){ public static void killAllSessions() { try { new CommandReadOperation<>("admin", - new BsonDocument("killAllSessions", new BsonArray()), new BsonDocumentCodec()).execute(getBinding(), OPERATION_CONTEXT); + new BsonDocument("killAllSessions", new BsonArray()), new BsonDocumentCodec()).execute(getBinding(), ClusterFixture.createOperationContext()); } catch (MongoCommandException e) { // ignore exception caused by killing the implicit session that the killAllSessions command itself is running in } @@ -537,7 +537,7 @@ public void renameCollection(final MongoNamespace newNamespace) { new CommandReadOperation<>("admin", new BsonDocument("renameCollection", new BsonString(getNamespace().getFullName())) .append("to", new BsonString(newNamespace.getFullName())), new BsonDocumentCodec()).execute( - getBinding(), OPERATION_CONTEXT); + getBinding(), ClusterFixture.createOperationContext()); } catch (MongoCommandException e) { // do nothing } @@ -549,11 +549,11 @@ public void runAdminCommand(final String command) { public void runAdminCommand(final BsonDocument command) { new CommandReadOperation<>("admin", command, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void runAdminCommand(final BsonDocument command, final ReadPreference readPreference) { new CommandReadOperation<>("admin", command, new BsonDocumentCodec()) - .execute(getBinding(readPreference), OPERATION_CONTEXT); + .execute(getBinding(readPreference), ClusterFixture.createOperationContext()); } } diff --git a/driver-core/src/test/functional/com/mongodb/connection/ConnectionSpecification.groovy b/driver-core/src/test/functional/com/mongodb/connection/ConnectionSpecification.groovy index 5658ec5ea43..5dd6145f063 100644 --- a/driver-core/src/test/functional/com/mongodb/connection/ConnectionSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/connection/ConnectionSpecification.groovy @@ -17,13 +17,14 @@ package com.mongodb.connection import com.mongodb.OperationFunctionalSpecification +import com.mongodb.internal.connection.OperationContext import com.mongodb.internal.operation.CommandReadOperation import org.bson.BsonDocument import org.bson.BsonInt32 import org.bson.codecs.BsonDocumentCodec import static com.mongodb.ClusterFixture.LEGACY_HELLO -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getBinding import static com.mongodb.connection.ConnectionDescription.getDefaultMaxMessageSize import static com.mongodb.connection.ConnectionDescription.getDefaultMaxWriteBatchSize @@ -32,8 +33,9 @@ class ConnectionSpecification extends OperationFunctionalSpecification { def 'should have id'() { when: - def source = getBinding().getReadConnectionSource(OPERATION_CONTEXT) - def connection = source.getConnection(OPERATION_CONTEXT) + def operationContext = createOperationContext() + def source = getBinding().getReadConnectionSource(operationContext) + def connection = source.getConnection(operationContext) then: connection.getDescription().getConnectionId() != null @@ -45,13 +47,14 @@ class ConnectionSpecification extends OperationFunctionalSpecification { def 'should have description'() { when: - def commandResult = getHelloResult() + def operationContext = createOperationContext() + def commandResult = getHelloResult(operationContext) def expectedMaxMessageSize = commandResult.getNumber('maxMessageSizeBytes', new BsonInt32(getDefaultMaxMessageSize())).intValue() def expectedMaxBatchCount = commandResult.getNumber('maxWriteBatchSize', new BsonInt32(getDefaultMaxWriteBatchSize())).intValue() - def source = getBinding().getReadConnectionSource(OPERATION_CONTEXT) - def connection = source.getConnection(OPERATION_CONTEXT) + def source = getBinding().getReadConnectionSource(operationContext) + def connection = source.getConnection(operationContext) then: connection.description.serverAddress == source.getServerDescription().getAddress() @@ -64,8 +67,8 @@ class ConnectionSpecification extends OperationFunctionalSpecification { connection?.release() source?.release() } - private static BsonDocument getHelloResult() { + private static BsonDocument getHelloResult(OperationContext operationContext) { new CommandReadOperation('admin', new BsonDocument(LEGACY_HELLO, new BsonInt32(1)), - new BsonDocumentCodec()).execute(getBinding(), OPERATION_CONTEXT) + new BsonDocumentCodec()).execute(getBinding(), operationContext) } } diff --git a/driver-core/src/test/functional/com/mongodb/connection/netty/NettyStreamSpecification.groovy b/driver-core/src/test/functional/com/mongodb/connection/netty/NettyStreamSpecification.groovy index e582e0fc398..8747b8c75b9 100644 --- a/driver-core/src/test/functional/com/mongodb/connection/netty/NettyStreamSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/connection/netty/NettyStreamSpecification.groovy @@ -18,7 +18,7 @@ import com.mongodb.spock.Slow import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getSslSettings class NettyStreamSpecification extends Specification { @@ -43,7 +43,7 @@ class NettyStreamSpecification extends Specification { def stream = factory.create(new ServerAddress()) when: - stream.open(OPERATION_CONTEXT) + stream.open(createOperationContext()) then: !stream.isClosed() @@ -69,7 +69,7 @@ class NettyStreamSpecification extends Specification { def stream = factory.create(new ServerAddress()) when: - stream.open(OPERATION_CONTEXT) + stream.open(createOperationContext()) then: thrown(MongoSocketOpenException) @@ -96,7 +96,7 @@ class NettyStreamSpecification extends Specification { def callback = new CallbackErrorHolder() when: - stream.openAsync(OPERATION_CONTEXT, callback) + stream.openAsync(createOperationContext(), callback) then: callback.getError().is(exception) diff --git a/driver-core/src/test/functional/com/mongodb/internal/binding/AsyncSessionBindingSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/binding/AsyncSessionBindingSpecification.groovy index 173cd9f0935..81ab2ead1e2 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/binding/AsyncSessionBindingSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/binding/AsyncSessionBindingSpecification.groovy @@ -16,17 +16,17 @@ package com.mongodb.internal.binding +import com.mongodb.ClusterFixture import com.mongodb.internal.async.SingleResultCallback import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT - class AsyncSessionBindingSpecification extends Specification { def 'should wrap the passed in async binding'() { given: def wrapped = Mock(AsyncReadWriteBinding) def binding = new AsyncSessionBinding(wrapped) + def operationContext = ClusterFixture.createOperationContext() when: binding.getCount() @@ -52,17 +52,18 @@ class AsyncSessionBindingSpecification extends Specification { then: 1 * wrapped.release() + when: - binding.getReadConnectionSource(OPERATION_CONTEXT, Stub(SingleResultCallback)) + binding.getReadConnectionSource(operationContext, Stub(SingleResultCallback)) then: - 1 * wrapped.getReadConnectionSource(OPERATION_CONTEXT, _) + 1 * wrapped.getReadConnectionSource(operationContext, _) when: - binding.getWriteConnectionSource(OPERATION_CONTEXT, Stub(SingleResultCallback)) + binding.getWriteConnectionSource(operationContext, Stub(SingleResultCallback)) then: - 1 * wrapped.getWriteConnectionSource(OPERATION_CONTEXT, _) + 1 * wrapped.getWriteConnectionSource(operationContext, _) } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncSocketChannelStreamSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncSocketChannelStreamSpecification.groovy index 85f23350984..2660693eccf 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncSocketChannelStreamSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncSocketChannelStreamSpecification.groovy @@ -13,7 +13,7 @@ import com.mongodb.spock.Slow import java.util.concurrent.CountDownLatch -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getSslSettings import static java.util.concurrent.TimeUnit.MILLISECONDS @@ -40,7 +40,7 @@ class AsyncSocketChannelStreamSpecification extends Specification { def stream = factory.create(new ServerAddress('host1')) when: - stream.open(OPERATION_CONTEXT) + stream.open(createOperationContext()) then: !stream.isClosed() @@ -66,7 +66,7 @@ class AsyncSocketChannelStreamSpecification extends Specification { def stream = factory.create(new ServerAddress()) when: - stream.open(OPERATION_CONTEXT) + stream.open(createOperationContext()) then: thrown(MongoSocketOpenException) @@ -90,7 +90,7 @@ class AsyncSocketChannelStreamSpecification extends Specification { def callback = new CallbackErrorHolder() when: - stream.openAsync(OPERATION_CONTEXT, callback) + stream.openAsync(createOperationContext(), callback) then: callback.getError().is(exception) diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncStreamTimeoutsSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncStreamTimeoutsSpecification.groovy index 3589362b8ac..fc4cd8be576 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncStreamTimeoutsSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncStreamTimeoutsSpecification.groovy @@ -30,7 +30,7 @@ import com.mongodb.spock.Slow import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getCredentialWithCache import static com.mongodb.ClusterFixture.getServerApi import static com.mongodb.ClusterFixture.getSslSettings @@ -49,7 +49,7 @@ class AsyncStreamTimeoutsSpecification extends OperationFunctionalSpecification .create(new ServerId(new ClusterId(), new ServerAddress(new InetSocketAddress('192.168.255.255', 27017)))) when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: thrown(MongoSocketOpenException) @@ -63,7 +63,7 @@ class AsyncStreamTimeoutsSpecification extends OperationFunctionalSpecification new ServerAddress(new InetSocketAddress('192.168.255.255', 27017)))) when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: thrown(MongoSocketOpenException) diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/AwsAuthenticationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/AwsAuthenticationSpecification.groovy index 8dd53bc1c03..501be77d6d9 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/AwsAuthenticationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/AwsAuthenticationSpecification.groovy @@ -19,7 +19,7 @@ import spock.lang.Specification import java.util.function.Supplier import static com.mongodb.AuthenticationMechanism.MONGODB_AWS -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getConnectionString import static com.mongodb.ClusterFixture.getCredential @@ -52,7 +52,7 @@ class AwsAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoCommandException) @@ -71,7 +71,7 @@ class AwsAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -101,7 +101,7 @@ class AwsAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -160,10 +160,10 @@ class AwsAuthenticationSpecification extends Specification { private static void openConnection(final InternalConnection connection, final boolean async) { if (async) { FutureResultCallback futureResultCallback = new FutureResultCallback() - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get(ClusterFixture.TIMEOUT, SECONDS) } else { - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) } } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy index f1585f82595..ec5f3644549 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy @@ -31,7 +31,7 @@ import java.util.concurrent.CountDownLatch import static com.mongodb.ClusterFixture.CLIENT_METADATA import static com.mongodb.ClusterFixture.LEGACY_HELLO -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getCredentialWithCache import static com.mongodb.ClusterFixture.getPrimary @@ -48,7 +48,7 @@ class CommandHelperSpecification extends Specification { new NettyStreamFactory(SocketSettings.builder().build(), getSslSettings()), getCredentialWithCache(), CLIENT_METADATA, [], LoggerSettings.builder().build(), null, getServerApi()) .create(new ServerId(new ClusterId(), getPrimary())) - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) } def cleanup() { @@ -62,7 +62,7 @@ class CommandHelperSpecification extends Specification { Throwable receivedException = null def latch1 = new CountDownLatch(1) executeCommandAsync('admin', new BsonDocument(LEGACY_HELLO, new BsonInt32(1)), getClusterConnectionMode(), - getServerApi(), connection, OPERATION_CONTEXT) + getServerApi(), connection, createOperationContext()) { document, exception -> receivedDocument = document; receivedException = exception; latch1.countDown() } latch1.await() @@ -74,7 +74,7 @@ class CommandHelperSpecification extends Specification { when: def latch2 = new CountDownLatch(1) executeCommandAsync('admin', new BsonDocument('non-existent-command', new BsonInt32(1)), getClusterConnectionMode(), - getServerApi(), connection, OPERATION_CONTEXT) + getServerApi(), connection, createOperationContext()) { document, exception -> receivedDocument = document; receivedException = exception; latch2.countDown() } latch2.await() diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java index 81e778b4a61..5187f692459 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoConnectionPoolClearedException; import com.mongodb.MongoServerUnavailableException; import com.mongodb.ServerAddress; @@ -60,7 +61,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.ClusterFixture.createOperationContext; @@ -173,7 +173,7 @@ public void shouldThrowOnPoolClosed() { String expectedExceptionMessage = "The server at 127.0.0.1:27017 is no longer available"; MongoServerUnavailableException exception; - exception = assertThrows(MongoServerUnavailableException.class, () -> provider.get(OPERATION_CONTEXT)); + exception = assertThrows(MongoServerUnavailableException.class, () -> provider.get(ClusterFixture.createOperationContext())); assertEquals(expectedExceptionMessage, exception.getMessage()); SupplyingCallback supplyingCallback = new SupplyingCallback<>(); provider.getAsync(createOperationContext(TIMEOUT_SETTINGS.withMaxWaitTimeMS(50)), supplyingCallback); @@ -194,10 +194,10 @@ public void shouldExpireConnectionAfterMaxLifeTime() throws InterruptedException provider.ready(); // when - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); sleep(100); provider.doMaintenance(); - provider.get(OPERATION_CONTEXT); + provider.get(ClusterFixture.createOperationContext()); // then assertTrue(connectionFactory.getNumCreatedConnections() >= 2); // should really be two, but it's racy @@ -215,7 +215,7 @@ public void shouldExpireConnectionAfterLifeTimeOnClose() throws InterruptedExcep provider.ready(); // when - InternalConnection connection = provider.get(OPERATION_CONTEXT); + InternalConnection connection = provider.get(ClusterFixture.createOperationContext()); sleep(50); connection.close(); @@ -236,10 +236,10 @@ public void shouldExpireConnectionAfterMaxIdleTime() throws InterruptedException provider.ready(); // when - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); sleep(100); provider.doMaintenance(); - provider.get(OPERATION_CONTEXT); + provider.get(ClusterFixture.createOperationContext()); // then assertTrue(connectionFactory.getNumCreatedConnections() >= 2); // should really be two, but it's racy @@ -258,10 +258,10 @@ public void shouldCloseConnectionAfterExpiration() throws InterruptedException { provider.ready(); // when - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); sleep(50); provider.doMaintenance(); - provider.get(OPERATION_CONTEXT); + provider.get(ClusterFixture.createOperationContext()); // then assertTrue(connectionFactory.getCreatedConnections().get(0).isClosed()); @@ -280,10 +280,10 @@ public void shouldCreateNewConnectionAfterExpiration() throws InterruptedExcepti provider.ready(); // when - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); sleep(50); provider.doMaintenance(); - InternalConnection secondConnection = provider.get(OPERATION_CONTEXT); + InternalConnection secondConnection = provider.get(ClusterFixture.createOperationContext()); // then assertNotNull(secondConnection); @@ -302,7 +302,7 @@ public void shouldPruneAfterMaintenanceTaskRuns() throws InterruptedException { .build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY); provider.ready(); - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); // when @@ -322,7 +322,7 @@ void infiniteMaxSize() { List connections = new ArrayList<>(); try { for (int i = 0; i < 2 * defaultMaxSize; i++) { - connections.add(provider.get(OPERATION_CONTEXT)); + connections.add(provider.get(ClusterFixture.createOperationContext())); } } finally { connections.forEach(connection -> { diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticationSpecification.groovy index cc3e0401bb5..fa649022aaa 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticationSpecification.groovy @@ -36,7 +36,7 @@ import javax.security.auth.Subject import javax.security.auth.login.LoginContext import static com.mongodb.AuthenticationMechanism.GSSAPI -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getConnectionString import static com.mongodb.ClusterFixture.getCredential @@ -58,7 +58,7 @@ class GSSAPIAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoCommandException) @@ -77,7 +77,7 @@ class GSSAPIAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -99,7 +99,7 @@ class GSSAPIAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoSecurityException) @@ -131,7 +131,7 @@ class GSSAPIAuthenticationSpecification extends Specification { def connection = createConnection(async, getMongoCredential(subject)) openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -175,7 +175,7 @@ class GSSAPIAuthenticationSpecification extends Specification { def connection = createConnection(async, getMongoCredential(saslClientProperties)) openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -219,10 +219,10 @@ class GSSAPIAuthenticationSpecification extends Specification { private static void openConnection(final InternalConnection connection, final boolean async) { if (async) { FutureResultCallback futureResultCallback = new FutureResultCallback() - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get(ClusterFixture.TIMEOUT, SECONDS) } else { - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) } } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticatorSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticatorSpecification.groovy index 223698d561c..f7fd3534960 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticatorSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticatorSpecification.groovy @@ -30,7 +30,7 @@ import spock.lang.Specification import javax.security.auth.login.LoginContext import static com.mongodb.AuthenticationMechanism.GSSAPI -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getLoginContextName import static com.mongodb.ClusterFixture.getPrimary import static com.mongodb.ClusterFixture.getServerApi @@ -57,7 +57,7 @@ class GSSAPIAuthenticatorSpecification extends Specification { .create(new ServerId(new ClusterId(), getPrimary())) when: - internalConnection.open(OPERATION_CONTEXT) + internalConnection.open(createOperationContext()) then: 1 * subjectProvider.getSubject() >> subject diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticationSpecification.groovy index e8c2a408220..b1078711f7d 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticationSpecification.groovy @@ -32,7 +32,7 @@ import spock.lang.IgnoreIf import spock.lang.Specification import static com.mongodb.AuthenticationMechanism.PLAIN -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getConnectionString import static com.mongodb.ClusterFixture.getCredential @@ -52,7 +52,7 @@ class PlainAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoCommandException) @@ -71,7 +71,7 @@ class PlainAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -90,7 +90,7 @@ class PlainAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoSecurityException) @@ -123,10 +123,10 @@ class PlainAuthenticationSpecification extends Specification { private static void openConnection(final InternalConnection connection, final boolean async) { if (async) { FutureResultCallback futureResultCallback = new FutureResultCallback() - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get(ClusterFixture.TIMEOUT, SECONDS) } else { - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) } } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java index b95b9c96894..6fb9f48d078 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.LoggerSettings; import com.mongodb.MongoCredential; import com.mongodb.MongoSecurityException; @@ -33,7 +34,6 @@ import java.util.Collections; import static com.mongodb.ClusterFixture.CLIENT_METADATA; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getClusterConnectionMode; import static com.mongodb.ClusterFixture.getServerApi; import static com.mongodb.ClusterFixture.getSslSettings; @@ -69,14 +69,14 @@ public void tearDown() { public void testSuccessfulAuthentication() { PlainAuthenticator authenticator = new PlainAuthenticator(getCredentialWithCache(userName, source, password.toCharArray()), getClusterConnectionMode(), getServerApi()); - authenticator.authenticate(internalConnection, connectionDescription, OPERATION_CONTEXT); + authenticator.authenticate(internalConnection, connectionDescription, ClusterFixture.createOperationContext()); } @Test(expected = MongoSecurityException.class) public void testUnsuccessfulAuthentication() { PlainAuthenticator authenticator = new PlainAuthenticator(getCredentialWithCache(userName, source, "wrong".toCharArray()), getClusterConnectionMode(), getServerApi()); - authenticator.authenticate(internalConnection, connectionDescription, OPERATION_CONTEXT); + authenticator.authenticate(internalConnection, connectionDescription, ClusterFixture.createOperationContext()); } private static MongoCredentialWithCache getCredentialWithCache(final String userName, final String source, final char[] password) { diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ScramSha256AuthenticationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/ScramSha256AuthenticationSpecification.groovy index 36aac9b6908..b5baa3837f3 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ScramSha256AuthenticationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ScramSha256AuthenticationSpecification.groovy @@ -33,7 +33,7 @@ import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.createAsyncCluster import static com.mongodb.ClusterFixture.createCluster import static com.mongodb.ClusterFixture.getBinding @@ -88,12 +88,12 @@ class ScramSha256AuthenticationSpecification extends Specification { def binding = getBinding() new CommandReadOperation<>('admin', new BsonDocumentWrapper(createUserCommand, new DocumentCodec()), new DocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) } def dropUser(final String userName) { def binding = getBinding() - def operationContext = ClusterFixture.getOperationContext(binding.getReadPreference()) + def operationContext = ClusterFixture.createOperationContext(binding.getReadPreference()) new CommandReadOperation<>('admin', new BsonDocument('dropUser', new BsonString(userName)), new BsonDocumentCodec()).execute(binding, operationContext) } @@ -105,7 +105,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .execute(new ClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT) + .execute(new ClusterBinding(cluster, ReadPreference.primary()), createOperationContext()) then: noExceptionThrown() @@ -128,7 +128,7 @@ class ScramSha256AuthenticationSpecification extends Specification { def binding = new AsyncClusterBinding(cluster, ReadPreference.primary()) new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .executeAsync(binding, OPERATION_CONTEXT, callback) + .executeAsync(binding, createOperationContext(), callback) callback.get() then: @@ -148,7 +148,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .execute(new ClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT) + .execute(new ClusterBinding(cluster, ReadPreference.primary()), createOperationContext()) then: thrown(MongoSecurityException) @@ -168,7 +168,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .executeAsync(new AsyncClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT, + .executeAsync(new AsyncClusterBinding(cluster, ReadPreference.primary()), createOperationContext(), callback) callback.get() @@ -189,7 +189,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .execute(new ClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT) + .execute(new ClusterBinding(cluster, ReadPreference.primary()), createOperationContext()) then: noExceptionThrown() @@ -209,7 +209,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .executeAsync(new AsyncClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT, + .executeAsync(new AsyncClusterBinding(cluster, ReadPreference.primary()), createOperationContext(), callback) callback.get() diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ServerHelper.java b/driver-core/src/test/functional/com/mongodb/internal/connection/ServerHelper.java index 0295e8c1f9f..8e9b2385ee5 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ServerHelper.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ServerHelper.java @@ -23,7 +23,6 @@ import com.mongodb.internal.binding.AsyncConnectionSource; import com.mongodb.internal.selector.ServerAddressSelector; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getAsyncCluster; import static com.mongodb.ClusterFixture.getCluster; import static com.mongodb.assertions.Assertions.fail; @@ -54,7 +53,7 @@ public static void waitForLastRelease(final Cluster cluster) { public static void waitForLastRelease(final ServerAddress address, final Cluster cluster) { ConcurrentPool pool = connectionPool( - cluster.selectServer(new ServerAddressSelector(address), OPERATION_CONTEXT).getServer()); + cluster.selectServer(new ServerAddressSelector(address), ClusterFixture.createOperationContext()).getServer()); long startTime = System.currentTimeMillis(); while (pool.getInUseCount() > 0) { try { @@ -70,7 +69,7 @@ public static void waitForLastRelease(final ServerAddress address, final Cluster } private static ConcurrentPool getConnectionPool(final ServerAddress address, final Cluster cluster) { - return connectionPool(cluster.selectServer(new ServerAddressSelector(address), OPERATION_CONTEXT).getServer()); + return connectionPool(cluster.selectServer(new ServerAddressSelector(address), ClusterFixture.createOperationContext()).getServer()); } private static void checkPool(final ServerAddress address, final Cluster cluster) { diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java index 62fa6c27032..a624a27454c 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.LoggerSettings; import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; @@ -37,7 +38,6 @@ import java.util.Collections; import static com.mongodb.ClusterFixture.CLIENT_METADATA; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY; import static com.mongodb.ClusterFixture.getCredential; import static com.mongodb.ClusterFixture.getDefaultDatabaseName; @@ -93,7 +93,7 @@ public void shouldGetServerWithOkDescription() { setUpCluster(getPrimary()); // when - ServerTuple serverTuple = cluster.selectServer(clusterDescription -> getPrimaries(clusterDescription), OPERATION_CONTEXT); + ServerTuple serverTuple = cluster.selectServer(clusterDescription -> getPrimaries(clusterDescription), ClusterFixture.createOperationContext()); // then assertTrue(serverTuple.getServerDescription().isOk()); @@ -102,7 +102,7 @@ public void shouldGetServerWithOkDescription() { @Test public void shouldSuccessfullyQueryASecondaryWithPrimaryReadPreference() { // given - OperationContext operationContext = OPERATION_CONTEXT; + OperationContext operationContext = ClusterFixture.createOperationContext(); ServerAddress secondary = getSecondary(); setUpCluster(secondary); String collectionName = getClass().getName(); diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy index 68a82fcbf74..ef0e7d09264 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy @@ -33,7 +33,6 @@ import javax.net.ssl.SSLSocket import javax.net.ssl.SSLSocketFactory import java.lang.reflect.Method -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getPrimary @@ -87,7 +86,7 @@ class SocketStreamHelperSpecification extends Specification { when: SocketStreamHelper.initialize( - OPERATION_CONTEXT.withTimeoutContext(new TimeoutContext( + createOperationContext().withTimeoutContext(new TimeoutContext( new TimeoutSettings( 1, 100, @@ -110,7 +109,8 @@ class SocketStreamHelperSpecification extends Specification { Socket socket = SocketFactory.default.createSocket() when: - SocketStreamHelper.initialize(OPERATION_CONTEXT, socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketStreamHelper.initialize(createOperationContext(), socket, getSocketAddresses(getPrimary(), + new DefaultInetAddressResolver()).get(0), SocketSettings.builder().build(), SslSettings.builder().build()) then: @@ -126,7 +126,8 @@ class SocketStreamHelperSpecification extends Specification { SSLSocket socket = SSLSocketFactory.default.createSocket() when: - SocketStreamHelper.initialize(OPERATION_CONTEXT, socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketStreamHelper.initialize(createOperationContext(), socket, getSocketAddresses(getPrimary(), + new DefaultInetAddressResolver()).get(0), SocketSettings.builder().build(), sslSettings) then: @@ -147,7 +148,8 @@ class SocketStreamHelperSpecification extends Specification { SSLSocket socket = SSLSocketFactory.default.createSocket() when: - SocketStreamHelper.initialize(OPERATION_CONTEXT, socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketStreamHelper.initialize(createOperationContext(), socket, getSocketAddresses(getPrimary(), + new DefaultInetAddressResolver()).get(0), SocketSettings.builder().build(), sslSettings) then: @@ -166,7 +168,8 @@ class SocketStreamHelperSpecification extends Specification { Socket socket = SocketFactory.default.createSocket() when: - SocketStreamHelper.initialize(OPERATION_CONTEXT, socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketStreamHelper.initialize(createOperationContext(), socket, getSocketAddresses(getPrimary(), + new DefaultInetAddressResolver()).get(0), SocketSettings.builder().build(), SslSettings.builder().enabled(true).build()) then: diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/StreamSocketAddressSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/StreamSocketAddressSpecification.groovy index 0283ce44f7b..520dd3932de 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/StreamSocketAddressSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/StreamSocketAddressSpecification.groovy @@ -13,7 +13,7 @@ import com.mongodb.spock.Slow import javax.net.SocketFactory import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getSslSettings class StreamSocketAddressSpecification extends Specification { @@ -44,7 +44,7 @@ class StreamSocketAddressSpecification extends Specification { def socketStream = new SocketStream(serverAddress, null, socketSettings, sslSettings, socketFactory, bufferProvider) when: - socketStream.open(OPERATION_CONTEXT) + socketStream.open(createOperationContext()) then: !socket0.isConnected() @@ -83,7 +83,7 @@ class StreamSocketAddressSpecification extends Specification { def socketStream = new SocketStream(serverAddress, inetAddressResolver, socketSettings, sslSettings, socketFactory, bufferProvider) when: - socketStream.open(OPERATION_CONTEXT) + socketStream.open(createOperationContext()) then: thrown(MongoSocketOpenException) diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/TlsChannelStreamFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/TlsChannelStreamFunctionalTest.java index 3af1eaa33e1..46e0cc6f964 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/TlsChannelStreamFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/TlsChannelStreamFunctionalTest.java @@ -184,11 +184,11 @@ void shouldNotCallBeginHandshakeMoreThenOnceDuringTlsSessionEstablishment() thro .build()); Stream stream = streamFactory.create(getPrimaryServerDescription().getAddress()); - stream.open(ClusterFixture.OPERATION_CONTEXT); + stream.open(ClusterFixture.createOperationContext()); ByteBuf wrap = new ByteBufNIO(ByteBuffer.wrap(new byte[]{1, 3, 4})); //when - stream.write(Collections.singletonList(wrap), ClusterFixture.OPERATION_CONTEXT); + stream.write(Collections.singletonList(wrap), ClusterFixture.createOperationContext()); //then SECONDS.sleep(5); diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateOperationSpecification.groovy index aa7506d6516..a39b7ca6448 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateOperationSpecification.groovy @@ -52,12 +52,11 @@ import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.collectCursorResults import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getAsyncCluster import static com.mongodb.ClusterFixture.getCluster -import static com.mongodb.ClusterFixture.getOperationContext +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.isSharded import static com.mongodb.ClusterFixture.isStandalone import static com.mongodb.ExplainVerbosity.QUERY_PLANNER @@ -232,7 +231,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def binding = ClusterFixture.getBinding(ClusterFixture.getCluster()) new CreateViewOperation(getDatabaseName(), viewName, getCollectionName(), [], WriteConcern.ACKNOWLEDGED) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) when: AggregateOperation operation = new AggregateOperation(viewNamespace, [], new DocumentCodec()) @@ -246,7 +245,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { cleanup: binding = ClusterFixture.getBinding(ClusterFixture.getCluster()) new DropCollectionOperation(viewNamespace, WriteConcern.ACKNOWLEDGED) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) where: async << [true, false] @@ -273,7 +272,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { .allowDiskUse(allowDiskUse) def binding = ClusterFixture.getBinding() - def cursor = operation.execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: cursor.next()*.getString('name') == ['Pete', 'Sam', 'Pete'] @@ -288,7 +287,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { .batchSize(batchSize) def binding = ClusterFixture.getBinding() - def cursor = operation.execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: cursor.next()*.getString('name') == ['Pete', 'Sam', 'Pete'] @@ -356,7 +355,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def binding = ClusterFixture.getBinding() new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(2)), - new BsonDocumentCodec()).execute(binding, getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, createOperationContext(binding.getReadPreference())) def expectedComment = 'this is a comment' def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) .comment(new BsonString(expectedComment)) @@ -372,7 +371,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { cleanup: binding = ClusterFixture.getBinding() new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(0)), - new BsonDocumentCodec()).execute(binding, getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, createOperationContext(binding.getReadPreference())) profileCollectionHelper.drop() where: @@ -381,7 +380,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -423,7 +422,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateToCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateToCollectionOperationSpecification.groovy index 6ebdcdc6b40..a14c4323fdd 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateToCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateToCollectionOperationSpecification.groovy @@ -278,7 +278,7 @@ class AggregateToCollectionOperationSpecification extends OperationFunctionalSpe def profileCollectionHelper = getCollectionHelper(new MongoNamespace(getDatabaseName(), 'system.profile')) def binding = getBinding() new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(2)), - new BsonDocumentCodec()).execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) def expectedComment = 'this is a comment' AggregateToCollectionOperation operation = createOperation(getNamespace(), [Aggregates.out('outputCollection').toBsonDocument(BsonDocument, registry)], ACKNOWLEDGED) @@ -293,7 +293,7 @@ class AggregateToCollectionOperationSpecification extends OperationFunctionalSpe cleanup: new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(0)), - new BsonDocumentCodec()).execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) profileCollectionHelper.drop() where: diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java index 58e3e47ba74..103a18df32e 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java @@ -17,6 +17,7 @@ package com.mongodb.internal.operation; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCursorNotFoundException; import com.mongodb.MongoQueryException; import com.mongodb.ReadPreference; @@ -55,7 +56,6 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.checkReferenceCountReachesTarget; import static com.mongodb.ClusterFixture.getAsyncBinding; import static com.mongodb.ClusterFixture.getConnection; @@ -111,7 +111,7 @@ void cleanup() { void shouldExhaustCursorAsyncWithMultipleBatches() { // given BsonDocument commandResult = executeFindCommand(0, 3); // Fetch in batches of size 3 - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); // when @@ -133,7 +133,7 @@ void shouldExhaustCursorAsyncWithMultipleBatches() { void shouldExhaustCursorAsyncWithClosedCursor() { // given BsonDocument commandResult = executeFindCommand(0, 3); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); cursor.close(); @@ -156,7 +156,7 @@ void shouldExhaustCursorAsyncWithEmptyCursor() { getCollectionHelper().deleteMany(Filters.empty()); BsonDocument commandResult = executeFindCommand(0, 3); // No documents to fetch - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); // when @@ -175,7 +175,7 @@ void theServerCursorShouldNotBeNull() { BsonDocument commandResult = executeFindCommand(2); AsyncCommandCursor coreCursor = new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); assertNotNull(coreCursor.getServerCursor()); @@ -187,7 +187,7 @@ void shouldGetExceptionsForOperationsOnTheCursorAfterClosing() { BsonDocument commandResult = executeFindCommand(5); AsyncCommandCursor coreCursor = new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); cursor.close(); @@ -202,7 +202,7 @@ void shouldGetExceptionsForOperationsOnTheCursorAfterClosing() { @DisplayName("should throw an Exception when going off the end") void shouldThrowAnExceptionWhenGoingOffTheEnd() { BsonDocument commandResult = executeFindCommand(2, 1); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); cursorNext(); @@ -216,7 +216,7 @@ void shouldThrowAnExceptionWhenGoingOffTheEnd() { @DisplayName("test normal exhaustion") void testNormalExhaustion() { BsonDocument commandResult = executeFindCommand(); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); assertEquals(10, cursorFlatten().size()); @@ -227,7 +227,7 @@ void testNormalExhaustion() { @DisplayName("test limit exhaustion") void testLimitExhaustion(final int limit, final int batchSize, final int expectedTotal) { BsonDocument commandResult = executeFindCommand(limit, batchSize); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, batchSize, DOCUMENT_DECODER, null, connectionSource, connection)); @@ -246,7 +246,7 @@ void shouldBlockWaitingForNextBatchOnATailableCursor(final boolean awaitData, fi BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, awaitData); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); assertFalse(cursor.isClosed()); @@ -269,7 +269,7 @@ void testTailableInterrupt() throws InterruptedException { BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); CountDownLatch latch = new CountDownLatch(1); @@ -304,7 +304,7 @@ void shouldKillCursorIfLimitIsReachedOnInitialQuery() { BsonDocument commandResult = executeFindCommand(5, 10); AsyncCommandCursor coreCursor = new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); assertNotNull(cursorNext()); @@ -319,7 +319,7 @@ void shouldKillCursorIfLimitIsReachedOnGetMore() { BsonDocument commandResult = executeFindCommand(5, 3); AsyncCommandCursor coreCursor = new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); ServerCursor serverCursor = coreCursor.getServerCursor(); @@ -341,7 +341,7 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnInitialQuery() { BsonDocument commandResult = executeFindCommand(5, 10); AsyncCommandCursor coreCursor = new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); assertDoesNotThrow(() -> checkReferenceCountReachesTarget(connectionSource, 1)); @@ -354,7 +354,7 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnInitialQuery() { void shouldReleaseConnectionSourceIfLimitIsReachedOnGetMore() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 3); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); assertNotNull(cursorNext()); @@ -367,7 +367,7 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnGetMore() { @DisplayName("test limit with get more") void testLimitWithGetMore() { BsonDocument commandResult = executeFindCommand(5, 2); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); assertNotNull(cursorNext()); @@ -390,7 +390,7 @@ void testLimitWithLargeDocuments() { ); BsonDocument commandResult = executeFindCommand(300, 0); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertEquals(300, cursorFlatten().size()); @@ -400,7 +400,7 @@ void testLimitWithLargeDocuments() { @DisplayName("should respect batch size") void shouldRespectBatchSize() { BsonDocument commandResult = executeFindCommand(2); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); assertEquals(2, cursor.getBatchSize()); @@ -419,7 +419,7 @@ void shouldThrowCursorNotFoundException() throws Throwable { BsonDocument commandResult = executeFindCommand(2); AsyncCommandCursor coreCursor = new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); ServerCursor serverCursor = coreCursor.getServerCursor(); @@ -428,7 +428,7 @@ void shouldThrowCursorNotFoundException() throws Throwable { this.block(cb -> localConnection.commandAsync(getNamespace().getDatabaseName(), new BsonDocument("killCursors", new BsonString(getNamespace().getCollectionName())) .append("cursors", new BsonArray(singletonList(new BsonInt64(serverCursor.getId())))), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), OPERATION_CONTEXT, cb)); + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), ClusterFixture.createOperationContext(), cb)); localConnection.release(); cursorNext(); @@ -494,7 +494,7 @@ private BsonDocument executeFindCommand(final BsonDocument filter, final int lim BsonDocument results = block(cb -> connection.commandAsync(getDatabaseName(), findCommand, NoOpFieldNameValidator.INSTANCE, readPreference, CommandResultDocumentCodec.create(DOCUMENT_DECODER, FIRST_BATCH), - OPERATION_CONTEXT, cb)); + ClusterFixture.createOperationContext(), cb)); assertNotNull(results); return results; diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/ChangeStreamOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/ChangeStreamOperationSpecification.groovy index 19285eda077..f2374e92160 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/ChangeStreamOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/ChangeStreamOperationSpecification.groovy @@ -53,7 +53,7 @@ import org.bson.codecs.DocumentCodec import org.bson.codecs.ValueCodecProvider import spock.lang.IgnoreIf -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getAsyncCluster import static com.mongodb.ClusterFixture.getCluster import static com.mongodb.ClusterFixture.isStandalone @@ -635,7 +635,7 @@ class ChangeStreamOperationSpecification extends OperationFunctionalSpecificatio def 'should set the startAtOperationTime on the sync cursor'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { getReadConcern() >> ReadConcern.DEFAULT getOperationTime() >> new BsonTimestamp() @@ -690,7 +690,7 @@ class ChangeStreamOperationSpecification extends OperationFunctionalSpecificatio def 'should set the startAtOperationTime on the async cursor'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { getReadConcern() >> ReadConcern.DEFAULT getOperationTime() >> new BsonTimestamp() diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java index 407b03f5246..946e5504db3 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java @@ -27,6 +27,7 @@ import com.mongodb.client.model.OperationTest; import com.mongodb.internal.binding.ConnectionSource; import com.mongodb.internal.connection.Connection; +import com.mongodb.internal.connection.OperationContext; import com.mongodb.internal.validator.NoOpFieldNameValidator; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -55,7 +56,6 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.checkReferenceCountReachesTarget; import static com.mongodb.ClusterFixture.getBinding; import static com.mongodb.ClusterFixture.getReferenceCountAfterTimeout; @@ -87,8 +87,9 @@ void setup() { .collect(Collectors.toList()); getCollectionHelper().insertDocuments(documents); - connectionSource = getBinding().getWriteConnectionSource(ClusterFixture.OPERATION_CONTEXT); - connection = connectionSource.getConnection(ClusterFixture.OPERATION_CONTEXT); + OperationContext operationContext = ClusterFixture.createOperationContext(); + connectionSource = getBinding().getWriteConnectionSource(operationContext); + connection = connectionSource.getConnection(operationContext); } @AfterEach @@ -109,7 +110,7 @@ void cleanup() { void shouldExhaustCursorWithMultipleBatches() { // given BsonDocument commandResult = executeFindCommand(0, 3); // Fetch in batches of size 3 - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); // when @@ -127,7 +128,7 @@ void shouldExhaustCursorWithMultipleBatches() { void shouldExhaustCursorWithClosedCursor() { // given BsonDocument commandResult = executeFindCommand(0, 3); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); cursor.close(); @@ -143,7 +144,7 @@ void shouldExhaustCursorWithEmptyCursor() { getCollectionHelper().deleteMany(Filters.empty()); BsonDocument commandResult = executeFindCommand(0, 3); // No documents to fetch - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); // when @@ -157,7 +158,7 @@ void shouldExhaustCursorWithEmptyCursor() { @DisplayName("server cursor should not be null") void theServerCursorShouldNotBeNull() { BsonDocument commandResult = executeFindCommand(2); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertNotNull(cursor.getServerCursor()); @@ -167,7 +168,7 @@ void theServerCursorShouldNotBeNull() { @DisplayName("test server address should not be null") void theServerAddressShouldNotNull() { BsonDocument commandResult = executeFindCommand(); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertNotNull(cursor.getServerAddress()); @@ -177,7 +178,7 @@ void theServerAddressShouldNotNull() { @DisplayName("should get Exceptions for operations on the cursor after closing") void shouldGetExceptionsForOperationsOnTheCursorAfterClosing() { BsonDocument commandResult = executeFindCommand(); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); cursor.close(); @@ -192,7 +193,7 @@ void shouldGetExceptionsForOperationsOnTheCursorAfterClosing() { @DisplayName("should throw an Exception when going off the end") void shouldThrowAnExceptionWhenGoingOffTheEnd() { BsonDocument commandResult = executeFindCommand(1); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); cursor.next(); @@ -204,7 +205,7 @@ void shouldThrowAnExceptionWhenGoingOffTheEnd() { @DisplayName("test cursor remove") void testCursorRemove() { BsonDocument commandResult = executeFindCommand(); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertThrows(UnsupportedOperationException.class, () -> cursor.remove()); @@ -214,7 +215,7 @@ void testCursorRemove() { @DisplayName("test normal exhaustion") void testNormalExhaustion() { BsonDocument commandResult = executeFindCommand(); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertEquals(10, cursorFlatten().size()); @@ -225,7 +226,7 @@ void testNormalExhaustion() { @DisplayName("test limit exhaustion") void testLimitExhaustion(final int limit, final int batchSize, final int expectedTotal) { BsonDocument commandResult = executeFindCommand(limit, batchSize); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertEquals(expectedTotal, cursorFlatten().size()); @@ -244,7 +245,7 @@ void shouldBlockWaitingForNextBatchOnATailableCursor(final boolean awaitData, fi BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, awaitData); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); assertTrue(cursor.hasNext()); @@ -267,7 +268,7 @@ void testTryNextWithTailable() { BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); List nextBatch = cursor.tryNext(); @@ -293,7 +294,7 @@ void hasNextShouldThrowWhenCursorIsClosedInAnotherThread() throws InterruptedExc BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); assertTrue(cursor.hasNext()); @@ -320,7 +321,7 @@ void testMaxTimeMS() { long maxTimeMS = 500; BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); List nextBatch = cursor.tryNext(); @@ -344,7 +345,7 @@ void testTailableInterrupt() throws InterruptedException { BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); CountDownLatch latch = new CountDownLatch(1); @@ -377,7 +378,7 @@ void testTailableInterrupt() throws InterruptedException { void shouldKillCursorIfLimitIsReachedOnInitialQuery() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 10); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertNotNull(cursor.next()); @@ -390,7 +391,7 @@ void shouldKillCursorIfLimitIsReachedOnInitialQuery() { void shouldKillCursorIfLimitIsReachedOnGetMore() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 3); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); ServerCursor serverCursor = cursor.getServerCursor(); @@ -409,7 +410,7 @@ void shouldKillCursorIfLimitIsReachedOnGetMore() { void shouldReleaseConnectionSourceIfLimitIsReachedOnInitialQuery() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 10); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertNull(cursor.getServerCursor()); @@ -422,7 +423,7 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnInitialQuery() { void shouldReleaseConnectionSourceIfLimitIsReachedOnGetMore() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 3); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); assertNotNull(cursor.next()); @@ -435,7 +436,7 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnGetMore() { @DisplayName("test limit with get more") void testLimitWithGetMore() { BsonDocument commandResult = executeFindCommand(5, 2); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); assertNotNull(cursor.next()); @@ -456,7 +457,7 @@ void testLimitWithLargeDocuments() { ); BsonDocument commandResult = executeFindCommand(300, 0); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); assertEquals(300, cursorFlatten().size()); @@ -466,7 +467,7 @@ void testLimitWithLargeDocuments() { @DisplayName("should respect batch size") void shouldRespectBatchSize() { BsonDocument commandResult = executeFindCommand(2); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); assertEquals(2, cursor.getBatchSize()); @@ -483,16 +484,16 @@ void shouldRespectBatchSize() { @DisplayName("should throw cursor not found exception") void shouldThrowCursorNotFoundException() { BsonDocument commandResult = executeFindCommand(2); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); ServerCursor serverCursor = cursor.getServerCursor(); assertNotNull(serverCursor); - Connection localConnection = connectionSource.getConnection(OPERATION_CONTEXT); + Connection localConnection = connectionSource.getConnection(ClusterFixture.createOperationContext()); localConnection.command(getNamespace().getDatabaseName(), new BsonDocument("killCursors", new BsonString(getNamespace().getCollectionName())) .append("cursors", new BsonArray(singletonList(new BsonInt64(serverCursor.getId())))), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), OPERATION_CONTEXT); + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), ClusterFixture.createOperationContext()); localConnection.release(); cursor.next(); @@ -506,7 +507,7 @@ void shouldThrowCursorNotFoundException() { @DisplayName("should report available documents") void shouldReportAvailableDocuments() { BsonDocument commandResult = executeFindCommand(3); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); assertEquals(3, cursor.available()); @@ -584,7 +585,7 @@ private BsonDocument executeFindCommand(final BsonDocument filter, final int lim BsonDocument results = connection.command(getDatabaseName(), findCommand, NoOpFieldNameValidator.INSTANCE, readPreference, CommandResultDocumentCodec.create(DOCUMENT_DECODER, FIRST_BATCH), - OPERATION_CONTEXT); + ClusterFixture.createOperationContext()); assertNotNull(results); return results; diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CountDocumentsOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CountDocumentsOperationSpecification.groovy index 1e538b1af11..dc9746053c3 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CountDocumentsOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CountDocumentsOperationSpecification.groovy @@ -46,7 +46,7 @@ import org.bson.BsonTimestamp import org.bson.Document import org.bson.codecs.DocumentCodec -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.connection.ServerType.STANDALONE import static com.mongodb.internal.operation.OperationReadConcernHelper.appendReadConcernToCommand @@ -156,7 +156,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def binding = ClusterFixture.getBinding() new CreateIndexesOperation(getNamespace(), [new IndexRequest(indexDefinition).sparse(true)], null) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) def operation = new CountDocumentsOperation(getNamespace()).hint(indexDefinition) when: @@ -259,7 +259,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -297,7 +297,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateCollectionOperationSpecification.groovy index 860ffb4a2bf..a0b80cc178a 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateCollectionOperationSpecification.groovy @@ -113,7 +113,7 @@ class CreateCollectionOperationSpecification extends OperationFunctionalSpecific then: def binding = ClusterFixture.getBinding() new ListCollectionsOperation(getDatabaseName(), new BsonDocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) .next() .find { it -> it.getString('name').value == getCollectionName() } .getDocument('options').getDocument('storageEngine') == operation.storageEngineOptions @@ -134,7 +134,7 @@ class CreateCollectionOperationSpecification extends OperationFunctionalSpecific then: def binding = ClusterFixture.getBinding() new ListCollectionsOperation(getDatabaseName(), new BsonDocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) .next() .find { it -> it.getString('name').value == getCollectionName() } .getDocument('options').getDocument('storageEngine') == operation.storageEngineOptions @@ -255,7 +255,7 @@ class CreateCollectionOperationSpecification extends OperationFunctionalSpecific def binding = getBinding() new ListCollectionsOperation(databaseName, new BsonDocumentCodec()).filter(new BsonDocument('name', new BsonString(collectionName))).execute(binding, - ClusterFixture.getOperationContext(binding.getReadPreference())).tryNext()?.head() + ClusterFixture.createOperationContext(binding.getReadPreference())).tryNext()?.head() } def collectionNameExists(String collectionName) { @@ -268,13 +268,13 @@ class CreateCollectionOperationSpecification extends OperationFunctionalSpecific def binding = getBinding() return new CommandReadOperation<>(getDatabaseName(), new BsonDocument('collStats', new BsonString(getCollectionName())), - new BsonDocumentCodec()).execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) } def binding = ClusterFixture.getBinding() BatchCursor cursor = new AggregateOperation( getNamespace(), singletonList(new BsonDocument('$collStats', new BsonDocument('storageStats', new BsonDocument()))), - new BsonDocumentCodec()).execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) try { return cursor.next().first().getDocument('storageStats') } finally { diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateIndexesOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateIndexesOperationSpecification.groovy index fce0904b786..5365f19f1de 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateIndexesOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateIndexesOperationSpecification.groovy @@ -494,7 +494,7 @@ class CreateIndexesOperationSpecification extends OperationFunctionalSpecificati def binding = ClusterFixture.getBinding() def cursor = new ListIndexesOperation(getNamespace(), new DocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) while (cursor.hasNext()) { indexes.addAll(cursor.next()) } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateViewOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateViewOperationSpecification.groovy index b8145de44b4..3e90c21363e 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateViewOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateViewOperationSpecification.groovy @@ -29,8 +29,8 @@ import org.bson.BsonString import org.bson.codecs.BsonDocumentCodec import spock.lang.IgnoreIf +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet class CreateViewOperationSpecification extends OperationFunctionalSpecification { @@ -124,7 +124,7 @@ class CreateViewOperationSpecification extends OperationFunctionalSpecification def getCollectionInfo(String collectionName) { def binding = getBinding() new ListCollectionsOperation(databaseName, new BsonDocumentCodec()).filter(new BsonDocument('name', - new BsonString(collectionName))).execute(binding, getOperationContext(binding.getReadPreference())).tryNext()?.head() + new BsonString(collectionName))).execute(binding, createOperationContext(binding.getReadPreference())).tryNext()?.head() } def collectionNameExists(String collectionName) { diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/DistinctOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/DistinctOperationSpecification.groovy index f73c301d422..66936080558 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/DistinctOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/DistinctOperationSpecification.groovy @@ -51,7 +51,7 @@ import org.bson.codecs.StringCodec import org.bson.codecs.ValueCodecProvider import org.bson.types.ObjectId -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.connection.ServerType.STANDALONE import static com.mongodb.internal.operation.OperationReadConcernHelper.appendReadConcernToCommand @@ -227,7 +227,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -266,7 +266,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/DropCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/DropCollectionOperationSpecification.groovy index eb8f3efa573..703d0e14212 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/DropCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/DropCollectionOperationSpecification.groovy @@ -39,7 +39,7 @@ class DropCollectionOperationSpecification extends OperationFunctionalSpecificat when: def binding = getBinding() new DropCollectionOperation(getNamespace(), WriteConcern.ACKNOWLEDGED) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: !collectionNameExists(getCollectionName()) @@ -64,7 +64,7 @@ class DropCollectionOperationSpecification extends OperationFunctionalSpecificat when: new DropCollectionOperation(namespace, WriteConcern.ACKNOWLEDGED) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: !collectionNameExists('nonExistingCollection') @@ -91,7 +91,7 @@ class DropCollectionOperationSpecification extends OperationFunctionalSpecificat when: def binding = getBinding() - async ? executeAsync(operation) : operation.execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + async ? executeAsync(operation) : operation.execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: def ex = thrown(MongoWriteConcernException) @@ -104,7 +104,7 @@ class DropCollectionOperationSpecification extends OperationFunctionalSpecificat def collectionNameExists(String collectionName) { def cursor = new ListCollectionsOperation(databaseName, new DocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) if (!cursor.hasNext()) { return false } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/DropDatabaseOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/DropDatabaseOperationSpecification.groovy index b56e2c1fe50..955b5efcaa3 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/DropDatabaseOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/DropDatabaseOperationSpecification.groovy @@ -16,7 +16,6 @@ package com.mongodb.internal.operation - import com.mongodb.MongoWriteConcernException import com.mongodb.OperationFunctionalSpecification import com.mongodb.WriteConcern @@ -25,10 +24,10 @@ import org.bson.Document import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.configureFailPoint import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet import static com.mongodb.ClusterFixture.isSharded @@ -80,7 +79,7 @@ class DropDatabaseOperationSpecification extends OperationFunctionalSpecificatio def binding = getBinding() when: - async ? executeAsync(operation) : operation.execute(binding, getOperationContext(binding.getReadPreference())) + async ? executeAsync(operation) : operation.execute(binding, createOperationContext(binding.getReadPreference())) then: def ex = thrown(MongoWriteConcernException) @@ -93,7 +92,7 @@ class DropDatabaseOperationSpecification extends OperationFunctionalSpecificatio def databaseNameExists(String databaseName) { new ListDatabasesOperation(new DocumentCodec()).execute(binding, - getOperationContext(binding.getReadPreference())).next()*.name.contains(databaseName) + createOperationContext(binding.getReadPreference())).next()*.name.contains(databaseName) } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/DropIndexOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/DropIndexOperationSpecification.groovy index 7b1f5b2a392..0aff76df1cb 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/DropIndexOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/DropIndexOperationSpecification.groovy @@ -159,7 +159,7 @@ class DropIndexOperationSpecification extends OperationFunctionalSpecification { def indexes = [] def binding = getBinding() def cursor = new ListIndexesOperation(getNamespace(), new DocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) while (cursor.hasNext()) { indexes.addAll(cursor.next()) } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy index 5eb707201d5..c584ee463f9 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy @@ -54,13 +54,12 @@ import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.executeSync import static com.mongodb.ClusterFixture.getAsyncCluster import static com.mongodb.ClusterFixture.getBinding import static com.mongodb.ClusterFixture.getCluster -import static com.mongodb.ClusterFixture.getOperationContext +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.isSharded import static com.mongodb.ClusterFixture.serverVersionLessThan import static com.mongodb.CursorType.NonTailable @@ -390,7 +389,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def binding = getBinding() new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(2)), - new BsonDocumentCodec()).execute(binding, getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, createOperationContext(binding.getReadPreference())) def expectedComment = 'this is a comment' def operation = new FindOperation(getNamespace(), new DocumentCodec()) .comment(new BsonString(expectedComment)) @@ -405,7 +404,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { cleanup: new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(0)), new BsonDocumentCodec()) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) profileCollectionHelper.drop() where: @@ -482,7 +481,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -522,7 +521,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) @@ -562,7 +561,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should add allowDiskUse to command if the server version >= 3.2'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -602,7 +601,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should add allowDiskUse to command if the server version >= 3.2 asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) @@ -646,7 +645,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def (cursorType, long maxAwaitTimeMS, long maxTimeMSForCursor) = cursorDetails def timeoutSettings = ClusterFixture.TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT.withMaxAwaitTimeMS(maxAwaitTimeMS) def timeoutContext = new TimeoutContext(timeoutSettings) - def operationContext = OPERATION_CONTEXT.withTimeoutContext(timeoutContext) + def operationContext = createOperationContext().withTimeoutContext(timeoutContext) collectionHelper.create(getCollectionName(), new CreateCollectionOptions().capped(true).sizeInBytes(1000)) def operation = new FindOperation(namespace, new BsonDocumentCodec()) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/ListCollectionsOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/ListCollectionsOperationSpecification.groovy index ad55b706ba2..b44b341d202 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/ListCollectionsOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/ListCollectionsOperationSpecification.groovy @@ -16,6 +16,7 @@ package com.mongodb.internal.operation +import com.mongodb.ClusterFixture import com.mongodb.MongoNamespace import com.mongodb.OperationFunctionalSpecification import com.mongodb.ReadPreference @@ -43,10 +44,9 @@ import org.bson.Document import org.bson.codecs.Decoder import org.bson.codecs.DocumentCodec -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext import static org.junit.jupiter.api.Assertions.assertEquals class ListCollectionsOperationSpecification extends OperationFunctionalSpecification { @@ -60,7 +60,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: !cursor.hasNext() @@ -98,7 +98,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collections = cursor.next() def names = collections*.get('name') @@ -121,7 +121,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference()) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference()) ) def collections = cursor.next() def names = collections*.get('name') @@ -143,7 +143,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collections = cursor.next() def names = collections*.get('name') @@ -161,7 +161,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collection = cursor.next()[0] then: @@ -178,7 +178,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collection = cursor.next()[0] then: @@ -195,7 +195,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collection = cursor.next()[0] then: @@ -227,14 +227,14 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() given: new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) when: binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: cursor.hasNext() @@ -247,13 +247,13 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() given: new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) when: binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def list = cursorToListWithNext(cursor) then: @@ -272,14 +272,14 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() given: new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) when: binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: cursor.hasNext() @@ -298,13 +298,13 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica given: def binding = getBinding() new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) when: binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def list = cursorToListWithTryNext(cursor) then: @@ -318,7 +318,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica given: def binding = getBinding() new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) @@ -344,7 +344,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica when: def binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collections = cursor.next() then: @@ -398,6 +398,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should use the readPreference to set secondaryOk'() { given: + def operationContext = ClusterFixture.createOperationContext() def connection = Mock(Connection) def connectionSource = Stub(ConnectionSource) { getConnection(_) >> connection @@ -410,12 +411,12 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def operation = new ListCollectionsOperation(helper.dbName, helper.decoder) when: '3.6.0' - operation.execute(readBinding, OPERATION_CONTEXT) + operation.execute(readBinding, operationContext) then: _ * connection.getDescription() >> helper.threeSixConnectionDescription 1 * connection.command(_, _, _, readPreference, _, _) >> { - assertEquals(((OperationContext) it[5]).getId(), OPERATION_CONTEXT.getId()) + assertEquals(((OperationContext) it[5]).getId(), operationContext.getId()) helper.commandResult } 1 * connection.release() @@ -426,6 +427,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should use the readPreference to set secondaryOk in async'() { given: + def operationContext = ClusterFixture.createOperationContext() def connection = Mock(AsyncConnection) def connectionSource = Stub(AsyncConnectionSource) { getConnection(_, _) >> { it[1].onResult(connection, null) } @@ -436,14 +438,13 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica getReadPreference() >> readPreference } def operation = new ListCollectionsOperation(helper.dbName, helper.decoder) - when: '3.6.0' - operation.executeAsync(readBinding, OPERATION_CONTEXT, Stub(SingleResultCallback)) + operation.executeAsync(readBinding, operationContext, Stub(SingleResultCallback)) then: _ * connection.getDescription() >> helper.threeSixConnectionDescription 1 * connection.commandAsync(helper.dbName, _, _, readPreference, _, _, *_) >> { - assertEquals(((OperationContext) it[5]).getId(), OPERATION_CONTEXT.getId()) + assertEquals(((OperationContext) it[5]).getId(), operationContext.getId()) it.last().onResult(helper.commandResult, null) } where: diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/ListDatabasesOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/ListDatabasesOperationSpecification.groovy index 55504d0babc..a6ed542bde2 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/ListDatabasesOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/ListDatabasesOperationSpecification.groovy @@ -33,7 +33,7 @@ import org.bson.Document import org.bson.codecs.Decoder import org.bson.codecs.DocumentCodec -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class ListDatabasesOperationSpecification extends OperationFunctionalSpecification { def codec = new DocumentCodec() @@ -82,7 +82,7 @@ class ListDatabasesOperationSpecification extends OperationFunctionalSpecificati def operation = new ListDatabasesOperation(helper.decoder) when: - operation.execute(readBinding, OPERATION_CONTEXT) + operation.execute(readBinding, createOperationContext()) then: _ * connection.getDescription() >> helper.connectionDescription @@ -107,7 +107,7 @@ class ListDatabasesOperationSpecification extends OperationFunctionalSpecificati def operation = new ListDatabasesOperation(helper.decoder) when: - operation.executeAsync(readBinding, OPERATION_CONTEXT, Stub(SingleResultCallback)) + operation.executeAsync(readBinding, createOperationContext(), Stub(SingleResultCallback)) then: _ * connection.getDescription() >> helper.connectionDescription diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/ListIndexesOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/ListIndexesOperationSpecification.groovy index c11d67bcf22..823de9bfd91 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/ListIndexesOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/ListIndexesOperationSpecification.groovy @@ -16,7 +16,7 @@ package com.mongodb.internal.operation - +import com.mongodb.ClusterFixture import com.mongodb.MongoNamespace import com.mongodb.OperationFunctionalSpecification import com.mongodb.ReadPreference @@ -44,10 +44,9 @@ import org.bson.codecs.Decoder import org.bson.codecs.DocumentCodec import org.junit.jupiter.api.Assertions -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext +import static com.mongodb.ClusterFixture.createOperationContext class ListIndexesOperationSpecification extends OperationFunctionalSpecification { @@ -58,7 +57,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: !cursor.hasNext() @@ -87,7 +86,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def binding = getBinding() when: - BatchCursor indexes = operation.execute(binding, getOperationContext(binding.getReadPreference())) + BatchCursor indexes = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: def firstBatch = indexes.next() @@ -122,11 +121,11 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def binding = getBinding() new CreateIndexesOperation(namespace, [new IndexRequest(new BsonDocument('unique', new BsonInt32(1))).unique(true)], null).execute(binding, - getOperationContext(binding.getReadPreference())) + createOperationContext(binding.getReadPreference())) when: binding = getBinding() - BatchCursor cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + BatchCursor cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: def indexes = cursor.next() @@ -146,7 +145,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def binding = getBinding() new CreateIndexesOperation(namespace, [new IndexRequest(new BsonDocument('unique', new BsonInt32(1))).unique(true)], null).execute(binding, - getOperationContext(binding.getReadPreference())) + createOperationContext(binding.getReadPreference())) when: def cursor = executeAsync(operation) @@ -172,7 +171,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collections = cursor.next() then: @@ -226,6 +225,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should use the readPreference to set secondaryOk'() { given: def connection = Mock(Connection) + def operationContext = ClusterFixture.createOperationContext() def connectionSource = Stub(ConnectionSource) { getConnection(_) >> connection getReadPreference() >> readPreference @@ -237,12 +237,12 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def operation = new ListIndexesOperation(helper.namespace, helper.decoder) when: '3.6.0' - operation.execute(readBinding, OPERATION_CONTEXT) + operation.execute(readBinding, operationContext) then: _ * connection.getDescription() >> helper.threeSixConnectionDescription 1 * connection.command(_, _, _, readPreference, _, _) >> { - Assertions.assertEquals(((OperationContext) it[5]).getId(), OPERATION_CONTEXT.getId()) + Assertions.assertEquals(((OperationContext) it[5]).getId(), operationContext.getId()) helper.commandResult } 1 * connection.release() @@ -265,7 +265,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def operation = new ListIndexesOperation(helper.namespace, helper.decoder) when: '3.6.0' - operation.executeAsync(readBinding, OPERATION_CONTEXT, Stub(SingleResultCallback)) + operation.executeAsync(readBinding, createOperationContext(), Stub(SingleResultCallback)) then: _ * connection.getDescription() >> helper.threeSixConnectionDescription diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceToCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceToCollectionOperationSpecification.groovy index 5d6be781d1f..31f8ed45715 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceToCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceToCollectionOperationSpecification.groovy @@ -64,7 +64,7 @@ class MapReduceToCollectionOperationSpecification extends OperationFunctionalSpe def cleanup() { def binding = getBinding() - def operationContext = ClusterFixture.getOperationContext(binding.getReadPreference()) + def operationContext = ClusterFixture.createOperationContext(binding.getReadPreference()) new DropCollectionOperation(mapReduceInputNamespace, WriteConcern.ACKNOWLEDGED) .execute(binding, operationContext) new DropCollectionOperation(mapReduceOutputNamespace, WriteConcern.ACKNOWLEDGED) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceWithInlineResultsOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceWithInlineResultsOperationSpecification.groovy index 8efd4e00f6c..14ee33d7ec5 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceWithInlineResultsOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceWithInlineResultsOperationSpecification.groovy @@ -46,7 +46,7 @@ import org.bson.Document import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.DocumentCodec -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.connection.ServerType.STANDALONE import static com.mongodb.internal.operation.OperationReadConcernHelper.appendReadConcernToCommand @@ -217,7 +217,7 @@ class MapReduceWithInlineResultsOperationSpecification extends OperationFunction def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -264,7 +264,7 @@ class MapReduceWithInlineResultsOperationSpecification extends OperationFunction def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/RenameCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/RenameCollectionOperationSpecification.groovy index bc55bf5a134..080861047f1 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/RenameCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/RenameCollectionOperationSpecification.groovy @@ -26,9 +26,9 @@ import org.bson.Document import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet import static com.mongodb.ClusterFixture.isSharded @@ -38,7 +38,7 @@ class RenameCollectionOperationSpecification extends OperationFunctionalSpecific def cleanup() { def binding = getBinding() new DropCollectionOperation(new MongoNamespace(getDatabaseName(), 'newCollection'), - WriteConcern.ACKNOWLEDGED).execute(binding, getOperationContext(binding.getReadPreference())) + WriteConcern.ACKNOWLEDGED).execute(binding, createOperationContext(binding.getReadPreference())) } def 'should return rename a collection'() { @@ -87,7 +87,7 @@ class RenameCollectionOperationSpecification extends OperationFunctionalSpecific def binding = getBinding() when: - async ? executeAsync(operation) : operation.execute(binding, getOperationContext(binding.getReadPreference())) + async ? executeAsync(operation) : operation.execute(binding, createOperationContext(binding.getReadPreference())) then: def ex = thrown(MongoWriteConcernException) @@ -101,7 +101,7 @@ class RenameCollectionOperationSpecification extends OperationFunctionalSpecific def collectionNameExists(String collectionName) { def binding = getBinding() def cursor = new ListCollectionsOperation(databaseName, new DocumentCodec()).execute(binding, - getOperationContext(binding.getReadPreference())) + createOperationContext(binding.getReadPreference())) if (!cursor.hasNext()) { return false } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/TestOperationHelper.java b/driver-core/src/test/functional/com/mongodb/internal/operation/TestOperationHelper.java index 824517e10db..2565521deb7 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/TestOperationHelper.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/TestOperationHelper.java @@ -16,6 +16,7 @@ package com.mongodb.internal.operation; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCommandException; import com.mongodb.MongoCursorNotFoundException; import com.mongodb.MongoNamespace; @@ -31,8 +32,6 @@ import org.bson.BsonString; import org.bson.codecs.BsonDocumentCodec; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; - final class TestOperationHelper { static BsonDocument getKeyPattern(final BsonDocument explainPlan) { @@ -56,7 +55,7 @@ static void makeAdditionalGetMoreCall(final MongoNamespace namespace, final Serv connection.command(namespace.getDatabaseName(), new BsonDocument("getMore", new BsonInt64(serverCursor.getId())) .append("collection", new BsonString(namespace.getCollectionName())), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), OPERATION_CONTEXT)); + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), ClusterFixture.createOperationContext())); } static void makeAdditionalGetMoreCall(final MongoNamespace namespace, final ServerCursor serverCursor, @@ -66,7 +65,7 @@ static void makeAdditionalGetMoreCall(final MongoNamespace namespace, final Serv connection.commandAsync(namespace.getDatabaseName(), new BsonDocument("getMore", new BsonInt64(serverCursor.getId())) .append("collection", new BsonString(namespace.getCollectionName())), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), OPERATION_CONTEXT, callback); + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), ClusterFixture.createOperationContext(), callback); callback.get(); }); } diff --git a/driver-core/src/test/unit/com/mongodb/internal/binding/SingleServerBindingSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/binding/SingleServerBindingSpecification.groovy index d52fb593a70..64bd904aaed 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/binding/SingleServerBindingSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/binding/SingleServerBindingSpecification.groovy @@ -26,7 +26,7 @@ import com.mongodb.internal.connection.Server import com.mongodb.internal.connection.ServerTuple import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class SingleServerBindingSpecification extends Specification { @@ -68,7 +68,7 @@ class SingleServerBindingSpecification extends Specification { binding.count == 1 when: - def source = binding.getReadConnectionSource(OPERATION_CONTEXT) + def source = binding.getReadConnectionSource(createOperationContext()) then: source.count == 1 @@ -96,7 +96,7 @@ class SingleServerBindingSpecification extends Specification { binding.count == 1 when: - source = binding.getWriteConnectionSource(OPERATION_CONTEXT) + source = binding.getWriteConnectionSource(createOperationContext()) then: source.count == 1 diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java index 69a2c236048..deaeacafb07 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java @@ -77,7 +77,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.assertions.Assertions.assertFalse; @@ -542,7 +541,7 @@ private Event getNextEvent(final Iterator eventsIterator, final private static void executeAdminCommand(final BsonDocument command) { new CommandReadOperation<>("admin", command, new BsonDocumentCodec()) - .execute(ClusterFixture.getBinding(), OPERATION_CONTEXT); + .execute(ClusterFixture.getBinding(), ClusterFixture.createOperationContext()); } private void setFailPoint() { diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java index 6e63f9c586a..52287a23d13 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.ConnectionString; import com.mongodb.MongoSocketReadException; import com.mongodb.MongoSocketReadTimeoutException; @@ -43,7 +44,6 @@ import java.util.concurrent.TimeUnit; import static com.mongodb.ClusterFixture.CLIENT_METADATA; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.connection.ServerConnectionState.CONNECTING; import static com.mongodb.internal.connection.DescriptionHelper.createServerDescription; @@ -82,7 +82,8 @@ protected void applyResponse(final BsonArray response) { } protected void applyApplicationError(final BsonDocument applicationError) { - Timeout serverSelectionTimeout = OPERATION_CONTEXT.getTimeoutContext().computeServerSelectionTimeout(); + OperationContext operationContext = ClusterFixture.createOperationContext(); + Timeout serverSelectionTimeout = operationContext.getTimeoutContext().computeServerSelectionTimeout(); ServerAddress serverAddress = new ServerAddress(applicationError.getString("address").getValue()); TimeoutContext timeoutContext = new TimeoutContext(TIMEOUT_SETTINGS); int errorGeneration = applicationError.getNumber("generation", @@ -98,7 +99,7 @@ protected void applyApplicationError(final BsonDocument applicationError) { switch (type) { case "command": exception = getCommandFailureException(applicationError.getDocument("response"), serverAddress, - OPERATION_CONTEXT.getTimeoutContext()); + operationContext.getTimeoutContext()); break; case "network": exception = new MongoSocketReadException("Read error", serverAddress, new IOException()); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy index 56c500c6183..25a1b904e1f 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy @@ -42,7 +42,6 @@ import spock.lang.Specification import java.util.concurrent.CountDownLatch import static com.mongodb.ClusterFixture.CLIENT_METADATA -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE @@ -135,7 +134,7 @@ class BaseClusterSpecification extends Specification { factory.sendNotification(thirdServer, REPLICA_SET_PRIMARY, allServers) expect: - cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.secondary()), OPERATION_CONTEXT) + cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.secondary()), createOperationContext()) .serverDescription.address == firstServer } @@ -171,7 +170,7 @@ class BaseClusterSpecification extends Specification { factory.sendNotification(thirdServer, 1, REPLICA_SET_PRIMARY, allServers) expect: - cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.nearest()), OPERATION_CONTEXT) + cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.nearest()), createOperationContext()) .serverDescription.address == firstServer } @@ -189,7 +188,7 @@ class BaseClusterSpecification extends Specification { factory.sendNotification(thirdServer, 1, REPLICA_SET_PRIMARY, allServers) expect: // firstServer is the only secondary within the latency threshold - cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.secondary()), OPERATION_CONTEXT) + cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.secondary()), createOperationContext()) .serverDescription.address == firstServer } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterTest.java index 1cba6d91c3c..d1e6c454bc9 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterTest.java @@ -48,7 +48,7 @@ void selectServerToleratesWhenThereIsNoServerForTheSelectedAddress() { new ServerAddressSelector(serverAddressA), clusterDescriptionAB, serversSnapshotB, - ClusterFixture.OPERATION_CONTEXT.getServerDeprioritization(), + ClusterFixture.createOperationContext().getServerDeprioritization(), ClusterSettings.builder().build())); } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultConnectionPoolSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultConnectionPoolSpecification.groovy index b3e78d2dc54..3872f6cb1e2 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultConnectionPoolSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultConnectionPoolSpecification.groovy @@ -41,7 +41,6 @@ import java.util.concurrent.CountDownLatch import java.util.regex.Matcher import java.util.regex.Pattern -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.createOperationContext @@ -78,7 +77,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool.ready() expect: - pool.get(OPERATION_CONTEXT) != null + pool.get(createOperationContext()) != null } def 'should reuse released connection'() throws InterruptedException { @@ -86,10 +85,11 @@ class DefaultConnectionPoolSpecification extends Specification { pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(1).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() + def operationContext = createOperationContext() when: - pool.get(OPERATION_CONTEXT).close() - pool.get(OPERATION_CONTEXT) + pool.get(operationContext).close() + pool.get(operationContext) then: 1 * connectionFactory.create(SERVER_ID, _) @@ -102,7 +102,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool.ready() when: - pool.get(OPERATION_CONTEXT).close() + pool.get(createOperationContext()).close() then: !connectionFactory.getCreatedConnections().get(0).isClosed() @@ -220,7 +220,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: pool.ready() - pool.get(OPERATION_CONTEXT) + pool.get(createOperationContext()) then: 1 * listener.connectionCreated { it.connectionId.serverId == SERVER_ID } @@ -239,6 +239,7 @@ class DefaultConnectionPoolSpecification extends Specification { connectionDescription.getConnectionId() >> id connection.getDescription() >> connectionDescription connection.opened() >> false + def operationContext = createOperationContext() when: 'connection pool is created' pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, settings, mockSdamProvider(), OPERATION_CONTEXT_FACTORY) @@ -257,7 +258,7 @@ class DefaultConnectionPoolSpecification extends Specification { "Connection pool ready for ${SERVER_ADDRESS.getHost()}:${SERVER_ADDRESS.getPort()}" == poolReadyLogMessage when: 'connection is created' - pool.get(OPERATION_CONTEXT) + pool.get(operationContext) then: '"connection created" and "connection ready" log messages are emitted' def createdLogMessage = getMessage( "Connection created") def readyLogMessage = getMessage("Connection ready") @@ -267,7 +268,7 @@ class DefaultConnectionPoolSpecification extends Specification { ", driver-generated ID=${driverConnectionId}, established in=\\d+ ms" when: 'connection is released back into the pool on close' - pool.get(OPERATION_CONTEXT).close() + pool.get(operationContext).close() then: '"connection check out" and "connection checked in" log messages are emitted' def checkoutStartedMessage = getMessage("Connection checkout started") def connectionCheckedInMessage = getMessage("Connection checked in") @@ -302,7 +303,7 @@ class DefaultConnectionPoolSpecification extends Specification { "Connection pool closed for ${SERVER_ADDRESS.getHost()}:${SERVER_ADDRESS.getPort()}" == poolClosedLogMessage when: 'connection checked out on closed pool' - pool.get(OPERATION_CONTEXT) + pool.get(operationContext) then: thrown(MongoServerUnavailableException) def connectionCheckoutFailedInMessage = getMessage("Connection checkout failed") @@ -351,7 +352,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: pool.ready() - pool.get(OPERATION_CONTEXT).close() + pool.get(createOperationContext()).close() //not cool - but we have no way of waiting for connection to become idle Thread.sleep(500) pool.close(); @@ -386,11 +387,12 @@ class DefaultConnectionPoolSpecification extends Specification { def 'should log connection checkout failed with Reason.CONNECTION_ERROR if fails to open a connection'() { given: + def operationContext = createOperationContext() def listener = Mock(ConnectionPoolListener) def connection = Mock(InternalConnection) connection.getDescription() >> new ConnectionDescription(SERVER_ID) connection.opened() >> false - connection.open(OPERATION_CONTEXT) >> { throw new UncheckedIOException('expected failure', new IOException()) } + connection.open(operationContext) >> { throw new UncheckedIOException('expected failure', new IOException()) } connectionFactory.create(SERVER_ID, _) >> connection pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) @@ -398,7 +400,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: try { - pool.get(OPERATION_CONTEXT) + pool.get(operationContext) } catch (UncheckedIOException e) { if ('expected failure' != e.getMessage()) { throw e @@ -435,7 +437,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(10) .addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() - def connection = pool.get(OPERATION_CONTEXT) + def connection = pool.get(createOperationContext()) connection.close() when: @@ -463,15 +465,16 @@ class DefaultConnectionPoolSpecification extends Specification { def 'should fire connection pool events on check out and check in'() { given: + def operationContext = createOperationContext() def listener = Mock(ConnectionPoolListener) pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(1) .addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() - def connection = pool.get(OPERATION_CONTEXT) + def connection = pool.get(operationContext) connection.close() when: - connection = pool.get(OPERATION_CONTEXT) + connection = pool.get(operationContext) then: 1 * listener.connectionCheckedOut { it.connectionId.serverId == SERVER_ID } @@ -493,7 +496,7 @@ class DefaultConnectionPoolSpecification extends Specification { connection.close() when: - connection = pool.get(OPERATION_CONTEXT) + connection = pool.get(createOperationContext()) then: 1 * listener.connectionCheckedOut { it.connectionId.serverId == SERVER_ID } @@ -507,11 +510,12 @@ class DefaultConnectionPoolSpecification extends Specification { def 'should fire connection checkout failed with Reason.CONNECTION_ERROR if fails to open a connection'() { given: + def operationContext = createOperationContext() def listener = Mock(ConnectionPoolListener) def connection = Mock(InternalConnection) connection.getDescription() >> new ConnectionDescription(SERVER_ID) connection.opened() >> false - connection.open(OPERATION_CONTEXT) >> { throw new UncheckedIOException('expected failure', new IOException()) } + connection.open(operationContext) >> { throw new UncheckedIOException('expected failure', new IOException()) } connectionFactory.create(SERVER_ID, _) >> connection pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) @@ -519,7 +523,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: try { - pool.get(OPERATION_CONTEXT) + pool.get(operationContext) } catch (UncheckedIOException e) { if ('expected failure' != e.getMessage()) { throw e @@ -564,7 +568,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: try { - pool.get(OPERATION_CONTEXT) + pool.get(createOperationContext()) } catch (MongoConnectionPoolClearedException e) { caught = e } @@ -579,7 +583,7 @@ class DefaultConnectionPoolSpecification extends Specification { CompletableFuture caught = new CompletableFuture<>() when: - pool.getAsync(OPERATION_CONTEXT) { InternalConnection result, Throwable t -> + pool.getAsync(createOperationContext()) { InternalConnection result, Throwable t -> if (t != null) { caught.complete(t) } @@ -599,7 +603,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: pool.invalidate(cause) try { - pool.get(OPERATION_CONTEXT) + pool.get(createOperationContext()) } catch (MongoConnectionPoolClearedException e) { caught = e } @@ -630,7 +634,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(1) .addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() - def connection = pool.get(OPERATION_CONTEXT) + def connection = pool.get(createOperationContext()) pool.close() when: @@ -674,7 +678,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool.ready() when: - def connection = pool.get(OPERATION_CONTEXT) + def connection = pool.get(createOperationContext()) def connectionLatch = selectConnectionAsync(pool) connection.close() @@ -684,12 +688,13 @@ class DefaultConnectionPoolSpecification extends Specification { def 'when getting a connection asynchronously should send MongoTimeoutException to callback after timeout period'() { given: + def operationContext = createOperationContext() pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(1).maxWaitTime(5, MILLISECONDS).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() - pool.get(OPERATION_CONTEXT) - def firstConnectionLatch = selectConnectionAsync(pool) - def secondConnectionLatch = selectConnectionAsync(pool) + pool.get(operationContext) + def firstConnectionLatch = selectConnectionAsync(pool, operationContext) + def secondConnectionLatch = selectConnectionAsync(pool, operationContext) when: firstConnectionLatch.get() @@ -721,9 +726,9 @@ class DefaultConnectionPoolSpecification extends Specification { selectConnectionAsync(pool).get() } - def selectConnectionAsync(DefaultConnectionPool pool) { + def selectConnectionAsync(DefaultConnectionPool pool, operationContext = createOperationContext()) { def serverLatch = new ConnectionLatch() - pool.getAsync(OPERATION_CONTEXT) { InternalConnection result, Throwable e -> + pool.getAsync(operationContext) { InternalConnection result, Throwable e -> serverLatch.connection = result serverLatch.throwable = e serverLatch.latch.countDown() diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerConnectionSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerConnectionSpecification.groovy index be6fbe06b83..26348f16198 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerConnectionSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerConnectionSpecification.groovy @@ -16,7 +16,7 @@ package com.mongodb.internal.connection - +import com.mongodb.ClusterFixture import com.mongodb.ReadPreference import com.mongodb.connection.ClusterConnectionMode import com.mongodb.internal.async.SingleResultCallback @@ -27,7 +27,6 @@ import org.bson.BsonInt32 import org.bson.codecs.BsonDocumentCodec import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.CustomMatchers.compare import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback import static com.mongodb.internal.connection.MessageHelper.LEGACY_HELLO_LOWER @@ -43,14 +42,16 @@ class DefaultServerConnectionSpecification extends Specification { def codec = new BsonDocumentCodec() def executor = Mock(ProtocolExecutor) def connection = new DefaultServerConnection(internalConnection, executor, ClusterConnectionMode.MULTIPLE) + def operationContext = ClusterFixture.createOperationContext() + when: - connection.commandAsync('test', command, validator, ReadPreference.primary(), codec, OPERATION_CONTEXT, callback) + connection.commandAsync('test', command, validator, ReadPreference.primary(), codec, operationContext, callback) then: 1 * executor.executeAsync({ compare(new CommandProtocolImpl('test', command, validator, ReadPreference.primary(), codec, true, - MessageSequences.EmptyMessageSequences.INSTANCE, ClusterConnectionMode.MULTIPLE, OPERATION_CONTEXT), it) - }, internalConnection, OPERATION_CONTEXT.getSessionContext(), callback) + MessageSequences.EmptyMessageSequences.INSTANCE, ClusterConnectionMode.MULTIPLE, operationContext), it) + }, internalConnection, operationContext.getSessionContext(), callback) } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy index 394ae48b1d0..9dcd4c19bad 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy @@ -54,7 +54,7 @@ import spock.lang.Specification import java.util.concurrent.CountDownLatch import static com.mongodb.ClusterFixture.CLIENT_METADATA -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.MongoCredential.createCredential import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE import static com.mongodb.connection.ClusterConnectionMode.SINGLE @@ -74,7 +74,7 @@ class DefaultServerSpecification extends Specification { Mock(SdamServerDescriptionManager), Mock(ServerListener), Mock(CommandListener), new ClusterClock(), false) when: - def receivedConnection = server.getConnection(OPERATION_CONTEXT) + def receivedConnection = server.getConnection(createOperationContext()) then: receivedConnection @@ -100,7 +100,7 @@ class DefaultServerSpecification extends Specification { when: def callback = new SupplyingCallback() - server.getConnectionAsync(OPERATION_CONTEXT, callback) + server.getConnectionAsync(createOperationContext(), callback) then: callback.get() == connection @@ -117,7 +117,7 @@ class DefaultServerSpecification extends Specification { server.close() when: - server.getConnection(OPERATION_CONTEXT) + server.getConnection(createOperationContext()) then: def ex = thrown(MongoServerUnavailableException) @@ -127,7 +127,7 @@ class DefaultServerSpecification extends Specification { def latch = new CountDownLatch(1) def receivedConnection = null def receivedThrowable = null - server.getConnectionAsync(OPERATION_CONTEXT) { + server.getConnectionAsync(createOperationContext()) { result, throwable -> receivedConnection = result; receivedThrowable = throwable; latch.countDown() } @@ -210,7 +210,6 @@ class DefaultServerSpecification extends Specification { given: def connectionPool = Mock(ConnectionPool) def serverMonitor = Mock(ServerMonitor) - connectionPool.get(OPERATION_CONTEXT) >> { throw exceptionToThrow } def server = defaultServer(connectionPool, serverMonitor) server.close() @@ -242,7 +241,7 @@ class DefaultServerSpecification extends Specification { def server = defaultServer(connectionPool, serverMonitor) when: - server.getConnection(OPERATION_CONTEXT) + server.getConnection(createOperationContext()) then: def e = thrown(MongoException) @@ -289,7 +288,7 @@ class DefaultServerSpecification extends Specification { def server = defaultServer(connectionPool, serverMonitor) when: - server.getConnection(OPERATION_CONTEXT) + server.getConnection(createOperationContext()) then: def e = thrown(MongoSecurityException) @@ -314,7 +313,7 @@ class DefaultServerSpecification extends Specification { def latch = new CountDownLatch(1) def receivedConnection = null def receivedThrowable = null - server.getConnectionAsync(OPERATION_CONTEXT) { + server.getConnectionAsync(createOperationContext()) { result, throwable -> receivedConnection = result; receivedThrowable = throwable; latch.countDown() } @@ -347,7 +346,7 @@ class DefaultServerSpecification extends Specification { def latch = new CountDownLatch(1) def receivedConnection = null def receivedThrowable = null - server.getConnectionAsync(OPERATION_CONTEXT) { + server.getConnectionAsync(createOperationContext()) { result, throwable -> receivedConnection = result; receivedThrowable = throwable; latch.countDown() } @@ -372,7 +371,7 @@ class DefaultServerSpecification extends Specification { clusterClock.advance(clusterClockClusterTime) def server = new DefaultServer(serverId, SINGLE, Mock(ConnectionPool), new TestConnectionFactory(), Mock(ServerMonitor), Mock(SdamServerDescriptionManager), Mock(ServerListener), Mock(CommandListener), clusterClock, false) - def testConnection = (TestConnection) server.getConnection(OPERATION_CONTEXT) + def testConnection = (TestConnection) server.getConnection(createOperationContext()) def sessionContext = new TestSessionContext(initialClusterTime) def response = BsonDocument.parse( '''{ @@ -383,7 +382,7 @@ class DefaultServerSpecification extends Specification { ''') def protocol = new TestCommandProtocol(response) testConnection.enqueueProtocol(protocol) - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) when: if (async) { diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy index 3cdabf31da3..10074ba9d3c 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy @@ -58,7 +58,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT import static com.mongodb.ReadPreference.primary import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE @@ -114,7 +114,7 @@ class InternalStreamConnectionSpecification extends Specification { def getOpenedConnection() { def connection = getConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) connection } @@ -132,7 +132,7 @@ class InternalStreamConnectionSpecification extends Specification { .lastUpdateTimeNanos(connection.getInitialServerDescription().getLastUpdateTime(NANOSECONDS)) .build() when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: connection.opened() @@ -159,7 +159,7 @@ class InternalStreamConnectionSpecification extends Specification { .build() when: - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -177,7 +177,7 @@ class InternalStreamConnectionSpecification extends Specification { failedInitializer) when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: thrown MongoInternalException @@ -195,7 +195,7 @@ class InternalStreamConnectionSpecification extends Specification { when: def futureResultCallback = new FutureResultCallback() - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -212,14 +212,14 @@ class InternalStreamConnectionSpecification extends Specification { def (buffers2, messageId2) = helper.hello() when: - connection.sendMessage(buffers1, messageId1, OPERATION_CONTEXT) + connection.sendMessage(buffers1, messageId1, createOperationContext()) then: connection.isClosed() thrown MongoSocketWriteException when: - connection.sendMessage(buffers2, messageId2, OPERATION_CONTEXT) + connection.sendMessage(buffers2, messageId2, createOperationContext()) then: thrown MongoSocketClosedException @@ -243,7 +243,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessageAsync(buffers1, messageId1, OPERATION_CONTEXT, sndCallbck1) + connection.sendMessageAsync(buffers1, messageId1, createOperationContext(), sndCallbck1) sndCallbck1.get(10, SECONDS) then: @@ -251,7 +251,7 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() when: - connection.sendMessageAsync(buffers2, messageId2, OPERATION_CONTEXT, sndCallbck2) + connection.sendMessageAsync(buffers2, messageId2, createOperationContext(), sndCallbck2) sndCallbck2.get(10, SECONDS) then: @@ -267,16 +267,16 @@ class InternalStreamConnectionSpecification extends Specification { def (buffers2, messageId2) = helper.hello() when: - connection.sendMessage(buffers1, messageId1, OPERATION_CONTEXT) - connection.sendMessage(buffers2, messageId2, OPERATION_CONTEXT) - connection.receiveMessage(messageId1, OPERATION_CONTEXT) + connection.sendMessage(buffers1, messageId1, createOperationContext()) + connection.sendMessage(buffers2, messageId2, createOperationContext()) + connection.receiveMessage(messageId1, createOperationContext()) then: connection.isClosed() thrown MongoSocketReadException when: - connection.receiveMessage(messageId2, OPERATION_CONTEXT) + connection.receiveMessage(messageId2, createOperationContext()) then: thrown MongoSocketClosedException @@ -289,7 +289,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: thrown(MongoInternalException) @@ -306,7 +306,7 @@ class InternalStreamConnectionSpecification extends Specification { def callback = new FutureResultCallback() when: - connection.receiveMessageAsync(1, OPERATION_CONTEXT, callback) + connection.receiveMessageAsync(1, createOperationContext(), callback) callback.get() then: @@ -321,7 +321,7 @@ class InternalStreamConnectionSpecification extends Specification { Thread.currentThread().interrupt() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: Thread.interrupted() @@ -335,7 +335,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: !Thread.interrupted() @@ -350,7 +350,7 @@ class InternalStreamConnectionSpecification extends Specification { Thread.currentThread().interrupt() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: Thread.interrupted() @@ -365,7 +365,7 @@ class InternalStreamConnectionSpecification extends Specification { Thread.currentThread().interrupt() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: Thread.interrupted() @@ -379,7 +379,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: thrown(MongoSocketWriteException) @@ -393,7 +393,7 @@ class InternalStreamConnectionSpecification extends Specification { Thread.currentThread().interrupt() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: Thread.interrupted() @@ -407,7 +407,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: !Thread.interrupted() @@ -422,7 +422,7 @@ class InternalStreamConnectionSpecification extends Specification { Thread.currentThread().interrupt() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: Thread.interrupted() @@ -437,7 +437,7 @@ class InternalStreamConnectionSpecification extends Specification { Thread.currentThread().interrupt() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: Thread.interrupted() @@ -451,7 +451,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: thrown(MongoSocketReadException) @@ -464,7 +464,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT.withTimeoutContext( + connection.receiveMessage(1, createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT))) then: @@ -482,7 +482,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT.withTimeoutContext( + connection.receiveMessage(1, createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT))) then: @@ -502,7 +502,7 @@ class InternalStreamConnectionSpecification extends Specification { } def connection = getOpenedConnection() def callback = new FutureResultCallback() - def operationContext = OPERATION_CONTEXT.withTimeoutContext( + def operationContext = createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT)) when: connection.receiveMessageAsync(1, operationContext, callback) @@ -525,7 +525,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() def callback = new FutureResultCallback() - def operationContext = OPERATION_CONTEXT.withTimeoutContext( + def operationContext = createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT)) when: connection.receiveMessageAsync(1, operationContext, callback) @@ -563,10 +563,10 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessageAsync(buffers1, messageId1, OPERATION_CONTEXT, sndCallbck1) - connection.sendMessageAsync(buffers2, messageId2, OPERATION_CONTEXT, sndCallbck2) - connection.receiveMessageAsync(messageId1, OPERATION_CONTEXT, rcvdCallbck1) - connection.receiveMessageAsync(messageId2, OPERATION_CONTEXT, rcvdCallbck2) + connection.sendMessageAsync(buffers1, messageId1, createOperationContext(), sndCallbck1) + connection.sendMessageAsync(buffers2, messageId2, createOperationContext(), sndCallbck2) + connection.receiveMessageAsync(messageId1, createOperationContext(), rcvdCallbck1) + connection.receiveMessageAsync(messageId2, createOperationContext(), rcvdCallbck2) rcvdCallbck1.get(1, SECONDS) then: @@ -588,14 +588,14 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: connection.isClosed() thrown MongoSocketReadException when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: thrown MongoSocketClosedException @@ -620,9 +620,9 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessageAsync(buffers1, messageId1, OPERATION_CONTEXT, sndCallbck1) - connection.sendMessageAsync(buffers2, messageId2, OPERATION_CONTEXT, sndCallbck2) - connection.receiveMessageAsync(messageId1, OPERATION_CONTEXT, rcvdCallbck1) + connection.sendMessageAsync(buffers1, messageId1, createOperationContext(), sndCallbck1) + connection.sendMessageAsync(buffers2, messageId2, createOperationContext(), sndCallbck2) + connection.receiveMessageAsync(messageId1, createOperationContext(), rcvdCallbck1) rcvdCallbck1.get(1, SECONDS) then: @@ -630,7 +630,7 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() when: - connection.receiveMessageAsync(messageId2, OPERATION_CONTEXT, rcvdCallbck2) + connection.receiveMessageAsync(messageId2, createOperationContext(), rcvdCallbck2) rcvdCallbck2.get(1, SECONDS) then: @@ -649,7 +649,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(_, _) >> helper.reply(response) when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: thrown(MongoCommandException) @@ -677,7 +677,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -705,7 +705,7 @@ class InternalStreamConnectionSpecification extends Specification { def callbacks = [] (1..numberOfOperations).each { n -> def (buffers, messageId, sndCallbck, rcvdCallbck) = messages.pop() - connection.sendMessageAsync(buffers, messageId, OPERATION_CONTEXT, sndCallbck) + connection.sendMessageAsync(buffers, messageId, createOperationContext(), sndCallbck) callbacks.add(sndCallbck) } streamLatch.countDown() @@ -730,7 +730,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(90, _) >> helper.defaultReply() when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: commandListener.eventsWereDelivered([ @@ -753,7 +753,7 @@ class InternalStreamConnectionSpecification extends Specification { when: connection.sendAndReceive(commandMessage, { BsonReader reader, DecoderContext decoderContext -> throw new CodecConfigurationException('') - }, OPERATION_CONTEXT) + }, createOperationContext()) then: thrown(CodecConfigurationException) @@ -783,7 +783,7 @@ class InternalStreamConnectionSpecification extends Specification { 1 * advanceClusterTime(BsonDocument.parse(response).getDocument('$clusterTime')) getReadConcern() >> ReadConcern.DEFAULT } - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) when: connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), operationContext) @@ -819,7 +819,7 @@ class InternalStreamConnectionSpecification extends Specification { 1 * advanceClusterTime(BsonDocument.parse(response).getDocument('$clusterTime')) getReadConcern() >> ReadConcern.DEFAULT } - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) when: connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), operationContext, callback) @@ -839,7 +839,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.write(_, _) >> { throw new MongoSocketWriteException('Failed to write', serverAddress, new IOException()) } when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: def e = thrown(MongoSocketWriteException) @@ -859,7 +859,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(16, _) >> { throw new MongoSocketReadException('Failed to read', serverAddress) } when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: def e = thrown(MongoSocketReadException) @@ -880,7 +880,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(90, _) >> { throw new MongoSocketReadException('Failed to read', serverAddress) } when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: def e = thrown(MongoSocketException) @@ -902,7 +902,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(_, _) >> helper.reply(response) when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: def e = thrown(MongoCommandException) @@ -923,7 +923,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(90, _) >> helper.defaultReply() when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: commandListener.eventsWereDelivered([ @@ -959,7 +959,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(_, _) >> helper.reply('{ok : 0, errmsg : "failed"}') when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: thrown(MongoCommandException) @@ -1005,7 +1005,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1038,7 +1038,7 @@ class InternalStreamConnectionSpecification extends Specification { when: connection.sendAndReceiveAsync(commandMessage, { BsonReader reader, DecoderContext decoderContext -> throw new CodecConfigurationException('') - }, OPERATION_CONTEXT, callback) + }, createOperationContext(), callback) callback.get() then: @@ -1065,7 +1065,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1093,7 +1093,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1124,7 +1124,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1156,7 +1156,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1187,7 +1187,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/JMXConnectionPoolListenerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/JMXConnectionPoolListenerSpecification.groovy index 374687f7d01..5a7bcd3e492 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/JMXConnectionPoolListenerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/JMXConnectionPoolListenerSpecification.groovy @@ -29,7 +29,7 @@ import spock.lang.Unroll import javax.management.ObjectName import java.lang.management.ManagementFactory -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY class JMXConnectionPoolListenerSpecification extends Specification { @@ -50,8 +50,8 @@ class JMXConnectionPoolListenerSpecification extends Specification { provider.ready() when: - provider.get(OPERATION_CONTEXT) - provider.get(OPERATION_CONTEXT).close() + provider.get(createOperationContext()) + provider.get(createOperationContext()).close() then: with(jmxListener.getMBean(SERVER_ID)) { diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java index 7366a03b584..87dd1581045 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoClientException; import com.mongodb.MongoConfigurationException; import com.mongodb.MongoException; @@ -52,7 +53,6 @@ import java.util.concurrent.atomic.AtomicReference; import static com.mongodb.ClusterFixture.CLIENT_METADATA; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.ClusterFixture.createOperationContext; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -96,14 +96,14 @@ public void shouldSelectServerWhenThereIsNoSRVLookup() { mock(DnsSrvRecordMonitorFactory.class)); // when - ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), OPERATION_CONTEXT); + ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), ClusterFixture.createOperationContext()); // then assertServerTupleExpectations(serverAddress, expectedServer, serverTuple); // when FutureResultCallback callback = new FutureResultCallback<>(); - cluster.selectServerAsync(mock(ServerSelector.class), OPERATION_CONTEXT, callback); + cluster.selectServerAsync(mock(ServerSelector.class), ClusterFixture.createOperationContext(), callback); serverTuple = callback.get(); // then @@ -131,7 +131,7 @@ public void shouldSelectServerWhenThereIsSRVLookup() { cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); // when - ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), OPERATION_CONTEXT); + ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), ClusterFixture.createOperationContext()); // then assertServerTupleExpectations(resolvedServerAddress, expectedServer, serverTuple); @@ -159,7 +159,7 @@ public void shouldSelectServerAsynchronouslyWhenThereIsSRVLookup() { // when FutureResultCallback callback = new FutureResultCallback<>(); - cluster.selectServerAsync(mock(ServerSelector.class), OPERATION_CONTEXT, callback); + cluster.selectServerAsync(mock(ServerSelector.class), ClusterFixture.createOperationContext(), callback); ServerTuple serverTuple = callback.get(); // then @@ -185,7 +185,7 @@ public void shouldFailSelectServerWhenThereIsSRVMisconfiguration() { cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); MongoClientException exception = assertThrows(MongoClientException.class, () -> cluster.selectServer(mock(ServerSelector.class), - OPERATION_CONTEXT)); + ClusterFixture.createOperationContext())); assertEquals("In load balancing mode, the host must resolve to a single SRV record, but instead it resolved to multiple hosts", exception.getMessage()); } @@ -209,7 +209,7 @@ public void shouldFailSelectServerAsynchronouslyWhenThereIsSRVMisconfiguration() cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); FutureResultCallback callback = new FutureResultCallback<>(); - cluster.selectServerAsync(mock(ServerSelector.class), OPERATION_CONTEXT, callback); + cluster.selectServerAsync(mock(ServerSelector.class), ClusterFixture.createOperationContext(), callback); MongoClientException exception = assertThrows(MongoClientException.class, callback::get); assertEquals("In load balancing mode, the host must resolve to a single SRV record, but instead it resolved to multiple hosts", diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy index e6f6afb02e0..de12c35af5e 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy @@ -39,7 +39,7 @@ import org.bson.BsonInt32 import org.bson.BsonString import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE import static com.mongodb.connection.ClusterConnectionMode.SINGLE import static com.mongodb.internal.operation.ServerVersionHelper.LATEST_WIRE_VERSION @@ -63,7 +63,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def logger = Stub(Logger) { isDebugEnabled() >> debugLoggingEnabled } - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, commandListener, operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) @@ -109,7 +109,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def logger = Mock(Logger) { isDebugEnabled() >> true } - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, commandListener, operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) @@ -166,7 +166,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def logger = Mock(Logger) { isDebugEnabled() >> true } - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, null, operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) @@ -200,7 +200,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def logger = Mock(Logger) { isDebugEnabled() >> true } - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() def sender = new LoggingCommandEventSender(['createUser'] as Set, [] as Set, connectionDescription, null, operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy index a3cf8104fd3..737aead0300 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy @@ -29,7 +29,7 @@ import org.bson.types.ObjectId import spock.lang.Specification import static com.mongodb.ClusterFixture.CLIENT_METADATA -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE import static com.mongodb.connection.ClusterType.REPLICA_SET import static com.mongodb.connection.ClusterType.SHARDED @@ -93,11 +93,12 @@ class MultiServerClusterSpecification extends Specification { def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts(Arrays.asList(firstServer)).mode(MULTIPLE).build(), factory, CLIENT_METADATA) cluster.close() + def operationContext = createOperationContext() when: cluster.getServersSnapshot( - OPERATION_CONTEXT.getTimeoutContext().computeServerSelectionTimeout(), - OPERATION_CONTEXT.getTimeoutContext()) + operationContext.getTimeoutContext().computeServerSelectionTimeout(), + operationContext.getTimeoutContext()) then: thrown(IllegalStateException) @@ -386,7 +387,7 @@ class MultiServerClusterSpecification extends Specification { cluster.close() when: - cluster.selectServer(new WritableServerSelector(), OPERATION_CONTEXT) + cluster.selectServer(new WritableServerSelector(), createOperationContext()) then: thrown(IllegalStateException) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/PlainAuthenticatorUnitTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/PlainAuthenticatorUnitTest.java index 12d8e9fa7c3..a535262b4b4 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/PlainAuthenticatorUnitTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/PlainAuthenticatorUnitTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import com.mongodb.async.FutureResultCallback; @@ -30,7 +31,6 @@ import java.util.List; import java.util.concurrent.ExecutionException; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getServerApi; import static com.mongodb.internal.connection.MessageHelper.getApiVersionField; import static com.mongodb.internal.connection.MessageHelper.getDbField; @@ -54,7 +54,7 @@ public void before() { public void testSuccessfulAuthentication() { enqueueSuccessfulReply(); - subject.authenticate(connection, connectionDescription, OPERATION_CONTEXT); + subject.authenticate(connection, connectionDescription, ClusterFixture.createOperationContext()); validateMessages(); } @@ -64,7 +64,7 @@ public void testSuccessfulAuthenticationAsync() throws ExecutionException, Inter enqueueSuccessfulReply(); FutureResultCallback futureCallback = new FutureResultCallback<>(); - subject.authenticateAsync(connection, connectionDescription, OPERATION_CONTEXT, futureCallback); + subject.authenticateAsync(connection, connectionDescription, ClusterFixture.createOperationContext(), futureCallback); futureCallback.get(); validateMessages(); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java index f1c8f69eb29..3c1a7aad390 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; import com.mongodb.MongoConnectionPoolClearedException; +import com.mongodb.MongoException; import com.mongodb.ServerAddress; import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.connection.ClusterDescription; @@ -25,30 +26,42 @@ import com.mongodb.connection.ServerDescription; import com.mongodb.connection.ServerId; import com.mongodb.internal.connection.OperationContext.ServerDeprioritization; +import com.mongodb.internal.mockito.MongoMockito; +import com.mongodb.selector.ServerSelector; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.ClusterFixture.createOperationContext; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; +import static org.junit.jupiter.params.provider.Arguments.of; +import static org.mockito.ArgumentMatchers.any; final class ServerDeprioritizationTest { private static final ServerDescription SERVER_A = serverDescription("a"); private static final ServerDescription SERVER_B = serverDescription("b"); private static final ServerDescription SERVER_C = serverDescription("c"); private static final List ALL_SERVERS = unmodifiableList(asList(SERVER_A, SERVER_B, SERVER_C)); - private static final ClusterDescription REPLICA_SET = clusterDescription(ClusterType.REPLICA_SET); - private static final ClusterDescription SHARDED_CLUSTER = clusterDescription(ClusterType.SHARDED); - + private static final ClusterDescription REPLICA_SET_CLUSTER = multipleModeClusterDescription(ClusterType.REPLICA_SET); + private static final ClusterDescription SHARDED_CLUSTER = multipleModeClusterDescription(ClusterType.SHARDED); + private static final ClusterDescription UNKNOWN_CLUSTER = multipleModeClusterDescription(ClusterType.UNKNOWN); + private static final List CLUSTERS = asList(SHARDED_CLUSTER, REPLICA_SET_CLUSTER, UNKNOWN_CLUSTER); private ServerDeprioritization serverDeprioritization; @BeforeEach @@ -56,48 +69,137 @@ void beforeEach() { serverDeprioritization = createOperationContext(TIMEOUT_SETTINGS).getServerDeprioritization(); } - @Test - void selectNoneDeprioritized() { - assertAll( - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(SHARDED_CLUSTER)), - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(REPLICA_SET)) - ); + private static Stream selectNoneDeprioritized() { + return CLUSTERS.stream().flatMap(clusterDescription -> + Stream.of( + namedArguments(clusterDescription), + namedArguments(clusterDescription, SERVER_A), + namedArguments(clusterDescription, SERVER_B), + namedArguments(clusterDescription, SERVER_C), + namedArguments(clusterDescription, SERVER_A, SERVER_B), + namedArguments(clusterDescription, SERVER_B, SERVER_A), + namedArguments(clusterDescription, SERVER_A, SERVER_C), + namedArguments(clusterDescription, SERVER_C, SERVER_A), + namedArguments(clusterDescription, SERVER_A, SERVER_B, SERVER_C) + )); } - @Test - void selectSomeDeprioritized() { - deprioritize(SERVER_B); + @ParameterizedTest + @MethodSource + void selectNoneDeprioritized(final ClusterDescription clusterDescription, final List selectorResult) { + ServerSelector wrappedSelector = createAssertingSelector(ALL_SERVERS, selectorResult); + assertEquals(selectorResult, serverDeprioritization.apply(wrappedSelector).select(clusterDescription)); + } + + @ParameterizedTest + @EnumSource(value = ClusterType.class, names = {"STANDALONE", "LOAD_BALANCED"}) + void selectNoneDeprioritizedSingleServerCluster(final ClusterType clusterType) { + ClusterDescription cluster = singleModeClusterDescription(clusterType); + ServerSelector wrappedSelector = createAssertingSelector(singletonList(SERVER_A), singletonList(SERVER_A)); + ServerSelector emptyListWrappedSelector = createAssertingSelector(singletonList(SERVER_A), emptyList()); assertAll( - () -> assertEquals(asList(SERVER_A, SERVER_C), serverDeprioritization.getServerSelector().select(SHARDED_CLUSTER)), - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(REPLICA_SET)) + () -> assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(wrappedSelector).select(cluster)), + () -> assertEquals(emptyList(), serverDeprioritization.apply(emptyListWrappedSelector).select(cluster)) ); } - @Test - void selectAllDeprioritized() { - deprioritize(SERVER_A); - deprioritize(SERVER_B); - deprioritize(SERVER_C); - assertAll( - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(SHARDED_CLUSTER)), - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(REPLICA_SET)) + private static Stream deprioritizableClusters() { + return Stream.of( + of(SHARDED_CLUSTER, new RuntimeException()), + of(SHARDED_CLUSTER, new MongoException(0, "test")), + of(REPLICA_SET_CLUSTER, createSystemOverloadedError()), + of(UNKNOWN_CLUSTER, createSystemOverloadedError()) ); } + private static Stream selectSomeDeprioritized() { + return deprioritizableClusters().flatMap(args -> { + ClusterDescription clusterDescription = (ClusterDescription) args.get()[0]; + Throwable exception = (Throwable) args.get()[1]; + return Stream.of( + namedArguments(clusterDescription, exception, SERVER_A), + namedArguments(clusterDescription, exception, SERVER_C), + namedArguments(clusterDescription, exception, SERVER_A, SERVER_C), + namedArguments(clusterDescription, exception, SERVER_C, SERVER_A) + ); + }); + } + + @ParameterizedTest + @MethodSource + void selectSomeDeprioritized(final ClusterDescription clusterDescription, final Throwable exception, + final List selectorResult) { + deprioritize(clusterDescription.getType(), exception, SERVER_B); + List expectedWrappedSelectorFilteredInput = asList(SERVER_A, SERVER_C); + ServerSelector wrappedSelector = createAssertingSelector(expectedWrappedSelectorFilteredInput, selectorResult); + assertEquals(selectorResult, serverDeprioritization.apply(wrappedSelector).select(clusterDescription)); + } + + private static Stream selectAllDeprioritized() { + return deprioritizableClusters().flatMap(args -> { + ClusterDescription clusterDescription = (ClusterDescription) args.get()[0]; + Throwable exception = (Throwable) args.get()[1]; + return Stream.of( + namedArguments(clusterDescription, exception), + namedArguments(clusterDescription, exception, SERVER_A), + namedArguments(clusterDescription, exception, SERVER_B), + namedArguments(clusterDescription, exception, SERVER_C), + namedArguments(clusterDescription, exception, SERVER_A, SERVER_B), + namedArguments(clusterDescription, exception, SERVER_B, SERVER_A), + namedArguments(clusterDescription, exception, SERVER_A, SERVER_C), + namedArguments(clusterDescription, exception, SERVER_C, SERVER_A), + namedArguments(clusterDescription, exception, SERVER_A, SERVER_B, SERVER_C) + ); + }); + } + + @ParameterizedTest + @MethodSource + void selectAllDeprioritized(final ClusterDescription clusterDescription, final Throwable exception, + final List selectorResult) { + deprioritize(clusterDescription.getType(), exception, SERVER_A); + deprioritize(clusterDescription.getType(), exception, SERVER_B); + deprioritize(clusterDescription.getType(), exception, SERVER_C); + ServerSelector selector = createAssertingSelector(ALL_SERVERS, selectorResult); + assertEquals(selectorResult, serverDeprioritization.apply(selector).select(clusterDescription)); + } + + @ParameterizedTest + @EnumSource(value = ClusterType.class, names = {"STANDALONE", "LOAD_BALANCED"}) + void selectAllDeprioritizedSingleServerCluster(final ClusterType clusterType) { + ClusterDescription cluster = singleModeClusterDescription(clusterType); + deprioritize(clusterType, createSystemOverloadedError(), SERVER_A); + ServerSelector selector = createAssertingSelector(singletonList(SERVER_A), singletonList(SERVER_A)); + assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster)); + } + @ParameterizedTest - @EnumSource(value = ClusterType.class, mode = EXCLUDE, names = {"SHARDED"}) - void serverSelectorSelectsAllIfNotShardedCluster(final ClusterType clusterType) { - serverDeprioritization.updateCandidate(SERVER_A.getAddress()); - serverDeprioritization.onAttemptFailure(new RuntimeException()); - assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(clusterDescription(clusterType))); + @MethodSource("selectSomeDeprioritized") + void selectWithRetryWhenWrappedReturnsEmpty(final ClusterDescription clusterDescription, + final Throwable exception, + final List selectorResult) { + deprioritize(clusterDescription.getType(), exception, SERVER_B); + ServerSelector selector = MongoMockito.mock(ServerSelector.class, tuner -> + Mockito.when(tuner.select(any(ClusterDescription.class))) + .thenAnswer(invocation -> { + assertEquals(asList(SERVER_A, SERVER_C), invocation.getArgument(0).getServerDescriptions()); + return emptyList(); + }) + .thenAnswer(invocation -> { + assertEquals(ALL_SERVERS, invocation.getArgument(0).getServerDescriptions()); + return selectorResult; + }) + ); + assertEquals(selectorResult, serverDeprioritization.apply(selector).select(clusterDescription)); } @Test void onAttemptFailureIgnoresIfPoolClearedException() { - serverDeprioritization.updateCandidate(SERVER_A.getAddress()); + serverDeprioritization.updateCandidate(SERVER_A.getAddress(), ClusterType.SHARDED); serverDeprioritization.onAttemptFailure( new MongoConnectionPoolClearedException(new ServerId(new ClusterId(), new ServerAddress()), null)); - assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(SHARDED_CLUSTER)); + ServerSelector selector = createAssertingSelector(ALL_SERVERS, ALL_SERVERS); + assertEquals(ALL_SERVERS, serverDeprioritization.apply(selector).select(SHARDED_CLUSTER)); } @Test @@ -105,13 +207,43 @@ void onAttemptFailureDoesNotThrowIfNoCandidate() { assertDoesNotThrow(() -> serverDeprioritization.onAttemptFailure(new RuntimeException())); } - private void deprioritize(final ServerDescription... serverDescriptions) { + @ParameterizedTest + @EnumSource(value = ClusterType.class, names = "SHARDED", mode = EnumSource.Mode.EXCLUDE) + void onAttemptFailureIgnoresIfNonShardedWithoutOverloadError(final ClusterType clusterType) { + ClusterDescription cluster = multipleModeClusterDescription(clusterType); + ServerSelector selector = createAssertingSelector(ALL_SERVERS, singletonList(SERVER_A)); + + assertAll(() -> { + serverDeprioritization.updateCandidate(SERVER_B.getAddress(), clusterType); + serverDeprioritization.onAttemptFailure(new RuntimeException()); + assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), + "Expected no deprioritization for " + clusterType + " with RuntimeException"); + }, () -> { + serverDeprioritization = createOperationContext(TIMEOUT_SETTINGS).getServerDeprioritization(); + serverDeprioritization.updateCandidate(SERVER_B.getAddress(), clusterType); + serverDeprioritization.onAttemptFailure(new MongoException(1, "error")); + assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), + "Expected no deprioritization for " + clusterType + " with no SystemOverloadedError MongoException"); + } + ); + } + + private void deprioritize(final ClusterType clusterType, final Throwable exception, final ServerDescription... serverDescriptions) { for (ServerDescription serverDescription : serverDescriptions) { - serverDeprioritization.updateCandidate(serverDescription.getAddress()); - serverDeprioritization.onAttemptFailure(new RuntimeException()); + serverDeprioritization.updateCandidate(serverDescription.getAddress(), clusterType); + serverDeprioritization.onAttemptFailure(exception); } } + private static ServerSelector createAssertingSelector( + final List expectedInput, + final List selectorResult) { + return clusterDescription -> { + assertEquals(expectedInput, clusterDescription.getServerDescriptions()); + return selectorResult; + }; + } + private static ServerDescription serverDescription(final String host) { return ServerDescription.builder() .state(ServerConnectionState.CONNECTED) @@ -120,7 +252,39 @@ private static ServerDescription serverDescription(final String host) { .build(); } - private static ClusterDescription clusterDescription(final ClusterType clusterType) { + private static ClusterDescription multipleModeClusterDescription(final ClusterType clusterType) { return new ClusterDescription(ClusterConnectionMode.MULTIPLE, clusterType, ALL_SERVERS); } + + private static ClusterDescription singleModeClusterDescription(final ClusterType clusterType) { + return new ClusterDescription(ClusterConnectionMode.SINGLE, clusterType, singletonList(SERVER_A)); + } + + private static MongoException createSystemOverloadedError() { + MongoException e = new MongoException(6, "overloaded"); + e.addLabel("SystemOverloadedError"); + return e; + } + + private static Arguments namedArguments(final ClusterDescription clusterDescription, final ServerDescription... serverDescriptions) { + return of(Named.of(generateArgumentName(clusterDescription), clusterDescription), + Named.of(generateArgumentName(asList(serverDescriptions)), asList(serverDescriptions))); + } + + private static Arguments namedArguments(final ClusterDescription clusterDescription, final Throwable exception, final ServerDescription... serverDescriptions) { + return of(Named.of(generateArgumentName(clusterDescription), clusterDescription), + exception, + Named.of(generateArgumentName(asList(serverDescriptions)), asList(serverDescriptions))); + } + + private static String generateArgumentName(final List servers) { + return "[" + servers.stream() + .map(ServerDescription::getAddress) + .map(ServerAddress::getHost) + .collect(Collectors.joining(", ")) + "]"; + } + + private static String generateArgumentName(final ClusterDescription clusterDescription) { + return "[" + clusterDescription.getType() + ", " + generateArgumentName(clusterDescription.getServerDescriptions()) + "]"; + } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java index 2a70deaf90d..0406a8a9bbb 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.ServerAddress; import com.mongodb.connection.ClusterType; import com.mongodb.connection.ServerDescription; @@ -32,7 +33,6 @@ import java.util.Collection; import java.util.stream.Collectors; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getClusterDescription; import static com.mongodb.internal.connection.ClusterDescriptionHelper.getPrimaries; import static com.mongodb.internal.event.EventListenerHelper.NO_OP_CLUSTER_LISTENER; @@ -151,9 +151,10 @@ private void assertServer(final String serverName, final BsonDocument expectedSe if (expectedServerDescriptionDocument.isDocument("pool")) { int expectedGeneration = expectedServerDescriptionDocument.getDocument("pool").getNumber("generation").intValue(); - Timeout serverSelectionTimeout = OPERATION_CONTEXT.getTimeoutContext().computeServerSelectionTimeout(); + OperationContext operationContext = ClusterFixture.createOperationContext(); + Timeout serverSelectionTimeout = operationContext.getTimeoutContext().computeServerSelectionTimeout(); DefaultServer server = (DefaultServer) getCluster() - .getServersSnapshot(serverSelectionTimeout, OPERATION_CONTEXT.getTimeoutContext()) + .getServersSnapshot(serverSelectionTimeout, operationContext.getTimeoutContext()) .getServer(new ServerAddress(serverName)); assertEquals(expectedGeneration, server.getConnectionPool().getGeneration()); } diff --git a/driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java similarity index 56% rename from driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java rename to driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java index 8b878fa77c5..ed8f6fa9550 100644 --- a/driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java @@ -14,19 +14,34 @@ * limitations under the License. */ -package com.mongodb.connection; +package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoConfigurationException; +import com.mongodb.MongoException; +import com.mongodb.MongoTimeoutException; import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; import com.mongodb.Tag; import com.mongodb.TagSet; -import com.mongodb.internal.selector.LatencyMinimizingServerSelector; +import com.mongodb.assertions.Assertions; +import com.mongodb.connection.ClusterConnectionMode; +import com.mongodb.connection.ClusterDescription; +import com.mongodb.connection.ClusterId; +import com.mongodb.connection.ClusterSettings; +import com.mongodb.connection.ClusterType; +import com.mongodb.connection.ServerConnectionState; +import com.mongodb.connection.ServerDescription; +import com.mongodb.connection.ServerSettings; +import com.mongodb.connection.ServerType; +import com.mongodb.event.ServerDescriptionChangedEvent; +import com.mongodb.internal.TimeoutContext; +import com.mongodb.internal.mockito.MongoMockito; import com.mongodb.internal.selector.ReadPreferenceServerSelector; import com.mongodb.internal.selector.WritableServerSelector; +import com.mongodb.internal.time.Timeout; import com.mongodb.lang.NonNull; import com.mongodb.lang.Nullable; -import com.mongodb.selector.CompositeServerSelector; import com.mongodb.selector.ServerSelector; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -34,36 +49,54 @@ import org.bson.BsonInt64; import org.bson.BsonString; import org.bson.BsonValue; +import org.bson.json.JsonWriterSettings; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import util.JsonPoweredTestHelper; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; +import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; +import static com.mongodb.connection.ServerDescription.MIN_DRIVER_WIRE_VERSION; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; +import static org.mockito.Mockito.when; -// See https://github.com/mongodb/specifications/tree/master/source/server-selection/tests +/** + * See Server Selection Tests. + */ @RunWith(Parameterized.class) public class ServerSelectionSelectionTest { private final String description; private final BsonDocument definition; private final ClusterDescription clusterDescription; - private final long heartbeatFrequencyMS; private final boolean error; + private static final Set TOPOLOGY_DESCRIPTION_FIELDS = new HashSet<>(Arrays.asList("type", "servers")); + private static final Set SERVER_DESCRIPTION_FIELDS = new HashSet<>(Arrays.asList( + "address", "type", "tags", "avg_rtt_ms", "lastWrite", "lastUpdateTime", "maxWireVersion")); + private static final Set READ_PREFERENCE_FIELDS = new HashSet<>( + Arrays.asList("mode", "tag_sets", "maxStalenessSeconds")); + public ServerSelectionSelectionTest(final String description, final BsonDocument definition) { this.description = description; this.definition = definition; - this.heartbeatFrequencyMS = definition.getNumber("heartbeatFrequencyMS", new BsonInt64(10000)).longValue(); + + long heartbeatFrequencyMS = definition.getNumber("heartbeatFrequencyMS", new BsonInt64(10000)).longValue(); this.error = definition.getBoolean("error", BsonBoolean.FALSE).getValue(); this.clusterDescription = buildClusterDescription(definition.getDocument("topology_description"), ServerSettings.builder().heartbeatFrequency(heartbeatFrequencyMS, TimeUnit.MILLISECONDS).build()); @@ -73,37 +106,38 @@ public ServerSelectionSelectionTest(final String description, final BsonDocument public void shouldPassAllOutcomes() { // skip this test because the driver prohibits maxStaleness or tagSets with mode of primary at a much lower level assumeFalse(description.endsWith("/max-staleness/tests/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.json")); - assumeFalse(description.contains("Deprioritized")); // TODO JAVA-6021 deprioritized server selection" - - ServerSelector serverSelector = null; - List suitableServers = buildServerDescriptions(definition.getArray("suitable_servers", new BsonArray())); - List selectedServers = null; - try { - serverSelector = getServerSelector(); - selectedServers = serverSelector.select(clusterDescription); + ServerTuple serverTuple; + ServerSelector serverSelector = getServerSelector(); + OperationContext operationContext = createOperationContext(); + Cluster.ServersSnapshot serversSnapshot = createServersSnapshot(clusterDescription); + List inLatencyWindowServers = buildServerDescriptions(definition.getArray("in_latency_window", new BsonArray())); + + try (BaseCluster cluster = new TestCluster(clusterDescription, serversSnapshot)) { + serverTuple = cluster.selectServer(serverSelector, operationContext); if (error) { - fail("Should have thrown exception"); + fail(format("Should have thrown exception")); } } catch (MongoConfigurationException e) { if (!error) { - fail("Should not have thrown exception: " + e); + fail(format("Should not have thrown exception: %s", e)); } return; + } catch (MongoTimeoutException mongoTimeoutException) { + assertTrue(format("Expected empty but was %s", inLatencyWindowServers.size()), + inLatencyWindowServers.isEmpty()); + return; } - assertServers(selectedServers, suitableServers); - - ServerSelector latencyBasedServerSelector = new CompositeServerSelector(asList(serverSelector, - new LatencyMinimizingServerSelector(15, TimeUnit.MILLISECONDS))); - List inLatencyWindowServers = buildServerDescriptions(definition.getArray("in_latency_window")); - List latencyBasedSelectedServers = latencyBasedServerSelector.select(clusterDescription); - assertServers(latencyBasedSelectedServers, inLatencyWindowServers); + assertNotNull(format("Server tuple should not be null"), serverTuple); + assertTrue(format("Selected server should be in latency window. Selected server: %s", serverTuple.getServerDescription()), + inLatencyWindowServers.stream().anyMatch(s -> s.equals(serverTuple.getServerDescription()))); } @Parameterized.Parameters(name = "{0}") public static Collection data() { List data = new ArrayList<>(); for (BsonDocument testDocument : JsonPoweredTestHelper.getSpecTestDocuments("server-selection/tests/server_selection")) { - data.add(new Object[]{testDocument.getString("resourcePath").getValue(), testDocument}); + String resourcePath = testDocument.getString("resourcePath").getValue(); + data.add(new Object[]{resourcePath, testDocument}); } for (BsonDocument testDocument : JsonPoweredTestHelper.getSpecTestDocuments("max-staleness/tests")) { data.add(new Object[]{testDocument.getString("resourcePath").getValue(), testDocument}); @@ -112,11 +146,12 @@ public static Collection data() { } public static ClusterDescription buildClusterDescription(final BsonDocument topologyDescription, - @Nullable final ServerSettings serverSettings) { + @Nullable final ServerSettings serverSettings) { + validateTestDescriptionFields(topologyDescription.keySet(), TOPOLOGY_DESCRIPTION_FIELDS); ClusterType clusterType = getClusterType(topologyDescription.getString("type").getValue()); ClusterConnectionMode connectionMode = getClusterConnectionMode(clusterType); List servers = buildServerDescriptions(topologyDescription.getArray("servers")); - return new ClusterDescription(connectionMode, clusterType, servers, null, + return new ClusterDescription(connectionMode, clusterType, servers, ClusterSettings.builder().build(), serverSettings == null ? ServerSettings.builder().build() : serverSettings); } @@ -153,6 +188,7 @@ private static List buildServerDescriptions(final BsonArray s } private static ServerDescription buildServerDescription(final BsonDocument serverDescription) { + validateTestDescriptionFields(serverDescription.keySet(), SERVER_DESCRIPTION_FIELDS); ServerDescription.Builder builder = ServerDescription.builder(); builder.address(new ServerAddress(serverDescription.getString("address").getValue())); ServerType serverType = getServerType(serverDescription.getString("type").getValue()); @@ -175,6 +211,8 @@ private static ServerDescription buildServerDescription(final BsonDocument serve } if (serverDescription.containsKey("maxWireVersion")) { builder.maxWireVersion(serverDescription.getNumber("maxWireVersion").intValue()); + } else { + builder.maxWireVersion(MIN_DRIVER_WIRE_VERSION); } return builder.build(); } @@ -229,6 +267,7 @@ private ServerSelector getServerSelector() { return new WritableServerSelector(); } else { BsonDocument readPreferenceDefinition = definition.getDocument("read_preference"); + validateTestDescriptionFields(readPreferenceDefinition.keySet(), READ_PREFERENCE_FIELDS); ReadPreference readPreference; if (readPreferenceDefinition.getString("mode").getValue().equals("Primary")) { readPreference = ReadPreference.valueOf("Primary"); @@ -244,8 +283,82 @@ private ServerSelector getServerSelector() { } } - private void assertServers(final List actual, final List expected) { - assertEquals(expected.size(), actual.size()); - assertTrue(actual.containsAll(expected)); + private static List extractDeprioritizedServerAddresses(final BsonDocument definition) { + if (!definition.containsKey("deprioritized_servers")) { + return Collections.emptyList(); + } + return definition.getArray("deprioritized_servers") + .stream() + .map(BsonValue::asDocument) + .map(ServerSelectionSelectionTest::buildServerDescription) + .map(ServerDescription::getAddress) + .collect(Collectors.toList()); + } + + private OperationContext createOperationContext() { + OperationContext operationContext = + OperationContext.simpleOperationContext( + new TimeoutContext(TIMEOUT_SETTINGS.withServerSelectionTimeoutMS(0))); + OperationContext.ServerDeprioritization serverDeprioritization = operationContext.getServerDeprioritization(); + for (ServerAddress address : extractDeprioritizedServerAddresses(definition)) { + serverDeprioritization.updateCandidate(address, clusterDescription.getType()); + // The spec defines deprioritized_servers as a pre-populated list to feed into the selection mechanism - not as "simulate the + // failure that caused deprioritization." Thus, SystemOverloadedError used unconditionally regardless of the cluster type. + MongoException error = new MongoException("test"); + error.addLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL); + serverDeprioritization.onAttemptFailure(error); + } + return operationContext; + } + + private static Cluster.ServersSnapshot createServersSnapshot( + final ClusterDescription clusterDescription) { + Map serverMap = new HashMap<>(); + for (ServerDescription desc : clusterDescription.getServerDescriptions()) { + serverMap.put(desc.getAddress(), MongoMockito.mock(Server.class, server -> { + // `MinimumOperationCountServerSelector` should select any server since they all have 0 operation count. + when(server.operationCount()).thenReturn(0); + })); + } + return serverMap::get; + } + + private static void validateTestDescriptionFields(final Set actualFields, final Set knownFields) { + Set unknownFields = new HashSet<>(actualFields); + unknownFields.removeAll(knownFields); + if (!unknownFields.isEmpty()) { + throw new UnsupportedOperationException("Unknown fields: " + unknownFields); + } + } + + private static class TestCluster extends BaseCluster { + private final ServersSnapshot serversSnapshot; + + TestCluster(final ClusterDescription clusterDescription, final ServersSnapshot serversSnapshot) { + super(new ClusterId(), clusterDescription.getClusterSettings(), new TestClusterableServerFactory(), + ClusterFixture.CLIENT_METADATA); + this.serversSnapshot = serversSnapshot; + updateDescription(clusterDescription); + } + + @Override + protected void connect() { + // NOOP: this method may be invoked in test cases where no server is expected to be selected. + } + + @Override + public ServersSnapshot getServersSnapshot(final Timeout serverSelectionTimeout, final TimeoutContext timeoutContext) { + return serversSnapshot; + } + + @Override + public void onChange(final ServerDescriptionChangedEvent event) { + Assertions.fail(); + } + } + + private String format(final String messageFormat, final Object... args) { + String message = String.format(messageFormat, args); + return message + "\nTest Definition:\n" + definition.toJson(JsonWriterSettings.builder().indent(true).build()); } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionWithinLatencyWindowTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionWithinLatencyWindowTest.java index 14d6c59b0c6..23e1f59d3a7 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionWithinLatencyWindowTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionWithinLatencyWindowTest.java @@ -43,7 +43,7 @@ import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.ClusterFixture.createOperationContext; -import static com.mongodb.connection.ServerSelectionSelectionTest.buildClusterDescription; +import static com.mongodb.internal.connection.ServerSelectionSelectionTest.buildClusterDescription; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toMap; import static org.junit.Assert.assertEquals; diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy index faa04a188f9..126cadce0c0 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy @@ -29,7 +29,7 @@ import com.mongodb.internal.selector.WritableServerSelector import spock.lang.Specification import static com.mongodb.ClusterFixture.CLIENT_METADATA -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.connection.ClusterConnectionMode.SINGLE import static com.mongodb.connection.ClusterType.REPLICA_SET import static com.mongodb.connection.ClusterType.UNKNOWN @@ -78,10 +78,9 @@ class SingleServerClusterSpecification extends Specification { sendNotification(firstServer, STANDALONE) then: - cluster.getServersSnapshot(OPERATION_CONTEXT - .getTimeoutContext() - .computeServerSelectionTimeout(), - OPERATION_CONTEXT.getTimeoutContext()).getServer(firstServer) == factory.getServer(firstServer) + def operationContext = createOperationContext() + cluster.getServersSnapshot(operationContext.getTimeoutContext().computeServerSelectionTimeout(), + operationContext.getTimeoutContext()).getServer(firstServer) == factory.getServer(firstServer) cleanup: cluster?.close() @@ -95,8 +94,9 @@ class SingleServerClusterSpecification extends Specification { cluster.close() when: - cluster.getServersSnapshot(OPERATION_CONTEXT.getTimeoutContext().computeServerSelectionTimeout(), - OPERATION_CONTEXT.getTimeoutContext()) + def operationContext = createOperationContext() + cluster.getServersSnapshot(operationContext.getTimeoutContext().computeServerSelectionTimeout(), + operationContext.getTimeoutContext()) then: thrown(IllegalStateException) @@ -146,7 +146,7 @@ class SingleServerClusterSpecification extends Specification { sendNotification(firstServer, getBuilder(firstServer).minWireVersion(1000).maxWireVersion(1000).build()) when: - cluster.selectServer(new WritableServerSelector(), OPERATION_CONTEXT) + cluster.selectServer(new WritableServerSelector(), createOperationContext()) then: thrown(MongoIncompatibleDriverException) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/UsageTrackingConnectionSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/UsageTrackingConnectionSpecification.groovy index 78d79fba8b2..379d60a2fe7 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/UsageTrackingConnectionSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/UsageTrackingConnectionSpecification.groovy @@ -26,7 +26,7 @@ import org.bson.BsonInt32 import org.bson.codecs.BsonDocumentCodec import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ReadPreference.primary import static com.mongodb.connection.ClusterConnectionMode.SINGLE @@ -49,7 +49,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.openedAt == Long.MAX_VALUE when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: connection.openedAt <= System.currentTimeMillis() @@ -65,7 +65,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.openedAt == Long.MAX_VALUE when: - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -80,7 +80,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.lastUsedAt == Long.MAX_VALUE when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: connection.lastUsedAt <= System.currentTimeMillis() @@ -96,7 +96,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.lastUsedAt == Long.MAX_VALUE when: - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -106,11 +106,11 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on sendMessage'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt when: - connection.sendMessage([], 1, OPERATION_CONTEXT) + connection.sendMessage([], 1, createOperationContext()) then: connection.lastUsedAt >= openedLastUsedAt @@ -121,12 +121,12 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on sendMessage asynchronously'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt def futureResultCallback = new FutureResultCallback() when: - connection.sendMessageAsync([], 1, OPERATION_CONTEXT, futureResultCallback) + connection.sendMessageAsync([], 1, createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -137,10 +137,10 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on receiveMessage'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: connection.lastUsedAt >= openedLastUsedAt @@ -150,12 +150,12 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on receiveMessage asynchronously'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt def futureResultCallback = new FutureResultCallback() when: - connection.receiveMessageAsync(1, OPERATION_CONTEXT, futureResultCallback) + connection.receiveMessageAsync(1, createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -166,13 +166,13 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on sendAndReceive'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt when: connection.sendAndReceive(new CommandMessage('test', new BsonDocument('ping', new BsonInt32(1)), NoOpFieldNameValidator.INSTANCE, primary(), - MessageSettings.builder().build(), SINGLE, null), new BsonDocumentCodec(), OPERATION_CONTEXT) + MessageSettings.builder().build(), SINGLE, null), new BsonDocumentCodec(), createOperationContext()) then: connection.lastUsedAt >= openedLastUsedAt @@ -182,7 +182,7 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on sendAndReceive asynchronously'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt def futureResultCallback = new FutureResultCallback() @@ -190,7 +190,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.sendAndReceiveAsync(new CommandMessage('test', new BsonDocument('ping', new BsonInt32(1)), NoOpFieldNameValidator.INSTANCE, primary(), MessageSettings.builder().build(), SINGLE, null), - new BsonDocumentCodec(), OPERATION_CONTEXT, futureResultCallback) + new BsonDocumentCodec(), createOperationContext(), futureResultCallback) futureResultCallback.get() then: diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorNoUserNameTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorNoUserNameTest.java index 5326c8c723d..0259b41930a 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorNoUserNameTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorNoUserNameTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import com.mongodb.async.FutureResultCallback; @@ -32,7 +33,6 @@ import java.util.List; import java.util.concurrent.ExecutionException; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getServerApi; import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE; import static com.mongodb.internal.connection.MessageHelper.buildSuccessfulReply; @@ -58,7 +58,7 @@ public void testSuccessfulAuthentication() { enqueueSuccessfulAuthenticationReply(); new X509Authenticator(getCredentialWithCache(), MULTIPLE, getServerApi()) - .authenticate(connection, connectionDescriptionThreeSix, OPERATION_CONTEXT); + .authenticate(connection, connectionDescriptionThreeSix, ClusterFixture.createOperationContext()); validateMessages(); } @@ -69,7 +69,7 @@ public void testSuccessfulAuthenticationAsync() throws ExecutionException, Inter FutureResultCallback futureCallback = new FutureResultCallback<>(); new X509Authenticator(getCredentialWithCache(), MULTIPLE, getServerApi()).authenticateAsync(connection, - connectionDescriptionThreeSix, OPERATION_CONTEXT, futureCallback); + connectionDescriptionThreeSix, ClusterFixture.createOperationContext(), futureCallback); futureCallback.get(); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorUnitTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorUnitTest.java index a8b2d7b71d5..80d0b2c0411 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorUnitTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorUnitTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCredential; import com.mongodb.MongoSecurityException; import com.mongodb.ServerAddress; @@ -31,7 +32,6 @@ import java.util.List; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getServerApi; import static com.mongodb.internal.connection.MessageHelper.buildSuccessfulReply; import static com.mongodb.internal.connection.MessageHelper.getApiVersionField; @@ -58,7 +58,7 @@ public void testFailedAuthentication() { enqueueFailedAuthenticationReply(); try { - subject.authenticate(connection, connectionDescription, OPERATION_CONTEXT); + subject.authenticate(connection, connectionDescription, ClusterFixture.createOperationContext()); fail(); } catch (MongoSecurityException e) { // all good @@ -70,7 +70,7 @@ public void testFailedAuthenticationAsync() { enqueueFailedAuthenticationReply(); FutureResultCallback futureCallback = new FutureResultCallback<>(); - subject.authenticateAsync(connection, connectionDescription, OPERATION_CONTEXT, futureCallback); + subject.authenticateAsync(connection, connectionDescription, ClusterFixture.createOperationContext(), futureCallback); try { futureCallback.get(); @@ -92,7 +92,7 @@ private void enqueueFailedAuthenticationReply() { public void testSuccessfulAuthentication() { enqueueSuccessfulAuthenticationReply(); - subject.authenticate(connection, connectionDescription, OPERATION_CONTEXT); + subject.authenticate(connection, connectionDescription, ClusterFixture.createOperationContext()); validateMessages(); } @@ -102,7 +102,7 @@ public void testSuccessfulAuthenticationAsync() { enqueueSuccessfulAuthenticationReply(); FutureResultCallback futureCallback = new FutureResultCallback<>(); - subject.authenticateAsync(connection, connectionDescription, OPERATION_CONTEXT, futureCallback); + subject.authenticateAsync(connection, connectionDescription, ClusterFixture.createOperationContext(), futureCallback); futureCallback.get(); @@ -117,7 +117,7 @@ public void testSpeculativeAuthentication() { + "user: \"CN=client,OU=kerneluser,O=10Gen,L=New York City,ST=New York,C=US\", " + "mechanism: \"MONGODB-X509\", db: \"$external\"}"); subject.setSpeculativeAuthenticateResponse(BsonDocument.parse(speculativeAuthenticateResponse)); - subject.authenticate(connection, connectionDescription, OPERATION_CONTEXT); + subject.authenticate(connection, connectionDescription, ClusterFixture.createOperationContext()); assertEquals(connection.getSent().size(), 0); assertEquals(expectedSpeculativeAuthenticateCommand, subject.createSpeculativeAuthenticateCommand(connection)); diff --git a/driver-core/src/test/unit/com/mongodb/internal/mockito/InsufficientStubbingDetectorDemoTest.java b/driver-core/src/test/unit/com/mongodb/internal/mockito/InsufficientStubbingDetectorDemoTest.java index 5d8bd8e61b1..9142a097678 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/mockito/InsufficientStubbingDetectorDemoTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/mockito/InsufficientStubbingDetectorDemoTest.java @@ -15,6 +15,7 @@ */ package com.mongodb.internal.mockito; +import com.mongodb.ClusterFixture; import com.mongodb.internal.binding.ReadBinding; import com.mongodb.internal.operation.ListCollectionsOperation; import org.bson.BsonDocument; @@ -24,7 +25,6 @@ import org.mockito.Mockito; import org.mockito.internal.stubbing.answers.ThrowsException; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; @@ -40,33 +40,33 @@ void beforeEach() { @Test void mockObjectWithDefaultAnswer() { ReadBinding binding = Mockito.mock(ReadBinding.class); - assertThrows(NullPointerException.class, () -> operation.execute(binding, OPERATION_CONTEXT)); + assertThrows(NullPointerException.class, () -> operation.execute(binding, ClusterFixture.createOperationContext())); } @Test void mockObjectWithThrowsException() { ReadBinding binding = Mockito.mock(ReadBinding.class, new ThrowsException(new AssertionError("Insufficient stubbing for " + ReadBinding.class))); - assertThrows(AssertionError.class, () -> operation.execute(binding, OPERATION_CONTEXT)); + assertThrows(AssertionError.class, () -> operation.execute(binding, ClusterFixture.createOperationContext())); } @Test void mockObjectWithInsufficientStubbingDetector() { ReadBinding binding = MongoMockito.mock(ReadBinding.class); - assertThrows(AssertionError.class, () -> operation.execute(binding, OPERATION_CONTEXT)); + assertThrows(AssertionError.class, () -> operation.execute(binding, ClusterFixture.createOperationContext())); } @Test void stubbingWithThrowsException() { ReadBinding binding = Mockito.mock(ReadBinding.class, new ThrowsException(new AssertionError("Unfortunately, you cannot do stubbing"))); - assertThrows(AssertionError.class, () -> when(binding.getReadConnectionSource(OPERATION_CONTEXT)).thenReturn(null)); + assertThrows(AssertionError.class, () -> when(binding.getReadConnectionSource(ClusterFixture.createOperationContext())).thenReturn(null)); } @Test void stubbingWithInsufficientStubbingDetector() { MongoMockito.mock(ReadBinding.class, bindingMock -> - when(bindingMock.getReadConnectionSource(OPERATION_CONTEXT)).thenReturn(null) + when(bindingMock.getReadConnectionSource(ClusterFixture.createOperationContext())).thenReturn(null) ); } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncOperationHelperSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncOperationHelperSpecification.groovy index d573822cab7..6080f8bb727 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncOperationHelperSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncOperationHelperSpecification.groovy @@ -36,7 +36,7 @@ import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.Decoder import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ReadPreference.primary import static com.mongodb.internal.operation.AsyncOperationHelper.CommandReadTransformerAsync import static com.mongodb.internal.operation.AsyncOperationHelper.executeCommandAsync @@ -74,7 +74,7 @@ class AsyncOperationHelperSpecification extends Specification { _ * getDescription() >> connectionDescription } - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { hasSession() >> true hasActiveTransaction() >> false @@ -116,7 +116,7 @@ class AsyncOperationHelperSpecification extends Specification { def connectionDescription = Stub(ConnectionDescription) when: - executeCommandAsync(asyncWriteBinding, OPERATION_CONTEXT, dbName, command, connection, { t, conn -> t }, callback) + executeCommandAsync(asyncWriteBinding, createOperationContext(), dbName, command, connection, { t, conn -> t }, callback) then: _ * connection.getDescription() >> connectionDescription @@ -143,7 +143,7 @@ class AsyncOperationHelperSpecification extends Specification { def connectionDescription = Stub(ConnectionDescription) when: - executeRetryableReadAsync(asyncReadBinding, OPERATION_CONTEXT, dbName, commandCreator, decoder, function, false, callback) + executeRetryableReadAsync(asyncReadBinding, createOperationContext(), dbName, commandCreator, decoder, function, false, callback) then: _ * connection.getDescription() >> connectionDescription diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java index 5de1992b69d..18a66816abf 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java @@ -124,7 +124,7 @@ void shouldIgnoreSuccessfulCursorResultWhenVerboseResultIsFalse() { false, getDefaultCodecRegistry()); //when - ClientBulkWriteResult result = op.execute(binding, ClusterFixture.OPERATION_CONTEXT); + ClientBulkWriteResult result = op.execute(binding, ClusterFixture.createOperationContext()); //then assertEquals( @@ -176,7 +176,7 @@ void shouldUseDefaultNumberOfModifiedDocumentsWhenMissingInCursor() { false, getDefaultCodecRegistry()); //when - ClientBulkWriteResult result = op.execute(binding, ClusterFixture.OPERATION_CONTEXT); + ClientBulkWriteResult result = op.execute(binding, ClusterFixture.createOperationContext()); //then assertEquals(1, result.getInsertedCount()); diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/CommitTransactionOperationUnitSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/CommitTransactionOperationUnitSpecification.groovy index 75ed9e6c5f3..aa4db2372ff 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/CommitTransactionOperationUnitSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/CommitTransactionOperationUnitSpecification.groovy @@ -27,7 +27,7 @@ import com.mongodb.internal.binding.WriteBinding import com.mongodb.internal.connection.OperationContext import com.mongodb.internal.session.SessionContext -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class CommitTransactionOperationUnitSpecification extends OperationUnitSpecification { def 'should add UnknownTransactionCommitResult error label to MongoTimeoutException'() { @@ -42,7 +42,7 @@ class CommitTransactionOperationUnitSpecification extends OperationUnitSpecifica def operation = new CommitTransactionOperation(WriteConcern.ACKNOWLEDGED) when: - operation.execute(writeBinding, OPERATION_CONTEXT.withSessionContext(sessionContext)) + operation.execute(writeBinding, createOperationContext().withSessionContext(sessionContext)) then: def e = thrown(MongoTimeoutException) @@ -64,7 +64,7 @@ class CommitTransactionOperationUnitSpecification extends OperationUnitSpecifica def callback = new FutureResultCallback() when: - operation.executeAsync(writeBinding, OPERATION_CONTEXT.withSessionContext(sessionContext), callback) + operation.executeAsync(writeBinding, createOperationContext().withSessionContext(sessionContext), callback) callback.get() then: diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/CursorResourceManagerTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/CursorResourceManagerTest.java index 68b3bf7f606..26fd6e6eab5 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/CursorResourceManagerTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/CursorResourceManagerTest.java @@ -15,6 +15,7 @@ */ package com.mongodb.internal.operation; +import com.mongodb.ClusterFixture; import com.mongodb.MongoNamespace; import com.mongodb.ServerCursor; import com.mongodb.internal.binding.AsyncConnectionSource; @@ -24,7 +25,6 @@ import com.mongodb.internal.mockito.MongoMockito; import org.junit.jupiter.api.Test; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.Mockito.when; @@ -50,12 +50,12 @@ void doClose(final OperationContext operationContext) { cursorResourceManager.tryStartOperation(); try { assertDoesNotThrow(() -> { - cursorResourceManager.close(OPERATION_CONTEXT); - cursorResourceManager.close(OPERATION_CONTEXT); + cursorResourceManager.close(ClusterFixture.createOperationContext()); + cursorResourceManager.close(ClusterFixture.createOperationContext()); cursorResourceManager.setServerCursor(null); }); } finally { - cursorResourceManager.endOperation(OPERATION_CONTEXT); + cursorResourceManager.endOperation(ClusterFixture.createOperationContext()); } } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/ListCollectionsOperationTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/ListCollectionsOperationTest.java index de1bfe405ed..ca7730ff2b6 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/ListCollectionsOperationTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/ListCollectionsOperationTest.java @@ -15,6 +15,7 @@ */ package com.mongodb.internal.operation; +import com.mongodb.ClusterFixture; import com.mongodb.MongoNamespace; import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; @@ -39,7 +40,6 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.assertions.Assertions.assertNotNull; import static com.mongodb.internal.mockito.MongoMockito.mock; import static java.util.Collections.emptyList; @@ -99,7 +99,7 @@ void authorizedCollectionsIsFalseByDefault() { } private BsonDocument executeOperationAndCaptureCommand() { - operation.execute(mocks.readBinding(), OPERATION_CONTEXT); + operation.execute(mocks.readBinding(), ClusterFixture.createOperationContext()); ArgumentCaptor commandCaptor = forClass(BsonDocument.class); verify(mocks.connection()).command(any(), commandCaptor.capture(), any(), any(), any(), any()); return commandCaptor.getValue(); diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy index fd9786e8dbf..ac51ebcede5 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy @@ -32,7 +32,7 @@ import org.bson.BsonArray import org.bson.BsonDocument import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.WriteConcern.ACKNOWLEDGED import static com.mongodb.WriteConcern.UNACKNOWLEDGED import static com.mongodb.connection.ServerConnectionState.CONNECTED @@ -108,8 +108,8 @@ class OperationHelperSpecification extends Specification { } expect: - canRetryRead(retryableServerDescription, OPERATION_CONTEXT.withSessionContext(noTransactionSessionContext)) - !canRetryRead(retryableServerDescription, OPERATION_CONTEXT.withSessionContext(activeTransactionSessionContext)) + canRetryRead(retryableServerDescription, createOperationContext().withSessionContext(noTransactionSessionContext)) + !canRetryRead(retryableServerDescription, createOperationContext().withSessionContext(activeTransactionSessionContext)) } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationUnitSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationUnitSpecification.groovy index ec5cb74156f..52934c3bfad 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationUnitSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationUnitSpecification.groovy @@ -41,7 +41,7 @@ import spock.lang.Specification import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class OperationUnitSpecification extends Specification { @@ -97,7 +97,7 @@ class OperationUnitSpecification extends Specification { def testSyncOperation(operation, List serverVersion, result, Boolean checkCommand=true, BsonDocument expectedCommand=null, Boolean checkSecondaryOk=false, ReadPreference readPreference=ReadPreference.primary()) { - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasActiveTransaction() >> false getReadConcern() >> ReadConcern.DEFAULT @@ -151,7 +151,7 @@ class OperationUnitSpecification extends Specification { Boolean checkCommand=true, BsonDocument expectedCommand=null, Boolean checkSecondaryOk=false, ReadPreference readPreference=ReadPreference.primary()) { - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasActiveTransaction() >> false getReadConcern() >> ReadConcern.DEFAULT diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/SyncOperationHelperSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/SyncOperationHelperSpecification.groovy index bd9bd2f2578..8b66947c026 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/SyncOperationHelperSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/SyncOperationHelperSpecification.groovy @@ -34,7 +34,7 @@ import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.Decoder import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ReadPreference.primary import static com.mongodb.internal.operation.OperationUnitSpecification.getMaxWireVersionForServerVersion import static com.mongodb.internal.operation.SyncOperationHelper.CommandReadTransformer @@ -61,7 +61,7 @@ class SyncOperationHelperSpecification extends Specification { def connectionDescription = Stub(ConnectionDescription) when: - executeCommand(writeBinding, OPERATION_CONTEXT, dbName, command, decoder, function) + executeCommand(writeBinding, createOperationContext(), dbName, command, decoder, function) then: _ * connection.getDescription() >> connectionDescription @@ -71,7 +71,7 @@ class SyncOperationHelperSpecification extends Specification { def 'should retry with retryable exception'() { given: - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasSession() >> true hasActiveTransaction() >> false @@ -132,7 +132,7 @@ class SyncOperationHelperSpecification extends Specification { def connectionDescription = Stub(ConnectionDescription) when: - executeRetryableRead(readBinding, OPERATION_CONTEXT, dbName, commandCreator, decoder, function, false) + executeRetryableRead(readBinding, createOperationContext(), dbName, commandCreator, decoder, function, false) then: _ * connection.getDescription() >> connectionDescription diff --git a/driver-core/src/test/unit/com/mongodb/internal/session/BaseClientSessionImplTest.java b/driver-core/src/test/unit/com/mongodb/internal/session/BaseClientSessionImplTest.java index c7fc1d73e20..495523f90a3 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/session/BaseClientSessionImplTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/session/BaseClientSessionImplTest.java @@ -17,10 +17,10 @@ package com.mongodb.internal.session; import com.mongodb.ClientSessionOptions; +import com.mongodb.ClusterFixture; import com.mongodb.session.ClientSession; import org.junit.jupiter.api.Test; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getCluster; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -28,7 +28,7 @@ class BaseClientSessionImplTest { @Test void shouldNotCheckoutServerSessionIfNeverRequested() { - ServerSessionPool serverSessionPool = new ServerSessionPool(getCluster(), OPERATION_CONTEXT); + ServerSessionPool serverSessionPool = new ServerSessionPool(getCluster(), ClusterFixture.createOperationContext()); ClientSession clientSession = new BaseClientSessionImpl(serverSessionPool, new Object(), ClientSessionOptions.builder().build()); assertEquals(0, serverSessionPool.getInUseCount()); @@ -40,7 +40,7 @@ void shouldNotCheckoutServerSessionIfNeverRequested() { @Test void shouldDelayServerSessionCheckoutUntilRequested() { - ServerSessionPool serverSessionPool = new ServerSessionPool(getCluster(), OPERATION_CONTEXT); + ServerSessionPool serverSessionPool = new ServerSessionPool(getCluster(), ClusterFixture.createOperationContext()); ClientSession clientSession = new BaseClientSessionImpl(serverSessionPool, new Object(), ClientSessionOptions.builder().build()); assertEquals(0, serverSessionPool.getInUseCount()); diff --git a/driver-core/src/test/unit/com/mongodb/internal/session/ServerSessionPoolSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/session/ServerSessionPoolSpecification.groovy index 19bfa994200..0fc4564d322 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/session/ServerSessionPoolSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/session/ServerSessionPoolSpecification.groovy @@ -32,7 +32,7 @@ import org.bson.BsonDocument import org.bson.codecs.BsonDocumentCodec import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.getServerApi import static com.mongodb.ReadPreference.primaryPreferred @@ -120,7 +120,7 @@ class ServerSessionPoolSpecification extends Specification { millis() >>> [0, MINUTES.toMillis(29) + 1, ] } - def pool = new ServerSessionPool(cluster, OPERATION_CONTEXT, clock) + def pool = new ServerSessionPool(cluster, createOperationContext(), clock) def sessionOne = pool.get() when: @@ -146,7 +146,7 @@ class ServerSessionPoolSpecification extends Specification { def clock = Stub(ServerSessionPool.Clock) { millis() >>> [0, 0, 0] } - def pool = new ServerSessionPool(cluster, OPERATION_CONTEXT, clock) + def pool = new ServerSessionPool(cluster, createOperationContext(), clock) def session = pool.get() when: @@ -165,7 +165,7 @@ class ServerSessionPoolSpecification extends Specification { def clock = Stub(ServerSessionPool.Clock) { millis() >> 42 } - def pool = new ServerSessionPool(cluster, OPERATION_CONTEXT, clock) + def pool = new ServerSessionPool(cluster, createOperationContext(), clock) when: def session = pool.get() as ServerSessionPool.ServerSessionImpl @@ -187,7 +187,7 @@ class ServerSessionPoolSpecification extends Specification { def clock = Stub(ServerSessionPool.Clock) { millis() >> 42 } - def pool = new ServerSessionPool(cluster, OPERATION_CONTEXT, clock) + def pool = new ServerSessionPool(cluster, createOperationContext(), clock) when: def session = pool.get() as ServerSessionPool.ServerSessionImpl diff --git a/driver-legacy/src/test/functional/com/mongodb/DBTest.java b/driver-legacy/src/test/functional/com/mongodb/DBTest.java index cf44573a2b4..b483e326081 100644 --- a/driver-legacy/src/test/functional/com/mongodb/DBTest.java +++ b/driver-legacy/src/test/functional/com/mongodb/DBTest.java @@ -31,7 +31,6 @@ import java.util.Locale; import java.util.UUID; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.disableMaxTimeFailPoint; import static com.mongodb.ClusterFixture.enableMaxTimeFailPoint; import static com.mongodb.ClusterFixture.getBinding; @@ -345,7 +344,7 @@ public void shouldApplyUuidRepresentationToCommandEncodingAndDecoding() { BsonDocument getCollectionInfo(final String collectionName) { return new ListCollectionsOperation<>(getDefaultDatabaseName(), new BsonDocumentCodec()) - .filter(new BsonDocument("name", new BsonString(collectionName))).execute(getBinding(), OPERATION_CONTEXT).next().get(0); + .filter(new BsonDocument("name", new BsonString(collectionName))).execute(getBinding(), ClusterFixture.createOperationContext()).next().get(0); } private boolean isCapped(final DBCollection collection) { diff --git a/driver-legacy/src/test/functional/com/mongodb/LegacyMixedBulkWriteOperationSpecification.groovy b/driver-legacy/src/test/functional/com/mongodb/LegacyMixedBulkWriteOperationSpecification.groovy index 6a9c511c3bc..2db1da67e22 100644 --- a/driver-legacy/src/test/functional/com/mongodb/LegacyMixedBulkWriteOperationSpecification.groovy +++ b/driver-legacy/src/test/functional/com/mongodb/LegacyMixedBulkWriteOperationSpecification.groovy @@ -184,7 +184,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def insert = new InsertRequest(new BsonDocument('_id', new BsonInt32(1))) def binding = getBinding() createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, asList(insert)) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) def replacement = new UpdateRequest(new BsonDocument('_id', new BsonInt32(1)), new BsonDocument('_id', new BsonInt32(1)).append('x', new BsonInt32(1)), REPLACE) diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java index 84dd0d733bf..bb748f00601 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java @@ -16,57 +16,14 @@ package com.mongodb.reactivestreams.client; -import com.mongodb.client.MongoCursor; -import com.mongodb.client.RetryableWritesProseTest; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.AbstractRetryableReadsProseTest; +import com.mongodb.client.MongoClient; import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; -import org.bson.Document; -import org.junit.jupiter.api.Test; -import static com.mongodb.client.model.Filters.eq; - -/** - * - * Prose Tests. - */ -final class RetryableReadsProseTest { - /** - * - * 1. PoolClearedError Retryability Test. - */ - @Test - void poolClearedExceptionMustBeRetryable() throws Exception { - RetryableWritesProseTest.poolClearedExceptionMustBeRetryable( - SyncMongoClient::new, - mongoCollection -> mongoCollection.find(eq(0)).iterator().hasNext(), "find", false); - } - - /** - * - * 2.1 Retryable Reads Are Retried on a Different mongos When One is Available. - */ - @Test - void retriesOnDifferentMongosWhenAvailable() { - RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable( - SyncMongoClient::new, - mongoCollection -> { - try (MongoCursor cursor = mongoCollection.find().iterator()) { - return cursor.hasNext(); - } - }, "find", false); - } - - /** - * - * 2.2 Retryable Reads Are Retried on the Same mongos When No Others are Available. - */ - @Test - void retriesOnSameMongosWhenAnotherNotAvailable() { - RetryableWritesProseTest.retriesOnSameMongosWhenAnotherNotAvailable( - SyncMongoClient::new, - mongoCollection -> { - try (MongoCursor cursor = mongoCollection.find().iterator()) { - return cursor.hasNext(); - } - }, "find", false); +final class RetryableReadsProseTest extends AbstractRetryableReadsProseTest { + @Override + protected MongoClient createClient(final MongoClientSettings settings) { + return new SyncMongoClient(settings); } } diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ClientSessionBindingSpecification.groovy b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ClientSessionBindingSpecification.groovy index cfe66a8031f..0fcbb5ac31a 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ClientSessionBindingSpecification.groovy +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ClientSessionBindingSpecification.groovy @@ -16,6 +16,7 @@ package com.mongodb.reactivestreams.client.internal +import com.mongodb.ClusterFixture import com.mongodb.ReadPreference import com.mongodb.ServerAddress import com.mongodb.async.FutureResultCallback @@ -31,32 +32,34 @@ import com.mongodb.internal.connection.ServerTuple import com.mongodb.reactivestreams.client.ClientSession import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class ClientSessionBindingSpecification extends Specification { def 'should return the session context from the connection source'() { given: def session = Stub(ClientSession) + def operationContext = ClusterFixture.createOperationContext() def wrappedBinding = Mock(AsyncClusterAwareReadWriteBinding); wrappedBinding.retain() >> wrappedBinding def binding = new ClientSessionBinding(session, false, wrappedBinding) when: def futureResultCallback = new FutureResultCallback() - binding.getReadConnectionSource(OPERATION_CONTEXT, futureResultCallback) + + binding.getReadConnectionSource(operationContext, futureResultCallback) then: - 1 * wrappedBinding.getReadConnectionSource(OPERATION_CONTEXT, _) >> { + 1 * wrappedBinding.getReadConnectionSource(operationContext, _) >> { it[1].onResult(Stub(AsyncConnectionSource), null) } when: futureResultCallback = new FutureResultCallback() - binding.getWriteConnectionSource(OPERATION_CONTEXT, futureResultCallback) + binding.getWriteConnectionSource(operationContext, futureResultCallback) then: - 1 * wrappedBinding.getWriteConnectionSource(OPERATION_CONTEXT, _) >> { + 1 * wrappedBinding.getWriteConnectionSource(operationContext, _) >> { it[1].onResult(Stub(AsyncConnectionSource), null) } } @@ -87,10 +90,10 @@ class ClientSessionBindingSpecification extends Specification { def wrappedBinding = createStubBinding() def binding = new ClientSessionBinding(session, true, wrappedBinding) def futureResultCallback = new FutureResultCallback() - binding.getReadConnectionSource(OPERATION_CONTEXT, futureResultCallback) + binding.getReadConnectionSource(createOperationContext(), futureResultCallback) def readConnectionSource = futureResultCallback.get() futureResultCallback = new FutureResultCallback() - binding.getWriteConnectionSource(OPERATION_CONTEXT, futureResultCallback) + binding.getWriteConnectionSource(createOperationContext(), futureResultCallback) def writeConnectionSource = futureResultCallback.get() when: diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java new file mode 100644 index 00000000000..fde4675c638 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java @@ -0,0 +1,232 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.ReadPreference; +import com.mongodb.ServerAddress; +import com.mongodb.client.test.CollectionHelper; +import com.mongodb.connection.ClusterDescription; +import com.mongodb.event.CommandFailedEvent; +import com.mongodb.event.CommandSucceededEvent; +import com.mongodb.internal.connection.TestClusterListener; +import com.mongodb.internal.connection.TestCommandListener; +import org.bson.BsonDocument; +import org.bson.Document; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.MongoException.RETRYABLE_ERROR_LABEL; +import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL; +import static com.mongodb.client.Fixture.getDefaultDatabaseName; +import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; +import static com.mongodb.client.Fixture.getPrimary; +import static com.mongodb.client.model.Filters.eq; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * + * Prose Tests. + */ +public abstract class AbstractRetryableReadsProseTest { + + private static final String COLLECTION_NAME = "test"; + + private final TestCommandListener commandListener = + new TestCommandListener(asList("commandFailedEvent", "commandSucceededEvent"), emptyList()); + private final TestClusterListener clusterListener = new TestClusterListener(); + + protected abstract MongoClient createClient(MongoClientSettings settings); + + @AfterEach + void afterEach() { + CollectionHelper.dropDatabase(getDefaultDatabaseName()); + commandListener.reset(); + clusterListener.clearClusterDescriptionChangedEvents(); + } + + /** + * + * 1. PoolClearedError Retryability Test. + */ + @Test + void poolClearedExceptionMustBeRetryable() throws Exception { + RetryableWritesProseTest.poolClearedExceptionMustBeRetryable(this::createClient, + mongoCollection -> mongoCollection.find(eq(0)).iterator().hasNext(), "find", false); + } + + /** + * + * 2.1 Retryable Reads Are Retried on a Different mongos When One is Available. + */ + @Test + void retriesOnDifferentMongosWhenAvailable() { + RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable(this::createClient, + mongoCollection -> { + try (MongoCursor cursor = mongoCollection.find().iterator()) { + return cursor.hasNext(); + } + }, "find", false); + } + + /** + * + * 2.2 Retryable Reads Are Retried on the Same mongos When No Others are Available. + */ + @Test + void retriesOnSameMongosWhenAnotherNotAvailable() { + RetryableWritesProseTest.retriesOnSameMongosWhenAnotherNotAvailable(this::createClient, + mongoCollection -> { + try (MongoCursor cursor = mongoCollection.find().iterator()) { + return cursor.hasNext(); + } + }, "find", false); + } + + /** + * + * 3.1 Retryable Reads Caused by Overload Errors Are Retried on a Different Replicaset Server When One is Available. + */ + //TODO-BACKPRESSURE Slav Babanin JAVA-6167 add overloadRetargeting into tests. + @Test + void overloadErrorRetriedOnDifferentReplicaSetServer() throws InterruptedException, TimeoutException { + //given + assumeTrue(serverVersionAtLeast(4, 4)); + assumeTrue(isDiscoverableReplicaSet()); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: [\"find\"],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "'],\n" + + " errorCode: 6\n" + + " }\n" + + "}\n"); + + try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary()); + MongoClient client = createClient(getMongoClientSettingsBuilder() + .retryReads(true) + .readPreference(ReadPreference.primaryPreferred()) + .addCommandListener(commandListener) + .applyToClusterSettings(builder -> builder.addClusterListener(clusterListener)) + .build())) { + + waitForClusterDiscovery(); + + MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) + .getCollection(COLLECTION_NAME); + commandListener.reset(); + + //when + collection.find().first(); + + //then + List commandFailedEvents = commandListener.getCommandFailedEvents(); + assertEquals(1, commandFailedEvents.size()); + List commandSucceededEvents = commandListener.getCommandSucceededEvents(); + assertEquals(1, commandSucceededEvents.size()); + + ServerAddress failedServer = commandFailedEvents.get(0).getConnectionDescription().getServerAddress(); + ServerAddress succeededServer = commandSucceededEvents.get(0).getConnectionDescription().getServerAddress(); + + assertNotEquals(failedServer, succeededServer, + format("Expected retry on different server but both were %s", failedServer)); + } + } + + /** + * + * 3.2 Retryable Reads Caused by Non-Overload Errors Are Retried on the Same Replicaset Server. + */ + //TODO-BACKPRESSURE Slav Babanin JAVA-6167 add overloadRetargeting into tests. + @Test + void nonOverloadErrorRetriedOnSameReplicaSetServer() throws InterruptedException, TimeoutException { + //given + assumeTrue(serverVersionAtLeast(4, 4)); + assumeTrue(isDiscoverableReplicaSet()); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: [\"find\"],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "'],\n" + + " errorCode: 6\n" + + " }\n" + + "}\n"); + + try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary()); + MongoClient client = createClient(getMongoClientSettingsBuilder() + .retryReads(true) + .readPreference(ReadPreference.primaryPreferred()) + .addCommandListener(commandListener) + .applyToClusterSettings(builder -> builder.addClusterListener(clusterListener)) + .build())) { + + waitForClusterDiscovery(); + + MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) + .getCollection(COLLECTION_NAME); + commandListener.reset(); + + //when + collection.find().first(); + + //then + List commandFailedEvents = commandListener.getCommandFailedEvents(); + assertEquals(1, commandFailedEvents.size()); + List commandSucceededEvents = commandListener.getCommandSucceededEvents(); + assertEquals(1, commandSucceededEvents.size()); + + ServerAddress failedServer = commandFailedEvents.get(0).getConnectionDescription().getServerAddress(); + ServerAddress succeededServer = commandSucceededEvents.get(0).getConnectionDescription().getServerAddress(); + + assertEquals(failedServer, succeededServer, + format("Expected retry on same server but got %s and %s", failedServer, succeededServer)); + } + } + + private void waitForClusterDiscovery() throws InterruptedException, TimeoutException { + clusterListener.waitForClusterDescriptionChangedEvents( + event -> { + ClusterDescription desc = event.getNewDescription(); + // We need both primary and secondary to be discovered (not UNKNOWN) before running the deprioritization tests. + // + // 1. The failpoint is set on the primary. If the primary is not yet discovered, + // primaryPreferred may route the find to a secondary, and the failpoint never fires. + // + // 2. When the primary is deprioritized on retry, primaryPreferred falls back to a secondary. + // If the secondaries are still UNKNOWN at that point, the fallback yields no selectable servers, + // causing the deprioritized primary to be selected again. + return desc.hasReadableServer(ReadPreference.primary()) + && desc.hasReadableServer(ReadPreference.secondary()); + }, + 1, Duration.ofSeconds(10)); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java index 59b6a9aad19..5ca0f75d56b 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java @@ -16,51 +16,11 @@ package com.mongodb.client; -import org.bson.Document; -import org.junit.jupiter.api.Test; +import com.mongodb.MongoClientSettings; -import static com.mongodb.client.model.Filters.eq; - -/** - * - * Prose Tests. - */ -final class RetryableReadsProseTest { - /** - * - * 1. PoolClearedError Retryability Test. - */ - @Test - void poolClearedExceptionMustBeRetryable() throws Exception { - RetryableWritesProseTest.poolClearedExceptionMustBeRetryable(MongoClients::create, - mongoCollection -> mongoCollection.find(eq(0)).iterator().hasNext(), "find", false); - } - - /** - * - * 2.1 Retryable Reads Are Retried on a Different mongos When One is Available. - */ - @Test - void retriesOnDifferentMongosWhenAvailable() { - RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable(MongoClients::create, - mongoCollection -> { - try (MongoCursor cursor = mongoCollection.find().iterator()) { - return cursor.hasNext(); - } - }, "find", false); - } - - /** - * - * 2.2 Retryable Reads Are Retried on the Same mongos When No Others are Available. - */ - @Test - void retriesOnSameMongosWhenAnotherNotAvailable() { - RetryableWritesProseTest.retriesOnSameMongosWhenAnotherNotAvailable(MongoClients::create, - mongoCollection -> { - try (MongoCursor cursor = mongoCollection.find().iterator()) { - return cursor.hasNext(); - } - }, "find", false); +final class RetryableReadsProseTest extends AbstractRetryableReadsProseTest { + @Override + protected MongoClient createClient(final MongoClientSettings settings) { + return MongoClients.create(settings); } } diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/ClientSessionBindingSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/ClientSessionBindingSpecification.groovy index e2e664f324d..f2ecac0c170 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/ClientSessionBindingSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/ClientSessionBindingSpecification.groovy @@ -16,7 +16,7 @@ package com.mongodb.client.internal - +import com.mongodb.ClusterFixture import com.mongodb.ReadPreference import com.mongodb.client.ClientSession import com.mongodb.internal.binding.ClusterBinding @@ -25,29 +25,28 @@ import com.mongodb.internal.binding.ReadWriteBinding import com.mongodb.internal.connection.Cluster import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT - class ClientSessionBindingSpecification extends Specification { def 'should call underlying wrapped binding'() { given: def session = Stub(ClientSession) + def operationContext = ClusterFixture.createOperationContext() def wrappedBinding = Mock(ClusterBinding); def binding = new ClientSessionBinding(session, false, wrappedBinding) when: - binding.getReadConnectionSource(OPERATION_CONTEXT) + binding.getReadConnectionSource(operationContext) then: - 1 * wrappedBinding.getReadConnectionSource(OPERATION_CONTEXT) >> { + 1 * wrappedBinding.getReadConnectionSource(operationContext) >> { Stub(ConnectionSource) } when: - binding.getWriteConnectionSource(OPERATION_CONTEXT) + binding.getWriteConnectionSource(operationContext) then: - 1 * wrappedBinding.getWriteConnectionSource(OPERATION_CONTEXT) >> { + 1 * wrappedBinding.getWriteConnectionSource(operationContext) >> { Stub(ConnectionSource) } } @@ -77,8 +76,9 @@ class ClientSessionBindingSpecification extends Specification { def session = Mock(ClientSession) def wrappedBinding = createStubBinding() def binding = new ClientSessionBinding(session, true, wrappedBinding) - def readConnectionSource = binding.getReadConnectionSource(OPERATION_CONTEXT) - def writeConnectionSource = binding.getWriteConnectionSource(OPERATION_CONTEXT) + def operationContext = ClusterFixture.createOperationContext() + def readConnectionSource = binding.getReadConnectionSource(operationContext) + def writeConnectionSource = binding.getWriteConnectionSource(operationContext) when: binding.release() diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/CryptConnectionSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/CryptConnectionSpecification.groovy index 8a38f966754..3ec9a889e29 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/CryptConnectionSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/CryptConnectionSpecification.groovy @@ -61,7 +61,7 @@ class CryptConnectionSpecification extends Specification { def cryptConnection = new CryptConnection(wrappedConnection, crypt) def codec = new DocumentCodec() def timeoutContext = Mock(TimeoutContext) - def operationContext = ClusterFixture.OPERATION_CONTEXT.withTimeoutContext(timeoutContext) + def operationContext = ClusterFixture.createOperationContext().withTimeoutContext(timeoutContext) def operationTimeout = Mock(Timeout) timeoutContext.getTimeout() >> operationTimeout @@ -127,7 +127,7 @@ class CryptConnectionSpecification extends Specification { def encryptedResponse = toRaw(new BsonDocument('ok', new BsonInt32(1))) def decryptedResponse = encryptedResponse def timeoutContext = Mock(TimeoutContext) - def operationContext = ClusterFixture.OPERATION_CONTEXT.withTimeoutContext(timeoutContext) + def operationContext = ClusterFixture.createOperationContext().withTimeoutContext(timeoutContext) def operationTimeout = Mock(Timeout) timeoutContext.getTimeout() >> operationTimeout @@ -183,7 +183,7 @@ class CryptConnectionSpecification extends Specification { def encryptedResponse = toRaw(new BsonDocument('ok', new BsonInt32(1))) def decryptedResponse = encryptedResponse def timeoutContext = Mock(TimeoutContext) - def operationContext = ClusterFixture.OPERATION_CONTEXT.withTimeoutContext(timeoutContext) + def operationContext = ClusterFixture.createOperationContext().withTimeoutContext(timeoutContext) def operationTimeout = Mock(Timeout) timeoutContext.getTimeout() >> operationTimeout From 135749da7914f709d05901273988eadc2be53308 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Wed, 22 Apr 2026 05:50:39 -0600 Subject: [PATCH 20/26] Implement prose backpressure tests (#1946) The relevant spec changes: - https://github.com/mongodb/specifications/blob/8a8a7c56429c80b51ec62268dcafc5e5e3c477ef/source/client-backpressure/tests/README.md JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141 --- .../client/BackpressureProseTest.java | 32 ++++ .../mongodb/client/BackpressureProseTest.java | 152 ++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/BackpressureProseTest.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/BackpressureProseTest.java diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/BackpressureProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/BackpressureProseTest.java new file mode 100644 index 00000000000..458738617b5 --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/BackpressureProseTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.reactivestreams.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; + +/** + * + * Prose Tests. + */ +final class BackpressureProseTest extends com.mongodb.client.BackpressureProseTest { + @Override + protected MongoClient createClient(final MongoClientSettings mongoClientSettings) { + return new SyncMongoClient(mongoClientSettings); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/BackpressureProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/BackpressureProseTest.java new file mode 100644 index 00000000000..ec76ffa57af --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/BackpressureProseTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoServerException; +import com.mongodb.internal.connection.TestCommandListener; +import com.mongodb.internal.time.StartTime; +import com.mongodb.lang.Nullable; +import org.bson.BsonDocument; +import org.bson.Document; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.MongoException.RETRYABLE_ERROR_LABEL; +import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL; +import static com.mongodb.client.Fixture.getDefaultDatabaseName; +import static com.mongodb.client.Fixture.getMongoClientSettings; +import static com.mongodb.client.Fixture.getPrimary; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * + * Prose Tests. + */ +public class BackpressureProseTest { + protected MongoClient createClient(final MongoClientSettings mongoClientSettings) { + return MongoClients.create(mongoClientSettings); + } + + /** + * + * Test 1: Operation Retry Uses Exponential Backoff. + */ + @Test + @Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141 if PR 1899 is merged") + void operationRetryUsesExponentialBackoff() throws InterruptedException { + assumeTrue(serverVersionAtLeast(4, 4)); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorCode: 2,\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(getMongoClientSettings()); + FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + MongoCollection collection = dropAndGetCollection("operationRetryUsesExponentialBackoff", client); + long noBackoffTimeMillis = measureFailedInsertDuration(collection, false).toMillis(); + long withBackoffTimeMillis = measureFailedInsertDuration(collection, true).toMillis(); + long expectedMaxVarianceMillis = 300; + long maxTotalBackoffMillis = 300; + long actualAbsDiffMillis = Math.abs(withBackoffTimeMillis - (noBackoffTimeMillis + maxTotalBackoffMillis)); + assertTrue(actualAbsDiffMillis < expectedMaxVarianceMillis, + format("Expected actualAbsDiffMillis < %d ms, but was %d ms (|%d ms - (%d ms + %d ms)|)", + expectedMaxVarianceMillis, actualAbsDiffMillis, withBackoffTimeMillis, noBackoffTimeMillis, maxTotalBackoffMillis)); + } + } + + private static Duration measureFailedInsertDuration(final MongoCollection collection, final boolean retryBackoff) { + // TODO-BACKPRESSURE Valentin uncomment below when https://github.com/mongodb/mongo-java-driver/pull/1899 is merged + // ExponentialBackoff.setTestJitterSupplier(() -> retryBackoff ? 1 : 0); + try { + StartTime startTime = StartTime.now(); + assertThrows(MongoServerException.class, () -> collection.insertOne(Document.parse("{a: 1}"))); + return startTime.elapsed(); + } finally { + // TODO-BACKPRESSURE Valentin uncomment below when https://github.com/mongodb/mongo-java-driver/pull/1899 is merged + // ExponentialBackoff.clearTestJitterSupplier(); + } + } + + /** + * + * Test 3: Overload Errors are Retried a Maximum of {@code MAX_RETRIES} times. + */ + @Test + @Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141") + void overloadErrorsAreRetriedAtMostMaxRetriesTimes() throws InterruptedException { + overloadErrorsAreRetriedLimitedNumberOfTimes(null); + } + + /** + * + * Test 4: Overload Errors are Retried a Maximum of {@code maxAdaptiveRetries} times when configured. + */ + @Test + @Disabled("TODO-BACKPRESSURE Valentin Enable when implementing JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141") + void overloadErrorsAreRetriedAtMostMaxAdaptiveRetriesTimesWhenConfigured() throws InterruptedException { + overloadErrorsAreRetriedLimitedNumberOfTimes(1); + } + + private void overloadErrorsAreRetriedLimitedNumberOfTimes(@Nullable final Integer maxAdaptiveRetries) + throws InterruptedException { + assumeTrue(serverVersionAtLeast(4, 4)); + TestCommandListener commandListener = new TestCommandListener(); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['find'],\n" + + " errorCode: 462,\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .maxAdaptiveRetries(maxAdaptiveRetries) + .addCommandListener(commandListener) + .build()); + FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + MongoCollection collection = dropAndGetCollection("overloadErrorsAreRetriedLimitedNumberOfTimes", client); + commandListener.reset(); + MongoServerException exception = assertThrows(MongoServerException.class, () -> collection.find().first()); + assertTrue(exception.hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL)); + assertTrue(exception.hasErrorLabel(RETRYABLE_ERROR_LABEL)); + // TODO-BACKPRESSURE Valentin replace 2 with `MAX_RETRIES` when implementing JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141 + int expectedAttempts = (maxAdaptiveRetries == null ? 2 : maxAdaptiveRetries) + 1; + assertEquals(expectedAttempts, commandListener.getCommandStartedEvents().size()); + } + } + + private static MongoCollection dropAndGetCollection(final String name, final MongoClient client) { + MongoCollection result = client.getDatabase(getDefaultDatabaseName()).getCollection(name); + result.drop(); + return result; + } +} From dd8d662bef7a2eba518347c7c89b71ad65161c61 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Thu, 23 Apr 2026 14:43:56 -0700 Subject: [PATCH 21/26] Add `enableOverloadRetargeting` API (#1943) - Add enableOverloadRetargeting boolean option to MongoClientSettings and ConnectionString to allow the driver to route requests to a different replica set member on retries when the previously used server is overloaded - Add prose test 3.3 to verify that overload errors are retried on the same server when retargeting is disabled JAVA-6167 --------- Co-authored-by: Ross Lawley --- .../main/com/mongodb/ConnectionString.java | 25 +++++++- .../main/com/mongodb/MongoClientSettings.java | 54 +++++++++++++++- .../internal/connection/OperationContext.java | 41 +++++++++--- .../mongodb/AbstractConnectionStringTest.java | 5 +- .../com/mongodb/ConnectionStringUnitTest.java | 13 +++- .../MongoClientSettingsSpecification.groovy | 2 + .../ServerDeprioritizationTest.java | 41 ++++++++---- .../ServerSelectionSelectionTest.java | 12 +++- .../connection/TestClusterListener.java | 10 +++ .../main/com/mongodb/MongoClientOptions.java | 28 ++++++++ .../src/main/com/mongodb/MongoClientURI.java | 7 ++ .../MongoClientOptionsSpecification.groovy | 5 ++ .../MongoClientURISpecification.groovy | 17 +++-- .../internal/OperationExecutorImpl.java | 3 +- .../client/RetryableWritesProseTest.java | 4 +- .../client/internal/MongoClientImpl.java | 2 +- .../client/internal/MongoClusterImpl.java | 28 ++++---- .../AbstractRetryableReadsProseTest.java | 64 ++++++++++++------- .../client/RetryableWritesProseTest.java | 19 +++++- .../internal/MongoClusterSpecification.groovy | 2 +- 20 files changed, 304 insertions(+), 78 deletions(-) diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index 36ab59d469f..c588695f7ca 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -276,6 +276,9 @@ *
    • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. * The maximum number of retry attempts when encountering a retryable overload error. * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
    • + *
    • {@code enableOverloadRetargeting=true|false}: This is {@linkplain Beta Beta API}. + * Whether to enable overload retargeting. Defaults to false. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information.
    • *
    • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
    • @@ -313,6 +316,7 @@ public class ConnectionString { private Boolean retryWrites; private Boolean retryReads; private Integer maxAdaptiveRetries; + private Boolean enableOverloadRetargeting; private ReadConcern readConcern; private Integer minConnectionPoolSize; @@ -564,6 +568,7 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient GENERAL_OPTIONS_KEYS.add("retrywrites"); GENERAL_OPTIONS_KEYS.add("retryreads"); GENERAL_OPTIONS_KEYS.add("maxadaptiveretries"); + GENERAL_OPTIONS_KEYS.add("enableoverloadretargeting"); GENERAL_OPTIONS_KEYS.add("appname"); @@ -718,6 +723,9 @@ private void translateOptions(final Map> optionsMap) { throw new IllegalArgumentException("maxAdaptiveRetries must be >= 0"); } break; + case "enableoverloadretargeting": + enableOverloadRetargeting = parseBoolean(value, "enableoverloadretargeting"); + break; case "uuidrepresentation": uuidRepresentation = createUuidRepresentation(value); break; @@ -1511,6 +1519,20 @@ public Integer getMaxAdaptiveRetries() { return maxAdaptiveRetries; } + /** + * Gets whether overload retargeting is enabled. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information. + * + * @return the enableOverloadRetargeting value, or null if not set + * @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean) + * @since 5.7 + */ + @Beta(Reason.CLIENT) + @Nullable + public Boolean getEnableOverloadRetargeting() { + return enableOverloadRetargeting; + } + /** * Gets the minimum connection pool size specified in the connection string. * @return the minimum connection pool size @@ -1825,6 +1847,7 @@ public boolean equals(final Object o) { && Objects.equals(retryWrites, that.retryWrites) && Objects.equals(retryReads, that.retryReads) && Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries) + && Objects.equals(enableOverloadRetargeting, that.enableOverloadRetargeting) && Objects.equals(readConcern, that.readConcern) && Objects.equals(minConnectionPoolSize, that.minConnectionPoolSize) && Objects.equals(maxConnectionPoolSize, that.maxConnectionPoolSize) @@ -1856,7 +1879,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { return Objects.hash(credential, isSrvProtocol, hosts, database, collection, directConnection, readPreference, - writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime, + writeConcern, retryWrites, retryReads, maxAdaptiveRetries, enableOverloadRetargeting, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime, maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, timeout, socketTimeout, sslEnabled, sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency, serverMonitoringMode, applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts, proxyHost, diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index c1b3c4a069a..d4a06c07d8c 100644 --- a/driver-core/src/main/com/mongodb/MongoClientSettings.java +++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java @@ -25,6 +25,7 @@ import com.mongodb.client.model.geojson.codecs.GeoJsonCodecProvider; import com.mongodb.client.model.mql.ExpressionCodecProvider; import com.mongodb.connection.ClusterSettings; +import com.mongodb.connection.ClusterType; import com.mongodb.connection.ConnectionPoolSettings; import com.mongodb.connection.ServerSettings; import com.mongodb.connection.SocketSettings; @@ -96,6 +97,7 @@ public final class MongoClientSettings { private final boolean retryReads; @Nullable private final Integer maxAdaptiveRetries; + private final boolean enableOverloadRetargeting; private final ReadConcern readConcern; private final MongoCredential credential; private final TransportSettings transportSettings; @@ -219,6 +221,7 @@ public static final class Builder { private boolean retryReads = true; @Nullable private Integer maxAdaptiveRetries; + private boolean enableOverloadRetargeting = false; private ReadConcern readConcern = ReadConcern.DEFAULT; private CodecRegistry codecRegistry = MongoClientSettings.getDefaultCodecRegistry(); private TransportSettings transportSettings; @@ -261,6 +264,7 @@ private Builder(final MongoClientSettings settings) { retryWrites = settings.getRetryWrites(); retryReads = settings.getRetryReads(); maxAdaptiveRetries = settings.getMaxAdaptiveRetries(); + enableOverloadRetargeting = settings.getEnableOverloadRetargeting(); readConcern = settings.getReadConcern(); credential = settings.getCredential(); uuidRepresentation = settings.getUuidRepresentation(); @@ -323,6 +327,10 @@ public Builder applyConnectionString(final ConnectionString connectionString) { if (connectionString.getMaxAdaptiveRetries() != null) { maxAdaptiveRetries = connectionString.getMaxAdaptiveRetries(); } + Boolean enableOverloadRetargetingValue = connectionString.getEnableOverloadRetargeting(); + if (enableOverloadRetargetingValue != null) { + enableOverloadRetargeting = enableOverloadRetargetingValue; + } if (connectionString.getUuidRepresentation() != null) { uuidRepresentation = connectionString.getUuidRepresentation(); } @@ -559,6 +567,31 @@ public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) { return this; } + /** + * Sets whether to enable overload retargeting. + * + *

      When enabled, the previously selected servers on which attempts failed with an error + * {@linkplain MongoException#hasErrorLabel(String) having} + * the {@value MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} label may be deprioritized during + * server selection on subsequent retry attempts. This applies to reads when + * {@linkplain #retryReads(boolean) retryReads} is enabled, and to writes when + * {@linkplain #retryWrites(boolean) retryWrites} is enabled.

      + * + *

      This setting does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}.

      + * + *

      Defaults to {@code false}.

      + * + * @param enableOverloadRetargeting whether to enable overload retargeting. + * @return this + * @see #getEnableOverloadRetargeting() + * @since 5.7 + */ + @Beta(Reason.CLIENT) + public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) { + this.enableOverloadRetargeting = enableOverloadRetargeting; + return this; + } + /** * Sets the read concern. * @@ -933,6 +966,19 @@ public Integer getMaxAdaptiveRetries() { return maxAdaptiveRetries; } + /** + * Returns whether overload retargeting is enabled. + * See {@link Builder#enableOverloadRetargeting(boolean)} for more information. + * + * @return the enableOverloadRetargeting value + * @see Builder#enableOverloadRetargeting(boolean) + * @since 5.7 + */ + @Beta(Reason.CLIENT) + public boolean getEnableOverloadRetargeting() { + return enableOverloadRetargeting; + } + /** * The read concern to use. * @@ -1207,6 +1253,7 @@ public boolean equals(final Object o) { return retryWrites == that.retryWrites && retryReads == that.retryReads && Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries) + && enableOverloadRetargeting == that.enableOverloadRetargeting && heartbeatSocketTimeoutSetExplicitly == that.heartbeatSocketTimeoutSetExplicitly && heartbeatConnectTimeoutSetExplicitly == that.heartbeatConnectTimeoutSetExplicitly && Objects.equals(readPreference, that.readPreference) @@ -1236,7 +1283,8 @@ public boolean equals(final Object o) { @Override public int hashCode() { - return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, credential, transportSettings, + return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, enableOverloadRetargeting, readConcern, + credential, transportSettings, commandListeners, codecRegistry, loggerSettings, clusterSettings, socketSettings, heartbeatSocketSettings, connectionPoolSettings, serverSettings, sslSettings, applicationName, compressorList, uuidRepresentation, serverApi, autoEncryptionSettings, heartbeatSocketTimeoutSetExplicitly, @@ -1252,6 +1300,7 @@ public String toString() { + ", retryWrites=" + retryWrites + ", retryReads=" + retryReads + ", maxAdaptiveRetries=" + maxAdaptiveRetries + + ", enableOverloadRetargeting=" + enableOverloadRetargeting + ", readConcern=" + readConcern + ", credential=" + credential + ", transportSettings=" + transportSettings @@ -1281,8 +1330,9 @@ private MongoClientSettings(final Builder builder) { readPreference = builder.readPreference; writeConcern = builder.writeConcern; retryWrites = builder.retryWrites; - maxAdaptiveRetries = builder.maxAdaptiveRetries; retryReads = builder.retryReads; + maxAdaptiveRetries = builder.maxAdaptiveRetries; + enableOverloadRetargeting = builder.enableOverloadRetargeting; readConcern = builder.readConcern; credential = builder.credential; transportSettings = builder.transportSettings; diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java index 71352abee60..06c2c9b9358 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java +++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java @@ -77,6 +77,18 @@ public OperationContext(final RequestContext requestContext, final SessionContex null); } + public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, + final TracingManager tracingManager, + @Nullable final ServerApi serverApi, + @Nullable final String operationName, + final ServerDeprioritization serverDeprioritization) { + this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, serverDeprioritization, + tracingManager, + serverApi, + operationName, + null); + } + static OperationContext simpleOperationContext( final TimeoutSettings timeoutSettings, @Nullable final ServerApi serverApi) { return new OperationContext( @@ -119,7 +131,8 @@ public OperationContext withOperationName(final String operationName) { * It is a temporary solution to handle cases where deprioritization state persists across operations. */ public OperationContext withNewServerDeprioritization() { - return new OperationContext(id, requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), tracingManager, serverApi, + return new OperationContext(id, requestContext, sessionContext, timeoutContext, + new ServerDeprioritization(serverDeprioritization.enableOverloadRetargeting), tracingManager, serverApi, operationName, tracingSpan); } @@ -206,7 +219,8 @@ public OperationContext withConnectionEstablishmentSessionContext() { } public OperationContext withMinRoundTripTime(final ServerDescription serverDescription) { - return withTimeoutContext(timeoutContext.withMinRoundTripTime(TimeUnit.NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos()))); + return withTimeoutContext( + timeoutContext.withMinRoundTripTime(TimeUnit.NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos()))); } public OperationContext withOverride(final TimeoutContextOverride timeoutContextOverrideFunction) { @@ -219,11 +233,17 @@ public static final class ServerDeprioritization { @Nullable private ClusterType clusterType; private final Set deprioritized; + private final boolean enableOverloadRetargeting; - private ServerDeprioritization() { - candidate = null; - deprioritized = new HashSet<>(); - clusterType = null; + public ServerDeprioritization() { + this(false); + } + + public ServerDeprioritization(final boolean enableOverloadRetargeting) { + this.enableOverloadRetargeting = enableOverloadRetargeting; + this.candidate = null; + this.deprioritized = new HashSet<>(); + this.clusterType = null; } /** @@ -250,10 +270,12 @@ public void onAttemptFailure(final Throwable failure) { return; } - // As per spec: sharded clusters deprioritize on any error, other topologies only on overload + // As per spec: sharded clusters deprioritize on any error, + // other topologies deprioritize on overload only when retargeting is enabled. boolean isSystemOverloadedError = failure instanceof MongoException && ((MongoException) failure).hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL); - if (clusterType == ClusterType.SHARDED || isSystemOverloadedError) { + + if (clusterType == ClusterType.SHARDED || (isSystemOverloadedError && enableOverloadRetargeting)) { deprioritized.add(candidate); } } @@ -303,6 +325,7 @@ public List select(final ClusterDescription clusterDescriptio } } - public interface TimeoutContextOverride extends Function {} + public interface TimeoutContextOverride extends Function { + } } diff --git a/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java b/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java index 8aa0b7d5a9e..4be89f4d9ad 100644 --- a/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java +++ b/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java @@ -112,7 +112,7 @@ protected void testValidOptions() { if (option.getKey().equals("authmechanism")) { String expected = option.getValue().asString().getValue(); - if (expected.equals("MONGODB-CR")) { + if (expected.equals("MONGODB-CR")) { assertNotNull(connectionString.getCredential()); assertNull(connectionString.getCredential().getAuthenticationMechanism()); } else { @@ -125,6 +125,9 @@ protected void testValidOptions() { } else if (option.getKey().equalsIgnoreCase("maxadaptiveretries")) { int expected = option.getValue().asInt32().getValue(); assertEquals(expected, connectionString.getMaxAdaptiveRetries().intValue()); + } else if (option.getKey().equalsIgnoreCase("enableoverloadretargeting")) { + boolean expected = option.getValue().asBoolean().getValue(); + assertEquals(expected, connectionString.getEnableOverloadRetargeting().booleanValue()); } else if (option.getKey().equalsIgnoreCase("replicaset")) { String expected = option.getValue().asString().getValue(); assertEquals(expected, connectionString.getRequiredReplicaSetName()); diff --git a/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java b/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java index c40ea5a0ab6..e0803bee6eb 100644 --- a/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java +++ b/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java @@ -46,7 +46,8 @@ void defaults() { @ParameterizedTest @ValueSource(strings = { "serverMonitoringMode=stream", - "maxAdaptiveRetries=42" + "maxAdaptiveRetries=42", + "enableOverloadRetargeting=true" }) void equalAndHashCode(final String connectionStringOptions) { ConnectionString default1 = new ConnectionString(DEFAULT_OPTIONS); @@ -129,4 +130,14 @@ void maxAdaptiveRetries() { () -> new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=invalid")) ); } + + @Test + void enableOverloadRetargeting() { + assertAll( + () -> assertNull(new ConnectionString("mongodb://localhost/").getEnableOverloadRetargeting()), + () -> assertEquals(false, new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=false").getEnableOverloadRetargeting()), + () -> assertEquals(true, new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=true").getEnableOverloadRetargeting()), + () -> assertNull(new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=foos").getEnableOverloadRetargeting()) + ); + } } diff --git a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy index d4b59f0cb52..57995d26516 100644 --- a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy @@ -576,6 +576,7 @@ class MongoClientSettingsSpecification extends Specification { def actual = MongoClientSettings.Builder.declaredFields.grep { !it.synthetic } *.name.sort() def expected = ['applicationName', 'autoEncryptionSettings', 'clusterSettingsBuilder', 'codecRegistry', 'commandListeners', 'compressorList', 'connectionPoolSettingsBuilder', 'contextProvider', 'credential', 'dnsClient', + 'enableOverloadRetargeting', 'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'loggerSettingsBuilder', 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern', 'readPreference', 'retryReads', @@ -595,6 +596,7 @@ class MongoClientSettingsSpecification extends Specification { 'applyToConnectionPoolSettings', 'applyToLoggerSettings', 'applyToServerSettings', 'applyToSocketSettings', 'applyToSslSettings', 'autoEncryptionSettings', 'build', 'codecRegistry', 'commandListenerList', 'compressorList', 'contextProvider', 'credential', 'dnsClient', + 'enableOverloadRetargeting', 'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern', 'readPreference', diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java index 3c1a7aad390..9ac2bbe7d40 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java @@ -41,8 +41,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; -import static com.mongodb.ClusterFixture.createOperationContext; +import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; @@ -62,11 +61,13 @@ final class ServerDeprioritizationTest { private static final ClusterDescription SHARDED_CLUSTER = multipleModeClusterDescription(ClusterType.SHARDED); private static final ClusterDescription UNKNOWN_CLUSTER = multipleModeClusterDescription(ClusterType.UNKNOWN); private static final List CLUSTERS = asList(SHARDED_CLUSTER, REPLICA_SET_CLUSTER, UNKNOWN_CLUSTER); + private static final RuntimeException RUNTIME_EXCEPTION = new RuntimeException(); + private static final MongoException MONGO_EXCEPTION_NO_LABEL = new MongoException(0, "test"); private ServerDeprioritization serverDeprioritization; @BeforeEach void beforeEach() { - serverDeprioritization = createOperationContext(TIMEOUT_SETTINGS).getServerDeprioritization(); + serverDeprioritization = new OperationContext.ServerDeprioritization(true); } private static Stream selectNoneDeprioritized() { @@ -105,8 +106,8 @@ void selectNoneDeprioritizedSingleServerCluster(final ClusterType clusterType) { private static Stream deprioritizableClusters() { return Stream.of( - of(SHARDED_CLUSTER, new RuntimeException()), - of(SHARDED_CLUSTER, new MongoException(0, "test")), + of(SHARDED_CLUSTER, RUNTIME_EXCEPTION), + of(SHARDED_CLUSTER, MONGO_EXCEPTION_NO_LABEL), of(REPLICA_SET_CLUSTER, createSystemOverloadedError()), of(UNKNOWN_CLUSTER, createSystemOverloadedError()) ); @@ -204,7 +205,7 @@ void onAttemptFailureIgnoresIfPoolClearedException() { @Test void onAttemptFailureDoesNotThrowIfNoCandidate() { - assertDoesNotThrow(() -> serverDeprioritization.onAttemptFailure(new RuntimeException())); + assertDoesNotThrow(() -> serverDeprioritization.onAttemptFailure(RUNTIME_EXCEPTION)); } @ParameterizedTest @@ -214,20 +215,32 @@ void onAttemptFailureIgnoresIfNonShardedWithoutOverloadError(final ClusterType c ServerSelector selector = createAssertingSelector(ALL_SERVERS, singletonList(SERVER_A)); assertAll(() -> { - serverDeprioritization.updateCandidate(SERVER_B.getAddress(), clusterType); - serverDeprioritization.onAttemptFailure(new RuntimeException()); + deprioritize(clusterType, RUNTIME_EXCEPTION, SERVER_B); assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), - "Expected no deprioritization for " + clusterType + " with RuntimeException"); - }, () -> { - serverDeprioritization = createOperationContext(TIMEOUT_SETTINGS).getServerDeprioritization(); - serverDeprioritization.updateCandidate(SERVER_B.getAddress(), clusterType); - serverDeprioritization.onAttemptFailure(new MongoException(1, "error")); + format("Expected no deprioritization for %s with RuntimeException", clusterType)); + }, + () -> { + deprioritize(clusterType, MONGO_EXCEPTION_NO_LABEL, SERVER_B); assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), - "Expected no deprioritization for " + clusterType + " with no SystemOverloadedError MongoException"); + format("Expected no deprioritization for %s with MongoException without SystemOverloadedError", clusterType)); } ); } + @ParameterizedTest + @EnumSource(value = ClusterType.class, names = "SHARDED", mode = EnumSource.Mode.EXCLUDE) + void onAttemptFailureIgnoresIfNonShardedWithOverloadErrorAndDisabledOverloadRetargeting(final ClusterType clusterType) { + ClusterDescription cluster = multipleModeClusterDescription(clusterType); + ServerSelector selector = createAssertingSelector(ALL_SERVERS, singletonList(SERVER_A)); + + ServerDeprioritization serverDeprioritization = new OperationContext.ServerDeprioritization(false); + serverDeprioritization.updateCandidate(SERVER_B.getAddress(), clusterType); + serverDeprioritization.onAttemptFailure(createSystemOverloadedError()); + + assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), + format("Expected no deprioritization when overloadRetargeting is disabled for %s with SystemOverloadedError", clusterType)); + } + private void deprioritize(final ClusterType clusterType, final Throwable exception, final ServerDescription... serverDescriptions) { for (ServerDescription serverDescription : serverDescriptions) { serverDeprioritization.updateCandidate(serverDescription.getAddress(), clusterType); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java index ed8f6fa9550..5d6b5e0e1e3 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java @@ -35,8 +35,10 @@ import com.mongodb.connection.ServerSettings; import com.mongodb.connection.ServerType; import com.mongodb.event.ServerDescriptionChangedEvent; +import com.mongodb.internal.IgnorableRequestContext; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.mockito.MongoMockito; +import com.mongodb.internal.observability.micrometer.TracingManager; import com.mongodb.internal.selector.ReadPreferenceServerSelector; import com.mongodb.internal.selector.WritableServerSelector; import com.mongodb.internal.time.Timeout; @@ -297,8 +299,14 @@ private static List extractDeprioritizedServerAddresses(final Bso private OperationContext createOperationContext() { OperationContext operationContext = - OperationContext.simpleOperationContext( - new TimeoutContext(TIMEOUT_SETTINGS.withServerSelectionTimeoutMS(0))); + new OperationContext( + IgnorableRequestContext.INSTANCE, + NoOpSessionContext.INSTANCE, + new TimeoutContext(TIMEOUT_SETTINGS.withServerSelectionTimeoutMS(0)), + TracingManager.NO_OP, + null, + null, + new OperationContext.ServerDeprioritization(true)); OperationContext.ServerDeprioritization serverDeprioritization = operationContext.getServerDeprioritization(); for (ServerAddress address : extractDeprioritizedServerAddresses(definition)) { serverDeprioritization.updateCandidate(address, clusterDescription.getType()); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/TestClusterListener.java b/driver-core/src/test/unit/com/mongodb/internal/connection/TestClusterListener.java index edf1babd028..e1abb2c42f9 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/TestClusterListener.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/TestClusterListener.java @@ -16,6 +16,8 @@ package com.mongodb.internal.connection; +import com.mongodb.connection.ServerDescription; +import com.mongodb.connection.ServerType; import com.mongodb.event.ClusterClosedEvent; import com.mongodb.event.ClusterDescriptionChangedEvent; import com.mongodb.event.ClusterListener; @@ -115,6 +117,14 @@ public void waitForClusterDescriptionChangedEvents( } } + public void waitForAllServersDiscovered(final Duration duration) throws InterruptedException, TimeoutException { + waitForClusterDescriptionChangedEvents( + event -> event.getNewDescription().getServerDescriptions().stream() + .map(ServerDescription::getType) + .noneMatch(ServerType.UNKNOWN::equals), + 1, duration); + } + /** * Waits for the cluster to be closed, which is signaled by a {@link ClusterClosedEvent}. */ diff --git a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java index fe7b827d362..c269a810d2f 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java @@ -488,6 +488,19 @@ public Integer getMaxAdaptiveRetries() { return wrapped.getMaxAdaptiveRetries(); } + /** + * Returns whether overload retargeting is enabled. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information. + * + * @return the enableOverloadRetargeting value + * @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean) + * @since 5.7 + */ + @Beta(Reason.CLIENT) + public boolean getEnableOverloadRetargeting() { + return wrapped.getEnableOverloadRetargeting(); + } + /** *

      The read concern to use.

      * @@ -1093,6 +1106,21 @@ public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) { return this; } + /** + * Sets whether to enable overload retargeting. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information. + * + * @param enableOverloadRetargeting whether to enable overload retargeting + * @return {@code this} + * @see #getEnableOverloadRetargeting() + * @since 5.7 + */ + @Beta(Reason.CLIENT) + public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) { + wrapped.enableOverloadRetargeting(enableOverloadRetargeting); + return this; + } + /** * Sets the read concern. * diff --git a/driver-legacy/src/main/com/mongodb/MongoClientURI.java b/driver-legacy/src/main/com/mongodb/MongoClientURI.java index 5d129bbd07f..e7ce89566dd 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientURI.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientURI.java @@ -218,6 +218,8 @@ *
    • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. * The maximum number of retry attempts when encountering a retryable overload error. * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
    • +*
    • {@code enableOverloadRetargeting=true|false}: Whether to enable overload retargeting. Defaults to false. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information.
    • *
    • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientOptions#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
    • @@ -390,6 +392,11 @@ public MongoClientOptions getOptions() { builder.maxAdaptiveRetries(maxAdaptiveRetries); } + Boolean enableOverloadRetargeting = proxied.getEnableOverloadRetargeting(); + if (enableOverloadRetargeting != null) { + builder.enableOverloadRetargeting(enableOverloadRetargeting); + } + Integer maxConnectionPoolSize = proxied.getMaxConnectionPoolSize(); if (maxConnectionPoolSize != null) { builder.connectionsPerHost(maxConnectionPoolSize); diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy index 723dddcc280..a386cd7f684 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy @@ -47,6 +47,7 @@ class MongoClientOptionsSpecification extends Specification { options.getRetryWrites() options.getRetryReads() options.getMaxAdaptiveRetries() == null + !options.getEnableOverloadRetargeting() options.getCodecRegistry() == MongoClientSettings.defaultCodecRegistry options.getUuidRepresentation() == UuidRepresentation.UNSPECIFIED options.getMinConnectionsPerHost() == 0 @@ -123,6 +124,7 @@ class MongoClientOptionsSpecification extends Specification { .retryWrites(true) .retryReads(false) .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .writeConcern(WriteConcern.JOURNALED) .readConcern(ReadConcern.MAJORITY) .minConnectionsPerHost(30) @@ -170,6 +172,7 @@ class MongoClientOptionsSpecification extends Specification { options.getRetryWrites() !options.getRetryReads() options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getServerSelectionTimeout() == 150 options.getTimeout() == 10_000 options.getMaxWaitTime() == 200 @@ -328,6 +331,7 @@ class MongoClientOptionsSpecification extends Specification { .applicationName('appName') .readPreference(ReadPreference.secondary()) .retryReads(true) + .enableOverloadRetargeting(true) .uuidRepresentation(UuidRepresentation.STANDARD) .writeConcern(WriteConcern.JOURNALED) .minConnectionsPerHost(30) @@ -630,6 +634,7 @@ class MongoClientOptionsSpecification extends Specification { .retryWrites(true) .retryReads(true) .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .uuidRepresentation(UuidRepresentation.STANDARD) .minConnectionsPerHost(30) .connectionsPerHost(500) diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy index fb2509554a4..3de1f77b6da 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy @@ -132,6 +132,7 @@ class MongoClientURISpecification extends Specification { + 'retryWrites=true&' + 'retryReads=true&' + 'maxAdaptiveRetries=42&' + + 'enableOverloadRetargeting=true&' + 'uuidRepresentation=csharpLegacy&' + 'appName=app1&' + 'timeoutMS=10000') @@ -160,6 +161,7 @@ class MongoClientURISpecification extends Specification { options.getRetryWrites() options.getRetryReads() options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getUuidRepresentation() == UuidRepresentation.C_SHARP_LEGACY options.getApplicationName() == 'app1' } @@ -181,6 +183,7 @@ class MongoClientURISpecification extends Specification { options.getRetryWrites() options.getRetryReads() options.getMaxAdaptiveRetries() == null + !options.getEnableOverloadRetargeting() options.getUuidRepresentation() == UuidRepresentation.UNSPECIFIED } @@ -192,6 +195,7 @@ class MongoClientURISpecification extends Specification { .retryWrites(true) .retryReads(true) .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .writeConcern(WriteConcern.JOURNALED) .minConnectionsPerHost(30) .connectionsPerHost(500) @@ -225,6 +229,7 @@ class MongoClientURISpecification extends Specification { options.getRetryWrites() options.getRetryReads() options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getTimeout() == 10_000 options.getServerSelectionTimeout() == 150 options.getMaxWaitTime() == 200 @@ -321,7 +326,8 @@ class MongoClientURISpecification extends Specification { given: def uri = new MongoClientURI('mongodb://localhost/', MongoClientOptions.builder() .connectionsPerHost(200) - .maxAdaptiveRetries(42)) + .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true)) when: def options = uri.getOptions() @@ -329,15 +335,17 @@ class MongoClientURISpecification extends Specification { then: options.getConnectionsPerHost() == 200 options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() } def 'should override MongoClientOptions builder'() { given: def uri = new MongoClientURI('mongodb://localhost/?' + 'maxPoolSize=250' - + '&maxAdaptiveRetries=43', - MongoClientOptions.builder(). - connectionsPerHost(200) + + '&maxAdaptiveRetries=43' + + '&enableOverloadRetargeting=false', + MongoClientOptions.builder() + .connectionsPerHost(200) .maxAdaptiveRetries(42)) when: @@ -346,6 +354,7 @@ class MongoClientURISpecification extends Specification { then: options.getConnectionsPerHost() == 250 options.getMaxAdaptiveRetries() == 43 + !options.getEnableOverloadRetargeting() } def 'should be equal to another MongoClientURI with the same string values'() { diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java index ef18c2c6b1f..35ff27f79ec 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java @@ -216,7 +216,8 @@ private OperationContext getOperationContext(final RequestContext requestContext createTimeoutContext(session, timeoutSettings), TracingManager.NO_OP, mongoClient.getSettings().getServerApi(), - commandName); + commandName, + new OperationContext.ServerDeprioritization(mongoClient.getSettings().getEnableOverloadRetargeting())); } private ReadPreference getReadPreferenceForBinding(final ReadPreference readPreference, @Nullable final ClientSession session) { diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableWritesProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableWritesProseTest.java index 38ef09a4771..fe1bcdfb97c 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableWritesProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableWritesProseTest.java @@ -21,6 +21,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.util.concurrent.TimeoutException; + /** * * Prose Tests. @@ -52,7 +54,7 @@ void originalErrorMustBePropagatedIfNoWritesPerformed() throws Exception { * 4. Test that in a sharded cluster writes are retried on a different mongos when one is available. */ @Test - void retriesOnDifferentMongosWhenAvailable() { + void retriesOnDifferentMongosWhenAvailable() throws InterruptedException, TimeoutException { com.mongodb.client.RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable( SyncMongoClient::new, mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true); diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java index bbeb7419bc7..9ba2139f18c 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java @@ -105,7 +105,7 @@ public MongoClientImpl(final Cluster cluster, (SynchronousContextProvider) settings.getContextProvider(), autoEncryptionSettings == null ? null : createCrypt(settings, autoEncryptionSettings), this, operationExecutor, settings.getReadConcern(), settings.getReadPreference(), settings.getRetryReads(), - settings.getRetryWrites(), settings.getServerApi(), + settings.getRetryWrites(), settings.getEnableOverloadRetargeting(), settings.getServerApi(), new ServerSessionPool(cluster, TimeoutSettings.create(settings), settings.getServerApi()), TimeoutSettings.create(settings), settings.getUuidRepresentation(), settings.getWriteConcern(), new TracingManager(settings.getObservabilitySettings())); diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java index 920feb1f986..b5604a7a846 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java @@ -104,6 +104,7 @@ final class MongoClusterImpl implements MongoCluster { private final ReadPreference readPreference; private final boolean retryReads; private final boolean retryWrites; + private final boolean enableOverloadRetargeting; @Nullable private final ServerApi serverApi; private final ServerSessionPool serverSessionPool; @@ -117,10 +118,9 @@ final class MongoClusterImpl implements MongoCluster { @Nullable final AutoEncryptionSettings autoEncryptionSettings, final Cluster cluster, final CodecRegistry codecRegistry, @Nullable final SynchronousContextProvider contextProvider, @Nullable final Crypt crypt, final Object originator, @Nullable final OperationExecutor operationExecutor, final ReadConcern readConcern, final ReadPreference readPreference, - final boolean retryReads, final boolean retryWrites, @Nullable final ServerApi serverApi, - final ServerSessionPool serverSessionPool, final TimeoutSettings timeoutSettings, final UuidRepresentation uuidRepresentation, - final WriteConcern writeConcern, - final TracingManager tracingManager) { + final boolean retryReads, final boolean retryWrites, final boolean enableOverloadRetargeting, + @Nullable final ServerApi serverApi, final ServerSessionPool serverSessionPool, final TimeoutSettings timeoutSettings, + final UuidRepresentation uuidRepresentation, final WriteConcern writeConcern, final TracingManager tracingManager) { this.autoEncryptionSettings = autoEncryptionSettings; this.cluster = cluster; this.codecRegistry = codecRegistry; @@ -132,6 +132,7 @@ final class MongoClusterImpl implements MongoCluster { this.readPreference = readPreference; this.retryReads = retryReads; this.retryWrites = retryWrites; + this.enableOverloadRetargeting = enableOverloadRetargeting; this.serverApi = serverApi; this.serverSessionPool = serverSessionPool; this.timeoutSettings = timeoutSettings; @@ -180,35 +181,35 @@ public Long getTimeout(final TimeUnit timeUnit) { @Override public MongoCluster withCodecRegistry(final CodecRegistry codecRegistry) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, tracingManager); } @Override public MongoCluster withReadPreference(final ReadPreference readPreference) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, tracingManager); } @Override public MongoCluster withWriteConcern(final WriteConcern writeConcern) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, tracingManager); } @Override public MongoCluster withReadConcern(final ReadConcern readConcern) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, tracingManager); } @Override public MongoCluster withTimeout(final long timeout, final TimeUnit timeUnit) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings.withTimeout(timeout, timeUnit), uuidRepresentation, writeConcern, tracingManager); } @@ -530,7 +531,8 @@ private OperationContext getOperationContext(final ClientSession session, final createTimeoutContext(session, executorTimeoutSettings), tracingManager, serverApi, - commandName); + commandName, + new OperationContext.ServerDeprioritization(enableOverloadRetargeting)); } private RequestContext getRequestContext() { @@ -591,9 +593,9 @@ ClientSession getClientSession(@Nullable final ClientSession clientSessionFromOp * Create a tracing span for the given operation, and set it on operation context. * * @param actualClientSession the session that the operation is part of - * @param operationContext the operation context for the operation - * @param commandName the name of the command - * @param namespace the namespace of the command + * @param operationContext the operation context for the operation + * @param commandName the name of the command + * @param namespace the namespace of the command * @return the created span, or null if tracing is not enabled */ @Nullable diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java index fde4675c638..4c6c536fac1 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java @@ -20,7 +20,6 @@ import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; import com.mongodb.client.test.CollectionHelper; -import com.mongodb.connection.ClusterDescription; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandSucceededEvent; import com.mongodb.internal.connection.TestClusterListener; @@ -85,7 +84,7 @@ void poolClearedExceptionMustBeRetryable() throws Exception { * 2.1 Retryable Reads Are Retried on a Different mongos When One is Available. */ @Test - void retriesOnDifferentMongosWhenAvailable() { + void retriesOnDifferentMongosWhenAvailable() throws InterruptedException, TimeoutException { RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable(this::createClient, mongoCollection -> { try (MongoCursor cursor = mongoCollection.find().iterator()) { @@ -109,10 +108,9 @@ void retriesOnSameMongosWhenAnotherNotAvailable() { } /** - * - * 3.1 Retryable Reads Caused by Overload Errors Are Retried on a Different Replicaset Server When One is Available. + * + * 3.1 Retryable Reads Caused by Overload Errors Are Retried on a Different Replicaset Server When One is Available and enableOverloadRetargeting is enabled. */ - //TODO-BACKPRESSURE Slav Babanin JAVA-6167 add overloadRetargeting into tests. @Test void overloadErrorRetriedOnDifferentReplicaSetServer() throws InterruptedException, TimeoutException { //given @@ -133,6 +131,7 @@ void overloadErrorRetriedOnDifferentReplicaSetServer() throws InterruptedExcepti MongoClient client = createClient(getMongoClientSettingsBuilder() .retryReads(true) .readPreference(ReadPreference.primaryPreferred()) + .enableOverloadRetargeting(true) .addCommandListener(commandListener) .applyToClusterSettings(builder -> builder.addClusterListener(clusterListener)) .build())) { @@ -164,12 +163,8 @@ void overloadErrorRetriedOnDifferentReplicaSetServer() throws InterruptedExcepti * * 3.2 Retryable Reads Caused by Non-Overload Errors Are Retried on the Same Replicaset Server. */ - //TODO-BACKPRESSURE Slav Babanin JAVA-6167 add overloadRetargeting into tests. @Test void nonOverloadErrorRetriedOnSameReplicaSetServer() throws InterruptedException, TimeoutException { - //given - assumeTrue(serverVersionAtLeast(4, 4)); - assumeTrue(isDiscoverableReplicaSet()); BsonDocument configureFailPoint = BsonDocument.parse( "{\n" + " configureFailPoint: \"failCommand\",\n" @@ -180,6 +175,33 @@ void nonOverloadErrorRetriedOnSameReplicaSetServer() throws InterruptedException + " errorCode: 6\n" + " }\n" + "}\n"); + testRetriedOnTheSameServer(configureFailPoint); + } + + /** + * + * 3.3 Retryable Reads Caused by Overload Errors Are Retried on Same Replicaset Server When enableOverloadRetargeting is disabled. + */ + @Test + void overloadErrorRetriedOnSameReplicaSetServerWhenRetargetingDisabled() throws InterruptedException, TimeoutException { + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: [\"find\"],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "'],\n" + + " errorCode: 6\n" + + " }\n" + + "}\n"); + testRetriedOnTheSameServer(configureFailPoint); + } + + private void testRetriedOnTheSameServer(final BsonDocument configureFailPoint) throws InterruptedException, TimeoutException { + //given + assumeTrue(serverVersionAtLeast(4, 4)); + assumeTrue(isDiscoverableReplicaSet()); + TestCommandListener commandListener = new TestCommandListener(asList("commandFailedEvent", "commandSucceededEvent"), emptyList()); try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary()); MongoClient client = createClient(getMongoClientSettingsBuilder() @@ -213,20 +235,14 @@ void nonOverloadErrorRetriedOnSameReplicaSetServer() throws InterruptedException } private void waitForClusterDiscovery() throws InterruptedException, TimeoutException { - clusterListener.waitForClusterDescriptionChangedEvents( - event -> { - ClusterDescription desc = event.getNewDescription(); - // We need both primary and secondary to be discovered (not UNKNOWN) before running the deprioritization tests. - // - // 1. The failpoint is set on the primary. If the primary is not yet discovered, - // primaryPreferred may route the find to a secondary, and the failpoint never fires. - // - // 2. When the primary is deprioritized on retry, primaryPreferred falls back to a secondary. - // If the secondaries are still UNKNOWN at that point, the fallback yields no selectable servers, - // causing the deprioritized primary to be selected again. - return desc.hasReadableServer(ReadPreference.primary()) - && desc.hasReadableServer(ReadPreference.secondary()); - }, - 1, Duration.ofSeconds(10)); + // We need both primary and secondary to be discovered (not UNKNOWN) before running the deprioritization tests. + // + // 1. The failpoint is set on the primary. If the primary is not yet discovered, + // primaryPreferred may route the find to a secondary, and the failpoint never fires. + // + // 2. When the primary is deprioritized on retry, primaryPreferred falls back to a secondary. + // If the secondaries are still UNKNOWN at that point, the fallback yields no selectable servers, + // causing the deprioritized primary to be selected again. + clusterListener.waitForAllServersDiscovered(Duration.ofSeconds(10)); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java index c49d1a8b4f1..87e8b533351 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java @@ -32,6 +32,7 @@ import com.mongodb.event.ConnectionCheckedOutEvent; import com.mongodb.event.ConnectionPoolClearedEvent; import com.mongodb.internal.connection.ServerAddressHelper; +import com.mongodb.internal.connection.TestClusterListener; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.internal.connection.TestConnectionPoolListener; import com.mongodb.internal.event.ConfigureFailPointCommandListener; @@ -42,6 +43,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -49,6 +51,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -224,7 +227,7 @@ public static void originalErrorMustBePropagatedIfNoWritesPerformed( * 4. Test that in a sharded cluster writes are retried on a different mongos when one is available. */ @Test - void retriesOnDifferentMongosWhenAvailable() { + void retriesOnDifferentMongosWhenAvailable() throws InterruptedException, TimeoutException { retriesOnDifferentMongosWhenAvailable(MongoClients::create, mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true); } @@ -232,7 +235,8 @@ void retriesOnDifferentMongosWhenAvailable() { @SuppressWarnings("try") public static void retriesOnDifferentMongosWhenAvailable( final Function clientCreator, - final Function, R> operation, final String expectedCommandName, final boolean write) { + final Function, R> operation, final String expectedCommandName, final boolean write) + throws InterruptedException, TimeoutException { if (write) { assumeTrue(serverVersionAtLeast(4, 4)); } @@ -253,6 +257,7 @@ public static void retriesOnDifferentMongosWhenAvailable( + " }\n" + "}\n"); TestCommandListener commandListener = new TestCommandListener(singletonList("commandFailedEvent"), emptyList()); + TestClusterListener clusterListener = new TestClusterListener(); try (FailPoint s0FailPoint = FailPoint.enable(configureFailPoint, s0Address); FailPoint s1FailPoint = FailPoint.enable(configureFailPoint, s1Address); MongoClient client = clientCreator.apply(getMultiMongosMongoClientSettingsBuilder() @@ -260,8 +265,16 @@ public static void retriesOnDifferentMongosWhenAvailable( .retryWrites(true) .addCommandListener(commandListener) // explicitly specify only s0 and s1, in case `getMultiMongosMongoClientSettingsBuilder` has more - .applyToClusterSettings(builder -> builder.hosts(asList(s0Address, s1Address))) + .applyToClusterSettings(builder -> builder + .hosts(asList(s0Address, s1Address)) + .addClusterListener(clusterListener)) .build())) { + // We need both mongos servers to be discovered (not UNKNOWN) before running the deprioritization test. + // When the first mongos is deprioritized on retry, the selector falls back to the second mongos. + // If the second mongos is still UNKNOWN at that point, the non-deprioritized pass yields no selectable servers, + // causing the deprioritized mongos to be selected again. + clusterListener.waitForAllServersDiscovered(Duration.ofSeconds(10)); + MongoCollection collection = dropAndGetCollection("retriesOnDifferentMongosWhenAvailable", client); commandListener.reset(); assertThrows(MongoServerException.class, () -> operation.apply(collection)); diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy index c75a4255595..34f46e7b007 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy @@ -259,7 +259,7 @@ class MongoClusterSpecification extends Specification { MongoClusterImpl createMongoCluster(final MongoClientSettings settings, final OperationExecutor operationExecutor) { new MongoClusterImpl(null, cluster, settings.codecRegistry, null, null, originator, operationExecutor, settings.readConcern, settings.readPreference, settings.retryReads, settings.retryWrites, - null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation, + settings.enableOverloadRetargeting, null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation, settings.writeConcern, TracingManager.NO_OP) } } From 328e95f0a7cdf5dce1e9fd4f51abb4f1342a017c Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 27 Apr 2026 16:06:58 +0100 Subject: [PATCH 22/26] Update driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../functional/com/mongodb/client/unified/EventMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java b/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java index e1abe106c1d..1bfe2e19a6f 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java @@ -454,7 +454,7 @@ private static boolean serverDescriptionChangedEventMatches(final BsonDocument e case "RSSecondary": return event.getNewDescription().getType() == ServerType.REPLICA_SET_SECONDARY; default: - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Unsupported server type " + newType); } } From 5a62b5c8bcef1d9cf9b93d507e494e2239eb0633 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 27 Apr 2026 16:52:19 +0100 Subject: [PATCH 23/26] Fixing DNS test --- .../internal/connection/DefaultServerSpecification.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy index 9dcd4c19bad..f5ee72e73bf 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy @@ -271,7 +271,7 @@ class DefaultServerSpecification extends Specification { def server = defaultServer(connectionPool, serverMonitor) when: - server.getConnection(OPERATION_CONTEXT) + server.getConnection(createOperationContext()) then: def e = thrown(MongoException) From 4d3eaea4faa809fa0afd54e1bf62667f128bf2e5 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 27 Apr 2026 17:23:49 +0100 Subject: [PATCH 24/26] Adding more unit tests for the backpressure labels --- .../connection/BackpressureErrorLabeler.java | 2 +- .../BackpressureErrorLabelerTest.java | 156 ++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 driver-core/src/test/unit/com/mongodb/internal/connection/BackpressureErrorLabelerTest.java diff --git a/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java b/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java index 85037b5bdb2..b47d7484659 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java +++ b/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java @@ -70,7 +70,7 @@ private static boolean isDnsLookupFailure(final Throwable t) { return false; } - private static boolean isTlsConfigurationError(final Throwable t) { + static boolean isTlsConfigurationError(final Throwable t) { Throwable cause = t.getCause(); while (cause != null) { if (cause instanceof CertificateException diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/BackpressureErrorLabelerTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/BackpressureErrorLabelerTest.java new file mode 100644 index 00000000000..ab16e80bd7d --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/BackpressureErrorLabelerTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.connection; + +import com.mongodb.MongoCredential; +import com.mongodb.MongoException; +import com.mongodb.MongoSecurityException; +import com.mongodb.MongoSocketException; +import com.mongodb.MongoSocketOpenException; +import com.mongodb.MongoSocketReadTimeoutException; +import com.mongodb.ServerAddress; +import org.bson.BsonDocument; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; +import java.io.IOException; +import java.net.UnknownHostException; +import java.security.cert.CertificateException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BackpressureErrorLabelerTest { + + private static final ServerAddress ADDRESS = new ServerAddress(); + + @Test + void mongoSocketExceptionIsLabeled() { + MongoSocketException e = new MongoSocketException("boom", ADDRESS); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertHasBackpressureLabels(e); + } + + @Test + void mongoSocketReadTimeoutIsLabeled() { + MongoSocketReadTimeoutException e = new MongoSocketReadTimeoutException("slow", ADDRESS, new IOException("read timed out")); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertHasBackpressureLabels(e); + } + + @Test + void mongoSocketOpenExceptionIsLabeled() { + MongoSocketOpenException e = new MongoSocketOpenException("open failed", ADDRESS, new IOException("connection refused")); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertHasBackpressureLabels(e); + } + + @Test + void dnsFailureIsNotLabeled() { + MongoSocketException e = new MongoSocketException("lookup failed", ADDRESS, new UnknownHostException("nope")); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + @Test + void dnsFailureDeepInCauseChainIsNotLabeled() { + Throwable dns = new UnknownHostException("nope"); + IOException wrap1 = new IOException("wrap1", dns); + MongoSocketException e = new MongoSocketException("wrap2", ADDRESS, wrap1); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + @Test + void tlsCertificateErrorIsNotLabeled() { + CertificateException cert = new CertificateException("bad cert"); + MongoSocketException e = new MongoSocketException("tls", ADDRESS, cert); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + @Test + void tlsPeerUnverifiedIsNotLabeled() { + SSLPeerUnverifiedException tls = new SSLPeerUnverifiedException("peer not verified"); + MongoSocketException e = new MongoSocketException("tls", ADDRESS, tls); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + @Test + void sslHandshakeWithCertificateMessageIsNotLabeled() { + SSLHandshakeException tls = new SSLHandshakeException("PKIX path building failed: certificate chain invalid"); + MongoSocketException e = new MongoSocketException("tls", ADDRESS, tls); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + @Test + void sslHandshakeWithoutConfigKeywordIsLabeled() { + SSLHandshakeException tls = new SSLHandshakeException("remote host closed connection during handshake"); + MongoSocketException e = new MongoSocketException("tls", ADDRESS, tls); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertHasBackpressureLabels(e); + } + + @Test + void authErrorIsNotLabeled() { + MongoCredential credential = MongoCredential.createCredential("user", "db", "pwd".toCharArray()); + MongoSecurityException e = new MongoSecurityException(credential, "auth failed"); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + @Test + void commandExceptionIsNotLabeled() { + MongoException e = new MongoException(42, "some command error"); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + @Test + void nonMongoThrowableIsNotLabeled() { + IOException e = new IOException("raw"); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + } + + @Test + void sdamIssueTlsCheckDelegatesToHelper() { + MongoSocketException tlsException = new MongoSocketException("tls", ADDRESS, new CertificateException("bad cert")); + assertTrue(BackpressureErrorLabeler.isTlsConfigurationError(tlsException)); + + MongoSocketException plainException = new MongoSocketException("plain", ADDRESS); + assertFalse(BackpressureErrorLabeler.isTlsConfigurationError(plainException)); + + assertFalse(BackpressureErrorLabeler.isTlsConfigurationError(new MongoException(0, "not socket", new BsonDocument()))); + } + + private static void assertHasBackpressureLabels(final MongoException e) { + assertTrue(e.hasErrorLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL), + "expected SystemOverloadedError label"); + assertTrue(e.hasErrorLabel(MongoException.RETRYABLE_ERROR_LABEL), + "expected RetryableError label"); + } + + private static void assertLacksBackpressureLabels(final MongoException e) { + assertFalse(e.hasErrorLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL), + "unexpected SystemOverloadedError label"); + assertFalse(e.hasErrorLabel(MongoException.RETRYABLE_ERROR_LABEL), + "unexpected RetryableError label"); + } +} From b24f7ecaffdd028187d5223fd139600415d8f9da Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 30 Apr 2026 22:39:29 +0100 Subject: [PATCH 25/26] Update driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java Co-authored-by: Viacheslav Babanin --- .../mongodb/internal/connection/BackpressureErrorLabeler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java b/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java index b47d7484659..1a815b743cc 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java +++ b/driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java @@ -70,7 +70,7 @@ private static boolean isDnsLookupFailure(final Throwable t) { return false; } - static boolean isTlsConfigurationError(final Throwable t) { + private static boolean isTlsConfigurationError(final MongoSocketException t) { Throwable cause = t.getCause(); while (cause != null) { if (cause instanceof CertificateException From 819810eeefde5a1049d9bd79631d0397882fd627 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 30 Apr 2026 22:39:50 +0100 Subject: [PATCH 26/26] Update driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java Co-authored-by: Viacheslav Babanin --- .../connection/DefaultSdamServerDescriptionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java index b0b456e97fb..371e709d807 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java @@ -137,7 +137,7 @@ private void handleException(final SdamIssue sdamIssue, final boolean beforeHand serverMonitor.connect(); } else if (sdamIssue.relatedToNetworkNotTimeout() || (beforeHandshake && (sdamIssue.relatedToNetworkTimeout() || sdamIssue.relatedToAuth()))) { - if (sdamIssue.hasBackpressureLabel()) { + if (sdamIssue.hasSystemOverloadedLabel()) { return; } updateDescription(sdamIssue.serverDescription());