Skip to content

Commit ae3f140

Browse files
committed
Restore changes lost after rebase
- Add SYSTEM_OVERLOADED_ERROR_LABEL and RETRYABLE_ERROR_LABEL constants to MongoException - Add backpressure:true to hello command in InternalStreamConnectionInitializer - Make CommandOperationHelper and its error label constants public - Replace hardcoded error label strings with constants in tests and examples - Refactor ExponentialBackoff: make TRANSACTION_BASE_MS and TRANSACTION_GROWTH private, split testCustomJitter into two tests, minor Javadoc/assertion message fixes - Remove redundant private constructor from TimeoutContext - Convert block comments to Javadoc in WithTransactionProseTest, refactor testRetryBackoffIsEnforced
1 parent 69630b0 commit ae3f140

12 files changed

Lines changed: 103 additions & 79 deletions

driver-core/src/main/com/mongodb/MongoException.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class MongoException extends RuntimeException {
3939
*
4040
* @see #hasErrorLabel(String)
4141
* @since 3.8
42+
* @mongodb.driver.manual core/transactions-in-applications/#std-label-transient-transaction-error
4243
*/
4344
public static final String TRANSIENT_TRANSACTION_ERROR_LABEL = "TransientTransactionError";
4445

@@ -47,9 +48,32 @@ public class MongoException extends RuntimeException {
4748
*
4849
* @see #hasErrorLabel(String)
4950
* @since 3.8
51+
* @mongodb.driver.manual core/transactions-in-applications/#std-label-unknown-transaction-commit-result
5052
*/
5153
public static final String UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL = "UnknownTransactionCommitResult";
5254

55+
/**
56+
* Server is overloaded and shedding load.
57+
* If you retry, use exponential backoff because the server has indicated overload.
58+
* This label on its own does not mean that the operation can be safely retried.
59+
*
60+
* @see #hasErrorLabel(String)
61+
* @since 5.7
62+
* @mongodb.server.release 8.3
63+
*/
64+
// 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
65+
public static final String SYSTEM_OVERLOADED_ERROR_LABEL = "SystemOverloadedError";
66+
67+
/**
68+
* The operation was not executed and is safe to retry.
69+
*
70+
* @see #hasErrorLabel(String)
71+
* @since 5.7
72+
* @mongodb.server.release 8.3
73+
*/
74+
// 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
75+
public static final String RETRYABLE_ERROR_LABEL = "RetryableError";
76+
5377
private static final long serialVersionUID = -4415279469780082174L;
5478

5579
private final int code;

driver-core/src/main/com/mongodb/internal/TimeoutContext.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,6 @@ public TimeoutContext(final TimeoutSettings timeoutSettings) {
109109
this(false, timeoutSettings, startTimeout(timeoutSettings.getTimeoutMS()));
110110
}
111111

112-
private TimeoutContext(final TimeoutSettings timeoutSettings, @Nullable final Timeout timeout) {
113-
this(false, timeoutSettings, timeout);
114-
}
115-
116112
private TimeoutContext(final boolean isMaintenanceContext,
117113
final TimeoutSettings timeoutSettings,
118114
@Nullable final Timeout timeout) {

driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ private InternalConnectionInitializationDescription createInitializationDescript
172172

173173
private BsonDocument createHelloCommand(final Authenticator authenticator, final InternalConnection connection) {
174174
BsonDocument helloCommandDocument = new BsonDocument(getHandshakeCommandName(), new BsonInt32(1))
175-
.append("helloOk", BsonBoolean.TRUE);
175+
.append("helloOk", BsonBoolean.TRUE)
176+
.append("backpressure", BsonBoolean.TRUE);
176177
if (clientMetadataDocument != null) {
177178
helloCommandDocument.append("client", clientMetadataDocument);
178179
}

driver-core/src/main/com/mongodb/internal/operation/CommandOperationHelper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
import static java.util.Arrays.asList;
5252

5353
@SuppressWarnings("overloads")
54-
final class CommandOperationHelper {
54+
public final class CommandOperationHelper {
5555
static WriteConcern validateAndGetEffectiveWriteConcern(final WriteConcern writeConcernSetting, final SessionContext sessionContext)
5656
throws MongoClientException {
5757
boolean activeTransaction = sessionContext.hasActiveTransaction();
@@ -223,8 +223,8 @@ static boolean isRetryWritesEnabled(@Nullable final BsonDocument command) {
223223
|| command.getFirstKey().equals("commitTransaction") || command.getFirstKey().equals("abortTransaction")));
224224
}
225225

226-
static final String RETRYABLE_WRITE_ERROR_LABEL = "RetryableWriteError";
227-
private static final String NO_WRITES_PERFORMED_ERROR_LABEL = "NoWritesPerformed";
226+
public static final String RETRYABLE_WRITE_ERROR_LABEL = "RetryableWriteError";
227+
public static final String NO_WRITES_PERFORMED_ERROR_LABEL = "NoWritesPerformed";
228228

229229
private static boolean decideRetryableAndAddRetryableWriteErrorLabel(final Throwable t, @Nullable final Integer maxWireVersion) {
230230
if (!(t instanceof MongoException)) {

driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,10 @@
2929
*/
3030
public final class ExponentialBackoff {
3131

32-
// Constants for transaction retry backoff
33-
@VisibleForTesting(otherwise = PRIVATE)
34-
static final double TRANSACTION_BASE_MS = 5.0;
32+
private static final double TRANSACTION_BASE_MS = 5.0;
3533
@VisibleForTesting(otherwise = PRIVATE)
3634
static final double TRANSACTION_MAX_MS = 500.0;
37-
@VisibleForTesting(otherwise = PRIVATE)
38-
static final double TRANSACTION_GROWTH = 1.5;
35+
private static final double TRANSACTION_GROWTH = 1.5;
3936

4037
// TODO-JAVA-6079
4138
private static DoubleSupplier testJitterSupplier = null;
@@ -46,11 +43,11 @@ private ExponentialBackoff() {
4643
/**
4744
* Calculate the backoff in milliseconds for transaction retries.
4845
*
49-
* @param attemptNumber 0-based attempt number
46+
* @param attemptNumber attempt number > 0
5047
* @return The calculated backoff in milliseconds.
5148
*/
5249
public static long calculateTransactionBackoffMs(final int attemptNumber) {
53-
assertTrue(attemptNumber > 0, "Attempt number must be greater than 0 in the context of transaction backoff calculation");
50+
assertTrue(attemptNumber > 0, "Attempt number must be at least 1 (1-based) in the context of transaction backoff calculation");
5451
double jitter = testJitterSupplier != null
5552
? testJitterSupplier.getAsDouble()
5653
: ThreadLocalRandom.current().nextDouble();
@@ -62,7 +59,7 @@ public static long calculateTransactionBackoffMs(final int attemptNumber) {
6259
/**
6360
* Set a custom jitter supplier for testing purposes.
6461
*
65-
* @param supplier A DoubleSupplier that returns values in [0, 1) range.
62+
* @param supplier A DoubleSupplier that returns values in [0, 1] range.
6663
*/
6764
@VisibleForTesting(otherwise = PRIVATE)
6865
public static void setTestJitterSupplier(final DoubleSupplier supplier) {

driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionInitializerSpecification.groovy

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification {
201201
def initializer = new InternalStreamConnectionInitializer(SINGLE, null, clientMetadataDocument, [], null)
202202
def expectedHelloCommandDocument = new BsonDocument(LEGACY_HELLO, new BsonInt32(1))
203203
.append('helloOk', BsonBoolean.TRUE)
204+
.append('backpressure', BsonBoolean.TRUE)
204205
.append('\$db', new BsonString('admin'))
205206
if (clientMetadataDocument != null) {
206207
expectedHelloCommandDocument.append('client', clientMetadataDocument)
@@ -233,6 +234,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification {
233234
def initializer = new InternalStreamConnectionInitializer(SINGLE, null, null, compressors, null)
234235
def expectedHelloCommandDocument = new BsonDocument(LEGACY_HELLO, new BsonInt32(1))
235236
.append('helloOk', BsonBoolean.TRUE)
237+
.append('backpressure', BsonBoolean.TRUE)
236238
.append('\$db', new BsonString('admin'))
237239

238240
def compressionArray = new BsonArray()
@@ -403,7 +405,8 @@ class InternalStreamConnectionInitializerSpecification extends Specification {
403405
((SpeculativeAuthenticator) authenticator).getSpeculativeAuthenticateResponse() == null
404406
((SpeculativeAuthenticator) authenticator)
405407
.createSpeculativeAuthenticateCommand(internalConnection) == null
406-
BsonDocument.parse("{$LEGACY_HELLO: 1, helloOk: true, '\$db': 'admin'}") == decodeCommand(internalConnection.getSent()[0])
408+
BsonDocument.parse("{$LEGACY_HELLO: 1, helloOk: true, backpressure: true, '\$db': 'admin'}") ==
409+
decodeCommand(internalConnection.getSent()[0])
407410

408411
where:
409412
async << [true, false]
@@ -500,7 +503,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification {
500503

501504
def createHelloCommand(final String firstClientChallenge, final String mechanism,
502505
final boolean hasSaslSupportedMechs) {
503-
String hello = "{$LEGACY_HELLO: 1, helloOk: true, " +
506+
String hello = "{$LEGACY_HELLO: 1, helloOk: true, backpressure: true, " +
504507
(hasSaslSupportedMechs ? 'saslSupportedMechs: "database.user", ' : '') +
505508
(mechanism == 'MONGODB-X509' ?
506509
'speculativeAuthenticate: { authenticate: 1, ' +

driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,17 @@ class ExponentialBackoffTest {
3333

3434
@Test
3535
void testCalculateTransactionBackoffMs() {
36-
// Test that the backoff sequence follows the expected pattern with growth factor ExponentialBackoff.TRANSACTION_GROWTH
37-
// Expected sequence (without jitter): 5, 7.5, 11.25, ...
38-
// With jitter, actual values will be between 0 and these maxima
39-
4036
for (int attemptNumber = 1; attemptNumber <= EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) {
4137
long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber);
4238
long expectedBackoff = Math.round(EXPECTED_BACKOFFS_MAX_VALUES[attemptNumber - 1]);
43-
assertTrue(backoff >= 0 && backoff <= expectedBackoff,
44-
String.format("Attempt %d: backoff should be between 0 ms and %d ms, got: %d", attemptNumber,
45-
expectedBackoff, backoff));
39+
assertTrue(backoff >= 0 && backoff <= expectedBackoff,
40+
String.format("Attempt %d: backoff should be between 0 ms and %d ms, got: %d", attemptNumber,
41+
expectedBackoff, backoff));
4642
}
4743
}
4844

4945
@Test
5046
void testCalculateTransactionBackoffMsRespectsMaximum() {
51-
// Even at high attempt numbers, backoff should never exceed ExponentialBackoff.TRANSACTION_MAX_MS
5247
for (int attemptNumber = 1; attemptNumber < EXPECTED_BACKOFFS_MAX_VALUES.length * 2; attemptNumber++) {
5348
long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber);
5449
assertTrue(backoff >= 0 && backoff <= ExponentialBackoff.TRANSACTION_MAX_MS,
@@ -58,24 +53,25 @@ void testCalculateTransactionBackoffMsRespectsMaximum() {
5853
}
5954

6055
@Test
61-
void testCustomJitter() {
62-
// Test with jitter = 1.0
56+
void testCustomJitterWithOne() {
6357
ExponentialBackoff.setTestJitterSupplier(() -> 1.0);
6458
try {
6559
for (int attemptNumber = 1; attemptNumber <= EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) {
6660
long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber);
6761
long expected = Math.round(EXPECTED_BACKOFFS_MAX_VALUES[attemptNumber - 1]);
6862
assertEquals(expected, backoff,
69-
String.format("Attempt %d: with jitter=1.0, backoff should be %d ms", attemptNumber, expected));
63+
String.format("Attempt %d: with jitter=1.0, backoff should be %d ms", attemptNumber, expected));
7064
}
7165
} finally {
7266
ExponentialBackoff.clearTestJitterSupplier();
7367
}
68+
}
7469

75-
// Test with jitter = 0, all backoffs should be 0
70+
@Test
71+
void testCustomJitterWithZero() {
7672
ExponentialBackoff.setTestJitterSupplier(() -> 0.0);
7773
try {
78-
for (int attemptNumber = 1; attemptNumber < EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) {
74+
for (int attemptNumber = 1; attemptNumber <= EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) {
7975
long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber);
8076
assertEquals(0, backoff, "With jitter=0, backoff should always be 0 ms");
8177
}

driver-sync/src/examples/documentation/TransactionExample.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ private void runTransactionWithRetry(final Runnable transactional) {
7777
System.out.println("Transaction aborted. Caught exception during transaction.");
7878

7979
if (e.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)) {
80-
System.out.println("TransientTransactionError, aborting transaction and retrying ...");
80+
System.out.printf("%s, aborting transaction and retrying ...%n",
81+
MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL);
8182
} else {
8283
throw e;
8384
}
@@ -94,7 +95,8 @@ private void commitWithRetry(final ClientSession clientSession) {
9495
} catch (MongoException e) {
9596
// can retry commit
9697
if (e.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
97-
System.out.println("UnknownTransactionCommitResult, retrying commit operation ...");
98+
System.out.printf("%s, retrying commit operation ...%n",
99+
MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
98100
} else {
99101
System.out.println("Exception during commit ...");
100102
throw e;

driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import static com.mongodb.ClusterFixture.isStandalone;
8585
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
8686
import static com.mongodb.ClusterFixture.sleep;
87+
import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL;
8788
import static com.mongodb.client.Fixture.getDefaultDatabaseName;
8889
import static com.mongodb.client.Fixture.getPrimary;
8990
import static com.mongodb.internal.connection.CommandHelper.HELLO;
@@ -685,7 +686,7 @@ public void test10CustomTestWithTransactionUsesASingleTimeoutWithLock() {
685686
+ " blockConnection: true,"
686687
+ " blockTimeMS: " + 25
687688
+ " errorCode: " + 24
688-
+ " errorLabels: [\"TransientTransactionError\"]"
689+
+ " errorLabels: [\"" + TRANSIENT_TRANSACTION_ERROR_LABEL + "\"]"
689690
+ " }"
690691
+ "}");
691692

driver-sync/src/test/functional/com/mongodb/client/MongoWriteConcernWithResponseExceptionTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
4444
import static com.mongodb.client.Fixture.getDefaultDatabaseName;
4545
import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder;
46+
import static com.mongodb.internal.operation.CommandOperationHelper.NO_WRITES_PERFORMED_ERROR_LABEL;
47+
import static com.mongodb.internal.operation.CommandOperationHelper.RETRYABLE_WRITE_ERROR_LABEL;
4648
import static java.util.Collections.singletonList;
4749
import static org.junit.Assert.assertThrows;
4850
import static org.junit.Assume.assumeTrue;
@@ -69,7 +71,7 @@ public static void doesNotLeak(final Function<MongoClientSettings, MongoClient>
6971
.append("data", new BsonDocument()
7072
.append("writeConcernError", new BsonDocument()
7173
.append("code", new BsonInt32(91))
72-
.append("errorLabels", new BsonArray(Stream.of("RetryableWriteError")
74+
.append("errorLabels", new BsonArray(Stream.of(RETRYABLE_WRITE_ERROR_LABEL)
7375
.map(BsonString::new).collect(Collectors.toList())))
7476
.append("errmsg", new BsonString(""))
7577
)
@@ -81,7 +83,7 @@ public static void doesNotLeak(final Function<MongoClientSettings, MongoClient>
8183
.append("data", new BsonDocument()
8284
.append("failCommands", new BsonArray(singletonList(new BsonString("insert"))))
8385
.append("errorCode", new BsonInt32(10107))
84-
.append("errorLabels", new BsonArray(Stream.of("RetryableWriteError", "NoWritesPerformed")
86+
.append("errorLabels", new BsonArray(Stream.of(RETRYABLE_WRITE_ERROR_LABEL, NO_WRITES_PERFORMED_ERROR_LABEL)
8587
.map(BsonString::new).collect(Collectors.toList()))));
8688
doesNotLeak(clientCreator, writeConcernErrorFpDoc, true, noWritesPerformedFpDoc);
8789
doesNotLeak(clientCreator, noWritesPerformedFpDoc, false, writeConcernErrorFpDoc);

0 commit comments

Comments
 (0)