Skip to content

Commit 874fe5d

Browse files
committed
Disable retryable writes when no session is available for any reason.
The only known reason that this covers is when the driver is configured with multiple credentials (which is deprecated), in which case the server supports retryable writes but sessions are still unsupported due to the multiple credentials. JAVA-2963
1 parent e60f11c commit 874fe5d

File tree

5 files changed

+51
-4
lines changed

5 files changed

+51
-4
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ public R call(final ConnectionSource source, final Connection connection) {
466466
@Override
467467
public R call(final ConnectionSource source, final Connection connection) {
468468
try {
469-
if (!canRetryWrite(source.getServerDescription(), connection.getDescription())) {
469+
if (!canRetryWrite(source.getServerDescription(), connection.getDescription(), binding.getSessionContext())) {
470470
throw originalException;
471471
}
472472
return transformer.apply(connection.command(database, originalCommand, fieldNameValidator,
@@ -559,7 +559,8 @@ private void retryableCommand(final Throwable originalError) {
559559
public void call(final AsyncConnectionSource source, final AsyncConnection connection, final Throwable t) {
560560
if (t != null) {
561561
callback.onResult(null, originalError);
562-
} else if (!canRetryWrite(source.getServerDescription(), connection.getDescription())) {
562+
} else if (!canRetryWrite(source.getServerDescription(), connection.getDescription(),
563+
binding.getSessionContext())) {
563564
releasingCallback(callback, source, connection).onResult(null, originalError);
564565
} else {
565566
connection.commandAsync(database, command, fieldNameValidator, readPreference,

driver-core/src/main/com/mongodb/operation/OperationHelper.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,12 @@ static boolean isRetryableWrite(final boolean retryWrites, final WriteConcern wr
260260
LOGGER.debug("retryWrites set to true but in an active transaction.");
261261
return false;
262262
} else {
263-
return canRetryWrite(serverDescription, connectionDescription);
263+
return canRetryWrite(serverDescription, connectionDescription, sessionContext);
264264
}
265265
}
266266

267-
static boolean canRetryWrite(final ServerDescription serverDescription, final ConnectionDescription connectionDescription) {
267+
static boolean canRetryWrite(final ServerDescription serverDescription, final ConnectionDescription connectionDescription,
268+
final SessionContext sessionContext) {
268269
if (connectionDescription.getServerVersion().compareTo(new ServerVersion(3, 6)) < 0) {
269270
LOGGER.debug("retryWrites set to true but the server does not support retryable writes.");
270271
return false;
@@ -274,6 +275,10 @@ static boolean canRetryWrite(final ServerDescription serverDescription, final Co
274275
} else if (connectionDescription.getServerType().equals(ServerType.STANDALONE)) {
275276
LOGGER.debug("retryWrites set to true but the server is a standalone server.");
276277
return false;
278+
} else if (!sessionContext.hasSession()) {
279+
LOGGER.debug("retryWrites set to true but there is no implicit session, likely because the MongoClient was created with " +
280+
"multiple MongoCredential instances and sessions can only be used with a single MongoCredential");
281+
return false;
277282
}
278283
return true;
279284
}

driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,18 @@ class OperationFunctionalSpecification extends Specification {
264264
getReadConnectionSource() >> connectionSource
265265
getReadPreference() >> readPreference
266266
getSessionContext() >> Stub(SessionContext) {
267+
hasSession() >> true
267268
hasActiveTransaction() >> false
268269
getReadConcern() >> readConcern
269270
}
270271
}
271272
def writeBinding = Stub(WriteBinding) {
272273
getWriteConnectionSource() >> connectionSource
274+
getSessionContext() >> Stub(SessionContext) {
275+
hasSession() >> true
276+
hasActiveTransaction() >> false
277+
getReadConcern() >> readConcern
278+
}
273279
}
274280

275281
if (retryable) {
@@ -334,12 +340,18 @@ class OperationFunctionalSpecification extends Specification {
334340
getReadConnectionSource(_) >> { it[0].onResult(connectionSource, null) }
335341
getReadPreference() >> readPreference
336342
getSessionContext() >> Stub(SessionContext) {
343+
hasSession() >> true
337344
hasActiveTransaction() >> false
338345
getReadConcern() >> readConcern
339346
}
340347
}
341348
def writeBinding = Stub(AsyncWriteBinding) {
342349
getWriteConnectionSource(_) >> { it[0].onResult(connectionSource, null) }
350+
getSessionContext() >> Stub(SessionContext) {
351+
hasSession() >> true
352+
hasActiveTransaction() >> false
353+
getReadConcern() >> readConcern
354+
}
343355
}
344356
def callback = new FutureResultCallback()
345357

@@ -413,6 +425,11 @@ class OperationFunctionalSpecification extends Specification {
413425
}
414426
def writeBinding = Stub(WriteBinding) {
415427
getWriteConnectionSource() >> connectionSource
428+
getSessionContext() >> Stub(SessionContext) {
429+
hasSession() >> true
430+
hasActiveTransaction() >> false
431+
getReadConcern() >> ReadConcern.DEFAULT
432+
}
416433
}
417434

418435
1 * connection.command(*_) >> { throw exception }
@@ -452,6 +469,11 @@ class OperationFunctionalSpecification extends Specification {
452469

453470
def writeBinding = Stub(AsyncWriteBinding) {
454471
getWriteConnectionSource(_) >> { it[0].onResult(connectionSource, null) }
472+
getSessionContext() >> Stub(SessionContext) {
473+
hasSession() >> true
474+
hasActiveTransaction() >> false
475+
getReadConcern() >> ReadConcern.DEFAULT
476+
}
455477
}
456478
def callback = new FutureResultCallback()
457479

driver-core/src/test/unit/com/mongodb/operation/CommandOperationHelperSpecification.groovy

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.mongodb.operation
1818

1919
import com.mongodb.MongoCommandException
2020
import com.mongodb.MongoWriteConcernException
21+
import com.mongodb.ReadConcern
2122
import com.mongodb.ReadPreference
2223
import com.mongodb.ServerAddress
2324
import com.mongodb.async.SingleResultCallback
@@ -34,6 +35,7 @@ import com.mongodb.connection.ServerDescription
3435
import com.mongodb.connection.ServerType
3536
import com.mongodb.connection.ServerVersion
3637
import com.mongodb.internal.validator.NoOpFieldNameValidator
38+
import com.mongodb.session.SessionContext
3739
import org.bson.BsonBoolean
3840
import org.bson.BsonDocument
3941
import org.bson.BsonInt32
@@ -160,6 +162,11 @@ class CommandOperationHelperSpecification extends Specification {
160162
}
161163
def writeBinding = Stub(WriteBinding) {
162164
getWriteConnectionSource() >> connectionSource
165+
getSessionContext() >> Stub(SessionContext) {
166+
hasSession() >> true
167+
hasActiveTransaction() >> false
168+
getReadConcern() >> ReadConcern.DEFAULT
169+
}
163170
}
164171

165172
when:
@@ -208,6 +215,11 @@ class CommandOperationHelperSpecification extends Specification {
208215
}
209216
def asyncWriteBinding = Stub(AsyncWriteBinding) {
210217
getWriteConnectionSource(_) >> { it[0].onResult(connectionSource, null) }
218+
getSessionContext() >> Stub(SessionContext) {
219+
hasSession() >> true
220+
hasActiveTransaction() >> false
221+
getReadConcern() >> ReadConcern.DEFAULT
222+
}
211223
}
212224

213225
when:

driver-core/src/test/unit/com/mongodb/operation/OperationHelperSpecification.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,15 +390,22 @@ class OperationHelperSpecification extends Specification {
390390
def 'should check if a valid retryable write'() {
391391
given:
392392
def activeTransactionSessionContext = Stub(SessionContext) {
393+
hasSession() >> true
393394
hasActiveTransaction() >> true
394395
}
395396
def noTransactionSessionContext = Stub(SessionContext) {
397+
hasSession() >> true
398+
hasActiveTransaction() >> false
399+
}
400+
def noOpSessionContext = Stub(SessionContext) {
401+
hasSession() >> false
396402
hasActiveTransaction() >> false
397403
}
398404

399405
expect:
400406
isRetryableWrite(retryWrites, writeConcern, serverDescription, connectionDescription, noTransactionSessionContext) == expected
401407
!isRetryableWrite(retryWrites, writeConcern, serverDescription, connectionDescription, activeTransactionSessionContext)
408+
!isRetryableWrite(retryWrites, writeConcern, serverDescription, connectionDescription, noOpSessionContext)
402409

403410
where:
404411
retryWrites | writeConcern | serverDescription | connectionDescription | expected

0 commit comments

Comments
 (0)