Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions .evergreen/.evg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1740,6 +1740,10 @@ axes:
display_name: "8.0"
variables:
VERSION: "8.0"
- id: "9.0"
display_name: "9.0"
variables:
VERSION: "9.0"
# 8.2 is used solely for Windows testing. MongoDB 8.0 binaries are affected by SERVER-116018 on Windows,
# and the fix is only available starting from 8.2.
- id: "8.2"
Expand Down Expand Up @@ -2341,7 +2345,7 @@ buildvariants:
#

- matrix_name: "tests-zlib-compression"
matrix_spec: { compressor: "zlib", auth: "noauth", ssl: "nossl", jdk: "jdk8", version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "latest" ], topology: "standalone", os: "linux" }
matrix_spec: { compressor: "zlib", auth: "noauth", ssl: "nossl", jdk: "jdk8", version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ], topology: "standalone", os: "linux" }
display_name: "${version} ${compressor} ${topology} ${auth} ${ssl} ${jdk} ${os} "
tags: [ "tests-variant" ]
tasks:
Expand All @@ -2351,7 +2355,7 @@ buildvariants:
- name: "test-legacy-task"

- matrix_name: "tests-snappy-compression"
matrix_spec: { compressor: "snappy", auth: "noauth", ssl: "nossl", jdk: "jdk8", version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "latest" ], topology: "standalone", os: "linux" }
matrix_spec: { compressor: "snappy", auth: "noauth", ssl: "nossl", jdk: "jdk8", version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ], topology: "standalone", os: "linux" }
display_name: "${version} ${compressor} ${topology} ${auth} ${ssl} ${jdk} ${os} "
tags: [ "tests-variant" ]
tasks:
Expand All @@ -2362,7 +2366,7 @@ buildvariants:

- matrix_name: "tests-zstd-compression"
matrix_spec: { compressor: "zstd", auth: "noauth", ssl: "nossl", jdk: "jdk8",
version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "latest" ],
version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ],
topology: "standalone", os: "linux" }
display_name: "${version} ${compressor} ${topology} ${auth} ${ssl} ${jdk} ${os} "
tags: [ "tests-variant" ]
Expand All @@ -2380,7 +2384,7 @@ buildvariants:
- name: "test-bson-and-crypt-task"

- matrix_name: "tests-jdk8-unsecure"
matrix_spec: { auth: "noauth", ssl: "nossl", jdk: "jdk8", version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "latest" ],
matrix_spec: { auth: "noauth", ssl: "nossl", jdk: "jdk8", version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ],
topology: "*", os: "linux" }
display_name: "${version} ${topology} ${auth} ${ssl} ${jdk} ${os} "
tags: [ "tests-variant" ]
Expand All @@ -2392,7 +2396,7 @@ buildvariants:

- matrix_name: "tests-jdk-secure"
matrix_spec: { auth: "auth", ssl: "ssl", jdk: [ "jdk8", "jdk17", "jdk21" ],
version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "latest" ],
version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ],
topology: "*", os: "linux" }
display_name: "${version} ${topology} ${auth} ${ssl} ${jdk} ${os} "
tags: [ "tests-variant" ]
Expand Down Expand Up @@ -2429,7 +2433,7 @@ buildvariants:
exec_timeout_secs: 7200

