diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java
index 36ab59d469..c588695f7c 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 c1b3c4a069..d4a06c07d8 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 71352abee6..06c2c9b935 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 8aa0b7d5a9..4be89f4d9a 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 c40ea5a0ab..e0803bee6e 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 d4b59f0cb5..57995d2651 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 3c1a7aad39..9ac2bbe7d4 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 ed8f6fa955..5d6b5e0e1e 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 edf1babd02..e1abb2c42f 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 fe7b827d36..c269a810d2 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 5d129bbd07..e7ce89566d 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 723dddcc28..a386cd7f68 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 fb2509554a..3de1f77b6d 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 ef18c2c6b1..35ff27f79e 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 38ef09a477..fe1bcdfb97 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 bbeb7419bc..9ba2139f18 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 920feb1f98..b5604a7a84 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 fde4675c63..4c6c536fac 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 c49d1a8b4f..87e8b53335 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 c75a425559..34f46e7b00 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)
}
}