- matrix_name: "tests-require-api-version"
matrix_spec: { api-version: "required", auth: "auth", ssl: "nossl", jdk: [ "jdk21" ], version: [ "5.0", "6.0", "7.0", "8.0", "latest" ],
matrix_spec: { api-version: "required", auth: "auth", ssl: "nossl", jdk: [ "jdk21" ], version: [ "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ],
topology: "standalone", os: "linux" }
display_name: "${version} ${topology} ${api-version} "
tags: [ "tests-variant" ]
Expand All @@ -2440,7 +2444,7 @@ buildvariants:
- name: "test-legacy-task"

- matrix_name: "tests-load-balancer-secure"
matrix_spec: { auth: "auth", ssl: "ssl", jdk: [ "jdk21" ], version: [ "5.0", "6.0", "7.0", "8.0", "latest" ], topology: "sharded-cluster",
matrix_spec: { auth: "auth", ssl: "ssl", jdk: [ "jdk21" ], version: [ "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ], topology: "sharded-cluster",
os: "ubuntu" }
display_name: "Load Balancer ${version} ${auth} ${ssl} ${jdk} ${os}"
tasks:
Expand Down Expand Up @@ -2500,7 +2504,7 @@ buildvariants:
- name: "gssapi-auth-test-task"

- matrix_name: "aws-auth-test"
matrix_spec: { ssl: "nossl", jdk: [ "jdk8", "jdk17", "jdk21" ], version: [ "4.4", "5.0", "6.0", "7.0", "8.0", "latest" ], os: "ubuntu",
matrix_spec: { ssl: "nossl", jdk: [ "jdk8", "jdk17", "jdk21" ], version: [ "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ], os: "ubuntu",
aws-credential-provider: "*" }
display_name: "MONGODB-AWS Basic Auth test ${version} ${jdk} ${aws-credential-provider}"
run_on: "ubuntu2204-small"
Expand All @@ -2519,15 +2523,15 @@ buildvariants:
- name: "aws-auth-test-with-web-identity-credentials-task"

- matrix_name: "accept-api-version-2-test"
matrix_spec: { ssl: "nossl", auth: "noauth", jdk: "jdk21", version: [ "5.0", "6.0", "7.0", "8.0", "latest" ], topology: "standalone",
matrix_spec: { ssl: "nossl", auth: "noauth", jdk: "jdk21", version: [ "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ], topology: "standalone",
os: "linux" }
display_name: "Accept API Version 2 ${version}"
run_on: "ubuntu2204-small"
tasks:
- name: "accept-api-version-2-test-task"

- matrix_name: "ocsp-test"
matrix_spec: { auth: "noauth", ssl: "ssl", jdk: "jdk21", version: [ "4.4", "5.0", "6.0", "7.0", "8.0", "latest" ], os: "ubuntu" }
matrix_spec: { auth: "noauth", ssl: "ssl", jdk: "jdk21", version: [ "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ], os: "ubuntu" }
display_name: "OCSP test ${version} ${os}"
tasks:
- name: ".ocsp"
Expand Down Expand Up @@ -2560,14 +2564,14 @@ buildvariants:
- name: ".csfle-aws-from-environment"

- matrix_name: "csfle-tests-with-mongocryptd"
matrix_spec: { os: "linux", version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "latest" ], topology: [ "replicaset" ] }
matrix_spec: { os: "linux", version: [ "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "latest" ], topology: [ "replicaset" ] }
display_name: "CSFLE with mongocryptd: ${version}"
tasks:
- name: "csfle-tests-with-mongocryptd-task"

- matrix_name: "csfle-tests-with-mongocryptd-windows"
matrix_spec: { os: "windows",
version: [ "8.0", "latest" ],
version: [ "8.0", "9.0", "latest" ],
topology: [ "replicaset" ] }
display_name: "${os} CSFLE with mongocryptd: ${version}"
tasks:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private InternalConnectionInitializationDescription createInitializationDescript
private BsonDocument createHelloCommand(final Authenticator authenticator, final InternalConnection connection) {
BsonDocument helloCommandDocument = new BsonDocument(getHandshakeCommandName(), new BsonInt32(1))
.append("helloOk", BsonBoolean.TRUE)
.append("backpressure", BsonBoolean.TRUE);
.append("backpressure", new BsonString("2"));
if (clientMetadataDocument != null) {
helloCommandDocument.append("client", clientMetadataDocument);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.mongodb.internal.operation;

import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCommandException;
import com.mongodb.MongoConnectionPoolClearedException;
import com.mongodb.MongoException;
import com.mongodb.MongoOperationTimeoutException;
Expand All @@ -33,6 +34,9 @@
import com.mongodb.internal.time.ExponentialBackoff;
import com.mongodb.lang.Nullable;

import org.bson.BsonDocument;
import org.bson.BsonValue;

import java.time.Duration;
import java.util.EnumMap;
import java.util.EnumSet;
Expand Down Expand Up @@ -66,6 +70,7 @@
*/
final class SpecRetryPolicy implements RetryPolicy {
private static final int INFINITE_ATTEMPTS = Integer.MAX_VALUE;
private static final String BASE_BACKOFF_MS_FIELD = "baseBackoffMS";

private final IndividualPolicies policies;
private int maxAttempts;
Expand Down Expand Up @@ -285,11 +290,28 @@ private static Duration calculateOverloadBackoff(final Throwable attemptFailedRe
assertFalse(attemptFailedResult instanceof OperationHelper.ResourceSupplierInternalException);
if (attemptFailedResult instanceof MongoException
&& ((MongoException) attemptFailedResult).hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL)) {
return ExponentialBackoff.calculateOverloadBackoff(immediateNextAttempt);
return ExponentialBackoff.calculateOverloadBackoff(immediateNextAttempt, extractBaseBackoffMs(attemptFailedResult));
}
return Duration.ZERO;
}

@Nullable
private static Long extractBaseBackoffMs(final Throwable attemptFailedResult) {
if (!(attemptFailedResult instanceof MongoCommandException)) {
return null;
}
BsonDocument response = ((MongoCommandException) attemptFailedResult).getResponse();
if (!response.containsKey(BASE_BACKOFF_MS_FIELD)) {
return null;
}
BsonValue value = response.get(BASE_BACKOFF_MS_FIELD);
if (!value.isNumber()) {
return null;
}
long parsed = value.asNumber().longValue();
return parsed > 0 ? parsed : null;
}

private static int maxAttempts(final int maxRetries) {
if (maxRetries == INFINITE_ATTEMPTS) {
return INFINITE_ATTEMPTS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.function.RetryControl;
import com.mongodb.lang.Nullable;

import java.time.Duration;
import java.util.concurrent.ThreadLocalRandom;
Expand Down Expand Up @@ -58,7 +59,20 @@ public static long calculateTransactionBackoffMs(final int attemptNumber) {
* See {@link #calculateTransactionBackoffMs(int)} for more details.
*/
public static Duration calculateOverloadBackoff(final int attemptNumber) {
return Duration.ofMillis(calculateBackoffMs(100, 10000, 2, attemptNumber));
return calculateOverloadBackoff(attemptNumber, null);
}

/**
* Calculate the backoff for command retries caused by
* {@linkplain com.mongodb.MongoException#SYSTEM_OVERLOADED_ERROR_LABEL overload},
* optionally using a server-supplied {@code baseBackoffMs} in place of the 100ms default.
* A {@code null} or non-positive {@code baseBackoffMs} falls back to the default.
*
* @param baseBackoffMs The server-supplied base backoff in milliseconds, or {@code null}.
*/
public static Duration calculateOverloadBackoff(final int attemptNumber, @Nullable final Long baseBackoffMs) {
double baseMs = baseBackoffMs != null && baseBackoffMs > 0 ? baseBackoffMs : 100;
return Duration.ofMillis(calculateBackoffMs(baseMs, 10000, 2, attemptNumber));
}

/**
Expand All @@ -70,7 +84,7 @@ private static long calculateBackoffMs(final double baseMs, final double maxMs,
? testJitterSupplier.getAsDouble()
: ThreadLocalRandom.current().nextDouble();
return Math.round(jitter * Math.min(
baseMs * Math.pow(growth, attemptNumber - 1),
baseMs * Math.pow(growth, attemptNumber),
maxMs));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification {
def initializer = new InternalStreamConnectionInitializer(SINGLE, null, clientMetadataDocument, [], null)
def expectedHelloCommandDocument = new BsonDocument(LEGACY_HELLO, new BsonInt32(1))
.append('helloOk', BsonBoolean.TRUE)
.append('backpressure', BsonBoolean.TRUE)
.append('backpressure', new BsonString('2'))
.append('\$db', new BsonString('admin'))
if (clientMetadataDocument != null) {
expectedHelloCommandDocument.append('client', clientMetadataDocument)
Expand Down Expand Up @@ -234,7 +234,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification {
def initializer = new InternalStreamConnectionInitializer(SINGLE, null, null, compressors, null)
def expectedHelloCommandDocument = new BsonDocument(LEGACY_HELLO, new BsonInt32(1))
.append('helloOk', BsonBoolean.TRUE)
.append('backpressure', BsonBoolean.TRUE)
.append('backpressure', new BsonString('2'))
.append('\$db', new BsonString('admin'))

def compressionArray = new BsonArray()
Expand Down Expand Up @@ -405,7 +405,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification {
((SpeculativeAuthenticator) authenticator).getSpeculativeAuthenticateResponse() == null
((SpeculativeAuthenticator) authenticator)
.createSpeculativeAuthenticateCommand(internalConnection) == null
BsonDocument.parse("{$LEGACY_HELLO: 1, helloOk: true, backpressure: true, '\$db': 'admin'}") ==
BsonDocument.parse("{$LEGACY_HELLO: 1, helloOk: true, backpressure: \"2\", '\$db': 'admin'}") ==
decodeCommand(internalConnection.getSent()[0])

where:
Expand Down Expand Up @@ -503,7 +503,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification {

def createHelloCommand(final String firstClientChallenge, final String mechanism,
final boolean hasSaslSupportedMechs) {
String hello = "{$LEGACY_HELLO: 1, helloOk: true, backpressure: true, " +
String hello = "{$LEGACY_HELLO: 1, helloOk: true, backpressure: \"2\", " +
(hasSaslSupportedMechs ? 'saslSupportedMechs: "database.user", ' : '') +
(mechanism == 'MONGODB-X509' ?
'speculativeAuthenticate: { authenticate: 1, ' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class ExponentialBackoffTest {
* Expected {@linkplain ExponentialBackoff#calculateTransactionBackoffMs(int) backoffs} with 1.0 as
* {@link ExponentialBackoff#setTestJitterSupplier(DoubleSupplier) jitter}.
*/
private static final double[] EXPECTED_BACKOFFS_MAX_VALUES = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125,
192.21679688, 288.32519531, 432.48779297, 500.0};
private static final double[] EXPECTED_BACKOFFS_MAX_VALUES = {7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125,
192.21679688, 288.32519531, 432.48779297, 500.0, 500.0};

@Test
void testCalculateTransactionBackoffMs() {
Expand Down Expand Up @@ -79,4 +79,52 @@ void testCalculateTransactionBackoffMsWithJitterZero() {
ExponentialBackoff.clearTestJitterSupplier();
}
}

@Test
void testCalculateOverloadBackoffUsesDefaultBaseWhenOverrideAbsent() {
ExponentialBackoff.setTestJitterSupplier(() -> 1.0);
try {
// 100 * 2^1 = 200, 100 * 2^2 = 400
assertEquals(200L, ExponentialBackoff.calculateOverloadBackoff(1).toMillis());
assertEquals(400L, ExponentialBackoff.calculateOverloadBackoff(2).toMillis());
// null override == default
assertEquals(200L, ExponentialBackoff.calculateOverloadBackoff(1, null).toMillis());
} finally {
ExponentialBackoff.clearTestJitterSupplier();
}
}

@Test
void testCalculateOverloadBackoffUsesServerBaseBackoffMsWhenPositive() {
ExponentialBackoff.setTestJitterSupplier(() -> 1.0);
try {
// 50 * 2^1 = 100, 50 * 2^2 = 200
assertEquals(100L, ExponentialBackoff.calculateOverloadBackoff(1, 50L).toMillis());
assertEquals(200L, ExponentialBackoff.calculateOverloadBackoff(2, 50L).toMillis());
} finally {
ExponentialBackoff.clearTestJitterSupplier();
}
}

@Test
void testCalculateOverloadBackoffIgnoresNonPositiveOverride() {
ExponentialBackoff.setTestJitterSupplier(() -> 1.0);
try {
assertEquals(200L, ExponentialBackoff.calculateOverloadBackoff(1, 0L).toMillis());
assertEquals(200L, ExponentialBackoff.calculateOverloadBackoff(1, -10L).toMillis());
} finally {
ExponentialBackoff.clearTestJitterSupplier();
}
}

@Test
void testCalculateOverloadBackoffRespectsMaxCapWithLargeBaseBackoffMs() {
ExponentialBackoff.setTestJitterSupplier(() -> 1.0);
try {
// base 20000 * 2^1 = 40000 -> capped at 10000
assertEquals(10000L, ExponentialBackoff.calculateOverloadBackoff(1, 20000L).toMillis());
} finally {
ExponentialBackoff.clearTestJitterSupplier();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import com.mongodb.internal.connection.TestCommandListener;
import com.mongodb.internal.connection.TestConnectionPoolListener;
import com.mongodb.lang.Nullable;
import org.bson.BsonBoolean;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -335,9 +335,9 @@ void testEmptyStringsAreConsideredUnsetWhenAppendingMetadataIdenticalToInitialMe
}
}

@DisplayName("Test 9: Handshake documents include backpressure: true")
@DisplayName("Test 9: Handshake documents include backpressure: \"2\"")
@Test
void testHandshakeDocumentsIncludeBackpressureTrue() {
void testHandshakeDocumentsIncludeBackpressureTwo() {
try (MongoClient mongoClient = createMongoClient(null, getMongoClientSettings())) {
commandListener.reset();
mongoClient.getDatabase("admin").runCommand(BsonDocument.parse("{ping: 1}"));
Expand All @@ -346,10 +346,8 @@ void testHandshakeDocumentsIncludeBackpressureTrue() {
assertFalse(handshakeEvents.isEmpty(), "Expected at least one handshake document to be captured");
for (CommandStartedEvent event : handshakeEvents) {
BsonDocument helloCommand = event.getCommand();
assertTrue(helloCommand.containsKey("backpressure"),
"Handshake document is missing 'backpressure' field");
assertEquals(BsonBoolean.TRUE, helloCommand.getBoolean("backpressure"),
"Handshake document 'backpressure' field is not true");
assertEquals(new BsonString("2"), helloCommand.get("backpressure"),
"Handshake document 'backpressure' field is not the string \"2\"");
}
}
}
Expand Down
Loading