diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 3a88f90de8c..31b81beebcb 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -122,7 +122,7 @@ - + diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index 3d92d0fb6d7..151845d59ba 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -15,9 +15,13 @@ --> + + + + + + + + + + @@ -295,5 +309,22 @@ - + + + + + + + + + + + + + + diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonArrayEncodingBenchmark.java b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonArrayEncodingBenchmark.java index 0768f4f63c6..f0a59967f0a 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonArrayEncodingBenchmark.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonArrayEncodingBenchmark.java @@ -17,7 +17,8 @@ package com.mongodb.benchmark.benchmarks; -import org.bson.BsonArray;import org.bson.BsonDocument; +import org.bson.BsonArray; +import org.bson.BsonDocument; import org.bson.RawBsonDocument; import org.bson.codecs.BsonDocumentCodec; @@ -52,4 +53,4 @@ public void setUp() throws IOException { public int getBytesPerRun() { return documentBytes.length * NUM_INTERNAL_ITERATIONS; } -} \ No newline at end of file +} diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index 659e8fd02aa..c588695f7ca 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -17,6 +17,7 @@ package com.mongodb; import com.mongodb.annotations.Alpha; +import com.mongodb.annotations.Beta; import com.mongodb.annotations.Reason; import com.mongodb.connection.ClusterSettings; import com.mongodb.connection.ConnectionPoolSettings; @@ -264,14 +265,20 @@ *

SRV configuration:

* *

General configuration:

* *

For internal use only

* @return the timeout to use - * @since 5.2 */ @Nullable + @Internal TimeoutContext getTimeoutContext(); + + /** + * For internal use only. + * + * @return The {@link ClientSession}-scoped state of the overload retry policy. + */ + @Internal + Object getOverloadRetryPolicyState(); } diff --git a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java index d4a6334095b..b6fa32fae37 100644 --- a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java +++ b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java @@ -66,6 +66,7 @@ import com.mongodb.internal.operation.DropDatabaseOperation; import com.mongodb.internal.operation.ReadOperation; import com.mongodb.internal.operation.WriteOperation; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.lang.Nullable; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; @@ -74,9 +75,7 @@ import org.bson.BsonInt32; import org.bson.BsonString; import org.bson.BsonValue; -import org.bson.Document; import org.bson.codecs.BsonDocumentCodec; -import org.bson.codecs.DocumentCodec; import javax.net.ssl.SSLException; import java.time.Duration; @@ -145,7 +144,6 @@ public final class ClusterFixture { private static Cluster asyncCluster; private static final Map BINDING_MAP = new HashMap<>(); private static final Map SESSION_CONTEXT_MAP = new HashMap<>(); - private static final Map ASYNC_SESSION_CONTEXT_MAP = new HashMap<>(); private static final Map ASYNC_BINDING_MAP = new HashMap<>(); private static ServerVersion mongoCryptVersion; @@ -193,19 +191,21 @@ public static ServerVersion getServerVersion() { if (serverVersion == null) { serverVersion = getVersion(new CommandReadOperation<>("admin", new BsonDocument("buildInfo", new BsonInt32(1)), new BsonDocumentCodec()) - .execute(new ClusterBinding(getCluster(), ReadPreference.nearest()), OPERATION_CONTEXT)); + .execute(new ClusterBinding(getCluster(), ReadPreference.nearest()), createOperationContext())); } return serverVersion; } - public static final OperationContext OPERATION_CONTEXT = new OperationContext( - IgnorableRequestContext.INSTANCE, - new ReadConcernAwareNoOpSessionContext(ReadConcern.DEFAULT), - new TimeoutContext(TIMEOUT_SETTINGS), - getServerApi()); + public static OperationContext createOperationContext() { + return new OperationContext( + IgnorableRequestContext.INSTANCE, + new ReadConcernAwareNoOpSessionContext(ReadConcern.DEFAULT), + new TimeoutContext(TIMEOUT_SETTINGS), + getServerApi()); + } public static final InternalOperationContextFactory OPERATION_CONTEXT_FACTORY = - new InternalOperationContextFactory(TIMEOUT_SETTINGS, getServerApi()); + new InternalOperationContextFactory(TIMEOUT_SETTINGS, getServerApi(), AsyncClientExecutor.NO_OP); public static OperationContext createOperationContext(final TimeoutSettings timeoutSettings) { return new OperationContext( @@ -252,25 +252,12 @@ public static boolean hasEncryptionTestsEnabled() { .count() == requiredSystemProperties.size(); } - public static Document getServerStatus() { - return new CommandReadOperation<>("admin", new BsonDocument("serverStatus", new BsonInt32(1)), - new DocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); - } - - public static boolean supportsFsync() { - Document serverStatus = getServerStatus(); - Document storageEngine = (Document) serverStatus.get("storageEngine"); - - return storageEngine != null && !storageEngine.get("name").equals("inMemory"); - } - static class ShutdownHook extends Thread { @Override public void run() { if (cluster != null) { try { - new DropDatabaseOperation(getDefaultDatabaseName(), WriteConcern.ACKNOWLEDGED).execute(getBinding(), OPERATION_CONTEXT); + new DropDatabaseOperation(getDefaultDatabaseName(), WriteConcern.ACKNOWLEDGED).execute(getBinding(), createOperationContext()); } catch (MongoCommandException e) { // if we do not have permission to drop the database, assume it is cleaned up in some other way if (!e.getMessage().contains("Command dropDatabase requires authentication")) { @@ -322,7 +309,7 @@ public static synchronized ConnectionString getConnectionString() { try { BsonDocument helloResult = new CommandReadOperation<>("admin", new BsonDocument(LEGACY_HELLO, new BsonInt32(1)), new BsonDocumentCodec()) - .execute(new ClusterBinding(cluster, ReadPreference.nearest()), OPERATION_CONTEXT); + .execute(new ClusterBinding(cluster, ReadPreference.nearest()), createOperationContext()); if (helloResult.containsKey("setName")) { connectionString = new ConnectionString(DEFAULT_URI + "/?replicaSet=" + helloResult.getString("setName").getValue()); @@ -388,7 +375,7 @@ private static ReadWriteBinding getBinding(final Cluster cluster, } public static SingleConnectionBinding getSingleConnectionBinding() { - return new SingleConnectionBinding(getCluster(), ReadPreference.primary(), OPERATION_CONTEXT); + return new SingleConnectionBinding(getCluster(), ReadPreference.primary(), createOperationContext()); } public static AsyncSingleConnectionBinding getAsyncSingleConnectionBinding() { @@ -396,7 +383,7 @@ public static AsyncSingleConnectionBinding getAsyncSingleConnectionBinding() { } public static AsyncSingleConnectionBinding getAsyncSingleConnectionBinding(final Cluster cluster) { - return new AsyncSingleConnectionBinding(cluster, ReadPreference.primary(), OPERATION_CONTEXT); + return new AsyncSingleConnectionBinding(cluster, ReadPreference.primary(), createOperationContext()); } public static AsyncReadWriteBinding getAsyncBinding(final Cluster cluster) { @@ -413,7 +400,6 @@ public static AsyncReadWriteBinding getAsyncBinding( if (!ASYNC_BINDING_MAP.containsKey(readPreference)) { AsyncReadWriteBinding binding = new AsyncClusterBinding(cluster, readPreference); ASYNC_BINDING_MAP.put(readPreference, binding); - ASYNC_SESSION_CONTEXT_MAP.put(readPreference, new SimpleSessionContext()); } return ASYNC_BINDING_MAP.get(readPreference); } @@ -449,7 +435,7 @@ private static Cluster createCluster(final MongoCredential credential, final Str return new DefaultClusterFactory().createCluster(ClusterSettings.builder().hosts(asList(getPrimary())).build(), ServerSettings.builder().build(), ConnectionPoolSettings.builder().maxSize(1).build(), InternalConnectionPoolSettings.builder().build(), - TIMEOUT_SETTINGS.connectionOnly(), streamFactory, TIMEOUT_SETTINGS.connectionOnly(), streamFactory, credential, + TIMEOUT_SETTINGS.connectionOnly(), streamFactory, TIMEOUT_SETTINGS.connectionOnly(), streamFactory, AsyncClientExecutor.NO_OP, credential, LoggerSettings.builder().build(), null, null, null, Collections.emptyList(), getServerApi(), null); } @@ -461,7 +447,7 @@ private static Cluster createCluster(final ConnectionString connectionString, fi InternalConnectionPoolSettings.builder().build(), TimeoutSettings.create(mongoClientSettings).connectionOnly(), streamFactory, TimeoutSettings.createHeartbeatSettings(mongoClientSettings).connectionOnly(), new SocketStreamFactory(new DefaultInetAddressResolver(), SocketSettings.builder().readTimeout(5, SECONDS).build(), - getSslSettings(connectionString)), + getSslSettings(connectionString)), AsyncClientExecutor.NO_OP, connectionString.getCredential(), LoggerSettings.builder().build(), null, null, null, connectionString.getCompressorList(), getServerApi(), null); @@ -575,7 +561,7 @@ public static BsonDocument getServerParameters() { if (serverParameters == null) { serverParameters = new CommandReadOperation<>("admin", new BsonDocument("getParameter", new BsonString("*")), new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), createOperationContext()); } return serverParameters; } @@ -643,7 +629,7 @@ public static void configureFailPoint(final BsonDocument failPointDocument) { if (!isSharded()) { try { new CommandReadOperation<>("admin", failPointDocument, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), createOperationContext()); } catch (MongoCommandException e) { if (e.getErrorCode() == COMMAND_NOT_FOUND_ERROR_CODE) { failsPointsSupported = false; @@ -659,7 +645,7 @@ public static void disableFailPoint(final String failPoint) { .append("mode", new BsonString("off")); try { new CommandReadOperation<>("admin", failPointDocument, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), createOperationContext()); } catch (MongoCommandException e) { // ignore } @@ -673,7 +659,7 @@ public static T executeSync(final WriteOperation op) { @SuppressWarnings("overloads") public static T executeSync(final WriteOperation op, final ReadWriteBinding binding) { - return op.execute(binding, applySessionContext(OPERATION_CONTEXT, binding.getReadPreference())); + return op.execute(binding, applySessionContext(createOperationContext(), binding.getReadPreference())); } @SuppressWarnings("overloads") @@ -683,7 +669,7 @@ public static T executeSync(final ReadOperation op) { @SuppressWarnings("overloads") public static T executeSync(final ReadOperation op, final ReadWriteBinding binding) { - return op.execute(binding, applySessionContext(OPERATION_CONTEXT, binding.getReadPreference())); + return op.execute(binding, applySessionContext(createOperationContext(), binding.getReadPreference())); } @SuppressWarnings("overloads") @@ -699,7 +685,7 @@ public static T executeAsync(final WriteOperation op) throws Throwable { @SuppressWarnings("overloads") public static T executeAsync(final WriteOperation op, final AsyncReadWriteBinding binding) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - op.executeAsync(binding, applySessionContext(OPERATION_CONTEXT, binding.getReadPreference()), futureResultCallback); + op.executeAsync(binding, applySessionContext(createOperationContext(), binding.getReadPreference()), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } @@ -711,7 +697,7 @@ public static T executeAsync(final ReadOperation op) throws Throwable @SuppressWarnings("overloads") public static T executeAsync(final ReadOperation op, final AsyncReadBinding binding) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - op.executeAsync(binding, OPERATION_CONTEXT, futureResultCallback); + op.executeAsync(binding, createOperationContext(), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } @@ -721,7 +707,7 @@ public static T executeAsync(final ReadOperation op, final AsyncReadBi return futureResultCallback.get(TIMEOUT, SECONDS); } - public static void loopCursor(final List> batchCursors, final Block block) throws Throwable { + public static void loopCursor(final List> batchCursors, final Block block) { List> futures = new ArrayList<>(); for (AsyncBatchCursor batchCursor : batchCursors) { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); @@ -763,7 +749,7 @@ public static void loopCursor(final AsyncBatchCursor batchCursor, final B }); } - public static List collectCursorResults(final AsyncBatchCursor batchCursor) throws Throwable { + public static List collectCursorResults(final AsyncBatchCursor batchCursor) { List results = new ArrayList<>(); FutureResultCallback futureResultCallback = new FutureResultCallback<>(); loopCursor(batchCursor, t -> results.add(t), futureResultCallback); @@ -781,19 +767,19 @@ public static List collectCursorResults(final BatchCursor batchCursor) public static AsyncConnectionSource getWriteConnectionSource(final AsyncReadWriteBinding binding) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - binding.getWriteConnectionSource(OPERATION_CONTEXT, futureResultCallback); + binding.getWriteConnectionSource(createOperationContext(), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } public static AsyncConnectionSource getReadConnectionSource(final AsyncReadWriteBinding binding) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - binding.getReadConnectionSource(OPERATION_CONTEXT, futureResultCallback); + binding.getReadConnectionSource(createOperationContext(), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } public static AsyncConnection getConnection(final AsyncConnectionSource source) throws Throwable { FutureResultCallback futureResultCallback = new FutureResultCallback<>(); - source.getConnection(OPERATION_CONTEXT, futureResultCallback); + source.getConnection(createOperationContext(), futureResultCallback); return futureResultCallback.get(TIMEOUT, SECONDS); } @@ -836,8 +822,8 @@ private static OperationContext applySessionContext(final OperationContext opera return operationContext.withSessionContext(simpleSessionContext); } - public static OperationContext getOperationContext(final ReadPreference readPreference) { - return applySessionContext(OPERATION_CONTEXT, readPreference); + public static OperationContext createOperationContext(final ReadPreference readPreference) { + return applySessionContext(createOperationContext(), readPreference); } public static boolean isWindows() { diff --git a/driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy b/driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy index 6648edc50c7..5a8d65d51ad 100644 --- a/driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/OperationFunctionalSpecification.groovy @@ -61,7 +61,7 @@ import spock.lang.Specification import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.TIMEOUT import static com.mongodb.ClusterFixture.checkReferenceCountReachesTarget import static com.mongodb.ClusterFixture.executeAsync @@ -108,13 +108,13 @@ class OperationFunctionalSpecification extends Specification { void acknowledgeWrite(final SingleConnectionBinding binding) { new MixedBulkWriteOperation(getNamespace(), [new InsertRequest(new BsonDocument())], true, - ACKNOWLEDGED, false).execute(binding, OPERATION_CONTEXT) + ACKNOWLEDGED, false, null).execute(binding, createOperationContext()) binding.release() } void acknowledgeWrite(final AsyncSingleConnectionBinding binding) { executeAsync(new MixedBulkWriteOperation(getNamespace(), [new InsertRequest(new BsonDocument())], - true, ACKNOWLEDGED, false), binding) + true, ACKNOWLEDGED, false, null), binding) binding.release() } @@ -234,24 +234,26 @@ class OperationFunctionalSpecification extends Specification { void testOperation(operation, List serverVersion, BsonDocument expectedCommand, boolean async, result = null, boolean checkCommand = true, boolean checkSecondaryOk = false, ReadPreference readPreference = ReadPreference.primary(), boolean retryable = false, - ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false) { + ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false, + int expectedConnectionReleaseCountPerAttempt = 1) { testOperation(operation, serverVersion, ReadConcern.DEFAULT, expectedCommand, async, result, checkCommand, checkSecondaryOk, - readPreference, retryable, serverType, activeTransaction) + readPreference, retryable, serverType, activeTransaction, expectedConnectionReleaseCountPerAttempt) } void testOperation(operation, List serverVersion, ReadConcern readConcern, BsonDocument expectedCommand, boolean async, result = null, boolean checkCommand = true, boolean checkSecondaryOk = false, ReadPreference readPreference = ReadPreference.primary(), boolean retryable = false, - ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false) { + ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false, + int expectedConnectionReleaseCountPerAttempt = 1) { def test = async ? this.&testAsyncOperation : this.&testSyncOperation test(operation, serverVersion, readConcern, result, checkCommand, expectedCommand, checkSecondaryOk, readPreference, retryable, - serverType, activeTransaction) + serverType, activeTransaction, expectedConnectionReleaseCountPerAttempt) } void testOperationRetries(operation, List serverVersion, BsonDocument expectedCommand, boolean async, result = null, - Boolean activeTransaction = false) { + Boolean activeTransaction = false, int expectedConnectionReleaseCountPerAttempt = 1) { testOperation(operation, serverVersion, expectedCommand, async, result, true, false, ReadPreference.primary(), true, - ServerType.REPLICA_SET_PRIMARY, activeTransaction) + ServerType.REPLICA_SET_PRIMARY, activeTransaction, expectedConnectionReleaseCountPerAttempt) } void testRetryableOperationThrowsOriginalError(operation, List> serverVersions, List serverTypes, @@ -278,8 +280,9 @@ class OperationFunctionalSpecification extends Specification { def testSyncOperation(operation, List serverVersion, ReadConcern readConcern, result, Boolean checkCommand=true, BsonDocument expectedCommand=null, Boolean checkSecondaryOk=false, ReadPreference readPreference=ReadPreference.primary(), Boolean retryable = false, - ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false) { - def operationContext = OPERATION_CONTEXT + ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false, + int expectedConnectionReleaseCountPerAttempt = 1) { + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasActiveTransaction() >> activeTransaction getReadConcern() >> readConcern @@ -338,9 +341,9 @@ class OperationFunctionalSpecification extends Specification { } if (retryable) { - 2 * connection.release() + (2 * expectedConnectionReleaseCountPerAttempt) * connection.release() } else { - 1 * connection.release() + expectedConnectionReleaseCountPerAttempt * connection.release() } if (operation instanceof ReadOperation) { operation.execute(readBinding, operationContext) @@ -352,8 +355,9 @@ class OperationFunctionalSpecification extends Specification { def testAsyncOperation(operation = operation, List serverVersion = serverVersion, ReadConcern readConcern, result = null, Boolean checkCommand = true, BsonDocument expectedCommand = null, Boolean checkSecondaryOk = false, ReadPreference readPreference = ReadPreference.primary(), Boolean retryable = false, - ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false) { - def operationContext = OPERATION_CONTEXT + ServerType serverType = ServerType.STANDALONE, Boolean activeTransaction = false, + int expectedConnectionReleaseCountPerAttempt = 1) { + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasActiveTransaction() >> activeTransaction getReadConcern() >> readConcern @@ -417,9 +421,9 @@ class OperationFunctionalSpecification extends Specification { } if (retryable) { - 2 * connection.release() + (2 * expectedConnectionReleaseCountPerAttempt) * connection.release() } else { - 1 * connection.release() + expectedConnectionReleaseCountPerAttempt * connection.release() } if (operation instanceof ReadOperation) { @@ -447,7 +451,7 @@ class OperationFunctionalSpecification extends Specification { } } - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { hasSession() >> true hasActiveTransaction() >> false @@ -488,7 +492,7 @@ class OperationFunctionalSpecification extends Specification { } } - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { hasSession() >> true hasActiveTransaction() >> false diff --git a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java index 935c2979fc4..a54ed99b5db 100644 --- a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java +++ b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java @@ -16,6 +16,7 @@ package com.mongodb.client.test; +import com.mongodb.ClusterFixture; import com.mongodb.MongoClientSettings; import com.mongodb.MongoCommandException; import com.mongodb.MongoNamespace; @@ -72,7 +73,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.executeAsync; import static com.mongodb.ClusterFixture.getBinding; import static java.lang.String.format; @@ -93,7 +93,7 @@ public CollectionHelper(final Codec codec, final MongoNamespace namespace) { public T hello() { return new CommandReadOperation<>("admin", BsonDocument.parse("{isMaster: 1}"), codec) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public static void drop(final MongoNamespace namespace) { @@ -106,7 +106,7 @@ public static void drop(final MongoNamespace namespace, final WriteConcern write boolean success = false; while (!success) { try { - new DropCollectionOperation(namespace, writeConcern).execute(getBinding(), OPERATION_CONTEXT); + new DropCollectionOperation(namespace, writeConcern).execute(getBinding(), ClusterFixture.createOperationContext()); success = true; } catch (MongoWriteConcernException e) { LOGGER.info("Retrying drop collection after a write concern error: " + e); @@ -131,7 +131,7 @@ public static void dropDatabase(final String name, final WriteConcern writeConce return; } try { - new DropDatabaseOperation(name, writeConcern).execute(getBinding(), OPERATION_CONTEXT); + new DropDatabaseOperation(name, writeConcern).execute(getBinding(), ClusterFixture.createOperationContext()); } catch (MongoCommandException e) { if (!e.getErrorMessage().contains("ns not found")) { throw e; @@ -141,7 +141,7 @@ public static void dropDatabase(final String name, final WriteConcern writeConce public static BsonDocument getCurrentClusterTime() { return new CommandReadOperation("admin", new BsonDocument("ping", new BsonInt32(1)), new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT).getDocument("$clusterTime", null); + .execute(getBinding(), ClusterFixture.createOperationContext()).getDocument("$clusterTime", null); } public MongoNamespace getNamespace() { @@ -235,7 +235,7 @@ public void create(final String collectionName, final CreateCollectionOptions op boolean success = false; while (!success) { try { - operation.execute(getBinding(), OPERATION_CONTEXT); + operation.execute(getBinding(), ClusterFixture.createOperationContext()); success = true; } catch (MongoCommandException e) { if ("Interrupted".equals(e.getErrorCodeName())) { @@ -254,7 +254,7 @@ public void killCursor(final MongoNamespace namespace, final ServerCursor server .append("cursors", new BsonArray(singletonList(new BsonInt64(serverCursor.getId())))); try { new CommandReadOperation<>(namespace.getDatabaseName(), command, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } catch (Exception e) { // Ignore any exceptions killing old cursors } @@ -286,7 +286,8 @@ public void insertDocuments(final List documents, final WriteConce for (BsonDocument document : documents) { insertRequests.add(new InsertRequest(document)); } - new MixedBulkWriteOperation(namespace, insertRequests, true, writeConcern, false).execute(binding, OPERATION_CONTEXT); + new MixedBulkWriteOperation(namespace, insertRequests, true, writeConcern, false, null).execute( + binding, ClusterFixture.createOperationContext()); } public void insertDocuments(final Document... documents) { @@ -328,8 +329,8 @@ public List find() { public Optional listSearchIndex(final String indexName) { ListSearchIndexesOperation listSearchIndexesOperation = - new ListSearchIndexesOperation<>(namespace, codec, indexName, null, null, null, null, true); - BatchCursor cursor = listSearchIndexesOperation.execute(getBinding(), OPERATION_CONTEXT); + new ListSearchIndexesOperation<>(namespace, codec, indexName, null, null, null, null, true, null); + BatchCursor cursor = listSearchIndexesOperation.execute(getBinding(), ClusterFixture.createOperationContext()); List results = new ArrayList<>(); while (cursor.hasNext()) { @@ -342,13 +343,14 @@ public Optional listSearchIndex(final String indexName) { public void createSearchIndex(final SearchIndexRequest searchIndexModel) { CreateSearchIndexesOperation searchIndexesOperation = new CreateSearchIndexesOperation(namespace, singletonList(searchIndexModel)); - searchIndexesOperation.execute(getBinding(), OPERATION_CONTEXT); + searchIndexesOperation.execute(getBinding(), ClusterFixture.createOperationContext()); } public List find(final Codec codec) { - BatchCursor cursor = new FindOperation<>(namespace, codec) + BatchCursor cursor = new FindOperation<>(namespace, codec, + null) .sort(new BsonDocument("_id", new BsonInt32(1))) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); List results = new ArrayList<>(); while (cursor.hasNext()) { results.addAll(cursor.next()); @@ -366,8 +368,8 @@ public void updateOne(final Bson filter, final Bson update, final boolean isUpse update.toBsonDocument(Document.class, registry), WriteRequest.Type.UPDATE) .upsert(isUpsert)), - true, WriteConcern.ACKNOWLEDGED, false) - .execute(getBinding(), OPERATION_CONTEXT); + true, WriteConcern.ACKNOWLEDGED, false, null) + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void replaceOne(final Bson filter, final Bson update, final boolean isUpsert) { @@ -376,8 +378,8 @@ public void replaceOne(final Bson filter, final Bson update, final boolean isUps update.toBsonDocument(Document.class, registry), WriteRequest.Type.REPLACE) .upsert(isUpsert)), - true, WriteConcern.ACKNOWLEDGED, false) - .execute(getBinding(), OPERATION_CONTEXT); + true, WriteConcern.ACKNOWLEDGED, false, null) + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void deleteOne(final Bson filter) { @@ -391,8 +393,8 @@ public void deleteMany(final Bson filter) { private void delete(final Bson filter, final boolean multi) { new MixedBulkWriteOperation(namespace, singletonList(new DeleteRequest(filter.toBsonDocument(Document.class, registry)).multi(multi)), - true, WriteConcern.ACKNOWLEDGED, false) - .execute(getBinding(), OPERATION_CONTEXT); + true, WriteConcern.ACKNOWLEDGED, false, null) + .execute(getBinding(), ClusterFixture.createOperationContext()); } public List find(final Bson filter) { @@ -416,8 +418,8 @@ private List aggregate(final List pipeline, final Decoder decode for (Bson cur : pipeline) { bsonDocumentPipeline.add(cur.toBsonDocument(Document.class, registry)); } - BatchCursor cursor = new AggregateOperation<>(namespace, bsonDocumentPipeline, decoder, level) - .execute(getBinding(), OPERATION_CONTEXT); + BatchCursor cursor = new AggregateOperation<>(namespace, bsonDocumentPipeline, decoder, level, null) + .execute(getBinding(), ClusterFixture.createOperationContext()); List results = new ArrayList<>(); while (cursor.hasNext()) { results.addAll(cursor.next()); @@ -451,8 +453,8 @@ public List find(final BsonDocument filter, final BsonDocument sort, fina } public List find(final BsonDocument filter, final BsonDocument sort, final BsonDocument projection, final Decoder decoder) { - BatchCursor cursor = new FindOperation<>(namespace, decoder).filter(filter).sort(sort) - .projection(projection).execute(getBinding(), OPERATION_CONTEXT); + BatchCursor cursor = new FindOperation<>(namespace, decoder, null).filter(filter).sort(sort) + .projection(projection).execute(getBinding(), ClusterFixture.createOperationContext()); List results = new ArrayList<>(); while (cursor.hasNext()) { results.addAll(cursor.next()); @@ -465,16 +467,16 @@ public long count() { } public long count(final ReadBinding binding) { - return new CountDocumentsOperation(namespace).execute(binding, OPERATION_CONTEXT); + return new CountDocumentsOperation(namespace, null).execute(binding, ClusterFixture.createOperationContext()); } public long count(final AsyncReadWriteBinding binding) throws Throwable { - return executeAsync(new CountDocumentsOperation(namespace), binding); + return executeAsync(new CountDocumentsOperation(namespace, null), binding); } public long count(final Bson filter) { - return new CountDocumentsOperation(namespace) - .filter(toBsonDocument(filter)).execute(getBinding(), OPERATION_CONTEXT); + return new CountDocumentsOperation(namespace, null) + .filter(toBsonDocument(filter)).execute(getBinding(), ClusterFixture.createOperationContext()); } public BsonDocument wrap(final Document document) { @@ -487,36 +489,36 @@ public BsonDocument toBsonDocument(final Bson document) { public void createIndex(final BsonDocument key) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(key)), WriteConcern.ACKNOWLEDGED) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void createIndex(final Document key) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(wrap(key))), WriteConcern.ACKNOWLEDGED) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void createUniqueIndex(final Document key) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(wrap(key)).unique(true)), WriteConcern.ACKNOWLEDGED) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void createIndex(final Document key, final String defaultLanguage) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(wrap(key)).defaultLanguage(defaultLanguage)), WriteConcern.ACKNOWLEDGED).execute( - getBinding(), OPERATION_CONTEXT); + getBinding(), ClusterFixture.createOperationContext()); } public void createIndex(final Bson key) { new CreateIndexesOperation(namespace, singletonList(new IndexRequest(key.toBsonDocument(Document.class, registry))), WriteConcern.ACKNOWLEDGED).execute( - getBinding(), OPERATION_CONTEXT); + getBinding(), ClusterFixture.createOperationContext()); } public List listIndexes(){ List indexes = new ArrayList<>(); - BatchCursor cursor = new ListIndexesOperation<>(namespace, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + BatchCursor cursor = new ListIndexesOperation<>(namespace, new BsonDocumentCodec(), null) + .execute(getBinding(), ClusterFixture.createOperationContext()); while (cursor.hasNext()) { indexes.addAll(cursor.next()); } @@ -526,7 +528,7 @@ public List listIndexes(){ public static void killAllSessions() { try { new CommandReadOperation<>("admin", - new BsonDocument("killAllSessions", new BsonArray()), new BsonDocumentCodec()).execute(getBinding(), OPERATION_CONTEXT); + new BsonDocument("killAllSessions", new BsonArray()), new BsonDocumentCodec()).execute(getBinding(), ClusterFixture.createOperationContext()); } catch (MongoCommandException e) { // ignore exception caused by killing the implicit session that the killAllSessions command itself is running in } @@ -537,7 +539,7 @@ public void renameCollection(final MongoNamespace newNamespace) { new CommandReadOperation<>("admin", new BsonDocument("renameCollection", new BsonString(getNamespace().getFullName())) .append("to", new BsonString(newNamespace.getFullName())), new BsonDocumentCodec()).execute( - getBinding(), OPERATION_CONTEXT); + getBinding(), ClusterFixture.createOperationContext()); } catch (MongoCommandException e) { // do nothing } @@ -549,11 +551,11 @@ public void runAdminCommand(final String command) { public void runAdminCommand(final BsonDocument command) { new CommandReadOperation<>("admin", command, new BsonDocumentCodec()) - .execute(getBinding(), OPERATION_CONTEXT); + .execute(getBinding(), ClusterFixture.createOperationContext()); } public void runAdminCommand(final BsonDocument command, final ReadPreference readPreference) { new CommandReadOperation<>("admin", command, new BsonDocumentCodec()) - .execute(getBinding(readPreference), OPERATION_CONTEXT); + .execute(getBinding(readPreference), ClusterFixture.createOperationContext()); } } diff --git a/driver-core/src/test/functional/com/mongodb/connection/ConnectionSpecification.groovy b/driver-core/src/test/functional/com/mongodb/connection/ConnectionSpecification.groovy index 5658ec5ea43..5dd6145f063 100644 --- a/driver-core/src/test/functional/com/mongodb/connection/ConnectionSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/connection/ConnectionSpecification.groovy @@ -17,13 +17,14 @@ package com.mongodb.connection import com.mongodb.OperationFunctionalSpecification +import com.mongodb.internal.connection.OperationContext import com.mongodb.internal.operation.CommandReadOperation import org.bson.BsonDocument import org.bson.BsonInt32 import org.bson.codecs.BsonDocumentCodec import static com.mongodb.ClusterFixture.LEGACY_HELLO -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getBinding import static com.mongodb.connection.ConnectionDescription.getDefaultMaxMessageSize import static com.mongodb.connection.ConnectionDescription.getDefaultMaxWriteBatchSize @@ -32,8 +33,9 @@ class ConnectionSpecification extends OperationFunctionalSpecification { def 'should have id'() { when: - def source = getBinding().getReadConnectionSource(OPERATION_CONTEXT) - def connection = source.getConnection(OPERATION_CONTEXT) + def operationContext = createOperationContext() + def source = getBinding().getReadConnectionSource(operationContext) + def connection = source.getConnection(operationContext) then: connection.getDescription().getConnectionId() != null @@ -45,13 +47,14 @@ class ConnectionSpecification extends OperationFunctionalSpecification { def 'should have description'() { when: - def commandResult = getHelloResult() + def operationContext = createOperationContext() + def commandResult = getHelloResult(operationContext) def expectedMaxMessageSize = commandResult.getNumber('maxMessageSizeBytes', new BsonInt32(getDefaultMaxMessageSize())).intValue() def expectedMaxBatchCount = commandResult.getNumber('maxWriteBatchSize', new BsonInt32(getDefaultMaxWriteBatchSize())).intValue() - def source = getBinding().getReadConnectionSource(OPERATION_CONTEXT) - def connection = source.getConnection(OPERATION_CONTEXT) + def source = getBinding().getReadConnectionSource(operationContext) + def connection = source.getConnection(operationContext) then: connection.description.serverAddress == source.getServerDescription().getAddress() @@ -64,8 +67,8 @@ class ConnectionSpecification extends OperationFunctionalSpecification { connection?.release() source?.release() } - private static BsonDocument getHelloResult() { + private static BsonDocument getHelloResult(OperationContext operationContext) { new CommandReadOperation('admin', new BsonDocument(LEGACY_HELLO, new BsonInt32(1)), - new BsonDocumentCodec()).execute(getBinding(), OPERATION_CONTEXT) + new BsonDocumentCodec()).execute(getBinding(), operationContext) } } diff --git a/driver-core/src/test/functional/com/mongodb/connection/netty/NettyStreamSpecification.groovy b/driver-core/src/test/functional/com/mongodb/connection/netty/NettyStreamSpecification.groovy index e582e0fc398..8747b8c75b9 100644 --- a/driver-core/src/test/functional/com/mongodb/connection/netty/NettyStreamSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/connection/netty/NettyStreamSpecification.groovy @@ -18,7 +18,7 @@ import com.mongodb.spock.Slow import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getSslSettings class NettyStreamSpecification extends Specification { @@ -43,7 +43,7 @@ class NettyStreamSpecification extends Specification { def stream = factory.create(new ServerAddress()) when: - stream.open(OPERATION_CONTEXT) + stream.open(createOperationContext()) then: !stream.isClosed() @@ -69,7 +69,7 @@ class NettyStreamSpecification extends Specification { def stream = factory.create(new ServerAddress()) when: - stream.open(OPERATION_CONTEXT) + stream.open(createOperationContext()) then: thrown(MongoSocketOpenException) @@ -96,7 +96,7 @@ class NettyStreamSpecification extends Specification { def callback = new CallbackErrorHolder() when: - stream.openAsync(OPERATION_CONTEXT, callback) + stream.openAsync(createOperationContext(), callback) then: callback.getError().is(exception) diff --git a/driver-core/src/test/functional/com/mongodb/internal/binding/AsyncSessionBindingSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/binding/AsyncSessionBindingSpecification.groovy index 173cd9f0935..81ab2ead1e2 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/binding/AsyncSessionBindingSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/binding/AsyncSessionBindingSpecification.groovy @@ -16,17 +16,17 @@ package com.mongodb.internal.binding +import com.mongodb.ClusterFixture import com.mongodb.internal.async.SingleResultCallback import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT - class AsyncSessionBindingSpecification extends Specification { def 'should wrap the passed in async binding'() { given: def wrapped = Mock(AsyncReadWriteBinding) def binding = new AsyncSessionBinding(wrapped) + def operationContext = ClusterFixture.createOperationContext() when: binding.getCount() @@ -52,17 +52,18 @@ class AsyncSessionBindingSpecification extends Specification { then: 1 * wrapped.release() + when: - binding.getReadConnectionSource(OPERATION_CONTEXT, Stub(SingleResultCallback)) + binding.getReadConnectionSource(operationContext, Stub(SingleResultCallback)) then: - 1 * wrapped.getReadConnectionSource(OPERATION_CONTEXT, _) + 1 * wrapped.getReadConnectionSource(operationContext, _) when: - binding.getWriteConnectionSource(OPERATION_CONTEXT, Stub(SingleResultCallback)) + binding.getWriteConnectionSource(operationContext, Stub(SingleResultCallback)) then: - 1 * wrapped.getWriteConnectionSource(OPERATION_CONTEXT, _) + 1 * wrapped.getWriteConnectionSource(operationContext, _) } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/binding/SimpleSessionContext.java b/driver-core/src/test/functional/com/mongodb/internal/binding/SimpleSessionContext.java index ee258fb28cf..83b110840a7 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/binding/SimpleSessionContext.java +++ b/driver-core/src/test/functional/com/mongodb/internal/binding/SimpleSessionContext.java @@ -17,6 +17,7 @@ package com.mongodb.internal.binding; import com.mongodb.ReadConcern; +import com.mongodb.internal.session.BaseClientSessionImpl.OverloadRetryPolicyState; import com.mongodb.internal.session.SessionContext; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; @@ -138,6 +139,11 @@ public boolean isSessionMarkedDirty() { return false; } + @Override + public OverloadRetryPolicyState getOverloadRetryPolicyState() { + return OverloadRetryPolicyState.NO_OP; + } + private static BsonDocument createNewServerSessionIdentifier() { UuidCodec uuidCodec = new UuidCodec(UuidRepresentation.STANDARD); BsonDocument holder = new BsonDocument(); diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncSocketChannelStreamSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncSocketChannelStreamSpecification.groovy index 85f23350984..fa373e2e740 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncSocketChannelStreamSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncSocketChannelStreamSpecification.groovy @@ -13,7 +13,7 @@ import com.mongodb.spock.Slow import java.util.concurrent.CountDownLatch -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getSslSettings import static java.util.concurrent.TimeUnit.MILLISECONDS @@ -40,10 +40,13 @@ class AsyncSocketChannelStreamSpecification extends Specification { def stream = factory.create(new ServerAddress('host1')) when: - stream.open(OPERATION_CONTEXT) + stream.open(createOperationContext()) then: !stream.isClosed() + + cleanup: + factoryFactory.close() } @Slow @@ -66,10 +69,13 @@ class AsyncSocketChannelStreamSpecification extends Specification { def stream = factory.create(new ServerAddress()) when: - stream.open(OPERATION_CONTEXT) + stream.open(createOperationContext()) then: thrown(MongoSocketOpenException) + + cleanup: + factoryFactory.close() } @IgnoreIf({ getSslSettings().isEnabled() }) @@ -90,7 +96,7 @@ class AsyncSocketChannelStreamSpecification extends Specification { def callback = new CallbackErrorHolder() when: - stream.openAsync(OPERATION_CONTEXT, callback) + stream.openAsync(createOperationContext(), callback) then: callback.getError().is(exception) diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncStreamTimeoutsSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncStreamTimeoutsSpecification.groovy index 3589362b8ac..fc4cd8be576 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncStreamTimeoutsSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/AsyncStreamTimeoutsSpecification.groovy @@ -30,7 +30,7 @@ import com.mongodb.spock.Slow import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getCredentialWithCache import static com.mongodb.ClusterFixture.getServerApi import static com.mongodb.ClusterFixture.getSslSettings @@ -49,7 +49,7 @@ class AsyncStreamTimeoutsSpecification extends OperationFunctionalSpecification .create(new ServerId(new ClusterId(), new ServerAddress(new InetSocketAddress('192.168.255.255', 27017)))) when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: thrown(MongoSocketOpenException) @@ -63,7 +63,7 @@ class AsyncStreamTimeoutsSpecification extends OperationFunctionalSpecification new ServerAddress(new InetSocketAddress('192.168.255.255', 27017)))) when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: thrown(MongoSocketOpenException) diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/AwsAuthenticationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/AwsAuthenticationSpecification.groovy index 8dd53bc1c03..501be77d6d9 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/AwsAuthenticationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/AwsAuthenticationSpecification.groovy @@ -19,7 +19,7 @@ import spock.lang.Specification import java.util.function.Supplier import static com.mongodb.AuthenticationMechanism.MONGODB_AWS -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getConnectionString import static com.mongodb.ClusterFixture.getCredential @@ -52,7 +52,7 @@ class AwsAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoCommandException) @@ -71,7 +71,7 @@ class AwsAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -101,7 +101,7 @@ class AwsAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -160,10 +160,10 @@ class AwsAuthenticationSpecification extends Specification { private static void openConnection(final InternalConnection connection, final boolean async) { if (async) { FutureResultCallback futureResultCallback = new FutureResultCallback() - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get(ClusterFixture.TIMEOUT, SECONDS) } else { - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) } } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy index f1585f82595..ec5f3644549 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy @@ -31,7 +31,7 @@ import java.util.concurrent.CountDownLatch import static com.mongodb.ClusterFixture.CLIENT_METADATA import static com.mongodb.ClusterFixture.LEGACY_HELLO -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getCredentialWithCache import static com.mongodb.ClusterFixture.getPrimary @@ -48,7 +48,7 @@ class CommandHelperSpecification extends Specification { new NettyStreamFactory(SocketSettings.builder().build(), getSslSettings()), getCredentialWithCache(), CLIENT_METADATA, [], LoggerSettings.builder().build(), null, getServerApi()) .create(new ServerId(new ClusterId(), getPrimary())) - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) } def cleanup() { @@ -62,7 +62,7 @@ class CommandHelperSpecification extends Specification { Throwable receivedException = null def latch1 = new CountDownLatch(1) executeCommandAsync('admin', new BsonDocument(LEGACY_HELLO, new BsonInt32(1)), getClusterConnectionMode(), - getServerApi(), connection, OPERATION_CONTEXT) + getServerApi(), connection, createOperationContext()) { document, exception -> receivedDocument = document; receivedException = exception; latch1.countDown() } latch1.await() @@ -74,7 +74,7 @@ class CommandHelperSpecification extends Specification { when: def latch2 = new CountDownLatch(1) executeCommandAsync('admin', new BsonDocument('non-existent-command', new BsonInt32(1)), getClusterConnectionMode(), - getServerApi(), connection, OPERATION_CONTEXT) + getServerApi(), connection, createOperationContext()) { document, exception -> receivedDocument = document; receivedException = exception; latch2.countDown() } latch2.await() diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java index 81e778b4a61..5187f692459 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoConnectionPoolClearedException; import com.mongodb.MongoServerUnavailableException; import com.mongodb.ServerAddress; @@ -60,7 +61,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.ClusterFixture.createOperationContext; @@ -173,7 +173,7 @@ public void shouldThrowOnPoolClosed() { String expectedExceptionMessage = "The server at 127.0.0.1:27017 is no longer available"; MongoServerUnavailableException exception; - exception = assertThrows(MongoServerUnavailableException.class, () -> provider.get(OPERATION_CONTEXT)); + exception = assertThrows(MongoServerUnavailableException.class, () -> provider.get(ClusterFixture.createOperationContext())); assertEquals(expectedExceptionMessage, exception.getMessage()); SupplyingCallback supplyingCallback = new SupplyingCallback<>(); provider.getAsync(createOperationContext(TIMEOUT_SETTINGS.withMaxWaitTimeMS(50)), supplyingCallback); @@ -194,10 +194,10 @@ public void shouldExpireConnectionAfterMaxLifeTime() throws InterruptedException provider.ready(); // when - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); sleep(100); provider.doMaintenance(); - provider.get(OPERATION_CONTEXT); + provider.get(ClusterFixture.createOperationContext()); // then assertTrue(connectionFactory.getNumCreatedConnections() >= 2); // should really be two, but it's racy @@ -215,7 +215,7 @@ public void shouldExpireConnectionAfterLifeTimeOnClose() throws InterruptedExcep provider.ready(); // when - InternalConnection connection = provider.get(OPERATION_CONTEXT); + InternalConnection connection = provider.get(ClusterFixture.createOperationContext()); sleep(50); connection.close(); @@ -236,10 +236,10 @@ public void shouldExpireConnectionAfterMaxIdleTime() throws InterruptedException provider.ready(); // when - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); sleep(100); provider.doMaintenance(); - provider.get(OPERATION_CONTEXT); + provider.get(ClusterFixture.createOperationContext()); // then assertTrue(connectionFactory.getNumCreatedConnections() >= 2); // should really be two, but it's racy @@ -258,10 +258,10 @@ public void shouldCloseConnectionAfterExpiration() throws InterruptedException { provider.ready(); // when - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); sleep(50); provider.doMaintenance(); - provider.get(OPERATION_CONTEXT); + provider.get(ClusterFixture.createOperationContext()); // then assertTrue(connectionFactory.getCreatedConnections().get(0).isClosed()); @@ -280,10 +280,10 @@ public void shouldCreateNewConnectionAfterExpiration() throws InterruptedExcepti provider.ready(); // when - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); sleep(50); provider.doMaintenance(); - InternalConnection secondConnection = provider.get(OPERATION_CONTEXT); + InternalConnection secondConnection = provider.get(ClusterFixture.createOperationContext()); // then assertNotNull(secondConnection); @@ -302,7 +302,7 @@ public void shouldPruneAfterMaintenanceTaskRuns() throws InterruptedException { .build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY); provider.ready(); - provider.get(OPERATION_CONTEXT).close(); + provider.get(ClusterFixture.createOperationContext()).close(); // when @@ -322,7 +322,7 @@ void infiniteMaxSize() { List connections = new ArrayList<>(); try { for (int i = 0; i < 2 * defaultMaxSize; i++) { - connections.add(provider.get(OPERATION_CONTEXT)); + connections.add(provider.get(ClusterFixture.createOperationContext())); } } finally { connections.forEach(connection -> { diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticationSpecification.groovy index cc3e0401bb5..fa649022aaa 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticationSpecification.groovy @@ -36,7 +36,7 @@ import javax.security.auth.Subject import javax.security.auth.login.LoginContext import static com.mongodb.AuthenticationMechanism.GSSAPI -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getConnectionString import static com.mongodb.ClusterFixture.getCredential @@ -58,7 +58,7 @@ class GSSAPIAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoCommandException) @@ -77,7 +77,7 @@ class GSSAPIAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -99,7 +99,7 @@ class GSSAPIAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoSecurityException) @@ -131,7 +131,7 @@ class GSSAPIAuthenticationSpecification extends Specification { def connection = createConnection(async, getMongoCredential(subject)) openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -175,7 +175,7 @@ class GSSAPIAuthenticationSpecification extends Specification { def connection = createConnection(async, getMongoCredential(saslClientProperties)) openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -219,10 +219,10 @@ class GSSAPIAuthenticationSpecification extends Specification { private static void openConnection(final InternalConnection connection, final boolean async) { if (async) { FutureResultCallback futureResultCallback = new FutureResultCallback() - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get(ClusterFixture.TIMEOUT, SECONDS) } else { - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) } } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticatorSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticatorSpecification.groovy index 223698d561c..f7fd3534960 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticatorSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/GSSAPIAuthenticatorSpecification.groovy @@ -30,7 +30,7 @@ import spock.lang.Specification import javax.security.auth.login.LoginContext import static com.mongodb.AuthenticationMechanism.GSSAPI -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getLoginContextName import static com.mongodb.ClusterFixture.getPrimary import static com.mongodb.ClusterFixture.getServerApi @@ -57,7 +57,7 @@ class GSSAPIAuthenticatorSpecification extends Specification { .create(new ServerId(new ClusterId(), getPrimary())) when: - internalConnection.open(OPERATION_CONTEXT) + internalConnection.open(createOperationContext()) then: 1 * subjectProvider.getSubject() >> subject diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticationSpecification.groovy index e8c2a408220..b1078711f7d 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticationSpecification.groovy @@ -32,7 +32,7 @@ import spock.lang.IgnoreIf import spock.lang.Specification import static com.mongodb.AuthenticationMechanism.PLAIN -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getConnectionString import static com.mongodb.ClusterFixture.getCredential @@ -52,7 +52,7 @@ class PlainAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoCommandException) @@ -71,7 +71,7 @@ class PlainAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: true @@ -90,7 +90,7 @@ class PlainAuthenticationSpecification extends Specification { when: openConnection(connection, async) executeCommand(getConnectionString().getDatabase(), new BsonDocument('count', new BsonString('test')), - getClusterConnectionMode(), null, connection, OPERATION_CONTEXT) + getClusterConnectionMode(), null, connection, createOperationContext()) then: thrown(MongoSecurityException) @@ -123,10 +123,10 @@ class PlainAuthenticationSpecification extends Specification { private static void openConnection(final InternalConnection connection, final boolean async) { if (async) { FutureResultCallback futureResultCallback = new FutureResultCallback() - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get(ClusterFixture.TIMEOUT, SECONDS) } else { - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) } } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java index b95b9c96894..6fb9f48d078 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.LoggerSettings; import com.mongodb.MongoCredential; import com.mongodb.MongoSecurityException; @@ -33,7 +34,6 @@ import java.util.Collections; import static com.mongodb.ClusterFixture.CLIENT_METADATA; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getClusterConnectionMode; import static com.mongodb.ClusterFixture.getServerApi; import static com.mongodb.ClusterFixture.getSslSettings; @@ -69,14 +69,14 @@ public void tearDown() { public void testSuccessfulAuthentication() { PlainAuthenticator authenticator = new PlainAuthenticator(getCredentialWithCache(userName, source, password.toCharArray()), getClusterConnectionMode(), getServerApi()); - authenticator.authenticate(internalConnection, connectionDescription, OPERATION_CONTEXT); + authenticator.authenticate(internalConnection, connectionDescription, ClusterFixture.createOperationContext()); } @Test(expected = MongoSecurityException.class) public void testUnsuccessfulAuthentication() { PlainAuthenticator authenticator = new PlainAuthenticator(getCredentialWithCache(userName, source, "wrong".toCharArray()), getClusterConnectionMode(), getServerApi()); - authenticator.authenticate(internalConnection, connectionDescription, OPERATION_CONTEXT); + authenticator.authenticate(internalConnection, connectionDescription, ClusterFixture.createOperationContext()); } private static MongoCredentialWithCache getCredentialWithCache(final String userName, final String source, final char[] password) { diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ScramSha256AuthenticationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/ScramSha256AuthenticationSpecification.groovy index 36aac9b6908..b5baa3837f3 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ScramSha256AuthenticationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ScramSha256AuthenticationSpecification.groovy @@ -33,7 +33,7 @@ import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.createAsyncCluster import static com.mongodb.ClusterFixture.createCluster import static com.mongodb.ClusterFixture.getBinding @@ -88,12 +88,12 @@ class ScramSha256AuthenticationSpecification extends Specification { def binding = getBinding() new CommandReadOperation<>('admin', new BsonDocumentWrapper(createUserCommand, new DocumentCodec()), new DocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) } def dropUser(final String userName) { def binding = getBinding() - def operationContext = ClusterFixture.getOperationContext(binding.getReadPreference()) + def operationContext = ClusterFixture.createOperationContext(binding.getReadPreference()) new CommandReadOperation<>('admin', new BsonDocument('dropUser', new BsonString(userName)), new BsonDocumentCodec()).execute(binding, operationContext) } @@ -105,7 +105,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .execute(new ClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT) + .execute(new ClusterBinding(cluster, ReadPreference.primary()), createOperationContext()) then: noExceptionThrown() @@ -128,7 +128,7 @@ class ScramSha256AuthenticationSpecification extends Specification { def binding = new AsyncClusterBinding(cluster, ReadPreference.primary()) new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .executeAsync(binding, OPERATION_CONTEXT, callback) + .executeAsync(binding, createOperationContext(), callback) callback.get() then: @@ -148,7 +148,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .execute(new ClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT) + .execute(new ClusterBinding(cluster, ReadPreference.primary()), createOperationContext()) then: thrown(MongoSecurityException) @@ -168,7 +168,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .executeAsync(new AsyncClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT, + .executeAsync(new AsyncClusterBinding(cluster, ReadPreference.primary()), createOperationContext(), callback) callback.get() @@ -189,7 +189,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .execute(new ClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT) + .execute(new ClusterBinding(cluster, ReadPreference.primary()), createOperationContext()) then: noExceptionThrown() @@ -209,7 +209,7 @@ class ScramSha256AuthenticationSpecification extends Specification { when: new CommandReadOperation('admin', new BsonDocumentWrapper(new Document('dbstats', 1), new DocumentCodec()), new DocumentCodec()) - .executeAsync(new AsyncClusterBinding(cluster, ReadPreference.primary()), OPERATION_CONTEXT, + .executeAsync(new AsyncClusterBinding(cluster, ReadPreference.primary()), createOperationContext(), callback) callback.get() diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ServerHelper.java b/driver-core/src/test/functional/com/mongodb/internal/connection/ServerHelper.java index 0295e8c1f9f..8e9b2385ee5 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ServerHelper.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ServerHelper.java @@ -23,7 +23,6 @@ import com.mongodb.internal.binding.AsyncConnectionSource; import com.mongodb.internal.selector.ServerAddressSelector; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getAsyncCluster; import static com.mongodb.ClusterFixture.getCluster; import static com.mongodb.assertions.Assertions.fail; @@ -54,7 +53,7 @@ public static void waitForLastRelease(final Cluster cluster) { public static void waitForLastRelease(final ServerAddress address, final Cluster cluster) { ConcurrentPool pool = connectionPool( - cluster.selectServer(new ServerAddressSelector(address), OPERATION_CONTEXT).getServer()); + cluster.selectServer(new ServerAddressSelector(address), ClusterFixture.createOperationContext()).getServer()); long startTime = System.currentTimeMillis(); while (pool.getInUseCount() > 0) { try { @@ -70,7 +69,7 @@ public static void waitForLastRelease(final ServerAddress address, final Cluster } private static ConcurrentPool getConnectionPool(final ServerAddress address, final Cluster cluster) { - return connectionPool(cluster.selectServer(new ServerAddressSelector(address), OPERATION_CONTEXT).getServer()); + return connectionPool(cluster.selectServer(new ServerAddressSelector(address), ClusterFixture.createOperationContext()).getServer()); } private static void checkPool(final ServerAddress address, final Cluster cluster) { diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java index 62fa6c27032..a624a27454c 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.LoggerSettings; import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; @@ -37,7 +38,6 @@ import java.util.Collections; import static com.mongodb.ClusterFixture.CLIENT_METADATA; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY; import static com.mongodb.ClusterFixture.getCredential; import static com.mongodb.ClusterFixture.getDefaultDatabaseName; @@ -93,7 +93,7 @@ public void shouldGetServerWithOkDescription() { setUpCluster(getPrimary()); // when - ServerTuple serverTuple = cluster.selectServer(clusterDescription -> getPrimaries(clusterDescription), OPERATION_CONTEXT); + ServerTuple serverTuple = cluster.selectServer(clusterDescription -> getPrimaries(clusterDescription), ClusterFixture.createOperationContext()); // then assertTrue(serverTuple.getServerDescription().isOk()); @@ -102,7 +102,7 @@ public void shouldGetServerWithOkDescription() { @Test public void shouldSuccessfullyQueryASecondaryWithPrimaryReadPreference() { // given - OperationContext operationContext = OPERATION_CONTEXT; + OperationContext operationContext = ClusterFixture.createOperationContext(); ServerAddress secondary = getSecondary(); setUpCluster(secondary); String collectionName = getClass().getName(); diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy index e528673c929..600be598080 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy @@ -33,7 +33,6 @@ import javax.net.ssl.SSLSocket import javax.net.ssl.SSLSocketFactory import java.lang.reflect.Method -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getPrimary @@ -89,7 +88,7 @@ class SocketStreamHelperSpecification extends Specification { when: SocketStreamHelper.initialize( - OPERATION_CONTEXT.withTimeoutContext(new TimeoutContext( + createOperationContext().withTimeoutContext(new TimeoutContext( new TimeoutSettings( 1, 100, @@ -112,7 +111,8 @@ class SocketStreamHelperSpecification extends Specification { Socket socket = SocketFactory.default.createSocket() when: - SocketStreamHelper.initialize(OPERATION_CONTEXT, socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketStreamHelper.initialize(createOperationContext(), socket, getSocketAddresses(getPrimary(), + new DefaultInetAddressResolver()).get(0), SocketSettings.builder().build(), SslSettings.builder().build()) then: @@ -128,7 +128,8 @@ class SocketStreamHelperSpecification extends Specification { SSLSocket socket = SSLSocketFactory.default.createSocket() when: - SocketStreamHelper.initialize(OPERATION_CONTEXT, socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketStreamHelper.initialize(createOperationContext(), socket, getSocketAddresses(getPrimary(), + new DefaultInetAddressResolver()).get(0), SocketSettings.builder().build(), sslSettings) then: @@ -149,7 +150,8 @@ class SocketStreamHelperSpecification extends Specification { SSLSocket socket = SSLSocketFactory.default.createSocket() when: - SocketStreamHelper.initialize(OPERATION_CONTEXT, socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketStreamHelper.initialize(createOperationContext(), socket, getSocketAddresses(getPrimary(), + new DefaultInetAddressResolver()).get(0), SocketSettings.builder().build(), sslSettings) then: @@ -168,7 +170,8 @@ class SocketStreamHelperSpecification extends Specification { Socket socket = SocketFactory.default.createSocket() when: - SocketStreamHelper.initialize(OPERATION_CONTEXT, socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketStreamHelper.initialize(createOperationContext(), socket, getSocketAddresses(getPrimary(), + new DefaultInetAddressResolver()).get(0), SocketSettings.builder().build(), SslSettings.builder().enabled(true).build()) then: diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/StreamSocketAddressSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/StreamSocketAddressSpecification.groovy index 0283ce44f7b..520dd3932de 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/StreamSocketAddressSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/StreamSocketAddressSpecification.groovy @@ -13,7 +13,7 @@ import com.mongodb.spock.Slow import javax.net.SocketFactory import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getSslSettings class StreamSocketAddressSpecification extends Specification { @@ -44,7 +44,7 @@ class StreamSocketAddressSpecification extends Specification { def socketStream = new SocketStream(serverAddress, null, socketSettings, sslSettings, socketFactory, bufferProvider) when: - socketStream.open(OPERATION_CONTEXT) + socketStream.open(createOperationContext()) then: !socket0.isConnected() @@ -83,7 +83,7 @@ class StreamSocketAddressSpecification extends Specification { def socketStream = new SocketStream(serverAddress, inetAddressResolver, socketSettings, sslSettings, socketFactory, bufferProvider) when: - socketStream.open(OPERATION_CONTEXT) + socketStream.open(createOperationContext()) then: thrown(MongoSocketOpenException) diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java b/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java index 9381ad842a1..97fb8c82a4f 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java @@ -17,6 +17,7 @@ package com.mongodb.internal.connection; import com.mongodb.MongoTimeoutException; +import com.mongodb.annotations.ThreadSafe; import com.mongodb.client.TestListener; import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandFailedEvent; @@ -56,6 +57,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +@ThreadSafe public class TestCommandListener implements CommandListener { private final List eventTypes; private final List ignoredCommandMonitoringEvents; diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/TlsChannelStreamFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/TlsChannelStreamFunctionalTest.java index 3af1eaa33e1..e7ae01c0879 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/TlsChannelStreamFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/TlsChannelStreamFunctionalTest.java @@ -21,7 +21,6 @@ import com.mongodb.ServerAddress; import com.mongodb.connection.SocketSettings; import com.mongodb.connection.SslSettings; -import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.TimeoutSettings; import org.bson.ByteBuf; import org.bson.ByteBufNIO; @@ -162,7 +161,7 @@ public T answer(final InvocationOnMock invocationOnMock) throws Throwable { } private static OperationContext createOperationContext(final int connectTimeoutMs) { - return simpleOperationContext(new TimeoutContext(TimeoutSettings.DEFAULT.withConnectTimeoutMS(connectTimeoutMs))); + return simpleOperationContext(TimeoutSettings.DEFAULT.withConnectTimeoutMS(connectTimeoutMs)); } @Test @@ -184,11 +183,11 @@ void shouldNotCallBeginHandshakeMoreThenOnceDuringTlsSessionEstablishment() thro .build()); Stream stream = streamFactory.create(getPrimaryServerDescription().getAddress()); - stream.open(ClusterFixture.OPERATION_CONTEXT); + stream.open(ClusterFixture.createOperationContext()); ByteBuf wrap = new ByteBufNIO(ByteBuffer.wrap(new byte[]{1, 3, 4})); //when - stream.write(Collections.singletonList(wrap), ClusterFixture.OPERATION_CONTEXT); + stream.write(Collections.singletonList(wrap), ClusterFixture.createOperationContext()); //then SECONDS.sleep(5); diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AbortTransactionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/AbortTransactionOperationSpecification.groovy index fe7cd511c0c..28846f61a5d 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AbortTransactionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AbortTransactionOperationSpecification.groovy @@ -33,13 +33,13 @@ class AbortTransactionOperationSpecification extends OperationFunctionalSpecific def expectedCommand = BsonDocument.parse('{abortTransaction: 1}') when: - def operation = new AbortTransactionOperation(ACKNOWLEDGED) + def operation = new AbortTransactionOperation(ACKNOWLEDGED, null) then: testOperationInTransaction(operation, [4, 0, 0], expectedCommand, async, cannedResult) when: - operation = new AbortTransactionOperation(MAJORITY) + operation = new AbortTransactionOperation(MAJORITY, null) expectedCommand.put('writeConcern', MAJORITY.asDocument()) then: @@ -56,14 +56,14 @@ class AbortTransactionOperationSpecification extends OperationFunctionalSpecific when: def writeConcern = MAJORITY.withWTimeout(10, TimeUnit.MILLISECONDS) - def operation = new AbortTransactionOperation(writeConcern) + def operation = new AbortTransactionOperation(writeConcern, null) then: testOperationRetries(operation, [4, 0, 0], expectedCommand, async, cannedResult, true) when: writeConcern = MAJORITY - operation = new AbortTransactionOperation(writeConcern) + operation = new AbortTransactionOperation(writeConcern, null) expectedCommand.put('writeConcern', writeConcern.asDocument()) then: @@ -71,7 +71,7 @@ class AbortTransactionOperationSpecification extends OperationFunctionalSpecific when: writeConcern = ACKNOWLEDGED - operation = new AbortTransactionOperation(writeConcern) + operation = new AbortTransactionOperation(writeConcern, null) expectedCommand.remove('writeConcern') then: diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateOperationSpecification.groovy index aa7506d6516..9e8d6a8277e 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateOperationSpecification.groovy @@ -52,12 +52,11 @@ import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.collectCursorResults import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getAsyncCluster import static com.mongodb.ClusterFixture.getCluster -import static com.mongodb.ClusterFixture.getOperationContext +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.isSharded import static com.mongodb.ClusterFixture.isStandalone import static com.mongodb.ExplainVerbosity.QUERY_PLANNER @@ -79,7 +78,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should have the correct defaults'() { when: - AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) then: operation.getAllowDiskUse() == null @@ -93,7 +92,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def hint = BsonDocument.parse('{a: 1}') when: - AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) .allowDiskUse(true) .batchSize(10) .collation(defaultCollation) @@ -109,7 +108,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should throw when using invalid hint'() { given: def hint = new BsonString('ok') - def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()).hint(hint) + def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null).hint(hint) when: operation.getHint() @@ -133,7 +132,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should create the expected command'() { when: def pipeline = [new BsonDocument('$match', new BsonDocument('a', new BsonString('A')))] - def operation = new AggregateOperation(helper.namespace, pipeline, new DocumentCodec()) + def operation = new AggregateOperation(helper.namespace, pipeline, new DocumentCodec(), null) def expectedCommand = new BsonDocument('aggregate', new BsonString(helper.namespace.getCollectionName())) .append('pipeline', new BsonArray(pipeline)) @@ -143,7 +142,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { testOperation(operation, [3, 4, 0], expectedCommand, async, helper.cursorResult) when: - operation = new AggregateOperation(helper.namespace, pipeline, new DocumentCodec()) + operation = new AggregateOperation(helper.namespace, pipeline, new DocumentCodec(), null) .allowDiskUse(true) .batchSize(10) .collation(defaultCollation) @@ -166,7 +165,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def document = BsonDocument.parse('{_id: 1, str: "foo"}') getCollectionHelper().insertDocuments(document) def pipeline = [BsonDocument.parse('{$match: {str: "FOO"}}')] - def operation = new AggregateOperation(namespace, pipeline, new BsonDocumentCodec()) + def operation = new AggregateOperation(namespace, pipeline, new BsonDocumentCodec(), null) .collation(caseInsensitiveCollation) when: @@ -184,7 +183,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { given: def expected = [createExpectedChangeNotification(namespace, 0), createExpectedChangeNotification(namespace, 1)] def pipeline = ['{$changeStream: {}}', '{$project: {"_id.clusterTime": 0, "_id.uuid": 0}}'].collect { BsonDocument.parse(it) } - def operation = new AggregateOperation(namespace, pipeline, new BsonDocumentCodec()) + def operation = new AggregateOperation(namespace, pipeline, new BsonDocumentCodec(), null) def helper = getCollectionHelper() when: @@ -212,7 +211,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should be able to aggregate'() { when: - AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) def batchCursor = execute(operation, async) def results = collectCursorResults(batchCursor)*.getString('name') @@ -232,10 +231,10 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def binding = ClusterFixture.getBinding(ClusterFixture.getCluster()) new CreateViewOperation(getDatabaseName(), viewName, getCollectionName(), [], WriteConcern.ACKNOWLEDGED) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) when: - AggregateOperation operation = new AggregateOperation(viewNamespace, [], new DocumentCodec()) + AggregateOperation operation = new AggregateOperation(viewNamespace, [], new DocumentCodec(), null) def batchCursor = execute(operation, async) def results = collectCursorResults(batchCursor)*.getString('name') @@ -246,7 +245,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { cleanup: binding = ClusterFixture.getBinding(ClusterFixture.getCluster()) new DropCollectionOperation(viewNamespace, WriteConcern.ACKNOWLEDGED) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) where: async << [true, false] @@ -255,7 +254,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should be able to aggregate with pipeline'() { when: AggregateOperation operation = new AggregateOperation(getNamespace(), - [new BsonDocument('$match', new BsonDocument('job', new BsonString('plumber')))], new DocumentCodec()) + [new BsonDocument('$match', new BsonDocument('job', new BsonString('plumber')))], new DocumentCodec(), null) def batchCursor = execute(operation, async) def results = collectCursorResults(batchCursor)*.getString('name') @@ -269,11 +268,11 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should allow disk usage'() { when: - AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) .allowDiskUse(allowDiskUse) def binding = ClusterFixture.getBinding() - def cursor = operation.execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: cursor.next()*.getString('name') == ['Pete', 'Sam', 'Pete'] @@ -284,11 +283,11 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should allow batch size'() { when: - AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + AggregateOperation operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) .batchSize(batchSize) def binding = ClusterFixture.getBinding() - def cursor = operation.execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: cursor.next()*.getString('name') == ['Pete', 'Sam', 'Pete'] @@ -299,7 +298,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should be able to explain an empty pipeline'() { given: - def operation = new AggregateOperation(getNamespace(), [], new BsonDocumentCodec()) + def operation = new AggregateOperation(getNamespace(), [], new BsonDocumentCodec(), null) operation = async ? operation.asExplainableOperation(QUERY_PLANNER, new BsonDocumentCodec()) : operation.asExplainableOperation(QUERY_PLANNER, new BsonDocumentCodec()) @@ -316,7 +315,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should be able to aggregate with collation'() { when: AggregateOperation operation = new AggregateOperation(getNamespace(), - [BsonDocument.parse('{$match: {job : "plumber"}}')], new DocumentCodec() + [BsonDocument.parse('{$match: {job : "plumber"}}')], new DocumentCodec(), null ).collation(options) def batchCursor = execute(operation, async) def results = collectCursorResults(batchCursor)*.getString('name') @@ -335,7 +334,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def index = new BsonDocument('a', new BsonInt32(1)) collectionHelper.createIndex(index) - def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) .hint(hint) when: @@ -356,9 +355,9 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def binding = ClusterFixture.getBinding() new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(2)), - new BsonDocumentCodec()).execute(binding, getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, createOperationContext(binding.getReadPreference())) def expectedComment = 'this is a comment' - def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) .comment(new BsonString(expectedComment)) when: @@ -372,7 +371,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { cleanup: binding = ClusterFixture.getBinding() new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(0)), - new BsonDocumentCodec()).execute(binding, getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, createOperationContext(binding.getReadPreference())) profileCollectionHelper.drop() where: @@ -381,7 +380,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -394,7 +393,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { .append('cursor', new BsonDocument()) appendReadConcernToCommand(operationContext.getSessionContext(), UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) when: operation.execute(binding, operationContext) @@ -423,7 +422,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) @@ -435,7 +434,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { .append('cursor', new BsonDocument()) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec()) + def operation = new AggregateOperation(getNamespace(), [], new DocumentCodec(), null) when: executeAsync(operation, binding, operationContext) @@ -463,7 +462,7 @@ class AggregateOperationSpecification extends OperationFunctionalSpecification { def 'should use the ReadBindings readPreference to set secondaryOk'() { when: - def operation = new AggregateOperation(helper.namespace, [], new BsonDocumentCodec()) + def operation = new AggregateOperation(helper.namespace, [], new BsonDocumentCodec(), null) then: testOperationSecondaryOk(operation, [2, 6, 0], readPreference, async, helper.cursorResult) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateToCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateToCollectionOperationSpecification.groovy index 6ebdcdc6b40..a14c4323fdd 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateToCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AggregateToCollectionOperationSpecification.groovy @@ -278,7 +278,7 @@ class AggregateToCollectionOperationSpecification extends OperationFunctionalSpe def profileCollectionHelper = getCollectionHelper(new MongoNamespace(getDatabaseName(), 'system.profile')) def binding = getBinding() new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(2)), - new BsonDocumentCodec()).execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) def expectedComment = 'this is a comment' AggregateToCollectionOperation operation = createOperation(getNamespace(), [Aggregates.out('outputCollection').toBsonDocument(BsonDocument, registry)], ACKNOWLEDGED) @@ -293,7 +293,7 @@ class AggregateToCollectionOperationSpecification extends OperationFunctionalSpe cleanup: new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(0)), - new BsonDocumentCodec()).execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) profileCollectionHelper.drop() where: diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java index 58e3e47ba74..6fab0c5e216 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java @@ -17,6 +17,7 @@ package com.mongodb.internal.operation; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCursorNotFoundException; import com.mongodb.MongoQueryException; import com.mongodb.ReadPreference; @@ -55,7 +56,6 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.checkReferenceCountReachesTarget; import static com.mongodb.ClusterFixture.getAsyncBinding; import static com.mongodb.ClusterFixture.getConnection; @@ -111,8 +111,8 @@ void cleanup() { void shouldExhaustCursorAsyncWithMultipleBatches() { // given BsonDocument commandResult = executeFindCommand(0, 3); // Fetch in batches of size 3 - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); // when FutureResultCallback>> futureCallback = new FutureResultCallback<>(); @@ -133,8 +133,8 @@ void shouldExhaustCursorAsyncWithMultipleBatches() { void shouldExhaustCursorAsyncWithClosedCursor() { // given BsonDocument commandResult = executeFindCommand(0, 3); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); cursor.close(); @@ -156,8 +156,8 @@ void shouldExhaustCursorAsyncWithEmptyCursor() { getCollectionHelper().deleteMany(Filters.empty()); BsonDocument commandResult = executeFindCommand(0, 3); // No documents to fetch - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); // when FutureResultCallback>> futureCallback = new FutureResultCallback<>(); @@ -174,8 +174,8 @@ void shouldExhaustCursorAsyncWithEmptyCursor() { void theServerCursorShouldNotBeNull() { BsonDocument commandResult = executeFindCommand(2); AsyncCommandCursor coreCursor = - new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); assertNotNull(coreCursor.getServerCursor()); @@ -186,8 +186,8 @@ void theServerCursorShouldNotBeNull() { void shouldGetExceptionsForOperationsOnTheCursorAfterClosing() { BsonDocument commandResult = executeFindCommand(5); AsyncCommandCursor coreCursor = - new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); cursor.close(); @@ -202,8 +202,8 @@ void shouldGetExceptionsForOperationsOnTheCursorAfterClosing() { @DisplayName("should throw an Exception when going off the end") void shouldThrowAnExceptionWhenGoingOffTheEnd() { BsonDocument commandResult = executeFindCommand(2, 1); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); cursorNext(); cursorNext(); @@ -216,8 +216,8 @@ void shouldThrowAnExceptionWhenGoingOffTheEnd() { @DisplayName("test normal exhaustion") void testNormalExhaustion() { BsonDocument commandResult = executeFindCommand(); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(10, cursorFlatten().size()); } @@ -227,8 +227,8 @@ void testNormalExhaustion() { @DisplayName("test limit exhaustion") void testLimitExhaustion(final int limit, final int batchSize, final int expectedTotal) { BsonDocument commandResult = executeFindCommand(limit, batchSize); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, batchSize, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, batchSize, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(expectedTotal, cursorFlatten().size()); @@ -246,8 +246,8 @@ void shouldBlockWaitingForNextBatchOnATailableCursor(final boolean awaitData, fi BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, awaitData); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertFalse(cursor.isClosed()); assertEquals(1, cursorNext().get(0).get("_id")); @@ -269,8 +269,8 @@ void testTailableInterrupt() throws InterruptedException { BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); CountDownLatch latch = new CountDownLatch(1); AtomicInteger seen = new AtomicInteger(); @@ -303,8 +303,8 @@ void shouldKillCursorIfLimitIsReachedOnInitialQuery() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 10); AsyncCommandCursor coreCursor = - new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); assertNotNull(cursorNext()); @@ -318,8 +318,8 @@ void shouldKillCursorIfLimitIsReachedOnGetMore() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 3); AsyncCommandCursor coreCursor = - new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); ServerCursor serverCursor = coreCursor.getServerCursor(); @@ -340,8 +340,8 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnInitialQuery() { BsonDocument commandResult = executeFindCommand(5, 10); AsyncCommandCursor coreCursor = - new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); assertDoesNotThrow(() -> checkReferenceCountReachesTarget(connectionSource, 1)); @@ -354,8 +354,8 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnInitialQuery() { void shouldReleaseConnectionSourceIfLimitIsReachedOnGetMore() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 3); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertNotNull(cursorNext()); assertNotNull(cursorNext()); @@ -367,8 +367,8 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnGetMore() { @DisplayName("test limit with get more") void testLimitWithGetMore() { BsonDocument commandResult = executeFindCommand(5, 2); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertNotNull(cursorNext()); assertNotNull(cursorNext()); @@ -390,8 +390,8 @@ void testLimitWithLargeDocuments() { ); BsonDocument commandResult = executeFindCommand(300, 0); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(300, cursorFlatten().size()); } @@ -400,8 +400,8 @@ void testLimitWithLargeDocuments() { @DisplayName("should respect batch size") void shouldRespectBatchSize() { BsonDocument commandResult = executeFindCommand(2); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(2, cursor.getBatchSize()); assertEquals(2, cursorNext().size()); @@ -418,8 +418,8 @@ void shouldRespectBatchSize() { void shouldThrowCursorNotFoundException() throws Throwable { BsonDocument commandResult = executeFindCommand(2); AsyncCommandCursor coreCursor = - new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection); - cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, + new AsyncCommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), coreCursor); ServerCursor serverCursor = coreCursor.getServerCursor(); @@ -428,7 +428,7 @@ void shouldThrowCursorNotFoundException() throws Throwable { this.block(cb -> localConnection.commandAsync(getNamespace().getDatabaseName(), new BsonDocument("killCursors", new BsonString(getNamespace().getCollectionName())) .append("cursors", new BsonArray(singletonList(new BsonInt64(serverCursor.getId())))), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), OPERATION_CONTEXT, cb)); + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), ClusterFixture.createOperationContext(), cb)); localConnection.release(); cursorNext(); @@ -494,7 +494,7 @@ private BsonDocument executeFindCommand(final BsonDocument filter, final int lim BsonDocument results = block(cb -> connection.commandAsync(getDatabaseName(), findCommand, NoOpFieldNameValidator.INSTANCE, readPreference, CommandResultDocumentCodec.create(DOCUMENT_DECODER, FIRST_BATCH), - OPERATION_CONTEXT, cb)); + ClusterFixture.createOperationContext(), cb)); assertNotNull(results); return results; diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandCursorTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandCursorTest.java index 6d2ef649bce..44d0ec149be 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandCursorTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandCursorTest.java @@ -235,6 +235,8 @@ private AsyncCursor createBatchCursor() { DOCUMENT_CODEC, null, connectionSource, - mockConnection); + mockConnection, + false, + null); } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/ChangeStreamOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/ChangeStreamOperationSpecification.groovy index 19285eda077..fc2431f03f3 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/ChangeStreamOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/ChangeStreamOperationSpecification.groovy @@ -53,7 +53,7 @@ import org.bson.codecs.DocumentCodec import org.bson.codecs.ValueCodecProvider import spock.lang.IgnoreIf -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getAsyncCluster import static com.mongodb.ClusterFixture.getCluster import static com.mongodb.ClusterFixture.isStandalone @@ -112,7 +112,7 @@ class ChangeStreamOperationSpecification extends OperationFunctionalSpecificatio .append('firstBatch', new BsonArrayWrapper([]))) def operation = new ChangeStreamOperation(namespace, FullDocument.DEFAULT, - FullDocumentBeforeChange.DEFAULT, pipeline, new DocumentCodec(), changeStreamLevel as ChangeStreamLevel) + FullDocumentBeforeChange.DEFAULT, pipeline, new DocumentCodec(), changeStreamLevel as ChangeStreamLevel, null) .batchSize(5) .collation(defaultCollation) .startAtOperationTime(new BsonTimestamp()) @@ -359,7 +359,7 @@ class ChangeStreamOperationSpecification extends OperationFunctionalSpecificatio def operation = new ChangeStreamOperation(helper.getNamespace(), FullDocument.UPDATE_LOOKUP, FullDocumentBeforeChange.DEFAULT, pipeline, createCodec(BsonDocument, fromProviders(new BsonValueCodecProvider(), new ValueCodecProvider())), - ChangeStreamLevel.DATABASE) + ChangeStreamLevel.DATABASE, null) helper.insertDocuments(BsonDocument.parse('{ _id : 2, x : 2, y : 3 }')) when: @@ -635,7 +635,7 @@ class ChangeStreamOperationSpecification extends OperationFunctionalSpecificatio def 'should set the startAtOperationTime on the sync cursor'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { getReadConcern() >> ReadConcern.DEFAULT getOperationTime() >> new BsonTimestamp() @@ -690,7 +690,7 @@ class ChangeStreamOperationSpecification extends OperationFunctionalSpecificatio def 'should set the startAtOperationTime on the async cursor'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { getReadConcern() >> ReadConcern.DEFAULT getOperationTime() >> new BsonTimestamp() diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java index 407b03f5246..927b2d1b840 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java @@ -27,6 +27,7 @@ import com.mongodb.client.model.OperationTest; import com.mongodb.internal.binding.ConnectionSource; import com.mongodb.internal.connection.Connection; +import com.mongodb.internal.connection.OperationContext; import com.mongodb.internal.validator.NoOpFieldNameValidator; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -55,7 +56,6 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.checkReferenceCountReachesTarget; import static com.mongodb.ClusterFixture.getBinding; import static com.mongodb.ClusterFixture.getReferenceCountAfterTimeout; @@ -87,8 +87,9 @@ void setup() { .collect(Collectors.toList()); getCollectionHelper().insertDocuments(documents); - connectionSource = getBinding().getWriteConnectionSource(ClusterFixture.OPERATION_CONTEXT); - connection = connectionSource.getConnection(ClusterFixture.OPERATION_CONTEXT); + OperationContext operationContext = ClusterFixture.createOperationContext(); + connectionSource = getBinding().getWriteConnectionSource(operationContext); + connection = connectionSource.getConnection(operationContext); } @AfterEach @@ -109,8 +110,8 @@ void cleanup() { void shouldExhaustCursorWithMultipleBatches() { // given BsonDocument commandResult = executeFindCommand(0, 3); // Fetch in batches of size 3 - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); // when List> result = cursor.exhaust(); @@ -127,8 +128,8 @@ void shouldExhaustCursorWithMultipleBatches() { void shouldExhaustCursorWithClosedCursor() { // given BsonDocument commandResult = executeFindCommand(0, 3); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); cursor.close(); // when & then @@ -143,8 +144,8 @@ void shouldExhaustCursorWithEmptyCursor() { getCollectionHelper().deleteMany(Filters.empty()); BsonDocument commandResult = executeFindCommand(0, 3); // No documents to fetch - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); // when List> result = cursor.exhaust(); @@ -157,8 +158,8 @@ void shouldExhaustCursorWithEmptyCursor() { @DisplayName("server cursor should not be null") void theServerCursorShouldNotBeNull() { BsonDocument commandResult = executeFindCommand(2); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertNotNull(cursor.getServerCursor()); } @@ -167,8 +168,8 @@ void theServerCursorShouldNotBeNull() { @DisplayName("test server address should not be null") void theServerAddressShouldNotNull() { BsonDocument commandResult = executeFindCommand(); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertNotNull(cursor.getServerAddress()); } @@ -177,8 +178,8 @@ void theServerAddressShouldNotNull() { @DisplayName("should get Exceptions for operations on the cursor after closing") void shouldGetExceptionsForOperationsOnTheCursorAfterClosing() { BsonDocument commandResult = executeFindCommand(); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); cursor.close(); @@ -192,8 +193,8 @@ void shouldGetExceptionsForOperationsOnTheCursorAfterClosing() { @DisplayName("should throw an Exception when going off the end") void shouldThrowAnExceptionWhenGoingOffTheEnd() { BsonDocument commandResult = executeFindCommand(1); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); cursor.next(); cursor.next(); @@ -204,8 +205,8 @@ void shouldThrowAnExceptionWhenGoingOffTheEnd() { @DisplayName("test cursor remove") void testCursorRemove() { BsonDocument commandResult = executeFindCommand(); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertThrows(UnsupportedOperationException.class, () -> cursor.remove()); } @@ -214,8 +215,8 @@ void testCursorRemove() { @DisplayName("test normal exhaustion") void testNormalExhaustion() { BsonDocument commandResult = executeFindCommand(); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(10, cursorFlatten().size()); } @@ -225,8 +226,8 @@ void testNormalExhaustion() { @DisplayName("test limit exhaustion") void testLimitExhaustion(final int limit, final int batchSize, final int expectedTotal) { BsonDocument commandResult = executeFindCommand(limit, batchSize); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(expectedTotal, cursorFlatten().size()); @@ -244,8 +245,8 @@ void shouldBlockWaitingForNextBatchOnATailableCursor(final boolean awaitData, fi BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, awaitData); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertTrue(cursor.hasNext()); assertEquals(1, cursor.next().get(0).get("_id")); @@ -267,8 +268,8 @@ void testTryNextWithTailable() { BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); List nextBatch = cursor.tryNext(); assertNotNull(nextBatch); @@ -293,8 +294,8 @@ void hasNextShouldThrowWhenCursorIsClosedInAnotherThread() throws InterruptedExc BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertTrue(cursor.hasNext()); assertEquals(1, cursor.next().get(0).get("_id")); @@ -320,8 +321,8 @@ void testMaxTimeMS() { long maxTimeMS = 500; BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); List nextBatch = cursor.tryNext(); assertNotNull(nextBatch); @@ -344,8 +345,8 @@ void testTailableInterrupt() throws InterruptedException { BsonDocument commandResult = executeFindCommand(new BsonDocument("ts", new BsonDocument("$gte", new BsonTimestamp(5, 0))), 0, 2, true, true); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); CountDownLatch latch = new CountDownLatch(1); AtomicInteger seen = new AtomicInteger(); @@ -377,8 +378,8 @@ void testTailableInterrupt() throws InterruptedException { void shouldKillCursorIfLimitIsReachedOnInitialQuery() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 10); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertNotNull(cursor.next()); assertFalse(cursor.hasNext()); @@ -390,8 +391,8 @@ void shouldKillCursorIfLimitIsReachedOnInitialQuery() { void shouldKillCursorIfLimitIsReachedOnGetMore() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 3); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); ServerCursor serverCursor = cursor.getServerCursor(); assertNotNull(serverCursor); @@ -409,8 +410,8 @@ void shouldKillCursorIfLimitIsReachedOnGetMore() { void shouldReleaseConnectionSourceIfLimitIsReachedOnInitialQuery() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 10); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertNull(cursor.getServerCursor()); assertDoesNotThrow(() -> checkReferenceCountReachesTarget(connectionSource, 1)); @@ -422,8 +423,8 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnInitialQuery() { void shouldReleaseConnectionSourceIfLimitIsReachedOnGetMore() { assumeFalse(isSharded()); BsonDocument commandResult = executeFindCommand(5, 3); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 3, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertNotNull(cursor.next()); assertNotNull(cursor.next()); @@ -435,8 +436,8 @@ void shouldReleaseConnectionSourceIfLimitIsReachedOnGetMore() { @DisplayName("test limit with get more") void testLimitWithGetMore() { BsonDocument commandResult = executeFindCommand(5, 2); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertNotNull(cursor.next()); assertNotNull(cursor.next()); @@ -456,8 +457,8 @@ void testLimitWithLargeDocuments() { ); BsonDocument commandResult = executeFindCommand(300, 0); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 0, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(300, cursorFlatten().size()); } @@ -466,8 +467,8 @@ void testLimitWithLargeDocuments() { @DisplayName("should respect batch size") void shouldRespectBatchSize() { BsonDocument commandResult = executeFindCommand(2); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(2, cursor.getBatchSize()); assertEquals(2, cursor.next().size()); @@ -483,16 +484,16 @@ void shouldRespectBatchSize() { @DisplayName("should throw cursor not found exception") void shouldThrowCursorNotFoundException() { BsonDocument commandResult = executeFindCommand(2); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); ServerCursor serverCursor = cursor.getServerCursor(); assertNotNull(serverCursor); - Connection localConnection = connectionSource.getConnection(OPERATION_CONTEXT); + Connection localConnection = connectionSource.getConnection(ClusterFixture.createOperationContext()); localConnection.command(getNamespace().getDatabaseName(), new BsonDocument("killCursors", new BsonString(getNamespace().getCollectionName())) .append("cursors", new BsonArray(singletonList(new BsonInt64(serverCursor.getId())))), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), OPERATION_CONTEXT); + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), ClusterFixture.createOperationContext()); localConnection.release(); cursor.next(); @@ -506,8 +507,8 @@ void shouldThrowCursorNotFoundException() { @DisplayName("should report available documents") void shouldReportAvailableDocuments() { BsonDocument commandResult = executeFindCommand(3); - cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, OPERATION_CONTEXT, - new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection)); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, 0, ClusterFixture.createOperationContext(), + new CommandCursor<>(commandResult, 2, DOCUMENT_DECODER, null, connectionSource, connection, false, null)); assertEquals(3, cursor.available()); @@ -584,7 +585,7 @@ private BsonDocument executeFindCommand(final BsonDocument filter, final int lim BsonDocument results = connection.command(getDatabaseName(), findCommand, NoOpFieldNameValidator.INSTANCE, readPreference, CommandResultDocumentCodec.create(DOCUMENT_DECODER, FIRST_BATCH), - OPERATION_CONTEXT); + ClusterFixture.createOperationContext()); assertNotNull(results); return results; diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CommitTransactionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CommitTransactionOperationSpecification.groovy index 0d91963d5bf..69c257462d1 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CommitTransactionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CommitTransactionOperationSpecification.groovy @@ -33,13 +33,13 @@ class CommitTransactionOperationSpecification extends OperationFunctionalSpecifi def expectedCommand = BsonDocument.parse('{commitTransaction: 1}') when: - def operation = new CommitTransactionOperation(ACKNOWLEDGED) + def operation = new CommitTransactionOperation(ACKNOWLEDGED, null, false) then: testOperationInTransaction(operation, [4, 0, 0], expectedCommand, async, cannedResult) when: - operation = new CommitTransactionOperation(MAJORITY) + operation = new CommitTransactionOperation(MAJORITY, null, false) expectedCommand.put('writeConcern', MAJORITY.asDocument()) then: @@ -56,14 +56,14 @@ class CommitTransactionOperationSpecification extends OperationFunctionalSpecifi when: def writeConcern = MAJORITY.withWTimeout(10, TimeUnit.MILLISECONDS) - def operation = new CommitTransactionOperation(writeConcern) + def operation = new CommitTransactionOperation(writeConcern, null, false) then: testOperationRetries(operation, [4, 0, 0], expectedCommand, async, cannedResult, true) when: writeConcern = MAJORITY - operation = new CommitTransactionOperation(writeConcern) + operation = new CommitTransactionOperation(writeConcern, null, false) expectedCommand.put('writeConcern', writeConcern.withWTimeout(10000, TimeUnit.MILLISECONDS).asDocument()) then: @@ -71,7 +71,7 @@ class CommitTransactionOperationSpecification extends OperationFunctionalSpecifi when: writeConcern = ACKNOWLEDGED - operation = new CommitTransactionOperation(writeConcern) + operation = new CommitTransactionOperation(writeConcern, null, false) expectedCommand.put('writeConcern', writeConcern.withW('majority').withWTimeout(10000, TimeUnit.MILLISECONDS).asDocument()) then: @@ -87,7 +87,7 @@ class CommitTransactionOperationSpecification extends OperationFunctionalSpecifi def expectedCommand = BsonDocument.parse('{commitTransaction: 1, writeConcern: {w: "majority", wtimeout: 10000}}') when: - def operation = new CommitTransactionOperation(ACKNOWLEDGED, true) + def operation = new CommitTransactionOperation(ACKNOWLEDGED, null, true) then: testOperationInTransaction(operation, [4, 0, 0], expectedCommand, async, cannedResult, true) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CountDocumentsOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CountDocumentsOperationSpecification.groovy index 1e538b1af11..2c61dc77eef 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CountDocumentsOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CountDocumentsOperationSpecification.groovy @@ -46,7 +46,7 @@ import org.bson.BsonTimestamp import org.bson.Document import org.bson.codecs.DocumentCodec -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.connection.ServerType.STANDALONE import static com.mongodb.internal.operation.OperationReadConcernHelper.appendReadConcernToCommand @@ -69,7 +69,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should have the correct defaults'() { when: - CountDocumentsOperation operation = new CountDocumentsOperation(getNamespace()) + CountDocumentsOperation operation = new CountDocumentsOperation(getNamespace(), null) then: operation.getFilter() == null @@ -84,7 +84,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def hint = new BsonString('hint') when: - CountDocumentsOperation operation = new CountDocumentsOperation(getNamespace()) + CountDocumentsOperation operation = new CountDocumentsOperation(getNamespace(), null) .filter(filter) .hint(hint) .limit(20) @@ -99,7 +99,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should get the count'() { expect: - execute(new CountDocumentsOperation(getNamespace()), async) == documents.size() + execute(new CountDocumentsOperation(getNamespace(), null), async) == documents.size() where: async << [true, false] @@ -110,7 +110,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat getCollectionHelper().drop() then: - execute(new CountDocumentsOperation(getNamespace()), async) == 0 + execute(new CountDocumentsOperation(getNamespace(), null), async) == 0 where: async << [true, false] @@ -122,7 +122,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat getCollectionHelper().create() then: - execute(new CountDocumentsOperation(getNamespace()), async) == 0 + execute(new CountDocumentsOperation(getNamespace(), null), async) == 0 where: async << [true, false] @@ -130,7 +130,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should use limit with the count'() { when: - def operation = new CountDocumentsOperation(getNamespace()).limit(1) + def operation = new CountDocumentsOperation(getNamespace(), null).limit(1) then: execute(operation, async) == 1 @@ -141,7 +141,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should use skip with the count'() { when: - def operation = new CountDocumentsOperation(getNamespace()).skip(documents.size() - 2) + def operation = new CountDocumentsOperation(getNamespace(), null).skip(documents.size() - 2) then: execute(operation, async) @@ -156,8 +156,8 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def binding = ClusterFixture.getBinding() new CreateIndexesOperation(getNamespace(), [new IndexRequest(indexDefinition).sparse(true)], null) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) - def operation = new CountDocumentsOperation(getNamespace()).hint(indexDefinition) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) + def operation = new CountDocumentsOperation(getNamespace(), null).hint(indexDefinition) when: def count = execute(operation, async) @@ -171,7 +171,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should support hints that are bson documents or strings'() { expect: - execute(new CountDocumentsOperation(getNamespace()).hint(hint), async) == 5 + execute(new CountDocumentsOperation(getNamespace(), null).hint(hint), async) == 5 where: [async, hint] << [[true, false], [new BsonString('_id_'), BsonDocument.parse('{_id: 1}')]].combinations() @@ -179,7 +179,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should throw with bad hint'() { given: - def operation = new CountDocumentsOperation(getNamespace()) + def operation = new CountDocumentsOperation(getNamespace(), null) .filter(new BsonDocument('a', new BsonInt32(1))) .hint(new BsonString('BAD HINT')) @@ -195,7 +195,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should use the ReadBindings readPreference to set secondaryOk'() { when: - def operation = new CountDocumentsOperation(helper.namespace) + def operation = new CountDocumentsOperation(helper.namespace, null) .filter(BsonDocument.parse('{a: 1}')) then: @@ -208,7 +208,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should create the expected aggregation command'() { when: def filter = new BsonDocument('filter', new BsonInt32(1)) - def operation = new CountDocumentsOperation(helper.namespace) + def operation = new CountDocumentsOperation(helper.namespace, null) def pipeline = [BsonDocument.parse('{ $match: {}}'), BsonDocument.parse('{$group: {_id: 1, n: {$sum: 1}}}')] def expectedCommand = new BsonDocument('aggregate', new BsonString(helper.namespace.getCollectionName())) .append('pipeline', new BsonArray(pipeline)) @@ -218,7 +218,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat testOperation(operation, [3, 4, 0], expectedCommand, async, helper.cursorResult) when: - operation = new CountDocumentsOperation(helper.namespace) + operation = new CountDocumentsOperation(helper.namespace, null) .filter(filter) .limit(20) .skip(30) @@ -243,7 +243,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should support collation'() { given: getCollectionHelper().insertDocuments(BsonDocument.parse('{str: "foo"}')) - def operation = new CountDocumentsOperation(namespace) + def operation = new CountDocumentsOperation(namespace, null) .filter(BsonDocument.parse('{str: "FOO"}')) .collation(caseInsensitiveCollation) @@ -259,7 +259,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -273,7 +273,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat .append('cursor', new BsonDocument()) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new CountDocumentsOperation(getNamespace()) + def operation = new CountDocumentsOperation(getNamespace(), null) when: operation.execute(binding, operationContext) @@ -297,7 +297,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) @@ -311,7 +311,7 @@ class CountDocumentsOperationSpecification extends OperationFunctionalSpecificat .append('cursor', new BsonDocument()) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new CountDocumentsOperation(getNamespace()) + def operation = new CountDocumentsOperation(getNamespace(), null) when: executeAsync(operation, binding, operationContext) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateCollectionOperationSpecification.groovy index 860ffb4a2bf..074ddfa422b 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateCollectionOperationSpecification.groovy @@ -112,8 +112,8 @@ class CreateCollectionOperationSpecification extends OperationFunctionalSpecific then: def binding = ClusterFixture.getBinding() - new ListCollectionsOperation(getDatabaseName(), new BsonDocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new ListCollectionsOperation(getDatabaseName(), new BsonDocumentCodec(), null) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) .next() .find { it -> it.getString('name').value == getCollectionName() } .getDocument('options').getDocument('storageEngine') == operation.storageEngineOptions @@ -133,8 +133,8 @@ class CreateCollectionOperationSpecification extends OperationFunctionalSpecific then: def binding = ClusterFixture.getBinding() - new ListCollectionsOperation(getDatabaseName(), new BsonDocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new ListCollectionsOperation(getDatabaseName(), new BsonDocumentCodec(), null) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) .next() .find { it -> it.getString('name').value == getCollectionName() } .getDocument('options').getDocument('storageEngine') == operation.storageEngineOptions @@ -253,9 +253,9 @@ class CreateCollectionOperationSpecification extends OperationFunctionalSpecific def getCollectionInfo(String collectionName) { def binding = getBinding() - new ListCollectionsOperation(databaseName, new BsonDocumentCodec()).filter(new BsonDocument('name', + new ListCollectionsOperation(databaseName, new BsonDocumentCodec(), null).filter(new BsonDocument('name', new BsonString(collectionName))).execute(binding, - ClusterFixture.getOperationContext(binding.getReadPreference())).tryNext()?.head() + ClusterFixture.createOperationContext(binding.getReadPreference())).tryNext()?.head() } def collectionNameExists(String collectionName) { @@ -268,13 +268,14 @@ class CreateCollectionOperationSpecification extends OperationFunctionalSpecific def binding = getBinding() return new CommandReadOperation<>(getDatabaseName(), new BsonDocument('collStats', new BsonString(getCollectionName())), - new BsonDocumentCodec()).execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) } def binding = ClusterFixture.getBinding() BatchCursor cursor = new AggregateOperation( getNamespace(), singletonList(new BsonDocument('$collStats', new BsonDocument('storageStats', new BsonDocument()))), - new BsonDocumentCodec()).execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec(), + null).execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) try { return cursor.next().first().getDocument('storageStats') } finally { diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateIndexesOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateIndexesOperationSpecification.groovy index fce0904b786..12e0335d751 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateIndexesOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateIndexesOperationSpecification.groovy @@ -493,8 +493,8 @@ class CreateIndexesOperationSpecification extends OperationFunctionalSpecificati def indexes = [] def binding = ClusterFixture.getBinding() - def cursor = new ListIndexesOperation(getNamespace(), new DocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + def cursor = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) while (cursor.hasNext()) { indexes.addAll(cursor.next()) } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateViewOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateViewOperationSpecification.groovy index b8145de44b4..b90c5ce4586 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CreateViewOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CreateViewOperationSpecification.groovy @@ -29,8 +29,8 @@ import org.bson.BsonString import org.bson.codecs.BsonDocumentCodec import spock.lang.IgnoreIf +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet class CreateViewOperationSpecification extends OperationFunctionalSpecification { @@ -123,8 +123,8 @@ class CreateViewOperationSpecification extends OperationFunctionalSpecification def getCollectionInfo(String collectionName) { def binding = getBinding() - new ListCollectionsOperation(databaseName, new BsonDocumentCodec()).filter(new BsonDocument('name', - new BsonString(collectionName))).execute(binding, getOperationContext(binding.getReadPreference())).tryNext()?.head() + new ListCollectionsOperation(databaseName, new BsonDocumentCodec(), null).filter(new BsonDocument('name', + new BsonString(collectionName))).execute(binding, createOperationContext(binding.getReadPreference())).tryNext()?.head() } def collectionNameExists(String collectionName) { diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/DistinctOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/DistinctOperationSpecification.groovy index f73c301d422..e59d03eef47 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/DistinctOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/DistinctOperationSpecification.groovy @@ -51,7 +51,7 @@ import org.bson.codecs.StringCodec import org.bson.codecs.ValueCodecProvider import org.bson.types.ObjectId -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.connection.ServerType.STANDALONE import static com.mongodb.internal.operation.OperationReadConcernHelper.appendReadConcernToCommand @@ -71,7 +71,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { def 'should have the correct defaults'() { when: - DistinctOperation operation = new DistinctOperation(getNamespace(), 'name', stringDecoder) + DistinctOperation operation = new DistinctOperation(getNamespace(), 'name', stringDecoder, null) then: operation.getFilter() == null @@ -83,7 +83,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { def filter = new BsonDocument('filter', new BsonInt32(1)) when: - DistinctOperation operation = new DistinctOperation(getNamespace(), 'name', stringDecoder) + DistinctOperation operation = new DistinctOperation(getNamespace(), 'name', stringDecoder, null) .filter(filter) .collation(defaultCollation) @@ -98,7 +98,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { Document sam = new Document('name', 'Sam').append('age', 21) Document pete2 = new Document('name', 'Pete').append('age', 25) getCollectionHelper().insertDocuments(new DocumentCodec(), pete, sam, pete2) - DistinctOperation operation = new DistinctOperation(getNamespace(), 'name', stringDecoder) + DistinctOperation operation = new DistinctOperation(getNamespace(), 'name', stringDecoder, null) when: def results = executeAndCollectBatchCursorResults(operation, async) @@ -116,7 +116,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { Document sam = new Document('name', 'Sam').append('age', 21) Document pete2 = new Document('name', 'Pete').append('age', 25) getCollectionHelper().insertDocuments(new DocumentCodec(), pete, sam, pete2) - def operation = new DistinctOperation(getNamespace(), 'name', stringDecoder) + def operation = new DistinctOperation(getNamespace(), 'name', stringDecoder, null) .filter(new BsonDocument('age', new BsonInt32(25))) when: @@ -147,7 +147,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { .append('numberOfJobs', sam.numberOfJobs) getCollectionHelper().insertDocuments(new Document('worker', peteDocument), new Document('worker', samDocument)) - DistinctOperation operation = new DistinctOperation(getNamespace(), 'worker', new WorkerCodec()) + DistinctOperation operation = new DistinctOperation(getNamespace(), 'worker', new WorkerCodec(), null) when: def results = executeAndCollectBatchCursorResults(operation, async) @@ -166,7 +166,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { Document sam = new Document('name', 1) Document pete2 = new Document('name', new Document('earle', 'Jones')) getCollectionHelper().insertDocuments(new DocumentCodec(), pete, sam, pete2) - DistinctOperation operation = new DistinctOperation(getNamespace(), 'name', stringDecoder) + DistinctOperation operation = new DistinctOperation(getNamespace(), 'name', stringDecoder, null) when: execute(operation, async) @@ -180,7 +180,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { def 'should use the ReadBindings readPreference to set secondaryOk'() { when: - def operation = new DistinctOperation(helper.namespace, 'name', helper.decoder) + def operation = new DistinctOperation(helper.namespace, 'name', helper.decoder, null) then: testOperationSecondaryOk(operation, [3, 4, 0], readPreference, async, helper.commandResult) @@ -191,7 +191,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { def 'should create the expected command'() { when: - def operation = new DistinctOperation(helper.namespace, 'name', new BsonDocumentCodec()) + def operation = new DistinctOperation(helper.namespace, 'name', new BsonDocumentCodec(), null) .filter(new BsonDocument('a', BsonBoolean.TRUE)) .collation(defaultCollation) @@ -211,7 +211,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { given: def document = Document.parse('{str: "foo"}') getCollectionHelper().insertDocuments(document) - def operation = new DistinctOperation(namespace, 'str', stringDecoder) + def operation = new DistinctOperation(namespace, 'str', stringDecoder, null) .filter(BsonDocument.parse('{str: "FOO"}}')) .collation(caseInsensitiveCollation) @@ -227,7 +227,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -239,7 +239,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { .append('key', new BsonString('str')) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new DistinctOperation(getNamespace(), 'str', new StringCodec()) + def operation = new DistinctOperation(getNamespace(), 'str', new StringCodec(), null) when: operation.execute(binding, operationContext) @@ -266,7 +266,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) @@ -278,7 +278,7 @@ class DistinctOperationSpecification extends OperationFunctionalSpecification { .append('key', new BsonString('str')) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new DistinctOperation(getNamespace(), 'str', new StringCodec()) + def operation = new DistinctOperation(getNamespace(), 'str', new StringCodec(), null) when: executeAsync(operation, binding, operationContext) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/DropCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/DropCollectionOperationSpecification.groovy index eb8f3efa573..d8f95daec3b 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/DropCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/DropCollectionOperationSpecification.groovy @@ -39,7 +39,7 @@ class DropCollectionOperationSpecification extends OperationFunctionalSpecificat when: def binding = getBinding() new DropCollectionOperation(getNamespace(), WriteConcern.ACKNOWLEDGED) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: !collectionNameExists(getCollectionName()) @@ -64,7 +64,7 @@ class DropCollectionOperationSpecification extends OperationFunctionalSpecificat when: new DropCollectionOperation(namespace, WriteConcern.ACKNOWLEDGED) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: !collectionNameExists('nonExistingCollection') @@ -91,7 +91,7 @@ class DropCollectionOperationSpecification extends OperationFunctionalSpecificat when: def binding = getBinding() - async ? executeAsync(operation) : operation.execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + async ? executeAsync(operation) : operation.execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) then: def ex = thrown(MongoWriteConcernException) @@ -103,8 +103,8 @@ class DropCollectionOperationSpecification extends OperationFunctionalSpecificat } def collectionNameExists(String collectionName) { - def cursor = new ListCollectionsOperation(databaseName, new DocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + def cursor = new ListCollectionsOperation(databaseName, new DocumentCodec(), null) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) if (!cursor.hasNext()) { return false } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/DropDatabaseOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/DropDatabaseOperationSpecification.groovy index b56e2c1fe50..a9a74fc3190 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/DropDatabaseOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/DropDatabaseOperationSpecification.groovy @@ -16,7 +16,6 @@ package com.mongodb.internal.operation - import com.mongodb.MongoWriteConcernException import com.mongodb.OperationFunctionalSpecification import com.mongodb.WriteConcern @@ -25,10 +24,10 @@ import org.bson.Document import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.configureFailPoint import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet import static com.mongodb.ClusterFixture.isSharded @@ -80,7 +79,7 @@ class DropDatabaseOperationSpecification extends OperationFunctionalSpecificatio def binding = getBinding() when: - async ? executeAsync(operation) : operation.execute(binding, getOperationContext(binding.getReadPreference())) + async ? executeAsync(operation) : operation.execute(binding, createOperationContext(binding.getReadPreference())) then: def ex = thrown(MongoWriteConcernException) @@ -91,9 +90,9 @@ class DropDatabaseOperationSpecification extends OperationFunctionalSpecificatio async << [true, false] } - def databaseNameExists(String databaseName) { - new ListDatabasesOperation(new DocumentCodec()).execute(binding, - getOperationContext(binding.getReadPreference())).next()*.name.contains(databaseName) + static databaseNameExists(String databaseName) { + new ListDatabasesOperation(new DocumentCodec(), null).execute(binding, + createOperationContext(binding.getReadPreference())).next()*.name.contains(databaseName) } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/DropIndexOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/DropIndexOperationSpecification.groovy index 7b1f5b2a392..74f88d0cfb6 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/DropIndexOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/DropIndexOperationSpecification.groovy @@ -158,8 +158,8 @@ class DropIndexOperationSpecification extends OperationFunctionalSpecification { def getIndexes() { def indexes = [] def binding = getBinding() - def cursor = new ListIndexesOperation(getNamespace(), new DocumentCodec()) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + def cursor = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) while (cursor.hasNext()) { indexes.addAll(cursor.next()) } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndDeleteOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndDeleteOperationSpecification.groovy index 64c6123a84b..e0f52e4ab60 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndDeleteOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndDeleteOperationSpecification.groovy @@ -51,7 +51,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati def 'should have the correct defaults'() { when: - def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec) + def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec) then: operation.getNamespace() == getNamespace() @@ -70,7 +70,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati def projection = BsonDocument.parse('{ projection : 1}') when: - def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec) + def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec) .filter(filter) .sort(sort) .projection(projection) @@ -91,7 +91,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati getCollectionHelper().insertDocuments(new DocumentCodec(), pete, sam) when: - def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec) + def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec) .filter(new BsonDocument('name', new BsonString('Pete'))) Document returnedDocument = execute(operation, async) @@ -113,7 +113,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati when: FindAndDeleteOperation operation = new FindAndDeleteOperation(getNamespace(), - ACKNOWLEDGED, false, workerCodec).filter(new BsonDocument('name', new BsonString('Pete'))) + ACKNOWLEDGED, false, null, workerCodec).filter(new BsonDocument('name', new BsonString('Pete'))) Worker returnedDocument = execute(operation, async) then: @@ -132,7 +132,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati CollectionHelper helper = new CollectionHelper(documentCodec, getNamespace()) Document pete = new Document('name', 'Pete').append('job', 'handyman') helper.insertDocuments(new DocumentCodec(), pete) - def operation = new FindAndDeleteOperation(getNamespace(), new WriteConcern(5, 1), false, + def operation = new FindAndDeleteOperation(getNamespace(), new WriteConcern(5, 1), false, null, documentCodec).filter(new BsonDocument('name', new BsonString('Pete'))) when: @@ -164,7 +164,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati "writeConcernError": {"code": 91, "errmsg": "Replication is being shut down"}}}''') configureFailPoint(failPoint) - def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec).filter(new BsonDocument('name', new BsonString('Pete'))) when: @@ -191,7 +191,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati def includeWriteConcern = writeConcern.isAcknowledged() && !writeConcern.isServerDefault() def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) def operation = new FindAndDeleteOperation(getNamespace(), writeConcern as WriteConcern, - retryWrites as boolean, documentCodec) + retryWrites as boolean, null, documentCodec) def expectedCommand = new BsonDocument('findAndModify', new BsonString(getNamespace().getCollectionName())) .append('remove', BsonBoolean.TRUE) @@ -244,7 +244,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati getCollectionHelper().insertDocuments(new DocumentCodec(), pete, sam) when: - def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, true, documentCodec) + def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec) .filter(new BsonDocument('name', new BsonString('Pete'))) enableOnPrimaryTransactionalWriteFailPoint(BsonDocument.parse('{times: 1}')) @@ -265,7 +265,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati def 'should retry if the connection initially fails'() { when: def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) - def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, true, documentCodec) + def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec) def expectedCommand = new BsonDocument('findAndModify', new BsonString(getNamespace().getCollectionName())) .append('remove', BsonBoolean.TRUE) .append('txnNumber', new BsonInt64(0)) @@ -279,7 +279,7 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati def 'should throw original error when retrying and failing'() { given: - def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, true, documentCodec) + def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec) def originalException = new MongoSocketException('Some failure', new ServerAddress()) when: @@ -299,14 +299,14 @@ class FindAndDeleteOperationSpecification extends OperationFunctionalSpecificati commandException == originalException where: - async << [false] + async << [true, false] } def 'should support collation'() { given: def document = Document.parse('{_id: 1, str: "foo"}') getCollectionHelper().insertDocuments(document) - def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec) + def operation = new FindAndDeleteOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec) .filter(BsonDocument.parse('{str: "FOO"}')) .collation(caseInsensitiveCollation) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndReplaceOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndReplaceOperationSpecification.groovy index 50dd68fa810..3cc738add44 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndReplaceOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndReplaceOperationSpecification.groovy @@ -59,7 +59,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat def 'should have the correct defaults and passed values'() { when: def replacement = new BsonDocument('replace', new BsonInt32(1)) - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, replacement) then: @@ -81,7 +81,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat def projection = new BsonDocument('projection', new BsonInt32(1)) when: - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, new BsonDocument('replace', new BsonInt32(1))).filter(filter).sort(sort).projection(projection) .bypassDocumentValidation(true).upsert(true).returnOriginal(false) .collation(defaultCollation) @@ -106,7 +106,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat helper.insertDocuments(new DocumentCodec(), pete, sam) when: - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, jordan) .filter(new BsonDocument('name', new BsonString('Pete'))) Document returnedDocument = execute(operation, async) @@ -117,7 +117,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat helper.find().get(0).getString('name') == 'Jordan' when: - operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, + operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, new BsonDocumentWrapper(pete, documentCodec)) .filter(new BsonDocument('name', new BsonString('Jordan'))) .returnOriginal(false) @@ -141,7 +141,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat helper.insertDocuments(new WorkerCodec(), pete, sam) when: - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, workerCodec, replacement).filter(new BsonDocument('name', new BsonString('Pete'))) Worker returnedDocument = execute(operation, async) @@ -151,7 +151,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat when: replacement = new BsonDocumentWrapper(pete, workerCodec) - operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, workerCodec, + operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, workerCodec, replacement) .filter(new BsonDocument('name', new BsonString('Jordan'))) .returnOriginal(false) @@ -167,7 +167,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat def 'should return null if query fails to match'() { when: BsonDocument jordan = BsonDocument.parse('{name: "Jordan", job: "sparky"}') - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, jordan) .filter(new BsonDocument('name', new BsonString('Pete'))) Document returnedDocument = execute(operation, async) @@ -182,7 +182,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat def 'should throw an exception if replacement contains update operators'() { given: def replacement = new BsonDocumentWrapper(['$inc': 1] as Document, documentCodec) - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, replacement) when: @@ -206,7 +206,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat when: def replacement = new BsonDocument('level', new BsonInt32(9)) - def operation = new FindAndReplaceOperation(namespace, ACKNOWLEDGED, false, + def operation = new FindAndReplaceOperation(namespace, ACKNOWLEDGED, false, null, documentCodec, replacement) execute(operation, async) @@ -246,7 +246,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat when: def operation = new FindAndReplaceOperation(getNamespace(), - new WriteConcern(5, 1), false, documentCodec, jordan) + new WriteConcern(5, 1), false, null, documentCodec, jordan) .filter(new BsonDocument('name', new BsonString('Pete'))) execute(operation, async) @@ -260,7 +260,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat when: operation = new FindAndReplaceOperation(getNamespace(), new WriteConcern(5, 1), - false, documentCodec, jordan).filter(new BsonDocument('name', new BsonString('Bob'))) + false, null, documentCodec, jordan).filter(new BsonDocument('name', new BsonString('Bob'))) .upsert(true) execute(operation, async) @@ -292,7 +292,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat BsonDocument jordan = BsonDocument.parse('{name: "Jordan", job: "sparky"}') def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, - false, documentCodec, jordan).filter(new BsonDocument('name', new BsonString('Pete'))) + false, null, documentCodec, jordan).filter(new BsonDocument('name', new BsonString('Pete'))) when: execute(operation, async) @@ -318,7 +318,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat def includeWriteConcern = writeConcern.isAcknowledged() && !writeConcern.isServerDefault() def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) def replacement = BsonDocument.parse('{ replacement: 1}') - def operation = new FindAndReplaceOperation(getNamespace(), writeConcern, retryWrites, documentCodec, replacement) + def operation = new FindAndReplaceOperation(getNamespace(), writeConcern, retryWrites, null, documentCodec, replacement) def expectedCommand = new BsonDocument('findAndModify', new BsonString(getNamespace().getCollectionName())) .append('update', replacement) if (includeWriteConcern) { @@ -375,7 +375,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat helper.insertDocuments(new DocumentCodec(), pete, sam) when: - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, true, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec, jordan) .filter(new BsonDocument('name', new BsonString('Pete'))) @@ -398,7 +398,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat when: def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) def replacement = BsonDocument.parse('{ replacement: 1}') - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, true, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec, replacement) def expectedCommand = new BsonDocument('findAndModify', new BsonString(getNamespace().getCollectionName())) .append('update', replacement) @@ -415,7 +415,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat def 'should throw original error when retrying and failing'() { given: def replacement = BsonDocument.parse('{ replacement: 1}') - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, true, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec, replacement) def originalException = new MongoSocketException('Some failure', new ServerAddress()) @@ -444,7 +444,7 @@ class FindAndReplaceOperationSpecification extends OperationFunctionalSpecificat def document = Document.parse('{_id: 1, str: "foo"}') getCollectionHelper().insertDocuments(document) def replacement = BsonDocument.parse('{str: "bar"}') - def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndReplaceOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, replacement) .filter(BsonDocument.parse('{str: "FOO"}')) .collation(caseInsensitiveCollation) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndUpdateOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndUpdateOperationSpecification.groovy index 292d5bb471e..f721ac57ef8 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndUpdateOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/FindAndUpdateOperationSpecification.groovy @@ -61,7 +61,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def 'should have the correct defaults and passed values'() { when: def update = new BsonDocument('update', new BsonInt32(1)) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) then: @@ -79,7 +79,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def 'should have the correct defaults and passed values using update pipelines'() { when: def updatePipeline = new BsonArray(singletonList(new BsonDocument('update', new BsonInt32(1)))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, updatePipeline) + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, updatePipeline) then: operation.getNamespace() == getNamespace() @@ -101,7 +101,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def operation = new FindAndUpdateOperation(getNamespace(), - ACKNOWLEDGED, false, documentCodec, new BsonDocument('update', new BsonInt32(1))) + ACKNOWLEDGED, false, null, documentCodec, new BsonDocument('update', new BsonInt32(1))) .filter(filter) .sort(sort) .projection(projection) @@ -126,7 +126,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def projection = new BsonDocument('projection', new BsonInt32(1)) when: - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, new BsonArray(singletonList(new BsonDocument('update', new BsonInt32(1))))) .filter(filter) .sort(sort) @@ -155,7 +155,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def update = new BsonDocument('$inc', new BsonDocument('numberOfJobs', new BsonInt32(1))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) Document returnedDocument = execute(operation, async) @@ -167,7 +167,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: update = new BsonDocument('$inc', new BsonDocument('numberOfJobs', new BsonInt32(1))) - operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) .returnOriginal(false) @@ -190,7 +190,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def update = new BsonArray(singletonList(new BsonDocument('$addFields', new BsonDocument('foo', new BsonInt32(1))))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, update) + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) .returnOriginal(false) Document returnedDocument = execute(operation, false) @@ -201,7 +201,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: update = new BsonArray(singletonList(new BsonDocument('$addFields', new BsonDocument('foo', new BsonInt32(1))))) - operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, update) + operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) .returnOriginal(false) returnedDocument = execute(operation, false) @@ -221,7 +221,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def update = new BsonDocument('$inc', new BsonDocument('numberOfJobs', new BsonInt32(1))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, workerCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) Worker returnedDocument = execute(operation, async) @@ -233,7 +233,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: update = new BsonDocument('$inc', new BsonDocument('numberOfJobs', new BsonInt32(1))) - operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, workerCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) .returnOriginal(false) @@ -256,7 +256,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def update = new BsonArray(singletonList(new BsonDocument('$project', new BsonDocument('name', new BsonInt32(1))))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, update) + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) .returnOriginal(false) Document returnedDocument = execute(operation, async) @@ -272,7 +272,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def 'should return null if query fails to match'() { when: def update = new BsonDocument('$inc', new BsonDocument('numberOfJobs', new BsonInt32(1))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, update) + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) Document returnedDocument = execute(operation, async) @@ -286,7 +286,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def 'should throw an exception if update contains fields that are not update operators'() { given: def update = new BsonDocument('x', new BsonInt32(1)) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) when: @@ -303,7 +303,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def 'should throw an exception if update pipeline contains operations that are not supported'() { when: def update = new BsonArray(singletonList(new BsonDocument('$foo', new BsonDocument('x', new BsonInt32(1))))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, update) + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) execute(operation, async) then: @@ -311,7 +311,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: update = singletonList(new BsonInt32(1)) - operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, documentCodec, update) + operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) execute(operation, async) then: @@ -331,7 +331,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def update = new BsonDocument('$inc', new BsonDocument('level', new BsonInt32(-1))) - def operation = new FindAndUpdateOperation(namespace, ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(namespace, ACKNOWLEDGED, false, null, documentCodec, update) execute(operation, async) @@ -368,7 +368,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def operation = new FindAndUpdateOperation(getNamespace(), - new WriteConcern(5, 1), false, documentCodec, update) + new WriteConcern(5, 1), false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) execute(operation, async) @@ -381,7 +381,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati ex.writeResult.upsertedId == null when: - operation = new FindAndUpdateOperation(getNamespace(), new WriteConcern(5, 1), false, + operation = new FindAndUpdateOperation(getNamespace(), new WriteConcern(5, 1), false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Bob'))) .upsert(true) @@ -411,7 +411,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati configureFailPoint(failPoint) def update = new BsonDocument('$inc', new BsonDocument('numberOfJobs', new BsonInt32(1))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) @@ -439,7 +439,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def includeWriteConcern = writeConcern.isAcknowledged() && !writeConcern.isServerDefault() def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) def update = BsonDocument.parse('{ update: 1}') - def operation = new FindAndUpdateOperation(getNamespace(), writeConcern, retryWrites, documentCodec, update) + def operation = new FindAndUpdateOperation(getNamespace(), writeConcern, retryWrites, null, documentCodec, update) def expectedCommand = new BsonDocument('findAndModify', new BsonString(getNamespace().getCollectionName())) .append('update', update) if (includeWriteConcern) { @@ -496,7 +496,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def update = new BsonDocument('$inc', new BsonDocument('numberOfJobs', new BsonInt32(1))) - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, true, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec, update) .filter(new BsonDocument('name', new BsonString('Pete'))) @@ -520,7 +520,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati when: def cannedResult = new BsonDocument('value', new BsonDocumentWrapper(BsonDocument.parse('{}'), new BsonDocumentCodec())) def update = BsonDocument.parse('{ update: 1}') - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, true, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec, update) def expectedCommand = new BsonDocument('findAndModify', new BsonString(getNamespace().getCollectionName())) .append('update', update) @@ -537,7 +537,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def 'should throw original error when retrying and failing'() { given: def update = BsonDocument.parse('{ update: 1}') - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, true, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, true, null, documentCodec, update) def originalException = new MongoSocketException('Some failure', new ServerAddress()) @@ -566,7 +566,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati def document = Document.parse('{_id: 1, str: "foo"}') getCollectionHelper().insertDocuments(document) def update = BsonDocument.parse('{ $set: {str: "bar"}}') - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .filter(BsonDocument.parse('{str: "FOO"}')) .collation(caseInsensitiveCollation) @@ -588,7 +588,7 @@ class FindAndUpdateOperationSpecification extends OperationFunctionalSpecificati getCollectionHelper().insertDocuments(documentOne, documentTwo) def update = BsonDocument.parse('{ $set: {"y.$[i].b": 2}}') def arrayFilters = [BsonDocument.parse('{"i.b": 3}')] - def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, + def operation = new FindAndUpdateOperation(getNamespace(), ACKNOWLEDGED, false, null, documentCodec, update) .returnOriginal(false) .arrayFilters(arrayFilters) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy index 261e036621a..c8b107cba2c 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/FindOperationSpecification.groovy @@ -54,13 +54,12 @@ import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.executeSync import static com.mongodb.ClusterFixture.getAsyncCluster import static com.mongodb.ClusterFixture.getBinding import static com.mongodb.ClusterFixture.getCluster -import static com.mongodb.ClusterFixture.getOperationContext +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.isSharded import static com.mongodb.ClusterFixture.serverVersionLessThan import static com.mongodb.CursorType.NonTailable @@ -78,7 +77,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def decoder = new DocumentCodec() when: - FindOperation operation = new FindOperation(getNamespace(), decoder) + FindOperation operation = new FindOperation(getNamespace(), decoder, null) then: operation.getNamespace() == getNamespace() @@ -102,7 +101,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def hint = new BsonString('a_1') when: - FindOperation operation = new FindOperation(getNamespace(), new DocumentCodec()) + FindOperation operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .filter(filter) .limit(20) .skip(30) @@ -132,7 +131,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { given: def document = new Document('_id', 1) getCollectionHelper().insertDocuments(new DocumentCodec(), document) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) when: def results = executeAndCollectBatchCursorResults(operation, async) @@ -158,7 +157,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { where: [async, operation] << [ [true, false], - [new FindOperation(getNamespace(), new DocumentCodec()) + [new FindOperation(getNamespace(), new DocumentCodec(), null) .filter(new BsonDocument('_id', new BsonInt32(1)))] ].combinations() } @@ -179,7 +178,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { where: [async, operation] << [ [true, false], - [new FindOperation(getNamespace(), new DocumentCodec()) + [new FindOperation(getNamespace(), new DocumentCodec(), null) .sort(new BsonDocument('_id', new BsonInt32(1)))] ].combinations() } @@ -188,7 +187,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { given: getCollectionHelper().insertDocuments(new DocumentCodec(), new Document('x', 5).append('y', 10), new Document('_id', 1).append('x', 10)) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .projection(new BsonDocument('_id', new BsonInt32(0)).append('x', new BsonInt32(1))) when: @@ -207,7 +206,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { new Document('_id', 5)] getCollectionHelper().insertDocuments(new DocumentCodec(), documents) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .sort(new BsonDocument('_id', new BsonInt32(1))) .skip(3) @@ -227,7 +226,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { new Document('_id', 5)] getCollectionHelper().insertDocuments(new DocumentCodec(), documents) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .sort(new BsonDocument('_id', new BsonInt32(1))) .limit(limit) @@ -246,7 +245,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def documents = [new Document('_id', 1), new Document('_id', 2), new Document('_id', 3), new Document('_id', 4), new Document('_id', 5)] getCollectionHelper().insertDocuments(new DocumentCodec(), documents) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .sort(new BsonDocument('_id', new BsonInt32(1))) .batchSize(batchSize) @@ -289,7 +288,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should throw query exception'() { given: - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .filter(new BsonDocument('x', new BsonDocument('$thisIsNotAnOperator', BsonBoolean.TRUE))) when: @@ -308,7 +307,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { collectionHelper.insertDocuments(new DocumentCodec(), new Document('x', 'y').append('count', it)) } collectionHelper.createIndex(new BsonDocument('count', new BsonInt32(1))) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .max(new BsonDocument('count', new BsonInt32(11))) .hint(new BsonDocument('count', new BsonInt32(1))) @@ -328,7 +327,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { collectionHelper.insertDocuments(new DocumentCodec(), new Document('x', 'y').append('count', it)) } collectionHelper.createIndex(new BsonDocument('count', new BsonInt32(1))) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .min(new BsonDocument('count', new BsonInt32(10))) .hint(new BsonDocument('count', new BsonInt32(1))) @@ -349,7 +348,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { } collectionHelper.createIndex(new BsonDocument('x', new BsonInt32(1))) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .filter(new BsonDocument('x', new BsonInt32(7))) .returnKey(true) @@ -368,7 +367,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def index = new BsonDocument('a', new BsonInt32(1)) collectionHelper.createIndex(index) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .hint((BsonValue) hint) .asExplainableOperation(null, new BsonDocumentCodec()) @@ -390,9 +389,9 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def binding = getBinding() new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(2)), - new BsonDocumentCodec()).execute(binding, getOperationContext(binding.getReadPreference())) + new BsonDocumentCodec()).execute(binding, createOperationContext(binding.getReadPreference())) def expectedComment = 'this is a comment' - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .comment(new BsonString(expectedComment)) when: @@ -405,7 +404,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { cleanup: new CommandReadOperation<>(getDatabaseName(), new BsonDocument('profile', new BsonInt32(0)), new BsonDocumentCodec()) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) profileCollectionHelper.drop() where: @@ -417,7 +416,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { String fieldName = '$recordId' collectionHelper.insertDocuments(new BsonDocument()) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) .showRecordId(true) when: @@ -434,7 +433,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should read from a secondary'() { given: collectionHelper.insertDocuments(new DocumentCodec(), new Document()) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) def syncBinding = new ClusterBinding(getCluster(), ReadPreference.secondary()) def asyncBinding = new AsyncClusterBinding(getAsyncCluster(), ReadPreference.secondary()) @@ -454,7 +453,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def documents = [new Document('_id', 3), new Document('_id', 1), new Document('_id', 2), new Document('_id', 5), new Document('_id', 4)] collectionHelper.insertDocuments(new DocumentCodec(), documents) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) when: def hedgeOptions = isHedgeEnabled != null ? @@ -482,7 +481,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -493,7 +492,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def commandDocument = new BsonDocument('find', new BsonString(getCollectionName())) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) when: operation.execute(binding, operationContext) @@ -522,7 +521,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) @@ -533,7 +532,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def commandDocument = new BsonDocument('find', new BsonString(getCollectionName())) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new FindOperation(getNamespace(), new DocumentCodec()) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null) when: executeAsync(operation, binding, operationContext) @@ -562,7 +561,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should add allowDiskUse to command if the server version >= 3.2'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -573,7 +572,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def commandDocument = new BsonDocument('find', new BsonString(getCollectionName())).append('allowDiskUse', BsonBoolean.TRUE) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new FindOperation(getNamespace(), new DocumentCodec()).allowDiskUse(true) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null).allowDiskUse(true) when: operation.execute(binding, operationContext) @@ -602,7 +601,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def 'should add allowDiskUse to command if the server version >= 3.2 asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) @@ -613,7 +612,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def commandDocument = new BsonDocument('find', new BsonString(getCollectionName())).append('allowDiskUse', BsonBoolean.TRUE) appendReadConcernToCommand(sessionContext, UNKNOWN_WIRE_VERSION, commandDocument) - def operation = new FindOperation(getNamespace(), new DocumentCodec()).allowDiskUse(true) + def operation = new FindOperation(getNamespace(), new DocumentCodec(), null).allowDiskUse(true) when: executeAsync(operation, binding, operationContext) @@ -646,10 +645,10 @@ class FindOperationSpecification extends OperationFunctionalSpecification { def (cursorType, long maxAwaitTimeMS, long maxTimeMSForCursor) = cursorDetails def timeoutSettings = ClusterFixture.TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT.withMaxAwaitTimeMS(maxAwaitTimeMS) def timeoutContext = new TimeoutContext(timeoutSettings) - def operationContext = OPERATION_CONTEXT.withTimeoutContext(timeoutContext) + def operationContext = createOperationContext().withTimeoutContext(timeoutContext) collectionHelper.create(getCollectionName(), new CreateCollectionOptions().capped(true).sizeInBytes(1000)) - def operation = new FindOperation(namespace, new BsonDocumentCodec()) + def operation = new FindOperation(namespace, new BsonDocumentCodec(), null) .cursorType(cursorType) when: @@ -677,7 +676,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { // sanity check that the server accepts the miscallaneous flags def 'should pass miscallaneous flags through'() { given: - def operation = new FindOperation(namespace, new BsonDocumentCodec()) + def operation = new FindOperation(namespace, new BsonDocumentCodec(), null) .noCursorTimeout(true) .partial(true) @@ -695,7 +694,7 @@ class FindOperationSpecification extends OperationFunctionalSpecification { given: def document = BsonDocument.parse('{_id: 1, str: "foo"}') getCollectionHelper().insertDocuments(document) - def operation = new FindOperation(getNamespace(), new BsonDocumentCodec()) + def operation = new FindOperation(getNamespace(), new BsonDocumentCodec(), null) .filter(BsonDocument.parse('{str: "FOO"}')) .collation(caseInsensitiveCollation) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/ListCollectionsOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/ListCollectionsOperationSpecification.groovy index ad55b706ba2..ceb3c96baac 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/ListCollectionsOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/ListCollectionsOperationSpecification.groovy @@ -16,6 +16,7 @@ package com.mongodb.internal.operation +import com.mongodb.ClusterFixture import com.mongodb.MongoNamespace import com.mongodb.OperationFunctionalSpecification import com.mongodb.ReadPreference @@ -43,10 +44,9 @@ import org.bson.Document import org.bson.codecs.Decoder import org.bson.codecs.DocumentCodec -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext import static org.junit.jupiter.api.Assertions.assertEquals class ListCollectionsOperationSpecification extends OperationFunctionalSpecification { @@ -55,12 +55,12 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should return empty set if database does not exist'() { given: - def operation = new ListCollectionsOperation(madeUpDatabase, new DocumentCodec()) + def operation = new ListCollectionsOperation(madeUpDatabase, new DocumentCodec(), null) def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: !cursor.hasNext() @@ -72,7 +72,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should return empty cursor if database does not exist asynchronously'() { given: - def operation = new ListCollectionsOperation(madeUpDatabase, new DocumentCodec()) + def operation = new ListCollectionsOperation(madeUpDatabase, new DocumentCodec(), null) when: def cursor = executeAsync(operation) @@ -88,7 +88,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should return collection names if a collection exists'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null) def helper = getCollectionHelper() def helper2 = getCollectionHelper(new MongoNamespace(databaseName, 'collection2')) def codec = new DocumentCodec() @@ -98,7 +98,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collections = cursor.next() def names = collections*.get('name') @@ -110,7 +110,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should filter collection names if a name filter is specified'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null) .filter(new BsonDocument('name', new BsonString('collection2'))) def helper = getCollectionHelper() def helper2 = getCollectionHelper(new MongoNamespace(databaseName, 'collection2')) @@ -121,7 +121,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference()) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference()) ) def collections = cursor.next() def names = collections*.get('name') @@ -133,7 +133,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should filter capped collections'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null) .filter(new BsonDocument('options.capped', BsonBoolean.TRUE)) def helper = getCollectionHelper() getCollectionHelper().create('collection3', new CreateCollectionOptions().capped(true).sizeInBytes(1000)) @@ -143,7 +143,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collections = cursor.next() def names = collections*.get('name') @@ -154,14 +154,14 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should only get collection names when nameOnly is requested'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null) .nameOnly(true) getCollectionHelper().create('collection5', new CreateCollectionOptions()) def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collection = cursor.next()[0] then: @@ -170,7 +170,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should only get collection names when nameOnly and authorizedCollections are requested'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null) .nameOnly(true) .authorizedCollections(true) getCollectionHelper().create('collection6', new CreateCollectionOptions()) @@ -178,7 +178,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collection = cursor.next()[0] then: @@ -187,7 +187,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should get all fields when authorizedCollections is requested and nameOnly is not requested'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null) .nameOnly(false) .authorizedCollections(true) getCollectionHelper().create('collection8', new CreateCollectionOptions()) @@ -195,7 +195,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collection = cursor.next()[0] then: @@ -204,7 +204,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should return collection names if a collection exists asynchronously'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null) def helper = getCollectionHelper() def helper2 = getCollectionHelper(new MongoNamespace(databaseName, 'collection2')) def codec = new DocumentCodec() @@ -227,14 +227,14 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() given: new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null).batchSize(2) when: binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: cursor.hasNext() @@ -247,13 +247,13 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() given: new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null).batchSize(2) when: binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def list = cursorToListWithNext(cursor) then: @@ -272,14 +272,14 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def binding = getBinding() given: new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null).batchSize(2) when: binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: cursor.hasNext() @@ -298,13 +298,13 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica given: def binding = getBinding() new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null).batchSize(2) when: binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def list = cursorToListWithTryNext(cursor) then: @@ -318,9 +318,9 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica given: def binding = getBinding() new DropDatabaseOperation(databaseName, WriteConcern.ACKNOWLEDGED) - .execute(binding, getOperationContext(binding.getReadPreference())) + .execute(binding, createOperationContext(binding.getReadPreference())) addSeveralIndexes() - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null).batchSize(2) when: def cursor = executeAsync(operation) @@ -333,7 +333,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should use the set batchSize of collections'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null).batchSize(2) def codec = new DocumentCodec() getCollectionHelper().insertDocuments(codec, ['a': 1] as Document) getCollectionHelper(new MongoNamespace(databaseName, 'collection2')).insertDocuments(codec, ['a': 1] as Document) @@ -344,7 +344,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica when: def binding = getBinding() - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collections = cursor.next() then: @@ -367,7 +367,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should use the set batchSize of collections asynchronously'() { given: - def operation = new ListCollectionsOperation(databaseName, new DocumentCodec()).batchSize(2) + def operation = new ListCollectionsOperation(databaseName, new DocumentCodec(), null).batchSize(2) def codec = new DocumentCodec() getCollectionHelper().insertDocuments(codec, ['a': 1] as Document) getCollectionHelper(new MongoNamespace(databaseName, 'collection2')).insertDocuments(codec, ['a': 1] as Document) @@ -398,6 +398,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should use the readPreference to set secondaryOk'() { given: + def operationContext = ClusterFixture.createOperationContext() def connection = Mock(Connection) def connectionSource = Stub(ConnectionSource) { getConnection(_) >> connection @@ -407,15 +408,15 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica getReadConnectionSource(_) >> connectionSource getReadPreference() >> readPreference } - def operation = new ListCollectionsOperation(helper.dbName, helper.decoder) + def operation = new ListCollectionsOperation(helper.dbName, helper.decoder, null) when: '3.6.0' - operation.execute(readBinding, OPERATION_CONTEXT) + operation.execute(readBinding, operationContext) then: _ * connection.getDescription() >> helper.threeSixConnectionDescription 1 * connection.command(_, _, _, readPreference, _, _) >> { - assertEquals(((OperationContext) it[5]).getId(), OPERATION_CONTEXT.getId()) + assertEquals(((OperationContext) it[5]).getId(), operationContext.getId()) helper.commandResult } 1 * connection.release() @@ -426,6 +427,7 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica def 'should use the readPreference to set secondaryOk in async'() { given: + def operationContext = ClusterFixture.createOperationContext() def connection = Mock(AsyncConnection) def connectionSource = Stub(AsyncConnectionSource) { getConnection(_, _) >> { it[1].onResult(connection, null) } @@ -435,15 +437,14 @@ class ListCollectionsOperationSpecification extends OperationFunctionalSpecifica getReadConnectionSource(_, _) >> { it[1].onResult(connectionSource, null) } getReadPreference() >> readPreference } - def operation = new ListCollectionsOperation(helper.dbName, helper.decoder) - + def operation = new ListCollectionsOperation(helper.dbName, helper.decoder, null) when: '3.6.0' - operation.executeAsync(readBinding, OPERATION_CONTEXT, Stub(SingleResultCallback)) + operation.executeAsync(readBinding, operationContext, Stub(SingleResultCallback)) then: _ * connection.getDescription() >> helper.threeSixConnectionDescription 1 * connection.commandAsync(helper.dbName, _, _, readPreference, _, _, *_) >> { - assertEquals(((OperationContext) it[5]).getId(), OPERATION_CONTEXT.getId()) + assertEquals(((OperationContext) it[5]).getId(), operationContext.getId()) it.last().onResult(helper.commandResult, null) } where: diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/ListDatabasesOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/ListDatabasesOperationSpecification.groovy index 55504d0babc..e0b3b342e7b 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/ListDatabasesOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/ListDatabasesOperationSpecification.groovy @@ -33,7 +33,7 @@ import org.bson.Document import org.bson.codecs.Decoder import org.bson.codecs.DocumentCodec -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class ListDatabasesOperationSpecification extends OperationFunctionalSpecification { def codec = new DocumentCodec() @@ -41,7 +41,7 @@ class ListDatabasesOperationSpecification extends OperationFunctionalSpecificati def 'should return a list of database names'() { given: getCollectionHelper().insertDocuments(new DocumentCodec(), new Document('_id', 1)) - def operation = new ListDatabasesOperation(codec) + def operation = new ListDatabasesOperation(codec, null) when: def names = executeAndCollectBatchCursorResults(operation, async)*.get('name') @@ -79,10 +79,10 @@ class ListDatabasesOperationSpecification extends OperationFunctionalSpecificati getReadConnectionSource(_) >> connectionSource getReadPreference() >> readPreference } - def operation = new ListDatabasesOperation(helper.decoder) + def operation = new ListDatabasesOperation(helper.decoder, null) when: - operation.execute(readBinding, OPERATION_CONTEXT) + operation.execute(readBinding, createOperationContext()) then: _ * connection.getDescription() >> helper.connectionDescription @@ -104,10 +104,10 @@ class ListDatabasesOperationSpecification extends OperationFunctionalSpecificati getReadPreference() >> readPreference getReadConnectionSource(_, _) >> { it[1].onResult(connectionSource, null) } } - def operation = new ListDatabasesOperation(helper.decoder) + def operation = new ListDatabasesOperation(helper.decoder, null) when: - operation.executeAsync(readBinding, OPERATION_CONTEXT, Stub(SingleResultCallback)) + operation.executeAsync(readBinding, createOperationContext(), Stub(SingleResultCallback)) then: _ * connection.getDescription() >> helper.connectionDescription diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/ListIndexesOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/ListIndexesOperationSpecification.groovy index c11d67bcf22..43b81f8aaa0 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/ListIndexesOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/ListIndexesOperationSpecification.groovy @@ -16,7 +16,7 @@ package com.mongodb.internal.operation - +import com.mongodb.ClusterFixture import com.mongodb.MongoNamespace import com.mongodb.OperationFunctionalSpecification import com.mongodb.ReadPreference @@ -44,21 +44,20 @@ import org.bson.codecs.Decoder import org.bson.codecs.DocumentCodec import org.junit.jupiter.api.Assertions -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext +import static com.mongodb.ClusterFixture.createOperationContext class ListIndexesOperationSpecification extends OperationFunctionalSpecification { def 'should return empty list for nonexistent collection'() { given: - def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec()) + def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null) def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: !cursor.hasNext() @@ -67,7 +66,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should return empty list for nonexistent collection asynchronously'() { given: - def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec()) + def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null) when: AsyncBatchCursor cursor = executeAsync(operation) @@ -81,13 +80,13 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should return default index on Collection that exists'() { given: - def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec()) + def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null) getCollectionHelper().insertDocuments(new DocumentCodec(), new Document('documentThat', 'forces creation of the Collection')) def binding = getBinding() when: - BatchCursor indexes = operation.execute(binding, getOperationContext(binding.getReadPreference())) + BatchCursor indexes = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: def firstBatch = indexes.next() @@ -99,7 +98,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should return default index on Collection that exists asynchronously'() { given: - def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec()) + def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null) getCollectionHelper().insertDocuments(new DocumentCodec(), new Document('documentThat', 'forces creation of the Collection')) when: @@ -115,18 +114,18 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should return created indexes on Collection'() { given: - def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec()) + def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null) collectionHelper.createIndex(new BsonDocument('theField', new BsonInt32(1))) collectionHelper.createIndex(new BsonDocument('compound', new BsonInt32(1)).append('index', new BsonInt32(-1))) def binding = getBinding() new CreateIndexesOperation(namespace, [new IndexRequest(new BsonDocument('unique', new BsonInt32(1))).unique(true)], null).execute(binding, - getOperationContext(binding.getReadPreference())) + createOperationContext(binding.getReadPreference())) when: binding = getBinding() - BatchCursor cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + BatchCursor cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) then: def indexes = cursor.next() @@ -139,14 +138,14 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should return created indexes on Collection asynchronously'() { given: - def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec()) + def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null) collectionHelper.createIndex(new BsonDocument('theField', new BsonInt32(1))) collectionHelper.createIndex(new BsonDocument('compound', new BsonInt32(1)).append('index', new BsonInt32(-1))) def binding = getBinding() new CreateIndexesOperation(namespace, [new IndexRequest(new BsonDocument('unique', new BsonInt32(1))).unique(true)], null).execute(binding, - getOperationContext(binding.getReadPreference())) + createOperationContext(binding.getReadPreference())) when: def cursor = executeAsync(operation) @@ -162,7 +161,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should use the set batchSize of collections'() { given: - def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec()).batchSize(2) + def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null).batchSize(2) collectionHelper.createIndex(new BsonDocument('collection1', new BsonInt32(1))) collectionHelper.createIndex(new BsonDocument('collection2', new BsonInt32(1))) collectionHelper.createIndex(new BsonDocument('collection3', new BsonInt32(1))) @@ -172,7 +171,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def binding = getBinding() when: - def cursor = operation.execute(binding, getOperationContext(binding.getReadPreference())) + def cursor = operation.execute(binding, createOperationContext(binding.getReadPreference())) def collections = cursor.next() then: @@ -195,7 +194,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should use the set batchSize of collections asynchronously'() { given: - def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec()).batchSize(2) + def operation = new ListIndexesOperation(getNamespace(), new DocumentCodec(), null).batchSize(2) collectionHelper.createIndex(new BsonDocument('collection1', new BsonInt32(1))) collectionHelper.createIndex(new BsonDocument('collection2', new BsonInt32(1))) collectionHelper.createIndex(new BsonDocument('collection3', new BsonInt32(1))) @@ -226,6 +225,7 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification def 'should use the readPreference to set secondaryOk'() { given: def connection = Mock(Connection) + def operationContext = ClusterFixture.createOperationContext() def connectionSource = Stub(ConnectionSource) { getConnection(_) >> connection getReadPreference() >> readPreference @@ -234,15 +234,15 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification getReadConnectionSource(_) >> connectionSource getReadPreference() >> readPreference } - def operation = new ListIndexesOperation(helper.namespace, helper.decoder) + def operation = new ListIndexesOperation(helper.namespace, helper.decoder, null) when: '3.6.0' - operation.execute(readBinding, OPERATION_CONTEXT) + operation.execute(readBinding, operationContext) then: _ * connection.getDescription() >> helper.threeSixConnectionDescription 1 * connection.command(_, _, _, readPreference, _, _) >> { - Assertions.assertEquals(((OperationContext) it[5]).getId(), OPERATION_CONTEXT.getId()) + Assertions.assertEquals(((OperationContext) it[5]).getId(), operationContext.getId()) helper.commandResult } 1 * connection.release() @@ -262,10 +262,10 @@ class ListIndexesOperationSpecification extends OperationFunctionalSpecification getReadPreference() >> readPreference getReadConnectionSource(_, _) >> { it[1].onResult(connectionSource, null) } } - def operation = new ListIndexesOperation(helper.namespace, helper.decoder) + def operation = new ListIndexesOperation(helper.namespace, helper.decoder, null) when: '3.6.0' - operation.executeAsync(readBinding, OPERATION_CONTEXT, Stub(SingleResultCallback)) + operation.executeAsync(readBinding, createOperationContext(), Stub(SingleResultCallback)) then: _ * connection.getDescription() >> helper.threeSixConnectionDescription diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceToCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceToCollectionOperationSpecification.groovy index 5d6be781d1f..31f8ed45715 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceToCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceToCollectionOperationSpecification.groovy @@ -64,7 +64,7 @@ class MapReduceToCollectionOperationSpecification extends OperationFunctionalSpe def cleanup() { def binding = getBinding() - def operationContext = ClusterFixture.getOperationContext(binding.getReadPreference()) + def operationContext = ClusterFixture.createOperationContext(binding.getReadPreference()) new DropCollectionOperation(mapReduceInputNamespace, WriteConcern.ACKNOWLEDGED) .execute(binding, operationContext) new DropCollectionOperation(mapReduceOutputNamespace, WriteConcern.ACKNOWLEDGED) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceWithInlineResultsOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceWithInlineResultsOperationSpecification.groovy index 8efd4e00f6c..14ee33d7ec5 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceWithInlineResultsOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/MapReduceWithInlineResultsOperationSpecification.groovy @@ -46,7 +46,7 @@ import org.bson.Document import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.DocumentCodec -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.connection.ServerType.STANDALONE import static com.mongodb.internal.operation.OperationReadConcernHelper.appendReadConcernToCommand @@ -217,7 +217,7 @@ class MapReduceWithInlineResultsOperationSpecification extends OperationFunction def 'should add read concern to command'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(ReadBinding) def source = Stub(ConnectionSource) def connection = Mock(Connection) @@ -264,7 +264,7 @@ class MapReduceWithInlineResultsOperationSpecification extends OperationFunction def 'should add read concern to command asynchronously'() { given: - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) def binding = Stub(AsyncReadBinding) def source = Stub(AsyncConnectionSource) def connection = Mock(AsyncConnection) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/MixedBulkWriteOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/MixedBulkWriteOperationSpecification.groovy index 619eb6747f7..57d62f33060 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/MixedBulkWriteOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/MixedBulkWriteOperationSpecification.groovy @@ -71,7 +71,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'should throw IllegalArgumentException for empty list of requests'() { when: - new MixedBulkWriteOperation(getNamespace(), [], true, ACKNOWLEDGED, false) + new MixedBulkWriteOperation(getNamespace(), [], true, ACKNOWLEDGED, false, null) then: thrown(IllegalArgumentException) @@ -79,7 +79,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'should have the expected passed values'() { when: - def operation = new MixedBulkWriteOperation(getNamespace(), requests, ordered, writeConcern, retryWrites) + def operation = new MixedBulkWriteOperation(getNamespace(), requests, ordered, writeConcern, retryWrites, null) .bypassDocumentValidation(bypassValidation) then: @@ -100,7 +100,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'when no document with the same id exists, should insert the document'() { given: def operation = new MixedBulkWriteOperation(getNamespace(), - [new InsertRequest(new BsonDocument('_id', new BsonInt32(1)))], ordered, ACKNOWLEDGED, false) + [new InsertRequest(new BsonDocument('_id', new BsonInt32(1)))], ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -120,7 +120,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def document = new BsonDocument('_id', new BsonInt32(1)) getCollectionHelper().insertDocuments(document) def operation = new MixedBulkWriteOperation(getNamespace(), [new InsertRequest(document)], ordered, - ACKNOWLEDGED, false) + ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -136,7 +136,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'RawBsonDocument should not generate an _id'() { given: def operation = new MixedBulkWriteOperation(getNamespace(), - [new InsertRequest(RawBsonDocument.parse('{_id: 1}'))], ordered, ACKNOWLEDGED, false) + [new InsertRequest(RawBsonDocument.parse('{_id: 1}'))], ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -156,7 +156,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat getCollectionHelper().insertDocuments(new DocumentCodec(), new Document('x', true), new Document('x', true)) def operation = new MixedBulkWriteOperation(getNamespace(), [new DeleteRequest(new BsonDocument('x', BsonBoolean.TRUE)).multi(false)], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -175,7 +175,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat new Document('x', false)) def operation = new MixedBulkWriteOperation(getNamespace(), [new DeleteRequest(new BsonDocument('x', BsonBoolean.TRUE))], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -195,7 +195,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat [new UpdateRequest(new BsonDocument('x', BsonBoolean.TRUE), new BsonDocument('$set', new BsonDocument('y', new BsonInt32(1))), UPDATE).multi(false)], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -214,7 +214,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def operation = new MixedBulkWriteOperation(getNamespace(), [new UpdateRequest(new BsonDocument('x', BsonBoolean.TRUE), new BsonDocument('$set', new BsonDocument('y', new BsonInt32(1))), - UPDATE).multi(true)], ordered, ACKNOWLEDGED, false) + UPDATE).multi(true)], ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -233,7 +233,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def query = new BsonDocument('_id', new BsonObjectId(id)) def operation = new MixedBulkWriteOperation(getNamespace(), [new UpdateRequest(query, new BsonDocument('$set', new BsonDocument('x', new BsonInt32(2))), - UPDATE).upsert(true)], ordered, ACKNOWLEDGED, false) + UPDATE).upsert(true)], ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -253,7 +253,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def operation = new MixedBulkWriteOperation(getNamespace(), [new UpdateRequest(query, new BsonDocument('$set', new BsonDocument('x', new BsonInt32(2))), UPDATE).upsert(true).multi(true)], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -274,7 +274,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat [new UpdateRequest(new BsonDocument('x', BsonBoolean.TRUE), new BsonDocument('$set', new BsonDocument('y', new BsonInt32(1))), UPDATE).multi(false).upsert(true)], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -294,7 +294,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat [new UpdateRequest(new BsonDocument('x', BsonBoolean.TRUE), new BsonDocument('$set', new BsonDocument('y', new BsonInt32(1))), UPDATE).upsert(true).multi(true)], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -312,7 +312,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def id = new ObjectId() def operation = new MixedBulkWriteOperation(getNamespace(), [new UpdateRequest(new BsonDocument('_id', new BsonObjectId(id)), new BsonDocument(), UPDATE)], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -329,7 +329,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def id = new ObjectId() def operation = new MixedBulkWriteOperation(getNamespace(), [new UpdateRequest(new BsonDocument('_id', new BsonObjectId(id)), new BsonDocument(), REPLACE)], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -346,7 +346,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def id = new ObjectId() def operation = new MixedBulkWriteOperation(getNamespace(), [new UpdateRequest(new BsonDocument('_id', new BsonObjectId(id)), new BsonDocument('a', new BsonInt32(1)), UPDATE)], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -365,7 +365,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def operation = new MixedBulkWriteOperation(getNamespace(), [new UpdateRequest(new BsonDocument('_id', new BsonObjectId(id)), new BsonDocument('$set', new BsonDocument('x', new BsonInt32(1))), REPLACE)], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -383,7 +383,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat given: def operation = new MixedBulkWriteOperation(getNamespace(), [new InsertRequest(new BsonDocument('$inc', new BsonDocument('x', new BsonInt32(1))))], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -403,7 +403,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat new BsonDocument('$set', new BsonDocument('x', new BsonInt32(1))), REPLACE) .upsert(true)], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -424,7 +424,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat .append('x', new BsonInt32(2)), REPLACE) .upsert(true)], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -453,7 +453,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat REPLACE) .upsert(true) ], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -485,7 +485,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat REPLACE) .upsert(true) ], - ordered, UNACKNOWLEDGED, false) + ordered, UNACKNOWLEDGED, false, null) when: def result = execute(operation, binding) @@ -507,7 +507,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat [new UpdateRequest(new BsonDocument('x', BsonBoolean.TRUE), new BsonDocument('y', new BsonInt32(1)).append('x', BsonBoolean.FALSE), REPLACE).upsert(true)], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -528,7 +528,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat [new UpdateRequest(new BsonDocument('_id', new BsonInt32(1)), new BsonDocument('_id', new BsonInt32(1)) .append('x', new BsonBinary(new byte[1024 * 1024 * 16 - 30])), - REPLACE).upsert(true)], true, ACKNOWLEDGED, false) + REPLACE).upsert(true)], true, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -554,7 +554,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat new BsonDocument('_id', new BsonInt32(2)) .append('x', new BsonBinary(new byte[1024 * 1024 * 16 - 30])), REPLACE)], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -577,7 +577,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat new InsertRequest(new BsonDocument('_id', new BsonObjectId()).append('b', bsonBinary)), new InsertRequest(new BsonDocument('_id', new BsonObjectId()).append('b', bsonBinary)) ], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, true) @@ -598,7 +598,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat new InsertRequest(new BsonDocument('_id', new BsonObjectId()).append('b', bsonBinary)), new InsertRequest(new BsonDocument('_id', new BsonObjectId()).append('b', bsonBinary)) ], - true, ACKNOWLEDGED, false) + true, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, true) @@ -613,7 +613,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'should handle multi-length runs of ordered insert, update, replace, and remove'() { given: getCollectionHelper().insertDocuments(getTestInserts()) - def operation = new MixedBulkWriteOperation(getNamespace(), getTestWrites(), ordered, ACKNOWLEDGED, false) + def operation = new MixedBulkWriteOperation(getNamespace(), getTestWrites(), ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -637,13 +637,13 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat given: getCollectionHelper().insertDocuments(getTestInserts()) def operation = new MixedBulkWriteOperation(getNamespace(), getTestWrites(), ordered, UNACKNOWLEDGED, - false) + false, null) def binding = async ? getAsyncSingleConnectionBinding() : getSingleConnectionBinding() when: def result = execute(operation, binding) execute(new MixedBulkWriteOperation(namespace, - [new InsertRequest(new BsonDocument('_id', new BsonInt32(9)))], true, ACKNOWLEDGED, false,), binding) + [new InsertRequest(new BsonDocument('_id', new BsonInt32(9)))], true, ACKNOWLEDGED, false, null), binding) then: !result.wasAcknowledged() @@ -672,7 +672,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat (1..numberOfWrites).each { writes.add(new InsertRequest(new BsonDocument())) } - def operation = new MixedBulkWriteOperation(getNamespace(), writes, ordered, ACKNOWLEDGED, false) + def operation = new MixedBulkWriteOperation(getNamespace(), writes, ordered, ACKNOWLEDGED, false, null) when: execute(operation, binding) @@ -695,7 +695,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat writeOperations.add(upsert) writeOperations.add(new DeleteRequest(new BsonDocument('key', new BsonInt32(it)))) } - def operation = new MixedBulkWriteOperation(getNamespace(), writeOperations, ordered, ACKNOWLEDGED, false) + def operation = new MixedBulkWriteOperation(getNamespace(), writeOperations, ordered, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -716,7 +716,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat new BsonDocument('$set', new BsonDocument('x', new BsonInt32(3))), UPDATE), new InsertRequest(new BsonDocument('_id', new BsonInt32(1))) // this should fail with index 2 - ], true, ACKNOWLEDGED, false) + ], true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -739,7 +739,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat new BsonDocument('$set', new BsonDocument('x', new BsonInt32(3))), UPDATE), new InsertRequest(new BsonDocument('_id', new BsonInt32(3))) // this should fail with index 2 - ], false, ACKNOWLEDGED, false) + ], false, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -762,7 +762,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat for (int i = 0; i < 2000; i++) { inserts.add(new InsertRequest(new BsonDocument('_id', new BsonInt32(i)))) } - def operation = new MixedBulkWriteOperation(getNamespace(), inserts, false, ACKNOWLEDGED, false) + def operation = new MixedBulkWriteOperation(getNamespace(), inserts, false, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -784,7 +784,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat for (int i = 0; i < 2000; i++) { inserts.add(new InsertRequest(new BsonDocument('_id', new BsonInt32(i)))) } - def operation = new MixedBulkWriteOperation(getNamespace(), inserts, true, ACKNOWLEDGED, false) + def operation = new MixedBulkWriteOperation(getNamespace(), inserts, true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -806,7 +806,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat given: def operation = new MixedBulkWriteOperation(getNamespace(), [new InsertRequest(new BsonDocument('_id', new BsonInt32(1)))], - false, new WriteConcern(5, 1), false) + false, new WriteConcern(5, 1), false, null) when: execute(operation, async) @@ -826,7 +826,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def operation = new MixedBulkWriteOperation(getNamespace(), [new InsertRequest(new BsonDocument('_id', new BsonInt32(7))), new InsertRequest(new BsonDocument('_id', new BsonInt32(1))) // duplicate key - ], false, new WriteConcern(4, 1), false) + ], false, new WriteConcern(4, 1), false, null) when: execute(operation, async) // This is assuming that it won't be able to replicate to 4 servers in 1 ms @@ -849,7 +849,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def operation = new MixedBulkWriteOperation(getNamespace(), [new DeleteRequest(new BsonDocument('_id', new BsonInt32(2))), // existing key new InsertRequest(new BsonDocument('_id', new BsonInt32(1))) // existing (duplicate) key - ], true, ACKNOWLEDGED, true) + ], true, ACKNOWLEDGED, true, null) def failPoint = BsonDocument.parse('''{ "configureFailPoint": "failCommand", @@ -878,7 +878,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'should throw IllegalArgumentException when passed an empty bulk operation'() { when: - new MixedBulkWriteOperation(getNamespace(), [], ordered, UNACKNOWLEDGED, false) + new MixedBulkWriteOperation(getNamespace(), [], ordered, UNACKNOWLEDGED, false, null) then: thrown(IllegalArgumentException) @@ -890,7 +890,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'should throw if bypassDocumentValidation is set and writeConcern is UNACKNOWLEDGED'() { given: def operation = new MixedBulkWriteOperation(getNamespace(), - [new InsertRequest(BsonDocument.parse('{ level: 9 }'))], true, UNACKNOWLEDGED, false) + [new InsertRequest(BsonDocument.parse('{ level: 9 }'))], true, UNACKNOWLEDGED, false, null) .bypassDocumentValidation(bypassDocumentValidation) when: @@ -906,7 +906,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'should throw if collation is set and write is UNACKNOWLEDGED'() { given: def operation = new MixedBulkWriteOperation(getNamespace(), - [new DeleteRequest(BsonDocument.parse('{ level: 9 }')).collation(defaultCollation)], true, UNACKNOWLEDGED, false) + [new DeleteRequest(BsonDocument.parse('{ level: 9 }')).collation(defaultCollation)], true, UNACKNOWLEDGED, false, null) when: execute(operation, async) @@ -925,7 +925,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat collectionHelper.create(namespace.getCollectionName(), new CreateCollectionOptions().validationOptions( new ValidationOptions().validator(gte('level', 10)))) def operation = new MixedBulkWriteOperation(namespace, - [new InsertRequest(BsonDocument.parse('{ level: 9 }'))], ordered, ACKNOWLEDGED, false) + [new InsertRequest(BsonDocument.parse('{ level: 9 }'))], ordered, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -961,7 +961,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat collectionHelper.insertDocuments(BsonDocument.parse('{ x: true, level: 10}')) def operation = new MixedBulkWriteOperation(namespace, [new UpdateRequest(BsonDocument.parse('{x: true}'), BsonDocument.parse('{$inc: {level: -1}}'), UPDATE).multi(false)], - ordered, ACKNOWLEDGED, false) + ordered, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -988,7 +988,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def requests = [new DeleteRequest(BsonDocument.parse('{str: "FOO"}}')).collation(caseInsensitiveCollation), new UpdateRequest(BsonDocument.parse('{str: "BAR"}}'), BsonDocument.parse('{str: "bar"}}'), REPLACE) .collation(caseInsensitiveCollation)] - def operation = new MixedBulkWriteOperation(namespace, requests, false, ACKNOWLEDGED, false) + def operation = new MixedBulkWriteOperation(namespace, requests, false, ACKNOWLEDGED, false, null) when: BulkWriteResult result = execute(operation, async) @@ -1006,7 +1006,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def testWrites = getTestWrites() Collections.shuffle(testWrites) getCollectionHelper().insertDocuments(getTestInserts()) - def operation = new MixedBulkWriteOperation(getNamespace(), testWrites, true, ACKNOWLEDGED, true) + def operation = new MixedBulkWriteOperation(getNamespace(), testWrites, true, ACKNOWLEDGED, true, null) when: if (isDiscoverableReplicaSet()) { @@ -1049,7 +1049,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat given: def testWrites = getTestWrites() getCollectionHelper().insertDocuments(getTestInserts()) - def operation = new MixedBulkWriteOperation(getNamespace(), testWrites, true, ACKNOWLEDGED, true) + def operation = new MixedBulkWriteOperation(getNamespace(), testWrites, true, ACKNOWLEDGED, true, null) when: enableOnPrimaryTransactionalWriteFailPoint(BsonDocument.parse(failPoint)) @@ -1074,7 +1074,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat given: def testWrites = getTestWrites() getCollectionHelper().insertDocuments(getTestInserts()) - def operation = new MixedBulkWriteOperation(getNamespace(), testWrites, true, UNACKNOWLEDGED, true) + def operation = new MixedBulkWriteOperation(getNamespace(), testWrites, true, UNACKNOWLEDGED, true, null) when: enableOnPrimaryTransactionalWriteFailPoint(BsonDocument.parse(failPoint)) @@ -1098,13 +1098,13 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat when: def cannedResult = BsonDocument.parse('{ok: 1.0, n: 1}') def operation = new MixedBulkWriteOperation(getNamespace(), - [new InsertRequest(BsonDocument.parse('{ level: 9 }'))], true, ACKNOWLEDGED, true) + [new InsertRequest(BsonDocument.parse('{ level: 9 }'))], true, ACKNOWLEDGED, true, null) def expectedCommand = new BsonDocument('insert', new BsonString(getNamespace().getCollectionName())) .append('ordered', BsonBoolean.TRUE) .append('txnNumber', new BsonInt64(0)) then: - testOperationRetries(operation, [3, 6, 0], expectedCommand, async, cannedResult) + testOperationRetries(operation, [3, 6, 0], expectedCommand, async, cannedResult, false, 2) where: async << [true, false] @@ -1113,20 +1113,20 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'should throw original error when retrying and failing'() { given: def operation = new MixedBulkWriteOperation(getNamespace(), - [new InsertRequest(BsonDocument.parse('{ level: 9 }'))], true, ACKNOWLEDGED, true) + [new InsertRequest(BsonDocument.parse('{ level: 9 }'))], true, ACKNOWLEDGED, true, null) def originalException = new MongoSocketException('Some failure', new ServerAddress()) when: - testRetryableOperationThrowsOriginalError(operation, [[3, 6, 0], [3, 6, 0], [3, 6, 0]], - [REPLICA_SET_PRIMARY, REPLICA_SET_PRIMARY, STANDALONE], originalException, async) + testRetryableOperationThrowsOriginalError(operation, [[3, 6, 0], [3, 6, 0]], + [REPLICA_SET_PRIMARY, STANDALONE], originalException, async, 4) then: Exception commandException = thrown() commandException == originalException when: - testRetryableOperationThrowsOriginalError(operation, [[3, 6, 0], [3, 6, 0]], - [REPLICA_SET_PRIMARY, REPLICA_SET_PRIMARY], originalException, async, 1) + testRetryableOperationThrowsOriginalError(operation, [[3, 6, 0]], + [REPLICA_SET_PRIMARY], originalException, async, 2) then: commandException = thrown() @@ -1138,7 +1138,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat def 'should not request retryable write for multi updates or deletes'() { given: - def operation = new MixedBulkWriteOperation(getNamespace(), writes, true, ACKNOWLEDGED, true) + def operation = new MixedBulkWriteOperation(getNamespace(), writes, true, ACKNOWLEDGED, true, null) when: executeWithSession(operation, async) @@ -1185,7 +1185,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat .multi(true) .arrayFilters([BsonDocument.parse('{"i.b": 1}')]), ] - def operation = new MixedBulkWriteOperation(namespace, requests, true, ACKNOWLEDGED, false) + def operation = new MixedBulkWriteOperation(namespace, requests, true, ACKNOWLEDGED, false, null) when: execute(operation, async) @@ -1206,7 +1206,7 @@ class MixedBulkWriteOperationSpecification extends OperationFunctionalSpecificat new UpdateRequest(new BsonDocument(), BsonDocument.parse('{ $set: {"y.$[i].b": 2}}'), UPDATE) .arrayFilters([BsonDocument.parse('{"i.b": 3}')]) ] - def operation = new MixedBulkWriteOperation(namespace, requests, true, UNACKNOWLEDGED, false) + def operation = new MixedBulkWriteOperation(namespace, requests, true, UNACKNOWLEDGED, false, null) when: execute(operation, async) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/RenameCollectionOperationSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/operation/RenameCollectionOperationSpecification.groovy index bc55bf5a134..5cf788922ad 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/RenameCollectionOperationSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/RenameCollectionOperationSpecification.groovy @@ -26,9 +26,9 @@ import org.bson.Document import org.bson.codecs.DocumentCodec import spock.lang.IgnoreIf +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.executeAsync import static com.mongodb.ClusterFixture.getBinding -import static com.mongodb.ClusterFixture.getOperationContext import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet import static com.mongodb.ClusterFixture.isSharded @@ -38,7 +38,7 @@ class RenameCollectionOperationSpecification extends OperationFunctionalSpecific def cleanup() { def binding = getBinding() new DropCollectionOperation(new MongoNamespace(getDatabaseName(), 'newCollection'), - WriteConcern.ACKNOWLEDGED).execute(binding, getOperationContext(binding.getReadPreference())) + WriteConcern.ACKNOWLEDGED).execute(binding, createOperationContext(binding.getReadPreference())) } def 'should return rename a collection'() { @@ -87,7 +87,7 @@ class RenameCollectionOperationSpecification extends OperationFunctionalSpecific def binding = getBinding() when: - async ? executeAsync(operation) : operation.execute(binding, getOperationContext(binding.getReadPreference())) + async ? executeAsync(operation) : operation.execute(binding, createOperationContext(binding.getReadPreference())) then: def ex = thrown(MongoWriteConcernException) @@ -100,8 +100,8 @@ class RenameCollectionOperationSpecification extends OperationFunctionalSpecific def collectionNameExists(String collectionName) { def binding = getBinding() - def cursor = new ListCollectionsOperation(databaseName, new DocumentCodec()).execute(binding, - getOperationContext(binding.getReadPreference())) + def cursor = new ListCollectionsOperation(databaseName, new DocumentCodec(), null).execute(binding, + createOperationContext(binding.getReadPreference())) if (!cursor.hasNext()) { return false } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/TestOperationHelper.java b/driver-core/src/test/functional/com/mongodb/internal/operation/TestOperationHelper.java index 824517e10db..2565521deb7 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/TestOperationHelper.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/TestOperationHelper.java @@ -16,6 +16,7 @@ package com.mongodb.internal.operation; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCommandException; import com.mongodb.MongoCursorNotFoundException; import com.mongodb.MongoNamespace; @@ -31,8 +32,6 @@ import org.bson.BsonString; import org.bson.codecs.BsonDocumentCodec; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; - final class TestOperationHelper { static BsonDocument getKeyPattern(final BsonDocument explainPlan) { @@ -56,7 +55,7 @@ static void makeAdditionalGetMoreCall(final MongoNamespace namespace, final Serv connection.command(namespace.getDatabaseName(), new BsonDocument("getMore", new BsonInt64(serverCursor.getId())) .append("collection", new BsonString(namespace.getCollectionName())), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), OPERATION_CONTEXT)); + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), ClusterFixture.createOperationContext())); } static void makeAdditionalGetMoreCall(final MongoNamespace namespace, final ServerCursor serverCursor, @@ -66,7 +65,7 @@ static void makeAdditionalGetMoreCall(final MongoNamespace namespace, final Serv connection.commandAsync(namespace.getDatabaseName(), new BsonDocument("getMore", new BsonInt64(serverCursor.getId())) .append("collection", new BsonString(namespace.getCollectionName())), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), OPERATION_CONTEXT, callback); + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), new BsonDocumentCodec(), ClusterFixture.createOperationContext(), callback); callback.get(); }); } diff --git a/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java b/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java index d511d2750eb..4be89f4d9ad 100644 --- a/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java +++ b/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java @@ -112,7 +112,7 @@ protected void testValidOptions() { if (option.getKey().equals("authmechanism")) { String expected = option.getValue().asString().getValue(); - if (expected.equals("MONGODB-CR")) { + if (expected.equals("MONGODB-CR")) { assertNotNull(connectionString.getCredential()); assertNull(connectionString.getCredential().getAuthenticationMechanism()); } else { @@ -122,6 +122,12 @@ protected void testValidOptions() { } else if (option.getKey().equalsIgnoreCase("retrywrites")) { boolean expected = option.getValue().asBoolean().getValue(); assertEquals(expected, connectionString.getRetryWritesValue().booleanValue()); + } else if (option.getKey().equalsIgnoreCase("maxadaptiveretries")) { + int expected = option.getValue().asInt32().getValue(); + assertEquals(expected, connectionString.getMaxAdaptiveRetries().intValue()); + } else if (option.getKey().equalsIgnoreCase("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 0b3dd1a0814..e0803bee6eb 100644 --- a/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java +++ b/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java @@ -37,7 +37,31 @@ final class ConnectionStringUnitTest { @Test void defaults() { ConnectionString connectionStringDefault = new ConnectionString(DEFAULT_OPTIONS); - assertAll(() -> assertNull(connectionStringDefault.getServerMonitoringMode())); + assertAll( + () -> assertNull(connectionStringDefault.getServerMonitoringMode()), + () -> assertNull(connectionStringDefault.getMaxAdaptiveRetries()) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "serverMonitoringMode=stream", + "maxAdaptiveRetries=42", + "enableOverloadRetargeting=true" + }) + void equalAndHashCode(final String connectionStringOptions) { + ConnectionString default1 = new ConnectionString(DEFAULT_OPTIONS); + ConnectionString default2 = new ConnectionString(DEFAULT_OPTIONS); + String connectionString = DEFAULT_OPTIONS + connectionStringOptions; + ConnectionString actual1 = new ConnectionString(connectionString); + ConnectionString actual2 = new ConnectionString(connectionString); + assertAll( + () -> assertEquals(default1, default2), + () -> assertEquals(default1.hashCode(), default2.hashCode()), + () -> assertEquals(actual1, actual2), + () -> assertEquals(actual1.hashCode(), actual2.hashCode()), + () -> assertNotEquals(default1, actual1) + ); } @Test @@ -68,22 +92,6 @@ private static String encode(final String string) { } } - @ParameterizedTest - @ValueSource(strings = {DEFAULT_OPTIONS + "serverMonitoringMode=stream"}) - void equalAndHashCode(final String connectionString) { - ConnectionString default1 = new ConnectionString(DEFAULT_OPTIONS); - ConnectionString default2 = new ConnectionString(DEFAULT_OPTIONS); - ConnectionString actual1 = new ConnectionString(connectionString); - ConnectionString actual2 = new ConnectionString(connectionString); - assertAll( - () -> assertEquals(default1, default2), - () -> assertEquals(default1.hashCode(), default2.hashCode()), - () -> assertEquals(actual1, actual2), - () -> assertEquals(actual1.hashCode(), actual2.hashCode()), - () -> assertNotEquals(default1, actual1) - ); - } - @Test void serverMonitoringMode() { assertAll( @@ -94,7 +102,6 @@ void serverMonitoringMode() { ); } - @ParameterizedTest @ValueSource(strings = {"mongodb://foo:bar/@hostname/java?", "mongodb://foo:bar?@hostname/java/", "mongodb+srv://foo:bar/@hostname/java?", "mongodb+srv://foo:bar?@hostname/java/", @@ -109,4 +116,28 @@ void unescapedPasswordsShouldNotBeLeakedInExceptionMessages(final String input) assertFalse(exception.getMessage().contains("bar")); assertFalse(exception.getMessage().contains("12345678")); } + + @Test + void maxAdaptiveRetries() { + assertAll( + () -> assertEquals(42, + new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=42").getMaxAdaptiveRetries()), + () -> assertEquals(0, + new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=0").getMaxAdaptiveRetries()), + () -> assertThrows(IllegalArgumentException.class, + () -> new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=-1")), + () -> assertThrows(IllegalArgumentException.class, + () -> new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=invalid")) + ); + } + + @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 c8910751552..57995d26516 100644 --- a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy @@ -46,6 +46,7 @@ class MongoClientSettingsSpecification extends Specification { settings.getWriteConcern() == WriteConcern.ACKNOWLEDGED settings.getRetryWrites() settings.getRetryReads() + settings.getMaxAdaptiveRetries() == null settings.getReadConcern() == ReadConcern.DEFAULT settings.getReadPreference() == ReadPreference.primary() settings.getCommandListeners().isEmpty() @@ -82,6 +83,11 @@ class MongoClientSettingsSpecification extends Specification { then: thrown(IllegalArgumentException) + when: + builder.maxAdaptiveRetries(-1) + then: + thrown(IllegalArgumentException) + when: builder.credential(null) then: @@ -135,6 +141,7 @@ class MongoClientSettingsSpecification extends Specification { .writeConcern(WriteConcern.JOURNALED) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) .readConcern(ReadConcern.LOCAL) .applicationName('app1') .addCommandListener(commandListener) @@ -160,6 +167,7 @@ class MongoClientSettingsSpecification extends Specification { settings.getWriteConcern() == WriteConcern.JOURNALED settings.getRetryWrites() settings.getRetryReads() + settings.getMaxAdaptiveRetries() == 42 settings.getReadConcern() == ReadConcern.LOCAL settings.getApplicationName() == 'app1' settings.getSocketSettings() == SocketSettings.builder().build() @@ -200,6 +208,7 @@ class MongoClientSettingsSpecification extends Specification { .writeConcern(WriteConcern.JOURNALED) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) .readConcern(ReadConcern.LOCAL) .applicationName('app1') .addCommandListener(commandListener) @@ -330,6 +339,7 @@ class MongoClientSettingsSpecification extends Specification { + '&replicaSet=test' + '&retryWrites=true' + '&retryReads=true' + + '&maxAdaptiveRetries=42' + '&ssl=true&sslInvalidHostNameAllowed=true' + '&w=majority&wTimeoutMS=2500' + '&readPreference=secondary' @@ -398,6 +408,7 @@ class MongoClientSettingsSpecification extends Specification { .compressorList([MongoCompressor.createZlibCompressor().withProperty(MongoCompressor.LEVEL, 5)]) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) .uuidRepresentation(UuidRepresentation.STANDARD) .timeout(10000, TimeUnit.MILLISECONDS) .build() @@ -462,6 +473,7 @@ class MongoClientSettingsSpecification extends Specification { .compressorList([MongoCompressor.createZlibCompressor().withProperty(MongoCompressor.LEVEL, 5)]) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(null) def expectedSettings = builder.build() def settingsWithDefaultConnectionStringApplied = builder @@ -546,14 +558,27 @@ class MongoClientSettingsSpecification extends Specification { .build() } + def 'should allow null, 0 maxAdaptiveRetries'() { + when: + def settings = MongoClientSettings.builder().maxAdaptiveRetries(null).build() + then: + settings.getMaxAdaptiveRetries() == null + + when: + settings = MongoClientSettings.builder().maxAdaptiveRetries(0).build() + then: + settings.getMaxAdaptiveRetries() == 0 + } + def 'should only have the following fields in the builder'() { when: // A regression test so that if anymore fields are added then the builder(final MongoClientSettings settings) should be updated 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', - 'observabilitySettings', + 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern', 'readPreference', 'retryReads', 'retryWrites', 'serverApi', 'serverSettingsBuilder', 'socketSettingsBuilder', 'sslSettingsBuilder', 'timeoutMS', 'transportSettings', 'uuidRepresentation', @@ -571,8 +596,9 @@ class MongoClientSettingsSpecification extends Specification { 'applyToConnectionPoolSettings', 'applyToLoggerSettings', 'applyToServerSettings', 'applyToSocketSettings', 'applyToSslSettings', 'autoEncryptionSettings', 'build', 'codecRegistry', 'commandListenerList', 'compressorList', 'contextProvider', 'credential', 'dnsClient', + 'enableOverloadRetargeting', 'heartbeatConnectTimeoutMS', - 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'observabilitySettings', 'readConcern', + 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern', 'readPreference', 'retryReads', 'retryWrites', 'serverApi', 'timeout', 'transportSettings', diff --git a/driver-core/src/test/unit/com/mongodb/UriOptionsTest.java b/driver-core/src/test/unit/com/mongodb/UriOptionsTest.java index 4213a67c732..11834fd1b97 100644 --- a/driver-core/src/test/unit/com/mongodb/UriOptionsTest.java +++ b/driver-core/src/test/unit/com/mongodb/UriOptionsTest.java @@ -45,7 +45,6 @@ public void shouldPassAllOutcomes() { assumeFalse(getDescription().contains("tlsDisableOCSPEndpointCheck")); // No CANONICALIZE_HOST_NAME support https://jira.mongodb.org/browse/JAVA-4278 assumeFalse(getDescription().equals("Valid auth options are parsed correctly (GSSAPI)")); - skipBackpressureTests(getDescription()); if (getDefinition().getBoolean("valid", BsonBoolean.TRUE).getValue()) { testValidOptions(); @@ -58,17 +57,4 @@ public void shouldPassAllOutcomes() { public static Collection data() { return JsonPoweredTestHelper.getTestData("uri-options"); } - - /** - * TODO-JAVA-6141. - * TODO-JAVA-6167. - */ - private void skipBackpressureTests(final String description) { - // TODO-JAVA-6141 https://jira.mongodb.org/browse/JAVA-6141 Remove skips for maxAdaptiveRetries - assumeFalse(description.equals("maxAdaptiveRetries is parsed correctly")); - assumeFalse(description.equals("maxAdaptiveRetries=0 is parsed correctly")); - // TODO-JAVA-6167 https://jira.mongodb.org/browse/JAVA-6167 Remove skips for enableOverloadRetargeting - assumeFalse(description.equals("enableOverloadRetargeting is parsed correctly")); - assumeFalse(description.equals("enableOverloadRetargeting=false is parsed correctly")); - } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/LoopControlTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/LoopControlTest.java new file mode 100644 index 00000000000..66615087a5b --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/LoopControlTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.async.function; + +import com.mongodb.client.syncadapter.SupplyingCallback; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +final class LoopControlTest { + @Test + void iterationsAndAdvance() { + LoopControl loopControl = new LoopControl(); + assertAll( + () -> assertTrue(loopControl.isFirstIteration()), + () -> assertEquals(0, loopControl.iteration()), + () -> assertFalse(loopControl.isLastIteration()), + () -> assertTrue(loopControl.advance()), + () -> assertFalse(loopControl.isFirstIteration()), + () -> assertEquals(1, loopControl.iteration()), + () -> assertFalse(loopControl.isLastIteration()) + ); + loopControl.markAsLastIteration(); + assertAll( + () -> assertFalse(loopControl.isFirstIteration()), + () -> assertEquals(1, loopControl.iteration()), + () -> assertTrue(loopControl.isLastIteration()), + () -> assertFalse(loopControl.advance()) + ); + } + + @Test + void markAsLastIteration() { + LoopControl loopControl = new LoopControl(); + loopControl.markAsLastIteration(); + assertTrue(loopControl.isLastIteration()); + assertFalse(loopControl.advance()); + } + + @Test + void breakAndCompleteIfFalse() { + LoopControl loopControl = new LoopControl(); + SupplyingCallback callback = new SupplyingCallback<>(); + assertFalse(loopControl.breakAndCompleteIf(() -> false, callback)); + assertFalse(callback.completed()); + } + + @Test + void breakAndCompleteIfTrue() { + LoopControl loopControl = new LoopControl(); + SupplyingCallback callback = new SupplyingCallback<>(); + assertTrue(loopControl.breakAndCompleteIf(() -> true, callback)); + assertTrue(callback.completed()); + } + + @Test + void breakAndCompleteIfPredicateThrows() { + LoopControl loopControl = new LoopControl(); + SupplyingCallback callback = new SupplyingCallback<>(); + RuntimeException e = new RuntimeException(); + assertTrue(loopControl.breakAndCompleteIf(() -> { + throw e; + }, callback)); + assertSame(e, assertThrows(e.getClass(), callback::get)); + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/LoopStateTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/LoopStateTest.java deleted file mode 100644 index c9a8ada7c0c..00000000000 --- a/driver-core/src/test/unit/com/mongodb/internal/async/function/LoopStateTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2008-present MongoDB, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.mongodb.internal.async.function; - -import com.mongodb.client.syncadapter.SupplyingCallback; -import com.mongodb.internal.async.function.LoopState.AttachmentKey; -import com.mongodb.internal.operation.retry.AttachmentKeys; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -final class LoopStateTest { - @Test - void iterationsAndAdvance() { - LoopState loopState = new LoopState(); - assertAll( - () -> assertTrue(loopState.isFirstIteration()), - () -> assertEquals(0, loopState.iteration()), - () -> assertFalse(loopState.isLastIteration()), - () -> assertTrue(loopState.advance()), - () -> assertFalse(loopState.isFirstIteration()), - () -> assertEquals(1, loopState.iteration()), - () -> assertFalse(loopState.isLastIteration()) - ); - loopState.markAsLastIteration(); - assertAll( - () -> assertFalse(loopState.isFirstIteration()), - () -> assertEquals(1, loopState.iteration()), - () -> assertTrue(loopState.isLastIteration()), - () -> assertFalse(loopState.advance()) - ); - } - - @Test - void maskAsLastIteration() { - LoopState loopState = new LoopState(); - loopState.markAsLastIteration(); - assertTrue(loopState.isLastIteration()); - assertFalse(loopState.advance()); - } - - @Test - void breakAndCompleteIfFalse() { - LoopState loopState = new LoopState(); - SupplyingCallback callback = new SupplyingCallback<>(); - assertFalse(loopState.breakAndCompleteIf(() -> false, callback)); - assertFalse(callback.completed()); - } - - @Test - void breakAndCompleteIfTrue() { - LoopState loopState = new LoopState(); - SupplyingCallback callback = new SupplyingCallback<>(); - assertTrue(loopState.breakAndCompleteIf(() -> true, callback)); - assertTrue(callback.completed()); - } - - @Test - void breakAndCompleteIfPredicateThrows() { - LoopState loopState = new LoopState(); - SupplyingCallback callback = new SupplyingCallback<>(); - RuntimeException e = new RuntimeException() { - }; - assertTrue(loopState.breakAndCompleteIf(() -> { - throw e; - }, callback)); - assertThrows(e.getClass(), callback::get); - } - - @Test - void attachAndAttachment() { - LoopState loopState = new LoopState(); - AttachmentKey attachmentKey = AttachmentKeys.maxWireVersion(); - int attachmentValue = 1; - assertFalse(loopState.attachment(attachmentKey).isPresent()); - loopState.attach(attachmentKey, attachmentValue, false); - assertEquals(attachmentValue, loopState.attachment(attachmentKey).get()); - loopState.advance(); - assertEquals(attachmentValue, loopState.attachment(attachmentKey).get()); - loopState.attach(attachmentKey, attachmentValue, true); - assertEquals(attachmentValue, loopState.attachment(attachmentKey).get()); - loopState.advance(); - assertFalse(loopState.attachment(attachmentKey).isPresent()); - } -} diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryControlTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryControlTest.java new file mode 100644 index 00000000000..71bba600689 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryControlTest.java @@ -0,0 +1,198 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.async.function; + +import com.mongodb.internal.async.function.RetryPolicy.Decision; +import com.mongodb.internal.async.function.RetryPolicy.Decision.RetryAttemptInfo; +import com.mongodb.internal.mockito.MongoMockito; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.time.Duration; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +final class RetryControlTest { + @Test + void isFirstAttempt() { + RetryControl retryControl = new RetryControl<>((retryContext, attemptFailedResult) -> + new Decision(attemptFailedResult, createRetryAttemptInfo())); + assertTrue(retryControl.isFirstAttempt()); + retryControl.advanceOrThrow(new RuntimeException()); + assertFalse(retryControl.isFirstAttempt()); + } + + @Test + void attempt() { + RetryControl retryControl = new RetryControl<>((retryContext, attemptFailedResult) -> + new Decision(attemptFailedResult, createRetryAttemptInfo())); + assertEquals(0, retryControl.attempt()); + retryControl.advanceOrThrow(new RuntimeException()); + assertEquals(1, retryControl.attempt()); + assertThrows(Throwable.class, () -> retryControl.breakAndThrowIfRetryAnd(() -> true)); + assertEquals(1, retryControl.attempt()); + } + + @Test + void getPolicy() { + RetryPolicy retryPolicy = (retryContext, attemptFailedResult) -> new Decision(attemptFailedResult, createRetryAttemptInfo()); + RetryControl retryControl = new RetryControl<>(retryPolicy); + assertSame(retryPolicy, retryControl.getPolicy()); + } + + @Test + void advanceOrThrowPassesCorrectArgumentsToAttemptFailure() { + RetryPolicy retryPolicy = MongoMockito.mock(RetryPolicy.class, retryPolicyMock -> { + when(retryPolicyMock.onAttemptFailure(any(), any())).thenReturn(new Decision(new RuntimeException(), createRetryAttemptInfo())); + }); + RetryControl retryControl = new RetryControl<>(retryPolicy); + RuntimeException attemptFailedResult = new RuntimeException(); + retryControl.advanceOrThrow(attemptFailedResult); + @SuppressWarnings("unchecked") + ArgumentCaptor> retryControlArgumentCaptor = ArgumentCaptor.forClass(RetryControl.class); + ArgumentCaptor attemptFailedResultArgumentCaptor = ArgumentCaptor.forClass(Throwable.class); + verify(retryPolicy).onAttemptFailure(retryControlArgumentCaptor.capture(), attemptFailedResultArgumentCaptor.capture()); + assertAll( + () -> assertSame(retryControl, retryControlArgumentCaptor.getValue()), + () -> assertSame(attemptFailedResult, attemptFailedResultArgumentCaptor.getValue()) + ); + } + + @Test + void advanceOrThrowReturnsIfAnotherAttempt() { + RetryAttemptInfo immediateNextAttemptInfo = createRetryAttemptInfo(); + RetryPolicy retryPolicy = (retryContext, attemptFailedResult) -> new Decision(attemptFailedResult, immediateNextAttemptInfo); + RetryControl retryControl = new RetryControl<>(retryPolicy); + RetryAttemptInfo actualImmediateNextAttemptInfo = retryControl.advanceOrThrow(new RuntimeException()); + assertSame(immediateNextAttemptInfo, actualImmediateNextAttemptInfo); + } + + @Test + void advanceOrThrowThrowsIfNoMoreAttempts() { + RuntimeException prospectiveFailedResult = new RuntimeException(); + RetryPolicy retryPolicy = (retryContext, attemptFailedResult) -> new Decision(prospectiveFailedResult, null); + RetryControl retryControl = new RetryControl<>(retryPolicy); + assertSame(prospectiveFailedResult, + assertThrows(prospectiveFailedResult.getClass(), () -> retryControl.advanceOrThrow(new RuntimeException()))); + } + + @Test + void advanceOrThrowThrowsIfLastAttempt() { + RuntimeException prospectiveFailedResult = new RuntimeException(); + RetryPolicy retryPolicy = (retryContext, attemptFailedResult) -> new Decision(prospectiveFailedResult, createRetryAttemptInfo()); + RetryControl retryControl = new RetryControl<>(retryPolicy); + retryControl.advanceOrThrow(new RuntimeException()); + assertThrows(prospectiveFailedResult.getClass(), () -> retryControl.breakAndThrowIfRetryAnd(() -> true)); + assertSame(prospectiveFailedResult, + assertThrows(prospectiveFailedResult.getClass(), () -> retryControl.advanceOrThrow(new RuntimeException()))); + } + + @Test + void advanceOrThrowStoresProspectiveFailedResult() { + RuntimeException prospectiveFailedResult = new RuntimeException(); + RetryPolicy retryPolicy = (retryContext, attemptFailedResult) -> new Decision(prospectiveFailedResult, createRetryAttemptInfo()); + RetryControl retryControl = new RetryControl<>(retryPolicy); + retryControl.advanceOrThrow(new RuntimeException()); + Optional actualProspectiveFailedResult = retryControl.getProspectiveFailedResult(); + if (actualProspectiveFailedResult.isPresent()) { + assertSame(prospectiveFailedResult, actualProspectiveFailedResult.get()); + } else { + fail(); + } + } + + @Test + void advanceOrThrowOverwritesProspectiveFailedResult() { + RetryPolicy retryPolicy = (retryContext, attemptFailedResult) -> new Decision(attemptFailedResult, createRetryAttemptInfo()); + RetryControl retryControl = new RetryControl<>(retryPolicy); + retryControl.advanceOrThrow(new RuntimeException()); + RuntimeException prospectiveFailedResult = new RuntimeException(); + retryControl.advanceOrThrow(prospectiveFailedResult); + Optional actualProspectiveFailedResult = retryControl.getProspectiveFailedResult(); + if (actualProspectiveFailedResult.isPresent()) { + assertSame(prospectiveFailedResult, actualProspectiveFailedResult.get()); + } else { + fail(); + } + } + + @Test + @DisplayName("breakAndThrowIfRetryAnd does nothing if first attempt") + void breakAndThrowIfRetryAndDoesNothingIfFirstAttempt() { + RetryControl retryControl = new RetryControl<>((retryContext, attemptFailedResult) -> + new Decision(attemptFailedResult, createRetryAttemptInfo())); + assertDoesNotThrow(() -> retryControl.breakAndThrowIfRetryAnd(() -> true)); + } + + @Test + @DisplayName("breakAndThrowIfRetryAnd throws if not first attempt") + void breakAndThrowIfRetryAndThrowsIfNotFirstAttempt() { + RuntimeException prospectiveFailedResult = new RuntimeException(); + RetryPolicy retryPolicy = (retryContext, attemptFailedResult) -> new Decision(prospectiveFailedResult, createRetryAttemptInfo()); + RetryControl retryControl = new RetryControl<>(retryPolicy); + retryControl.advanceOrThrow(new RuntimeException()); + assertSame(prospectiveFailedResult, + assertThrows(prospectiveFailedResult.getClass(), () -> retryControl.breakAndThrowIfRetryAnd(() -> true))); + } + + @Test + @DisplayName("breakAndThrowIfRetryAnd propagates if predicate throws") + void breakAndThrowIfRetryAndPropagatesIfPredicateThrows() { + RetryControl retryControl = new RetryControl<>((retryContext, attemptFailedResult) -> + new Decision(attemptFailedResult, createRetryAttemptInfo())); + retryControl.advanceOrThrow(new RuntimeException()); + RuntimeException predicateException = new RuntimeException(); + assertSame(predicateException, + assertThrows(predicateException.getClass(), + () -> retryControl.breakAndThrowIfRetryAnd(() -> { + throw predicateException; + }))); + } + + @Test + @DisplayName("breakAndThrowIfRetryAnd adds suppressed prospective failed result if predicate throws") + void breakAndThrowIfRetryAndAddsSuppressedProspectiveFailedResultIfPredicateThrows() { + RuntimeException prospectiveFailedResult = new RuntimeException(); + RetryControl retryControl = new RetryControl<>((retryContext, attemptFailedResult) -> + new Decision(prospectiveFailedResult, createRetryAttemptInfo())); + retryControl.advanceOrThrow(new RuntimeException()); + RuntimeException predicateException = new RuntimeException(); + Throwable[] suppressed = assertThrows(predicateException.getClass(), + () -> retryControl.breakAndThrowIfRetryAnd(() -> { + throw predicateException; + })).getSuppressed(); + assertAll( + () -> assertEquals(1, suppressed.length), + () -> assertSame(suppressed[0], prospectiveFailedResult) + ); + } + + private static RetryAttemptInfo createRetryAttemptInfo() { + return new RetryAttemptInfo(Duration.ZERO); + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java deleted file mode 100644 index 9d6fc2f586e..00000000000 --- a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright 2008-present MongoDB, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.mongodb.internal.async.function; - -import com.mongodb.MongoOperationTimeoutException; -import com.mongodb.client.syncadapter.SupplyingCallback; -import com.mongodb.internal.TimeoutContext; -import com.mongodb.internal.async.function.LoopState.AttachmentKey; -import com.mongodb.internal.operation.retry.AttachmentKeys; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.function.BiPredicate; -import java.util.function.BinaryOperator; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Named.named; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -final class RetryStateTest { - private static final String EXPECTED_TIMEOUT_MESSAGE = "Retry attempt exceeded the timeout limit."; - - private static Stream atMostTwoRetriesAndUnlimitedRetries() { - return Stream.of( - arguments(named("at most two retries", new RetryState(2))), - arguments(named("unlimited retries", new RetryState()))); - } - - private static Stream noRetries() { - return Stream.of( - arguments(named("no retries", new RetryState(0)))); - } - - @Test - void unlimitedAttemptsAndAdvance() { - final RetryState retryState = new RetryState(); - RuntimeException attemptException = new RuntimeException(); - assertAll( - () -> assertTrue(retryState.isFirstAttempt()), - () -> assertEquals(0, retryState.attempt()) - ); - retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true); - assertAll( - () -> assertFalse(retryState.isFirstAttempt()), - () -> assertEquals(1, retryState.attempt()) - ); - retryState.markAsLastAttempt(); - assertAll( - () -> assertFalse(retryState.isFirstAttempt()), - () -> assertEquals(1, retryState.attempt()), - () -> assertAdvanceOrThrowThrows(attemptException, retryState, attemptException) - ); - } - - @Test - void limitedAttemptsAndAdvance() { - RetryState retryState = new RetryState(0); - RuntimeException attemptException = new RuntimeException(); - assertAll( - () -> assertTrue(retryState.isFirstAttempt()), - () -> assertEquals(0, retryState.attempt()), - () -> assertAdvanceOrThrowThrows(attemptException, retryState, attemptException), - // when there is only one attempt, it is both the first and the last one - () -> assertTrue(retryState.isFirstAttempt()), - () -> assertEquals(0, retryState.attempt()) - ); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void markAsLastAttemptAdvanceWithRuntimeException(final RetryState retryState) { - retryState.markAsLastAttempt(); - RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrowThrows(attemptException, retryState, attemptException, (rs, e) -> fail()); - } - - @ParameterizedTest(name = "should advance with non-retryable error when marked as last attempt and : ''{0}''") - @MethodSource({"noRetries", "atMostTwoRetriesAndUnlimitedRetries"}) - void markAsLastAttemptAdvanceWithError(final RetryState retryState) { - retryState.markAsLastAttempt(); - Error attemptException = new Error(); - assertAdvanceOrThrowThrows(attemptException, retryState, attemptException, (rs, e) -> fail()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void breakAndThrowIfRetryAndFirstAttempt(final RetryState retryState) { - retryState.breakAndThrowIfRetryAnd(Assertions::fail); - assertAdvanceOrThrowDoesNotThrow(retryState, new RuntimeException()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void breakAndThrowIfRetryAndFalse(final RetryState retryState) { - advance(retryState); - retryState.breakAndThrowIfRetryAnd(() -> false); - assertAdvanceOrThrowDoesNotThrow(retryState, new RuntimeException()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void breakAndThrowIfRetryAndTrue(final RetryState retryState) { - advance(retryState); - assertThrows(RuntimeException.class, () -> retryState.breakAndThrowIfRetryAnd(() -> true)); - RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrowThrows(attemptException, retryState, attemptException); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void breakAndThrowIfRetryIfPredicateThrows(final RetryState retryState) { - advance(retryState); - RuntimeException exception = new RuntimeException(); - assertSame( - exception, - assertThrows(exception.getClass(), () -> retryState.breakAndThrowIfRetryAnd(() -> { - throw exception; - }))); - assertAdvanceOrThrowDoesNotThrow(retryState, exception); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void breakAndCompleteIfRetryAndFirstAttempt(final RetryState retryState) { - SupplyingCallback callback = new SupplyingCallback<>(); - assertFalse(retryState.breakAndCompleteIfRetryAnd(Assertions::fail, callback)); - assertFalse(callback.completed()); - assertAdvanceOrThrowDoesNotThrow(retryState, new RuntimeException()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void breakAndCompleteIfRetryAndFalse(final RetryState retryState) { - advance(retryState); - SupplyingCallback callback = new SupplyingCallback<>(); - assertFalse(retryState.breakAndCompleteIfRetryAnd(() -> false, callback)); - assertFalse(callback.completed()); - assertAdvanceOrThrowDoesNotThrow(retryState, new RuntimeException()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void breakAndCompleteIfRetryAndTrue(final RetryState retryState) { - advance(retryState); - SupplyingCallback callback = new SupplyingCallback<>(); - assertTrue(retryState.breakAndCompleteIfRetryAnd(() -> true, callback)); - assertThrows(RuntimeException.class, callback::get); - RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrowThrows(attemptException, retryState, attemptException); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void breakAndCompleteIfRetryAndPredicateThrows(final RetryState retryState) { - advance(retryState); - Error exception = new Error(); - SupplyingCallback callback = new SupplyingCallback<>(); - assertTrue(retryState.breakAndCompleteIfRetryAnd(() -> { - throw exception; - }, callback)); - assertSame( - exception, - assertThrows(exception.getClass(), callback::get)); - assertAdvanceOrThrowDoesNotThrow(retryState, exception); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void advanceOrThrowPredicateFalse(final RetryState retryState) { - RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrowThrows(attemptException, retryState, attemptException, (rs, e) -> false); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - @DisplayName("should rethrow detected timeout exception") - void advanceReThrowDetectedTimeoutException(final RetryState retryState) { - MongoOperationTimeoutException expectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); - assertAdvanceOrThrowThrows(expectedTimeoutException, retryState, expectedTimeoutException, - (e1, e2) -> expectedTimeoutException, - (rs, e) -> false); - } - - @Test - @DisplayName("should throw timeout exception from retry, when transformer swallows original timeout exception") - void advanceThrowTimeoutExceptionWhenTransformerSwallowOriginalTimeoutException() { - RetryState retryState = new RetryState(); - RuntimeException previousAttemptException = new RuntimeException(); - MongoOperationTimeoutException latestAttemptException = TimeoutContext.createMongoTimeoutException("Server selection failed"); - - retryState.advanceOrThrow(previousAttemptException, - (e1, e2) -> previousAttemptException, - (rs, e) -> true); - - MongoOperationTimeoutException actualTimeoutException = - assertThrows(MongoOperationTimeoutException.class, () -> retryState.advanceOrThrow(latestAttemptException, - (e1, e2) -> previousAttemptException, - (rs, e) -> false)); - - assertNotEquals(latestAttemptException, actualTimeoutException); - assertEquals(EXPECTED_TIMEOUT_MESSAGE, actualTimeoutException.getMessage()); - assertSame(previousAttemptException, actualTimeoutException.getCause(), - "Retry timeout exception should have a cause if transformer returned non-timeout exception."); - } - - - @Test - @DisplayName("should throw original timeout exception from retry, when transformer returns original timeout exception") - void advanceThrowOriginalTimeoutExceptionWhenTransformerReturnsOriginalTimeoutException() { - RetryState retryState = new RetryState(); - RuntimeException previousAttemptException = new RuntimeException(); - MongoOperationTimeoutException expectedTimeoutException = TimeoutContext - .createMongoTimeoutException("Server selection failed"); - - retryState.advanceOrThrow(previousAttemptException, - (e1, e2) -> previousAttemptException, - (rs, e) -> true); - - assertAdvanceOrThrowThrows(expectedTimeoutException, retryState, expectedTimeoutException, - (e1, e2) -> expectedTimeoutException, - (rs, e) -> false); - } - - @Test - void advanceOrThrowPredicateTrueAndLastAttempt() { - RetryState retryState = new RetryState(0); - Error attemptException = new Error(); - assertAdvanceOrThrowThrows(attemptException, retryState, attemptException); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void advanceOrThrowPredicateThrowsAfterFirstAttempt(final RetryState retryState) { - RuntimeException predicateException = new RuntimeException(); - RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrowThrows(predicateException, retryState, attemptException, - (e1, e2) -> e2, - (rs, e) -> { - assertTrue(rs.isFirstAttempt()); - assertSame(attemptException, e); - throw predicateException; - }); - } - - @Test - void advanceOrThrowPredicateThrowsTimeoutAfterFirstAttempt() { - RetryState retryState = new RetryState(); - RuntimeException predicateException = new RuntimeException(); - RuntimeException attemptException = new MongoOperationTimeoutException(EXPECTED_TIMEOUT_MESSAGE); - MongoOperationTimeoutException mongoOperationTimeoutException = assertThrows(MongoOperationTimeoutException.class, - () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> { - assertTrue(rs.isFirstAttempt()); - assertSame(attemptException, e); - throw predicateException; - })); - - assertEquals(EXPECTED_TIMEOUT_MESSAGE, mongoOperationTimeoutException.getMessage()); - assertNull(mongoOperationTimeoutException.getCause()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void advanceOrThrowPredicateThrows(final RetryState retryState) { - RuntimeException firstAttemptException = new RuntimeException(); - retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException secondAttemptException = new RuntimeException(); - RuntimeException predicateException = new RuntimeException(); - assertAdvanceOrThrowThrows(predicateException, retryState, secondAttemptException, - (e1, e2) -> e2, - (rs, e) -> { - assertEquals(1, rs.attempt()); - assertSame(secondAttemptException, e); - throw predicateException; - }); - } - - @ParameterizedTest - @MethodSource({"noRetries", "atMostTwoRetriesAndUnlimitedRetries"}) - void advanceOrThrowTransformerThrowsAfterFirstAttempt(final RetryState retryState) { - RuntimeException transformerException = new RuntimeException(); - assertAdvanceOrThrowThrows(transformerException, retryState, new AssertionError(), - (e1, e2) -> { - throw transformerException; - }, - (rs, e) -> fail()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void advanceOrThrowTransformerThrows(final RetryState retryState) throws Throwable { - Error firstAttemptException = new Error(); - retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException transformerException = new RuntimeException(); - assertAdvanceOrThrowThrows(transformerException, retryState, new AssertionError(), - (e1, e2) -> { - throw transformerException; - }, - (rs, e) -> fail()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void advanceOrThrowTransformAfterFirstAttempt(final RetryState retryState) { - RuntimeException attemptException = new RuntimeException(); - RuntimeException transformerResult = new RuntimeException(); - assertAdvanceOrThrowThrows(transformerResult, retryState, attemptException, - (e1, e2) -> { - assertNull(e1); - assertSame(attemptException, e2); - return transformerResult; - }, - (rs, e) -> { - assertSame(attemptException, e); - return false; - }); - } - - @Test - void advanceOrThrowTransformThrowsTimeoutExceptionAfterFirstAttempt() { - RetryState retryState = new RetryState(); - - RuntimeException attemptException = new MongoOperationTimeoutException(EXPECTED_TIMEOUT_MESSAGE); - RuntimeException transformerResult = new RuntimeException(); - - MongoOperationTimeoutException mongoOperationTimeoutException = - assertThrows(MongoOperationTimeoutException.class, () -> retryState.advanceOrThrow(attemptException, - (e1, e2) -> { - assertNull(e1); - assertSame(attemptException, e2); - return transformerResult; - }, - (rs, e) -> { - assertSame(attemptException, e); - return false; - })); - - assertEquals(EXPECTED_TIMEOUT_MESSAGE, mongoOperationTimeoutException.getMessage()); - assertSame(transformerResult, mongoOperationTimeoutException.getCause()); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void advanceOrThrowTransform(final RetryState retryState) { - RuntimeException firstAttemptException = new RuntimeException(); - retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException secondAttemptException = new RuntimeException(); - RuntimeException transformerResult = new RuntimeException(); - assertAdvanceOrThrowThrows(transformerResult, retryState, secondAttemptException, - (e1, e2) -> { - assertSame(firstAttemptException, e1); - assertSame(secondAttemptException, e2); - return transformerResult; - }, - (rs, e) -> { - assertSame(secondAttemptException, e); - return false; - }); - } - - @ParameterizedTest - @MethodSource({"atMostTwoRetriesAndUnlimitedRetries"}) - void attachAndAttachment(final RetryState retryState) { - AttachmentKey attachmentKey = AttachmentKeys.maxWireVersion(); - int attachmentValue = 1; - assertFalse(retryState.attachment(attachmentKey).isPresent()); - retryState.attach(attachmentKey, attachmentValue, false); - assertEquals(attachmentValue, retryState.attachment(attachmentKey).get()); - advance(retryState); - assertEquals(attachmentValue, retryState.attachment(attachmentKey).get()); - retryState.attach(attachmentKey, attachmentValue, true); - assertEquals(attachmentValue, retryState.attachment(attachmentKey).get()); - advance(retryState); - assertFalse(retryState.attachment(attachmentKey).isPresent()); - } - - private static void advance(final RetryState retryState) { - retryState.advanceOrThrow(new RuntimeException(), (e1, e2) -> e2, (rs, e) -> true); - } - - private static void assertAdvanceOrThrowDoesNotThrow( - final RetryState retryState, - final Throwable attemptException) { - assertDoesNotThrow(() -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true)); - } - - private static void assertAdvanceOrThrowThrows( - final Throwable expectedException, - final RetryState retryState, - final Throwable attemptException) { - assertAdvanceOrThrowThrows( - com.mongodb.assertions.Assertions.assertNotNull(expectedException), - retryState, attemptException, (rs, e) -> true); - } - - private static void assertAdvanceOrThrowThrows( - final Throwable expectedException, - final RetryState retryState, - final Throwable attemptException, - final BiPredicate retryPredicate) { - assertAdvanceOrThrowThrows( - com.mongodb.assertions.Assertions.assertNotNull(expectedException), - retryState, attemptException, (e1, e2) -> e2, retryPredicate); - } - - private static void assertAdvanceOrThrowThrows( - final Throwable expectedException, - final RetryState retryState, - final Throwable attemptException, - final BinaryOperator onAttemptFailureOperator, - final BiPredicate retryPredicate) { - com.mongodb.assertions.Assertions.assertNotNull(expectedException); - assertSame( - expectedException, - assertThrows(expectedException.getClass(), () -> - retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate))); - } -} diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplierTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplierTest.java new file mode 100644 index 00000000000..d21e0cf7a97 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplierTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.async.function; + +import com.mongodb.internal.async.function.RetryingSyncSupplierTest.AssertingUnusedRetryPolicy; +import com.mongodb.internal.thread.AsyncClientExecutor; +import com.mongodb.internal.time.StartTime; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static com.mongodb.internal.async.AsyncRunnable.beginAsync; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +final class RetryingAsyncCallbackSupplierTest { + private ExecutorService executorService; + private AsyncClientExecutor clientExecutor; + + @BeforeEach + void beforeEach() { + executorService = Executors.newSingleThreadScheduledExecutor(); + clientExecutor = AsyncClientExecutor.backedBy(executorService); + } + + @AfterEach + void afterEach() { + if (executorService != null) { + executorService.shutdownNow(); + } + } + + @Test + void doWhileDisabledThrowsAtFirstAttempt() { + RetryControl retryControl = new RetryControl<>(new AssertingUnusedRetryPolicy(false)); + RuntimeException exception = new RuntimeException(); + RetryingAsyncCallbackSupplier retryingSupplier = new RetryingAsyncCallbackSupplier<>( + clientExecutor, + retryControl, + callback -> { + retryControl.doWhileDisabledAsync(actionCallback -> { + actionCallback.completeExceptionally(exception); + }, callback); + }); + retryingSupplier.get((r, t) -> assertSame(exception, t)); + assertTrue(retryControl.isFirstAttempt()); + } + + @Test + void doWhileDisabledThrowsAtSecondAttempt() { + RetryControl retryControl = new RetryControl<>(new AssertingUnusedRetryPolicy(true)); + RuntimeException exception = new RuntimeException(); + RetryingAsyncCallbackSupplier retryingSupplier = new RetryingAsyncCallbackSupplier<>( + clientExecutor, + retryControl, + callback -> { + if (retryControl.isFirstAttempt()) { + callback.completeExceptionally(new RuntimeException()); + return; + } + retryControl.doWhileDisabledAsync(actionCallback -> { + actionCallback.completeExceptionally(exception); + }, callback); + }); + retryingSupplier.get((r, t) -> assertSame(exception, t)); + assertEquals(1, retryControl.attempt()); + } + + @Test + void doWhileDisabledCompletesNormally() { + RetryControl retryControl = new RetryControl<>(new AssertingUnusedRetryPolicy(true)); + Object result = new Object(); + RetryingAsyncCallbackSupplier retryingSupplier = new RetryingAsyncCallbackSupplier<>( + clientExecutor, + retryControl, + callback -> { + beginAsync().thenSupply(c -> { + retryControl.doWhileDisabledAsync(actionCallback -> actionCallback.complete(result), c); + }).thenApply((doWhileDisabledResult, c) -> { + if (retryControl.isFirstAttempt()) { + c.completeExceptionally(new RuntimeException()); + return; + } + c.complete(doWhileDisabledResult); + }).finish(callback); + }); + retryingSupplier.get((r, t) -> assertSame(result, r)); + assertEquals(1, retryControl.attempt()); + } + + @Test + void doWhileDisabledNestedThrowsAtFirstAttempt() { + RetryControl retryControl = new RetryControl<>(new AssertingUnusedRetryPolicy(false)); + RuntimeException exception = new RuntimeException(); + RetryingAsyncCallbackSupplier retryingSupplier = new RetryingAsyncCallbackSupplier<>( + clientExecutor, + retryControl, + callback -> { + retryControl.doWhileDisabledAsync(actionCallback -> { + beginAsync().thenSupply(c -> { + retryControl.doWhileDisabledAsync(nestedActionCallback -> { + nestedActionCallback.completeExceptionally(exception); + }, c); + }).thenConsume((doWhileDisabledResult, c) -> { + c.complete(c); + }).finish(actionCallback); + }, callback); + }); + retryingSupplier.get((r, t) -> assertSame(exception, t)); + assertTrue(retryControl.isFirstAttempt()); + } + + @Test + void backoff() throws Exception { + Duration backoff = Duration.ofMillis(400); + RetryControl retryControl = new RetryControl<>((retryContext, attemptFailedResult) -> + new RetryPolicy.Decision(attemptFailedResult, new RetryPolicy.Decision.RetryAttemptInfo(backoff))); + RetryingAsyncCallbackSupplier retryingSupplier = new RetryingAsyncCallbackSupplier<>( + AsyncClientExecutor.backedBy(executorService), + retryControl, + functionCallback -> { + beginAsync().thenRun(c -> { + if (retryControl.isFirstAttempt()) { + throw new RuntimeException(); + } + c.complete(c); + }).finish(functionCallback); + }); + StartTime startTime = StartTime.now(); + CompletableFuture durationFuture = new CompletableFuture<>(); + retryingSupplier.get((result, t) -> { + if (t != null) { + durationFuture.completeExceptionally(fail(t)); + } else { + durationFuture.complete(startTime.elapsed()); + } + }); + Duration duration = durationFuture.get(backoff.toMillis() * 2, MILLISECONDS); + assertTrue(duration.compareTo(backoff) >= 0); + assertTrue(duration.compareTo(backoff.multipliedBy(2)) < 0); + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryingSyncSupplierTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryingSyncSupplierTest.java new file mode 100644 index 00000000000..090d8e10280 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryingSyncSupplierTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.async.function; + +import com.mongodb.internal.async.function.RetryPolicy.Decision; +import com.mongodb.internal.async.function.RetryPolicy.Decision.RetryAttemptInfo; +import com.mongodb.internal.time.StartTime; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +final class RetryingSyncSupplierTest { + @Test + void doWhileDisabledThrowsAtFirstAttempt() { + RetryControl retryControl = new RetryControl<>(new AssertingUnusedRetryPolicy(false)); + RuntimeException exception = new RuntimeException(); + RetryingSyncSupplier retryingSupplier = new RetryingSyncSupplier<>( + retryControl, + () -> { + retryControl.doWhileDisabled(() -> { + throw exception; + }); + return null; + }); + assertSame(exception, assertThrows(exception.getClass(), () -> retryingSupplier.get())); + assertTrue(retryControl.isFirstAttempt()); + } + + @Test + void doWhileDisabledThrowsAtSecondAttempt() { + RetryControl retryControl = new RetryControl<>(new AssertingUnusedRetryPolicy(true)); + RuntimeException exception = new RuntimeException(); + RetryingSyncSupplier retryingSupplier = new RetryingSyncSupplier<>( + retryControl, + () -> { + if (retryControl.isFirstAttempt()) { + throw new RuntimeException(); + } + retryControl.doWhileDisabled(() -> { + throw exception; + }); + return null; + }); + assertSame(exception, assertThrows(exception.getClass(), () -> retryingSupplier.get())); + assertEquals(1, retryControl.attempt()); + } + + @Test + void doWhileDisabledCompletesNormally() { + RetryControl retryControl = new RetryControl<>(new AssertingUnusedRetryPolicy(true)); + Object result = new Object(); + RetryingSyncSupplier retryingSupplier = new RetryingSyncSupplier<>( + retryControl, + () -> { + Object doWhileDisabledResult = retryControl.doWhileDisabled(() -> result); + if (retryControl.isFirstAttempt()) { + throw new RuntimeException(); + } + return doWhileDisabledResult; + }); + assertSame(result, retryingSupplier.get()); + assertEquals(1, retryControl.attempt()); + } + + @Test + void doWhileDisabledNestedThrowsAtFirstAttempt() { + RetryControl retryControl = new RetryControl<>(new AssertingUnusedRetryPolicy(false)); + RuntimeException exception = new RuntimeException(); + RetryingSyncSupplier retryingSupplier = new RetryingSyncSupplier<>( + retryControl, + () -> { + retryControl.doWhileDisabled(() -> { + retryControl.doWhileDisabled(() -> { + throw exception; + }); + return null; + }); + return null; + }); + assertSame(exception, assertThrows(exception.getClass(), () -> retryingSupplier.get())); + assertTrue(retryControl.isFirstAttempt()); + } + + @Test + void backoff() { + Duration backoff = Duration.ofMillis(50); + RetryControl retryControl = new RetryControl<>((retryContext, attemptFailedResult) -> + new Decision(attemptFailedResult, new RetryAttemptInfo(backoff))); + RetryingSyncSupplier retryingSupplier = new RetryingSyncSupplier<>( + retryControl, + () -> { + if (retryControl.isFirstAttempt()) { + throw new RuntimeException(); + } + return null; + }); + StartTime startTime = StartTime.now(); + retryingSupplier.get(); + Duration duration = startTime.elapsed(); + assertTrue(duration.compareTo(backoff) >= 0); + assertTrue(duration.compareTo(backoff.multipliedBy(2)) < 0); + } + + static final class AssertingUnusedRetryPolicy implements RetryPolicy { + private final boolean skipFailingOnFirstAttempt; + + AssertingUnusedRetryPolicy(final boolean skipFailingOnFirstAttempt) { + this.skipFailingOnFirstAttempt = skipFailingOnFirstAttempt; + } + + @Override + public Decision onAttemptFailure(final RetryContext retryContext, final Throwable attemptFailedResult) { + if (skipFailingOnFirstAttempt && retryContext.isFirstAttempt()) { + return new Decision(attemptFailedResult, new RetryAttemptInfo(Duration.ZERO)); + } + return fail(); + } + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/binding/SingleServerBindingSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/binding/SingleServerBindingSpecification.groovy index d52fb593a70..64bd904aaed 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/binding/SingleServerBindingSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/binding/SingleServerBindingSpecification.groovy @@ -26,7 +26,7 @@ import com.mongodb.internal.connection.Server import com.mongodb.internal.connection.ServerTuple import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class SingleServerBindingSpecification extends Specification { @@ -68,7 +68,7 @@ class SingleServerBindingSpecification extends Specification { binding.count == 1 when: - def source = binding.getReadConnectionSource(OPERATION_CONTEXT) + def source = binding.getReadConnectionSource(createOperationContext()) then: source.count == 1 @@ -96,7 +96,7 @@ class SingleServerBindingSpecification extends Specification { binding.count == 1 when: - source = binding.getWriteConnectionSource(OPERATION_CONTEXT) + source = binding.getWriteConnectionSource(createOperationContext()) then: source.count == 1 diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java index 69a2c236048..deaeacafb07 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java @@ -77,7 +77,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.assertions.Assertions.assertFalse; @@ -542,7 +541,7 @@ private Event getNextEvent(final Iterator eventsIterator, final private static void executeAdminCommand(final BsonDocument command) { new CommandReadOperation<>("admin", command, new BsonDocumentCodec()) - .execute(ClusterFixture.getBinding(), OPERATION_CONTEXT); + .execute(ClusterFixture.getBinding(), ClusterFixture.createOperationContext()); } private void setFailPoint() { diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java index e187e94da7b..52287a23d13 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.ConnectionString; import com.mongodb.MongoSocketReadException; import com.mongodb.MongoSocketReadTimeoutException; @@ -43,7 +44,6 @@ import java.util.concurrent.TimeUnit; import static com.mongodb.ClusterFixture.CLIENT_METADATA; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.connection.ServerConnectionState.CONNECTING; import static com.mongodb.internal.connection.DescriptionHelper.createServerDescription; @@ -82,7 +82,8 @@ protected void applyResponse(final BsonArray response) { } protected void applyApplicationError(final BsonDocument applicationError) { - Timeout serverSelectionTimeout = OPERATION_CONTEXT.getTimeoutContext().computeServerSelectionTimeout(); + OperationContext operationContext = ClusterFixture.createOperationContext(); + Timeout serverSelectionTimeout = operationContext.getTimeoutContext().computeServerSelectionTimeout(); ServerAddress serverAddress = new ServerAddress(applicationError.getString("address").getValue()); TimeoutContext timeoutContext = new TimeoutContext(TIMEOUT_SETTINGS); int errorGeneration = applicationError.getNumber("generation", @@ -98,7 +99,7 @@ protected void applyApplicationError(final BsonDocument applicationError) { switch (type) { case "command": exception = getCommandFailureException(applicationError.getDocument("response"), serverAddress, - OPERATION_CONTEXT.getTimeoutContext()); + operationContext.getTimeoutContext()); break; case "network": exception = new MongoSocketReadException("Read error", serverAddress, new IOException()); @@ -112,6 +113,7 @@ protected void applyApplicationError(final BsonDocument applicationError) { switch (when) { case "beforeHandshakeCompletes": + BackpressureErrorLabeler.applyLabelsIfEligible(exception); server.sdamServerDescriptionManager().handleExceptionBeforeHandshake( SdamIssue.of(exception, new SdamIssue.Context(server.serverId(), errorGeneration, maxWireVersion))); break; diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AsynchronousSocketChannelStreamFactoryFactorySpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/AsynchronousSocketChannelStreamFactoryFactorySpecification.groovy index 245c6c87a5a..c41c5fc8aa1 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AsynchronousSocketChannelStreamFactoryFactorySpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AsynchronousSocketChannelStreamFactoryFactorySpecification.groovy @@ -27,8 +27,8 @@ class AsynchronousSocketChannelStreamFactoryFactorySpecification extends Specifi @Unroll def 'should create the expected #description AsynchronousSocketChannelStream'() { given: - def factory = new AsynchronousSocketChannelStreamFactoryFactory(new DefaultInetAddressResolver()) - .create(socketSettings, sslSettings) + def factoryFactory = new AsynchronousSocketChannelStreamFactoryFactory(new DefaultInetAddressResolver()) + def factory = factoryFactory.create(socketSettings, sslSettings) when: AsynchronousSocketChannelStream stream = factory.create(serverAddress) as AsynchronousSocketChannelStream @@ -36,6 +36,9 @@ class AsynchronousSocketChannelStreamFactoryFactorySpecification extends Specifi then: stream.getSettings() == socketSettings stream.getAddress() == serverAddress + + cleanup: + factoryFactory.close() } SocketSettings socketSettings = SocketSettings.builder().build() diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/BackpressureErrorLabelerTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/BackpressureErrorLabelerTest.java new file mode 100644 index 00000000000..2ad978f05aa --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/BackpressureErrorLabelerTest.java @@ -0,0 +1,312 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.connection; + +import com.mongodb.MongoCredential; +import com.mongodb.MongoException; +import com.mongodb.MongoSecurityException; +import com.mongodb.MongoSocketException; +import com.mongodb.MongoSocketOpenException; +import com.mongodb.MongoSocketReadTimeoutException; +import com.mongodb.MongoSocksProxyException; +import com.mongodb.ServerAddress; +import net.bytebuddy.ByteBuddy; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; +import java.io.EOFException; +import java.io.IOException; +import java.net.UnknownHostException; +import java.security.cert.CertPathBuilderException; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateException; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BackpressureErrorLabelerTest { + + private static final ServerAddress ADDRESS = new ServerAddress(); + + static Stream> networkErrorShouldBeLabeled() { + return Stream.of( + named(new MongoSocketException("boom", ADDRESS)), + named(new MongoSocketReadTimeoutException("slow", ADDRESS, new IOException("read timed out"))), + named(new MongoSocketOpenException("open failed", ADDRESS, new IOException("connection refused"))), + // FIN-during-handshake: server closed the TCP connection while the client was mid-handshake + // (no protocol-level alert). I/O failure → must be labeled per CMAP "I/O error during TLS handshake". + named(new MongoSocketException("tls", ADDRESS, initCause( + new SSLHandshakeException("Remote host terminated the handshake"), + new EOFException("SSL peer shut down incorrectly")))) + ); + } + + @ParameterizedTest + @MethodSource + void networkErrorShouldBeLabeled(final MongoSocketException e) { + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertHasBackpressureLabels(e); + } + + static Stream> dnsFailureShouldNotBeLabeled() { + return Stream.of( + named(new MongoSocketException("lookup failed", ADDRESS, new UnknownHostException("nope"))), + named(new MongoSocketException("wrap", ADDRESS, new IOException("wrap", new UnknownHostException("nope")))) + ); + } + + @ParameterizedTest + @MethodSource + void dnsFailureShouldNotBeLabeled(final MongoSocketException e) { + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + static Stream> socks5ProxyExceptionsShouldNotBeLabeled() { + return Stream.of( + // Proxy-TCP-connect / negotiation / authentication / mid-CONNECT I/O failures all + // surface with replyCode == null — no parsed CONNECT reply, so not a definitive + // mongod overload signal. + named(new MongoSocksProxyException("tcp connect to proxy failed", ADDRESS)), + named(new MongoSocksProxyException("negotiation failed", ADDRESS)), + named(new MongoSocksProxyException("auth failed", ADDRESS)), + named(new MongoSocksProxyException("connect relay io failure", ADDRESS, (Integer) null)), + // CONNECT reply parsed with proxy-side / ambiguous reply codes — not + // mongod-attributable: + // 0x01 GENERAL_FAILURE (too generic to attribute) + // 0x02 NOT_ALLOWED (proxy ACL) + // 0x06 TTL_EXPIRED (transient routing, ambiguous) + // 0x07 COMMAND_NOT_SUPPORTED (proxy capability) + // 0x08 ADDRESS_TYPE_NOT_SUPPORTED (proxy capability) + named(new MongoSocksProxyException("general failure", ADDRESS, 1)), + named(new MongoSocksProxyException("not allowed", ADDRESS, 2)), + named(new MongoSocksProxyException("ttl expired", ADDRESS, 6)), + named(new MongoSocksProxyException("command not supported", ADDRESS, 7)), + named(new MongoSocksProxyException("address type not supported", ADDRESS, 8)) + ); + } + + @ParameterizedTest + @MethodSource + void socks5ProxyExceptionsShouldNotBeLabeled(final MongoSocketException e) { + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + static Stream> socks5ProxyExceptionsShouldBeLabeled() { + // Parsed CONNECT reply codes 3 / 4 / 5 mean the proxy tried to reach mongod on our behalf + // and got a transport-level failure that mirrors a direct-connection socket-open outcome. + return Stream.of( + // 0x03 NET_UNREACHABLE — proxy → mongod network path is down (≈ NoRouteToHostException) + named(new MongoSocksProxyException("network unreachable", ADDRESS, 3)), + // 0x04 HOST_UNREACHABLE — proxy can't reach mongod host (≈ NoRouteToHostException) + named(new MongoSocksProxyException("host unreachable", ADDRESS, 4)), + // 0x05 CONN_REFUSED — mongod actively refused (≈ ConnectException) + named(new MongoSocksProxyException("connection refused", ADDRESS, 5)) + ); + } + + @ParameterizedTest + @MethodSource + void socks5ProxyExceptionsShouldBeLabeled(final MongoSocketException e) { + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertHasBackpressureLabels(e); + } + + static Stream> localTlsConfigErrorShouldNotBeLabeled() { + return Stream.of( + named(new CertificateException("bad cert")), + named(new CertPathBuilderException("path build failed")), + named(new CertPathValidatorException("validation failed")), + named(new SSLPeerUnverifiedException("peer not verified")), + named(new SSLProtocolException("protocol error")), + named(initCause( + new SSLHandshakeException("SSLHandshakeException invoking https://1.2.3.4:8443/api/methodName: " + + "sun.security.validator.ValidatorException: PKIX path building failed"), + initCause( + new SSLHandshakeException("sun.security.validator.ValidatorException: " + + "PKIX path building failed: " + + "sun.security.provider.certpath.SunCertPathBuilderException: " + + "unable to find valid certification path to requested target"), + new CertPathBuilderException( + "unable to find valid certification path to requested target")))) + ); + } + + @ParameterizedTest + @MethodSource + void localTlsConfigErrorShouldNotBeLabeled(final Throwable cause) { + MongoSocketException e = new MongoSocketException("tls", ADDRESS, cause); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + /** + * "Received fatal alert: " means the peer actively answered with a TLS protocol + * error — definitively a config/protocol issue, not an overload signal. Covers all 25 + * handshake-only RFC alert descriptions emitted by OpenJDK's JSSE provider. + */ + @ParameterizedTest(name = "Received fatal alert: {0}") + @ValueSource(strings = { + "handshake_failure", + "no_certificate", + "bad_certificate", + "unsupported_certificate", + "certificate_revoked", + "certificate_expired", + "certificate_unknown", + "illegal_parameter", + "unknown_ca", + "access_denied", + "decode_error", + "decrypt_error", + "export_restriction", + "protocol_version", + "insufficient_security", + "no_renegotiation", + "missing_extension", + "unsupported_extension", + "certificate_unobtainable", + "unrecognized_name", + "bad_certificate_status_response", + "bad_certificate_hash_value", + "unknown_psk_identity", + "certificate_required", + "no_application_protocol" + }) + void receivedTlsAlertShouldNotBeLabeled(final String alertDescription) { + SSLHandshakeException tls = new SSLHandshakeException("Received fatal alert: " + alertDescription); + MongoSocketException e = new MongoSocketException("tls", ADDRESS, tls); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + static Stream> nonSocketErrorShouldNotBeLabeled() { + return Stream.of( + named(new MongoSecurityException( + MongoCredential.createCredential("user", "db", "pwd".toCharArray()), "auth failed")), + named(new MongoException(42, "some command error")), + named(new IOException("raw")) + ); + } + + @ParameterizedTest + @MethodSource + void nonSocketErrorShouldNotBeLabeled(final Throwable e) { + BackpressureErrorLabeler.applyLabelsIfEligible(e); + if (e instanceof MongoException) { + assertLacksBackpressureLabels((MongoException) e); + } + } + + /** + * BouncyCastle isn't on the test classpath, so we use ByteBuddy to synthesise classes with the + * exact FQCNs the labeler matches against. This exercises the FQCN-walk in + * {@code isBouncyCastleTlsFatalType} without taking a compile- or runtime dependency on BC. + */ + @ParameterizedTest(name = "{0} with alert {1}") + @MethodSource + void bouncyCastleTlsFatalAlertShouldNotBeLabeled(final String bcFqcn, final String alertMessage) throws Exception { + Throwable bcCause = newBouncyCastleStub(bcFqcn, alertMessage); + MongoSocketException e = new MongoSocketException("tls", ADDRESS, bcCause); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + static Stream bouncyCastleTlsFatalAlertShouldNotBeLabeled() { + return Stream.of( + Arguments.of("org.bouncycastle.tls.TlsFatalAlert", "handshake_failure(40)"), + Arguments.of("org.bouncycastle.tls.TlsFatalAlertReceived", "unknown_ca(48)"), + Arguments.of("org.bouncycastle.tls.crypto.TlsCryptoException", "bad_certificate(42)")); + } + + /** + * Subclasses of known BC types must still match — the labeler walks the superclass chain. + */ + @Test + void bouncyCastleSubclassWithAlertShouldNotBeLabeled() throws Exception { + Class bcParent = new ByteBuddy() + .subclass(Exception.class) + .name("org.bouncycastle.tls.TlsFatalAlert") + .make() + .load(BackpressureErrorLabelerTest.class.getClassLoader()) + .getLoaded(); + Class bcSubclass = new ByteBuddy() + .subclass(bcParent) + .name("com.example.CustomBcSubclass") + .make() + .load(bcParent.getClassLoader()) + .getLoaded(); + Throwable cause = (Throwable) bcSubclass.getConstructor(String.class).newInstance("handshake_failure(40)"); + MongoSocketException e = new MongoSocketException("tls", ADDRESS, cause); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertLacksBackpressureLabels(e); + } + + /** + * BC type but the message has no recognised alert description — the alert-keyword filter + * rejects it, so the labeler falls through and applies backpressure labels. + */ + @Test + void bouncyCastleTypeWithoutAlertKeywordShouldBeLabeled() throws Exception { + Throwable bcCause = newBouncyCastleStub("org.bouncycastle.tls.TlsFatalAlert", "something unrelated"); + MongoSocketException e = new MongoSocketException("tls", ADDRESS, bcCause); + BackpressureErrorLabeler.applyLabelsIfEligible(e); + assertHasBackpressureLabels(e); + } + + private static Throwable newBouncyCastleStub(final String fqcn, final String message) throws Exception { + Class cls = new ByteBuddy() + .subclass(Exception.class) + .name(fqcn) + .make() + .load(BackpressureErrorLabelerTest.class.getClassLoader()) + .getLoaded(); + return (Throwable) cls.getConstructor(String.class).newInstance(message); + } + + private static Named named(final T e) { + return Named.of(e.getClass().getSimpleName(), e); + } + + private static T initCause(final T exception, final Throwable cause) { + exception.initCause(cause); + return exception; + } + + private static void assertHasBackpressureLabels(final MongoException e) { + assertTrue(e.hasErrorLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL), + "expected SystemOverloadedError label"); + assertTrue(e.hasErrorLabel(MongoException.RETRYABLE_ERROR_LABEL), + "expected RetryableError label"); + } + + private static void assertLacksBackpressureLabels(final MongoException e) { + assertFalse(e.hasErrorLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL), + "unexpected SystemOverloadedError label"); + assertFalse(e.hasErrorLabel(MongoException.RETRYABLE_ERROR_LABEL), + "unexpected RetryableError label"); + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy index 56c500c6183..25a1b904e1f 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy @@ -42,7 +42,6 @@ import spock.lang.Specification import java.util.concurrent.CountDownLatch import static com.mongodb.ClusterFixture.CLIENT_METADATA -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE @@ -135,7 +134,7 @@ class BaseClusterSpecification extends Specification { factory.sendNotification(thirdServer, REPLICA_SET_PRIMARY, allServers) expect: - cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.secondary()), OPERATION_CONTEXT) + cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.secondary()), createOperationContext()) .serverDescription.address == firstServer } @@ -171,7 +170,7 @@ class BaseClusterSpecification extends Specification { factory.sendNotification(thirdServer, 1, REPLICA_SET_PRIMARY, allServers) expect: - cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.nearest()), OPERATION_CONTEXT) + cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.nearest()), createOperationContext()) .serverDescription.address == firstServer } @@ -189,7 +188,7 @@ class BaseClusterSpecification extends Specification { factory.sendNotification(thirdServer, 1, REPLICA_SET_PRIMARY, allServers) expect: // firstServer is the only secondary within the latency threshold - cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.secondary()), OPERATION_CONTEXT) + cluster.selectServer(new ReadPreferenceServerSelector(ReadPreference.secondary()), createOperationContext()) .serverDescription.address == firstServer } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterTest.java index 1cba6d91c3c..d1e6c454bc9 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterTest.java @@ -48,7 +48,7 @@ void selectServerToleratesWhenThereIsNoServerForTheSelectedAddress() { new ServerAddressSelector(serverAddressA), clusterDescriptionAB, serversSnapshotB, - ClusterFixture.OPERATION_CONTEXT.getServerDeprioritization(), + ClusterFixture.createOperationContext().getServerDeprioritization(), ClusterSettings.builder().build())); } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java index 091518c715c..1d57d04b4a8 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java @@ -145,6 +145,8 @@ void getCommandDocumentFromClientBulkWrite() { clientBulkWriteOptions(), WriteConcern.MAJORITY, retryWrites, + false, + null, getDefaultCodecRegistry() ).new BatchEncoder(), (ConcreteClientBulkWriteOptions) clientBulkWriteOptions(), diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultConnectionPoolSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultConnectionPoolSpecification.groovy index b3e78d2dc54..3872f6cb1e2 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultConnectionPoolSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultConnectionPoolSpecification.groovy @@ -41,7 +41,6 @@ import java.util.concurrent.CountDownLatch import java.util.regex.Matcher import java.util.regex.Pattern -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.createOperationContext @@ -78,7 +77,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool.ready() expect: - pool.get(OPERATION_CONTEXT) != null + pool.get(createOperationContext()) != null } def 'should reuse released connection'() throws InterruptedException { @@ -86,10 +85,11 @@ class DefaultConnectionPoolSpecification extends Specification { pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(1).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() + def operationContext = createOperationContext() when: - pool.get(OPERATION_CONTEXT).close() - pool.get(OPERATION_CONTEXT) + pool.get(operationContext).close() + pool.get(operationContext) then: 1 * connectionFactory.create(SERVER_ID, _) @@ -102,7 +102,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool.ready() when: - pool.get(OPERATION_CONTEXT).close() + pool.get(createOperationContext()).close() then: !connectionFactory.getCreatedConnections().get(0).isClosed() @@ -220,7 +220,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: pool.ready() - pool.get(OPERATION_CONTEXT) + pool.get(createOperationContext()) then: 1 * listener.connectionCreated { it.connectionId.serverId == SERVER_ID } @@ -239,6 +239,7 @@ class DefaultConnectionPoolSpecification extends Specification { connectionDescription.getConnectionId() >> id connection.getDescription() >> connectionDescription connection.opened() >> false + def operationContext = createOperationContext() when: 'connection pool is created' pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, settings, mockSdamProvider(), OPERATION_CONTEXT_FACTORY) @@ -257,7 +258,7 @@ class DefaultConnectionPoolSpecification extends Specification { "Connection pool ready for ${SERVER_ADDRESS.getHost()}:${SERVER_ADDRESS.getPort()}" == poolReadyLogMessage when: 'connection is created' - pool.get(OPERATION_CONTEXT) + pool.get(operationContext) then: '"connection created" and "connection ready" log messages are emitted' def createdLogMessage = getMessage( "Connection created") def readyLogMessage = getMessage("Connection ready") @@ -267,7 +268,7 @@ class DefaultConnectionPoolSpecification extends Specification { ", driver-generated ID=${driverConnectionId}, established in=\\d+ ms" when: 'connection is released back into the pool on close' - pool.get(OPERATION_CONTEXT).close() + pool.get(operationContext).close() then: '"connection check out" and "connection checked in" log messages are emitted' def checkoutStartedMessage = getMessage("Connection checkout started") def connectionCheckedInMessage = getMessage("Connection checked in") @@ -302,7 +303,7 @@ class DefaultConnectionPoolSpecification extends Specification { "Connection pool closed for ${SERVER_ADDRESS.getHost()}:${SERVER_ADDRESS.getPort()}" == poolClosedLogMessage when: 'connection checked out on closed pool' - pool.get(OPERATION_CONTEXT) + pool.get(operationContext) then: thrown(MongoServerUnavailableException) def connectionCheckoutFailedInMessage = getMessage("Connection checkout failed") @@ -351,7 +352,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: pool.ready() - pool.get(OPERATION_CONTEXT).close() + pool.get(createOperationContext()).close() //not cool - but we have no way of waiting for connection to become idle Thread.sleep(500) pool.close(); @@ -386,11 +387,12 @@ class DefaultConnectionPoolSpecification extends Specification { def 'should log connection checkout failed with Reason.CONNECTION_ERROR if fails to open a connection'() { given: + def operationContext = createOperationContext() def listener = Mock(ConnectionPoolListener) def connection = Mock(InternalConnection) connection.getDescription() >> new ConnectionDescription(SERVER_ID) connection.opened() >> false - connection.open(OPERATION_CONTEXT) >> { throw new UncheckedIOException('expected failure', new IOException()) } + connection.open(operationContext) >> { throw new UncheckedIOException('expected failure', new IOException()) } connectionFactory.create(SERVER_ID, _) >> connection pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) @@ -398,7 +400,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: try { - pool.get(OPERATION_CONTEXT) + pool.get(operationContext) } catch (UncheckedIOException e) { if ('expected failure' != e.getMessage()) { throw e @@ -435,7 +437,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(10) .addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() - def connection = pool.get(OPERATION_CONTEXT) + def connection = pool.get(createOperationContext()) connection.close() when: @@ -463,15 +465,16 @@ class DefaultConnectionPoolSpecification extends Specification { def 'should fire connection pool events on check out and check in'() { given: + def operationContext = createOperationContext() def listener = Mock(ConnectionPoolListener) pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(1) .addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() - def connection = pool.get(OPERATION_CONTEXT) + def connection = pool.get(operationContext) connection.close() when: - connection = pool.get(OPERATION_CONTEXT) + connection = pool.get(operationContext) then: 1 * listener.connectionCheckedOut { it.connectionId.serverId == SERVER_ID } @@ -493,7 +496,7 @@ class DefaultConnectionPoolSpecification extends Specification { connection.close() when: - connection = pool.get(OPERATION_CONTEXT) + connection = pool.get(createOperationContext()) then: 1 * listener.connectionCheckedOut { it.connectionId.serverId == SERVER_ID } @@ -507,11 +510,12 @@ class DefaultConnectionPoolSpecification extends Specification { def 'should fire connection checkout failed with Reason.CONNECTION_ERROR if fails to open a connection'() { given: + def operationContext = createOperationContext() def listener = Mock(ConnectionPoolListener) def connection = Mock(InternalConnection) connection.getDescription() >> new ConnectionDescription(SERVER_ID) connection.opened() >> false - connection.open(OPERATION_CONTEXT) >> { throw new UncheckedIOException('expected failure', new IOException()) } + connection.open(operationContext) >> { throw new UncheckedIOException('expected failure', new IOException()) } connectionFactory.create(SERVER_ID, _) >> connection pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) @@ -519,7 +523,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: try { - pool.get(OPERATION_CONTEXT) + pool.get(operationContext) } catch (UncheckedIOException e) { if ('expected failure' != e.getMessage()) { throw e @@ -564,7 +568,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: try { - pool.get(OPERATION_CONTEXT) + pool.get(createOperationContext()) } catch (MongoConnectionPoolClearedException e) { caught = e } @@ -579,7 +583,7 @@ class DefaultConnectionPoolSpecification extends Specification { CompletableFuture caught = new CompletableFuture<>() when: - pool.getAsync(OPERATION_CONTEXT) { InternalConnection result, Throwable t -> + pool.getAsync(createOperationContext()) { InternalConnection result, Throwable t -> if (t != null) { caught.complete(t) } @@ -599,7 +603,7 @@ class DefaultConnectionPoolSpecification extends Specification { when: pool.invalidate(cause) try { - pool.get(OPERATION_CONTEXT) + pool.get(createOperationContext()) } catch (MongoConnectionPoolClearedException e) { caught = e } @@ -630,7 +634,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(1) .addConnectionPoolListener(listener).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() - def connection = pool.get(OPERATION_CONTEXT) + def connection = pool.get(createOperationContext()) pool.close() when: @@ -674,7 +678,7 @@ class DefaultConnectionPoolSpecification extends Specification { pool.ready() when: - def connection = pool.get(OPERATION_CONTEXT) + def connection = pool.get(createOperationContext()) def connectionLatch = selectConnectionAsync(pool) connection.close() @@ -684,12 +688,13 @@ class DefaultConnectionPoolSpecification extends Specification { def 'when getting a connection asynchronously should send MongoTimeoutException to callback after timeout period'() { given: + def operationContext = createOperationContext() pool = new DefaultConnectionPool(SERVER_ID, connectionFactory, builder().maxSize(1).maxWaitTime(5, MILLISECONDS).build(), mockSdamProvider(), OPERATION_CONTEXT_FACTORY) pool.ready() - pool.get(OPERATION_CONTEXT) - def firstConnectionLatch = selectConnectionAsync(pool) - def secondConnectionLatch = selectConnectionAsync(pool) + pool.get(operationContext) + def firstConnectionLatch = selectConnectionAsync(pool, operationContext) + def secondConnectionLatch = selectConnectionAsync(pool, operationContext) when: firstConnectionLatch.get() @@ -721,9 +726,9 @@ class DefaultConnectionPoolSpecification extends Specification { selectConnectionAsync(pool).get() } - def selectConnectionAsync(DefaultConnectionPool pool) { + def selectConnectionAsync(DefaultConnectionPool pool, operationContext = createOperationContext()) { def serverLatch = new ConnectionLatch() - pool.getAsync(OPERATION_CONTEXT) { InternalConnection result, Throwable e -> + pool.getAsync(operationContext) { InternalConnection result, Throwable e -> serverLatch.connection = result serverLatch.throwable = e serverLatch.latch.countDown() diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerConnectionSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerConnectionSpecification.groovy index be6fbe06b83..26348f16198 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerConnectionSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerConnectionSpecification.groovy @@ -16,7 +16,7 @@ package com.mongodb.internal.connection - +import com.mongodb.ClusterFixture import com.mongodb.ReadPreference import com.mongodb.connection.ClusterConnectionMode import com.mongodb.internal.async.SingleResultCallback @@ -27,7 +27,6 @@ import org.bson.BsonInt32 import org.bson.codecs.BsonDocumentCodec import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.CustomMatchers.compare import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback import static com.mongodb.internal.connection.MessageHelper.LEGACY_HELLO_LOWER @@ -43,14 +42,16 @@ class DefaultServerConnectionSpecification extends Specification { def codec = new BsonDocumentCodec() def executor = Mock(ProtocolExecutor) def connection = new DefaultServerConnection(internalConnection, executor, ClusterConnectionMode.MULTIPLE) + def operationContext = ClusterFixture.createOperationContext() + when: - connection.commandAsync('test', command, validator, ReadPreference.primary(), codec, OPERATION_CONTEXT, callback) + connection.commandAsync('test', command, validator, ReadPreference.primary(), codec, operationContext, callback) then: 1 * executor.executeAsync({ compare(new CommandProtocolImpl('test', command, validator, ReadPreference.primary(), codec, true, - MessageSequences.EmptyMessageSequences.INSTANCE, ClusterConnectionMode.MULTIPLE, OPERATION_CONTEXT), it) - }, internalConnection, OPERATION_CONTEXT.getSessionContext(), callback) + MessageSequences.EmptyMessageSequences.INSTANCE, ClusterConnectionMode.MULTIPLE, operationContext), it) + }, internalConnection, operationContext.getSessionContext(), callback) } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy index 3910da575f0..15ac00e6ba7 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy @@ -51,10 +51,11 @@ import org.bson.BsonInt32 import org.bson.codecs.BsonDocumentCodec import spock.lang.Specification +import java.security.cert.CertificateException import java.util.concurrent.CountDownLatch import static com.mongodb.ClusterFixture.CLIENT_METADATA -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.MongoCredential.createCredential import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE import static com.mongodb.connection.ClusterConnectionMode.SINGLE @@ -74,7 +75,7 @@ class DefaultServerSpecification extends Specification { Mock(SdamServerDescriptionManager), Mock(ServerListener), Mock(CommandListener), new ClusterClock(), false) when: - def receivedConnection = server.getConnection(OPERATION_CONTEXT) + def receivedConnection = server.getConnection(createOperationContext()) then: receivedConnection @@ -100,7 +101,7 @@ class DefaultServerSpecification extends Specification { when: def callback = new SupplyingCallback() - server.getConnectionAsync(OPERATION_CONTEXT, callback) + server.getConnectionAsync(createOperationContext(), callback) then: callback.get() == connection @@ -117,7 +118,7 @@ class DefaultServerSpecification extends Specification { server.close() when: - server.getConnection(OPERATION_CONTEXT) + server.getConnection(createOperationContext()) then: def ex = thrown(MongoServerUnavailableException) @@ -127,7 +128,7 @@ class DefaultServerSpecification extends Specification { def latch = new CountDownLatch(1) def receivedConnection = null def receivedThrowable = null - server.getConnectionAsync(OPERATION_CONTEXT) { + server.getConnectionAsync(createOperationContext()) { result, throwable -> receivedConnection = result; receivedThrowable = throwable; latch.countDown() } @@ -210,7 +211,6 @@ class DefaultServerSpecification extends Specification { given: def connectionPool = Mock(ConnectionPool) def serverMonitor = Mock(ServerMonitor) - connectionPool.get(OPERATION_CONTEXT) >> { throw exceptionToThrow } def server = defaultServer(connectionPool, serverMonitor) server.close() @@ -242,7 +242,7 @@ class DefaultServerSpecification extends Specification { def server = defaultServer(connectionPool, serverMonitor) when: - server.getConnection(OPERATION_CONTEXT) + server.getConnection(createOperationContext()) then: def e = thrown(MongoException) @@ -259,6 +259,55 @@ class DefaultServerSpecification extends Specification { ] } + def 'should invalidate the pool when the exception does not have the system overloaded label'() { + given: + def connectionPool = Mock(ConnectionPool) + connectionPool.get(_) >> { throw exceptionToThrow } + def serverMonitor = Mock(ServerMonitor) + def server = defaultServer(connectionPool, serverMonitor) + + when: + server.getConnection(createOperationContext()) + + then: + def e = thrown(MongoException) + e.is(exceptionToThrow) + 1 * connectionPool.invalidate(exceptionToThrow) + 1 * serverMonitor.cancelCurrentCheck() + + where: + exceptionToThrow << [ + new MongoSocketException('establishment failed', new ServerAddress()), + new MongoSocketOpenException('open failed', new ServerAddress(), new IOException()), + new MongoSocketReadTimeoutException('Read timed out', new ServerAddress(), new IOException()), + new MongoSocketException('DNS lookup failed', new ServerAddress(), + new UnknownHostException('no such host')), + new MongoSocketException('TLS config error', new ServerAddress(), + new CertificateException('bad cert')), + ] + } + + def 'should not invalidate the pool when the exception carries SystemOverloadedError'() { + given: + def exceptionToThrow = new MongoSocketException('rate-limited establishment', new ServerAddress()) + exceptionToThrow.addLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL) + + def connectionPool = Mock(ConnectionPool) + connectionPool.get(_) >> { throw exceptionToThrow } + def serverMonitor = Mock(ServerMonitor) + def server = defaultServer(connectionPool, serverMonitor) + + when: + server.getConnection(createOperationContext()) + + then: + def e = thrown(MongoException) + e.is(exceptionToThrow) + e.hasErrorLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL) + 0 * connectionPool.invalidate(_) + 0 * serverMonitor.cancelCurrentCheck() + } + def 'failed authentication should invalidate the connection pool'() { given: def connectionPool = Mock(ConnectionPool) @@ -267,7 +316,7 @@ class DefaultServerSpecification extends Specification { def server = defaultServer(connectionPool, serverMonitor) when: - server.getConnection(OPERATION_CONTEXT) + server.getConnection(createOperationContext()) then: def e = thrown(MongoSecurityException) @@ -292,7 +341,7 @@ class DefaultServerSpecification extends Specification { def latch = new CountDownLatch(1) def receivedConnection = null def receivedThrowable = null - server.getConnectionAsync(OPERATION_CONTEXT) { + server.getConnectionAsync(createOperationContext()) { result, throwable -> receivedConnection = result; receivedThrowable = throwable; latch.countDown() } @@ -325,7 +374,7 @@ class DefaultServerSpecification extends Specification { def latch = new CountDownLatch(1) def receivedConnection = null def receivedThrowable = null - server.getConnectionAsync(OPERATION_CONTEXT) { + server.getConnectionAsync(createOperationContext()) { result, throwable -> receivedConnection = result; receivedThrowable = throwable; latch.countDown() } @@ -350,7 +399,7 @@ class DefaultServerSpecification extends Specification { clusterClock.advance(clusterClockClusterTime) def server = new DefaultServer(serverId, SINGLE, Mock(ConnectionPool), new TestConnectionFactory(), Mock(ServerMonitor), Mock(SdamServerDescriptionManager), Mock(ServerListener), Mock(CommandListener), clusterClock, false) - def testConnection = (TestConnection) server.getConnection(OPERATION_CONTEXT) + def testConnection = (TestConnection) server.getConnection(createOperationContext()) def sessionContext = new TestSessionContext(initialClusterTime) def response = BsonDocument.parse( '''{ @@ -361,7 +410,7 @@ class DefaultServerSpecification extends Specification { ''') def protocol = new TestCommandProtocol(response) testConnection.enqueueProtocol(protocol) - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) when: if (async) { diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionInitializerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionInitializerSpecification.groovy index 1d44f8dde46..d3f05ea8b49 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionInitializerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionInitializerSpecification.groovy @@ -53,7 +53,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification { def serverId = new ServerId(new ClusterId(), new ServerAddress()) def internalConnection = new TestInternalConnection(serverId, ServerType.STANDALONE) - def operationContext = simpleOperationContext(TimeoutSettings.DEFAULT, null) + def operationContext = simpleOperationContext(TimeoutSettings.DEFAULT) def 'should create correct description'() { given: @@ -201,6 +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('\$db', new BsonString('admin')) if (clientMetadataDocument != null) { expectedHelloCommandDocument.append('client', clientMetadataDocument) @@ -233,6 +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('\$db', new BsonString('admin')) def compressionArray = new BsonArray() @@ -403,7 +405,8 @@ class InternalStreamConnectionInitializerSpecification extends Specification { ((SpeculativeAuthenticator) authenticator).getSpeculativeAuthenticateResponse() == null ((SpeculativeAuthenticator) authenticator) .createSpeculativeAuthenticateCommand(internalConnection) == null - BsonDocument.parse("{$LEGACY_HELLO: 1, helloOk: true, '\$db': 'admin'}") == decodeCommand(internalConnection.getSent()[0]) + BsonDocument.parse("{$LEGACY_HELLO: 1, helloOk: true, backpressure: true, '\$db': 'admin'}") == + decodeCommand(internalConnection.getSent()[0]) where: async << [true, false] @@ -500,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, " + + String hello = "{$LEGACY_HELLO: 1, helloOk: true, backpressure: true, " + (hasSaslSupportedMechs ? 'saslSupportedMechs: "database.user", ' : '') + (mechanism == 'MONGODB-X509' ? 'speculativeAuthenticate: { authenticate: 1, ' + diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy index c9d0525ad19..b41012ca0f5 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionSpecification.groovy @@ -59,7 +59,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT import static com.mongodb.ReadPreference.primary import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE @@ -115,7 +115,7 @@ class InternalStreamConnectionSpecification extends Specification { def getOpenedConnection() { def connection = getConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) connection } @@ -133,7 +133,7 @@ class InternalStreamConnectionSpecification extends Specification { .lastUpdateTimeNanos(connection.getInitialServerDescription().getLastUpdateTime(NANOSECONDS)) .build() when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: connection.opened() @@ -160,7 +160,7 @@ class InternalStreamConnectionSpecification extends Specification { .build() when: - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -178,7 +178,7 @@ class InternalStreamConnectionSpecification extends Specification { failedInitializer) when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: thrown MongoInternalException @@ -196,7 +196,7 @@ class InternalStreamConnectionSpecification extends Specification { when: def futureResultCallback = new FutureResultCallback() - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -213,14 +213,14 @@ class InternalStreamConnectionSpecification extends Specification { def (buffers2, messageId2) = helper.hello() when: - connection.sendMessage(buffers1, messageId1, OPERATION_CONTEXT) + connection.sendMessage(buffers1, messageId1, createOperationContext()) then: connection.isClosed() thrown MongoSocketWriteException when: - connection.sendMessage(buffers2, messageId2, OPERATION_CONTEXT) + connection.sendMessage(buffers2, messageId2, createOperationContext()) then: thrown MongoSocketClosedException @@ -244,7 +244,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessageAsync(buffers1, messageId1, OPERATION_CONTEXT, sndCallbck1) + connection.sendMessageAsync(buffers1, messageId1, createOperationContext(), sndCallbck1) sndCallbck1.get(10, SECONDS) then: @@ -252,7 +252,7 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() when: - connection.sendMessageAsync(buffers2, messageId2, OPERATION_CONTEXT, sndCallbck2) + connection.sendMessageAsync(buffers2, messageId2, createOperationContext(), sndCallbck2) sndCallbck2.get(10, SECONDS) then: @@ -268,16 +268,16 @@ class InternalStreamConnectionSpecification extends Specification { def (buffers2, messageId2) = helper.hello() when: - connection.sendMessage(buffers1, messageId1, OPERATION_CONTEXT) - connection.sendMessage(buffers2, messageId2, OPERATION_CONTEXT) - connection.receiveMessage(messageId1, OPERATION_CONTEXT) + connection.sendMessage(buffers1, messageId1, createOperationContext()) + connection.sendMessage(buffers2, messageId2, createOperationContext()) + connection.receiveMessage(messageId1, createOperationContext()) then: connection.isClosed() thrown MongoSocketReadException when: - connection.receiveMessage(messageId2, OPERATION_CONTEXT) + connection.receiveMessage(messageId2, createOperationContext()) then: thrown MongoSocketClosedException @@ -290,7 +290,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: thrown(MongoInternalException) @@ -307,7 +307,7 @@ class InternalStreamConnectionSpecification extends Specification { def callback = new FutureResultCallback() when: - connection.receiveMessageAsync(1, OPERATION_CONTEXT, callback) + connection.receiveMessageAsync(1, createOperationContext(), callback) callback.get() then: @@ -315,14 +315,14 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() } - def 'should throw MongoInterruptedException and leave the interrupt status set when Stream.write throws InterruptedIOException'() { + def 'should throw MongoInterruptedException and leave interrupted status set when Stream.write throws InterruptedIOException'() { given: stream.write(_, _) >> { throw new InterruptedIOException() } def connection = getOpenedConnection() Thread.currentThread().interrupt() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: Thread.interrupted() @@ -330,13 +330,13 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() } - def 'should throw MongoInterruptedException and leave the interrupt status unset when Stream.write throws InterruptedIOException'() { + def 'should throw MongoInterruptedException and leave interrupted status unset when Stream.write throws InterruptedIOException'() { given: stream.write(_, _) >> { throw new InterruptedIOException() } def connection = getOpenedConnection() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: !Thread.interrupted() @@ -344,14 +344,14 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() } - def 'should throw MongoInterruptedException and leave the interrupt status set when Stream.write throws ClosedByInterruptException'() { + def 'should throw MongoInterruptedException and leave interrupted status set when Stream.write throws ClosedByInterruptException'() { given: stream.write(_, _) >> { throw new ClosedByInterruptException() } def connection = getOpenedConnection() Thread.currentThread().interrupt() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: Thread.interrupted() @@ -366,7 +366,7 @@ class InternalStreamConnectionSpecification extends Specification { Thread.currentThread().interrupt() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: Thread.interrupted() @@ -380,21 +380,21 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, OPERATION_CONTEXT) + connection.sendMessage([new ByteBufNIO(ByteBuffer.allocate(1))], 1, createOperationContext()) then: thrown(MongoSocketWriteException) connection.isClosed() } - def 'should throw MongoInterruptedException and leave the interrupt status set when Stream.read throws InterruptedIOException'() { + def 'should throw MongoInterruptedException and leave interrupted status set when Stream.read throws InterruptedIOException'() { given: stream.read(_, _) >> { throw new InterruptedIOException() } def connection = getOpenedConnection() Thread.currentThread().interrupt() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: Thread.interrupted() @@ -402,13 +402,13 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() } - def 'should throw MongoInterruptedException and leave the interrupt status unset when Stream.read throws InterruptedIOException'() { + def 'should throw MongoInterruptedException and leave interrupted status unset when Stream.read throws InterruptedIOException'() { given: stream.read(_, _) >> { throw new InterruptedIOException() } def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: !Thread.interrupted() @@ -416,14 +416,14 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() } - def 'should throw MongoInterruptedException and leave the interrupt status set when Stream.read throws ClosedByInterruptException'() { + def 'should throw MongoInterruptedException and leave interrupted status set when Stream.read throws ClosedByInterruptException'() { given: stream.read(_, _) >> { throw new ClosedByInterruptException() } def connection = getOpenedConnection() Thread.currentThread().interrupt() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: Thread.interrupted() @@ -438,7 +438,7 @@ class InternalStreamConnectionSpecification extends Specification { Thread.currentThread().interrupt() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: Thread.interrupted() @@ -452,7 +452,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: thrown(MongoSocketReadException) @@ -465,7 +465,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT.withTimeoutContext( + connection.receiveMessage(1, createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT))) then: @@ -484,7 +484,7 @@ class InternalStreamConnectionSpecification extends Specification { def (buffers, messageId) = helper.hello() when: - connection.sendMessage(buffers, messageId, OPERATION_CONTEXT.withTimeoutContext( + connection.sendMessage(buffers, messageId, createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT))) then: @@ -502,7 +502,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT.withTimeoutContext( + connection.receiveMessage(1, createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT))) then: @@ -522,7 +522,7 @@ class InternalStreamConnectionSpecification extends Specification { } def connection = getOpenedConnection() def callback = new FutureResultCallback() - def operationContext = OPERATION_CONTEXT.withTimeoutContext( + def operationContext = createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT)) when: connection.receiveMessageAsync(1, operationContext, callback) @@ -545,7 +545,7 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() def callback = new FutureResultCallback() - def operationContext = OPERATION_CONTEXT.withTimeoutContext( + def operationContext = createOperationContext().withTimeoutContext( new TimeoutContext(TIMEOUT_SETTINGS_WITH_INFINITE_TIMEOUT)) when: connection.receiveMessageAsync(1, operationContext, callback) @@ -583,10 +583,10 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessageAsync(buffers1, messageId1, OPERATION_CONTEXT, sndCallbck1) - connection.sendMessageAsync(buffers2, messageId2, OPERATION_CONTEXT, sndCallbck2) - connection.receiveMessageAsync(messageId1, OPERATION_CONTEXT, rcvdCallbck1) - connection.receiveMessageAsync(messageId2, OPERATION_CONTEXT, rcvdCallbck2) + connection.sendMessageAsync(buffers1, messageId1, createOperationContext(), sndCallbck1) + connection.sendMessageAsync(buffers2, messageId2, createOperationContext(), sndCallbck2) + connection.receiveMessageAsync(messageId1, createOperationContext(), rcvdCallbck1) + connection.receiveMessageAsync(messageId2, createOperationContext(), rcvdCallbck2) rcvdCallbck1.get(1, SECONDS) then: @@ -608,14 +608,14 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: connection.isClosed() thrown MongoSocketReadException when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: thrown MongoSocketClosedException @@ -640,9 +640,9 @@ class InternalStreamConnectionSpecification extends Specification { def connection = getOpenedConnection() when: - connection.sendMessageAsync(buffers1, messageId1, OPERATION_CONTEXT, sndCallbck1) - connection.sendMessageAsync(buffers2, messageId2, OPERATION_CONTEXT, sndCallbck2) - connection.receiveMessageAsync(messageId1, OPERATION_CONTEXT, rcvdCallbck1) + connection.sendMessageAsync(buffers1, messageId1, createOperationContext(), sndCallbck1) + connection.sendMessageAsync(buffers2, messageId2, createOperationContext(), sndCallbck2) + connection.receiveMessageAsync(messageId1, createOperationContext(), rcvdCallbck1) rcvdCallbck1.get(1, SECONDS) then: @@ -650,7 +650,7 @@ class InternalStreamConnectionSpecification extends Specification { connection.isClosed() when: - connection.receiveMessageAsync(messageId2, OPERATION_CONTEXT, rcvdCallbck2) + connection.receiveMessageAsync(messageId2, createOperationContext(), rcvdCallbck2) rcvdCallbck2.get(1, SECONDS) then: @@ -669,7 +669,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(_, _) >> helper.reply(response) when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: thrown(MongoCommandException) @@ -697,7 +697,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -725,7 +725,7 @@ class InternalStreamConnectionSpecification extends Specification { def callbacks = [] (1..numberOfOperations).each { n -> def (buffers, messageId, sndCallbck, rcvdCallbck) = messages.pop() - connection.sendMessageAsync(buffers, messageId, OPERATION_CONTEXT, sndCallbck) + connection.sendMessageAsync(buffers, messageId, createOperationContext(), sndCallbck) callbacks.add(sndCallbck) } streamLatch.countDown() @@ -750,7 +750,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(90, _) >> helper.defaultReply() when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: commandListener.eventsWereDelivered([ @@ -773,7 +773,7 @@ class InternalStreamConnectionSpecification extends Specification { when: connection.sendAndReceive(commandMessage, { BsonReader reader, DecoderContext decoderContext -> throw new CodecConfigurationException('') - }, OPERATION_CONTEXT) + }, createOperationContext()) then: thrown(CodecConfigurationException) @@ -803,7 +803,7 @@ class InternalStreamConnectionSpecification extends Specification { 1 * advanceClusterTime(BsonDocument.parse(response).getDocument('$clusterTime')) getReadConcern() >> ReadConcern.DEFAULT } - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) when: connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), operationContext) @@ -839,7 +839,7 @@ class InternalStreamConnectionSpecification extends Specification { 1 * advanceClusterTime(BsonDocument.parse(response).getDocument('$clusterTime')) getReadConcern() >> ReadConcern.DEFAULT } - def operationContext = OPERATION_CONTEXT.withSessionContext(sessionContext) + def operationContext = createOperationContext().withSessionContext(sessionContext) when: connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), operationContext, callback) @@ -859,7 +859,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.write(_, _) >> { throw new MongoSocketWriteException('Failed to write', serverAddress, new IOException()) } when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: def e = thrown(MongoSocketWriteException) @@ -879,7 +879,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(16, _) >> { throw new MongoSocketReadException('Failed to read', serverAddress) } when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: def e = thrown(MongoSocketReadException) @@ -900,7 +900,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(90, _) >> { throw new MongoSocketReadException('Failed to read', serverAddress) } when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: def e = thrown(MongoSocketException) @@ -922,7 +922,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(_, _) >> helper.reply(response) when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: def e = thrown(MongoCommandException) @@ -943,7 +943,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(90, _) >> helper.defaultReply() when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: commandListener.eventsWereDelivered([ @@ -979,7 +979,7 @@ class InternalStreamConnectionSpecification extends Specification { stream.read(_, _) >> helper.reply('{ok : 0, errmsg : "failed"}') when: - connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT) + connection.sendAndReceive(commandMessage, new BsonDocumentCodec(), createOperationContext()) then: thrown(MongoCommandException) @@ -1025,7 +1025,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1058,7 +1058,7 @@ class InternalStreamConnectionSpecification extends Specification { when: connection.sendAndReceiveAsync(commandMessage, { BsonReader reader, DecoderContext decoderContext -> throw new CodecConfigurationException('') - }, OPERATION_CONTEXT, callback) + }, createOperationContext(), callback) callback.get() then: @@ -1085,7 +1085,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1113,7 +1113,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1144,7 +1144,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1176,7 +1176,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: @@ -1207,7 +1207,7 @@ class InternalStreamConnectionSpecification extends Specification { } when: - connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), OPERATION_CONTEXT, callback) + connection.sendAndReceiveAsync(commandMessage, new BsonDocumentCodec(), createOperationContext(), callback) callback.get() then: diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionTest.java index 2870c96ba5b..d9b8c81f250 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionTest.java @@ -36,9 +36,9 @@ import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandListener; import com.mongodb.event.CommandStartedEvent; -import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.TimeoutSettings; import com.mongodb.internal.async.SingleResultCallback; +import com.mongodb.internal.session.SessionContext; import com.mongodb.internal.validator.NoOpFieldNameValidator; import com.mongodb.lang.Nullable; import org.bson.BsonBinaryWriter; @@ -1422,7 +1422,7 @@ private CommandMessage createPingCommand() { } private OperationContext createOperationContext() { - return OperationContext.simpleOperationContext(new TimeoutContext(TimeoutSettings.DEFAULT)); + return OperationContext.simpleOperationContext(TimeoutSettings.DEFAULT); } /** @@ -1432,7 +1432,7 @@ private OperationContext createOperationContext() { */ private OperationContext createOperationContextWithTimeout() { return OperationContext.simpleOperationContext( - new TimeoutContext(new TimeoutSettings(0, 0, 0, 60_000L, 0))); + new TimeoutSettings(0, 0, 0, 60_000L, 0)); } /** diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/JMXConnectionPoolListenerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/JMXConnectionPoolListenerSpecification.groovy index 374687f7d01..5a7bcd3e492 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/JMXConnectionPoolListenerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/JMXConnectionPoolListenerSpecification.groovy @@ -29,7 +29,7 @@ import spock.lang.Unroll import javax.management.ObjectName import java.lang.management.ManagementFactory -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY class JMXConnectionPoolListenerSpecification extends Specification { @@ -50,8 +50,8 @@ class JMXConnectionPoolListenerSpecification extends Specification { provider.ready() when: - provider.get(OPERATION_CONTEXT) - provider.get(OPERATION_CONTEXT).close() + provider.get(createOperationContext()) + provider.get(createOperationContext()).close() then: with(jmxListener.getMBean(SERVER_ID)) { diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java index 7366a03b584..87dd1581045 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoClientException; import com.mongodb.MongoConfigurationException; import com.mongodb.MongoException; @@ -52,7 +53,6 @@ import java.util.concurrent.atomic.AtomicReference; import static com.mongodb.ClusterFixture.CLIENT_METADATA; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.ClusterFixture.createOperationContext; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -96,14 +96,14 @@ public void shouldSelectServerWhenThereIsNoSRVLookup() { mock(DnsSrvRecordMonitorFactory.class)); // when - ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), OPERATION_CONTEXT); + ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), ClusterFixture.createOperationContext()); // then assertServerTupleExpectations(serverAddress, expectedServer, serverTuple); // when FutureResultCallback callback = new FutureResultCallback<>(); - cluster.selectServerAsync(mock(ServerSelector.class), OPERATION_CONTEXT, callback); + cluster.selectServerAsync(mock(ServerSelector.class), ClusterFixture.createOperationContext(), callback); serverTuple = callback.get(); // then @@ -131,7 +131,7 @@ public void shouldSelectServerWhenThereIsSRVLookup() { cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); // when - ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), OPERATION_CONTEXT); + ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), ClusterFixture.createOperationContext()); // then assertServerTupleExpectations(resolvedServerAddress, expectedServer, serverTuple); @@ -159,7 +159,7 @@ public void shouldSelectServerAsynchronouslyWhenThereIsSRVLookup() { // when FutureResultCallback callback = new FutureResultCallback<>(); - cluster.selectServerAsync(mock(ServerSelector.class), OPERATION_CONTEXT, callback); + cluster.selectServerAsync(mock(ServerSelector.class), ClusterFixture.createOperationContext(), callback); ServerTuple serverTuple = callback.get(); // then @@ -185,7 +185,7 @@ public void shouldFailSelectServerWhenThereIsSRVMisconfiguration() { cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); MongoClientException exception = assertThrows(MongoClientException.class, () -> cluster.selectServer(mock(ServerSelector.class), - OPERATION_CONTEXT)); + ClusterFixture.createOperationContext())); assertEquals("In load balancing mode, the host must resolve to a single SRV record, but instead it resolved to multiple hosts", exception.getMessage()); } @@ -209,7 +209,7 @@ public void shouldFailSelectServerAsynchronouslyWhenThereIsSRVMisconfiguration() cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); FutureResultCallback callback = new FutureResultCallback<>(); - cluster.selectServerAsync(mock(ServerSelector.class), OPERATION_CONTEXT, callback); + cluster.selectServerAsync(mock(ServerSelector.class), ClusterFixture.createOperationContext(), callback); MongoClientException exception = assertThrows(MongoClientException.class, callback::get); assertEquals("In load balancing mode, the host must resolve to a single SRV record, but instead it resolved to multiple hosts", diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy index e6f6afb02e0..de12c35af5e 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy @@ -39,7 +39,7 @@ import org.bson.BsonInt32 import org.bson.BsonString import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE import static com.mongodb.connection.ClusterConnectionMode.SINGLE import static com.mongodb.internal.operation.ServerVersionHelper.LATEST_WIRE_VERSION @@ -63,7 +63,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def logger = Stub(Logger) { isDebugEnabled() >> debugLoggingEnabled } - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, commandListener, operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) @@ -109,7 +109,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def logger = Mock(Logger) { isDebugEnabled() >> true } - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, commandListener, operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) @@ -166,7 +166,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def logger = Mock(Logger) { isDebugEnabled() >> true } - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, null, operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) @@ -200,7 +200,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def logger = Mock(Logger) { isDebugEnabled() >> true } - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() def sender = new LoggingCommandEventSender(['createUser'] as Set, [] as Set, connectionDescription, null, operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy index a3cf8104fd3..737aead0300 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy @@ -29,7 +29,7 @@ import org.bson.types.ObjectId import spock.lang.Specification import static com.mongodb.ClusterFixture.CLIENT_METADATA -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE import static com.mongodb.connection.ClusterType.REPLICA_SET import static com.mongodb.connection.ClusterType.SHARDED @@ -93,11 +93,12 @@ class MultiServerClusterSpecification extends Specification { def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts(Arrays.asList(firstServer)).mode(MULTIPLE).build(), factory, CLIENT_METADATA) cluster.close() + def operationContext = createOperationContext() when: cluster.getServersSnapshot( - OPERATION_CONTEXT.getTimeoutContext().computeServerSelectionTimeout(), - OPERATION_CONTEXT.getTimeoutContext()) + operationContext.getTimeoutContext().computeServerSelectionTimeout(), + operationContext.getTimeoutContext()) then: thrown(IllegalStateException) @@ -386,7 +387,7 @@ class MultiServerClusterSpecification extends Specification { cluster.close() when: - cluster.selectServer(new WritableServerSelector(), OPERATION_CONTEXT) + cluster.selectServer(new WritableServerSelector(), createOperationContext()) then: thrown(IllegalStateException) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/PlainAuthenticatorUnitTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/PlainAuthenticatorUnitTest.java index 12d8e9fa7c3..a535262b4b4 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/PlainAuthenticatorUnitTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/PlainAuthenticatorUnitTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import com.mongodb.async.FutureResultCallback; @@ -30,7 +31,6 @@ import java.util.List; import java.util.concurrent.ExecutionException; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getServerApi; import static com.mongodb.internal.connection.MessageHelper.getApiVersionField; import static com.mongodb.internal.connection.MessageHelper.getDbField; @@ -54,7 +54,7 @@ public void before() { public void testSuccessfulAuthentication() { enqueueSuccessfulReply(); - subject.authenticate(connection, connectionDescription, OPERATION_CONTEXT); + subject.authenticate(connection, connectionDescription, ClusterFixture.createOperationContext()); validateMessages(); } @@ -64,7 +64,7 @@ public void testSuccessfulAuthenticationAsync() throws ExecutionException, Inter enqueueSuccessfulReply(); FutureResultCallback futureCallback = new FutureResultCallback<>(); - subject.authenticateAsync(connection, connectionDescription, OPERATION_CONTEXT, futureCallback); + subject.authenticateAsync(connection, connectionDescription, ClusterFixture.createOperationContext(), futureCallback); futureCallback.get(); validateMessages(); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ScramShaAuthenticatorSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/ScramShaAuthenticatorSpecification.groovy index 21f9bc28161..ed7d758c21b 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ScramShaAuthenticatorSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ScramShaAuthenticatorSpecification.groovy @@ -41,7 +41,7 @@ import static org.junit.Assert.assertEquals class ScramShaAuthenticatorSpecification extends Specification { def serverId = new ServerId(new ClusterId(), new ServerAddress('localhost', 27017)) def connectionDescription = new ConnectionDescription(serverId) - def operationContext = simpleOperationContext(TimeoutSettings.DEFAULT, null) + def operationContext = simpleOperationContext(TimeoutSettings.DEFAULT) private final static MongoCredentialWithCache SHA1_CREDENTIAL = new MongoCredentialWithCache(createScramSha1Credential('user', 'database', 'pencil' as char[])) private final static MongoCredentialWithCache SHA256_CREDENTIAL = diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java index f1c8f69eb29..9ac2bbe7d40 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; import com.mongodb.MongoConnectionPoolClearedException; +import com.mongodb.MongoException; import com.mongodb.ServerAddress; import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.connection.ClusterDescription; @@ -25,93 +26,237 @@ import com.mongodb.connection.ServerDescription; import com.mongodb.connection.ServerId; import com.mongodb.internal.connection.OperationContext.ServerDeprioritization; +import com.mongodb.internal.mockito.MongoMockito; +import com.mongodb.selector.ServerSelector; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; -import static com.mongodb.ClusterFixture.createOperationContext; +import static java.lang.String.format; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; +import static org.junit.jupiter.params.provider.Arguments.of; +import static org.mockito.ArgumentMatchers.any; final class ServerDeprioritizationTest { private static final ServerDescription SERVER_A = serverDescription("a"); private static final ServerDescription SERVER_B = serverDescription("b"); private static final ServerDescription SERVER_C = serverDescription("c"); private static final List ALL_SERVERS = unmodifiableList(asList(SERVER_A, SERVER_B, SERVER_C)); - private static final ClusterDescription REPLICA_SET = clusterDescription(ClusterType.REPLICA_SET); - private static final ClusterDescription SHARDED_CLUSTER = clusterDescription(ClusterType.SHARDED); - + private static final ClusterDescription REPLICA_SET_CLUSTER = multipleModeClusterDescription(ClusterType.REPLICA_SET); + private static final ClusterDescription SHARDED_CLUSTER = multipleModeClusterDescription(ClusterType.SHARDED); + private static final ClusterDescription UNKNOWN_CLUSTER = multipleModeClusterDescription(ClusterType.UNKNOWN); + private static final List CLUSTERS = asList(SHARDED_CLUSTER, REPLICA_SET_CLUSTER, UNKNOWN_CLUSTER); + private 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); } - @Test - void selectNoneDeprioritized() { - assertAll( - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(SHARDED_CLUSTER)), - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(REPLICA_SET)) - ); + private static Stream selectNoneDeprioritized() { + return CLUSTERS.stream().flatMap(clusterDescription -> + Stream.of( + namedArguments(clusterDescription), + namedArguments(clusterDescription, SERVER_A), + namedArguments(clusterDescription, SERVER_B), + namedArguments(clusterDescription, SERVER_C), + namedArguments(clusterDescription, SERVER_A, SERVER_B), + namedArguments(clusterDescription, SERVER_B, SERVER_A), + namedArguments(clusterDescription, SERVER_A, SERVER_C), + namedArguments(clusterDescription, SERVER_C, SERVER_A), + namedArguments(clusterDescription, SERVER_A, SERVER_B, SERVER_C) + )); } - @Test - void selectSomeDeprioritized() { - deprioritize(SERVER_B); + @ParameterizedTest + @MethodSource + void selectNoneDeprioritized(final ClusterDescription clusterDescription, final List selectorResult) { + ServerSelector wrappedSelector = createAssertingSelector(ALL_SERVERS, selectorResult); + assertEquals(selectorResult, serverDeprioritization.apply(wrappedSelector).select(clusterDescription)); + } + + @ParameterizedTest + @EnumSource(value = ClusterType.class, names = {"STANDALONE", "LOAD_BALANCED"}) + void selectNoneDeprioritizedSingleServerCluster(final ClusterType clusterType) { + ClusterDescription cluster = singleModeClusterDescription(clusterType); + ServerSelector wrappedSelector = createAssertingSelector(singletonList(SERVER_A), singletonList(SERVER_A)); + ServerSelector emptyListWrappedSelector = createAssertingSelector(singletonList(SERVER_A), emptyList()); assertAll( - () -> assertEquals(asList(SERVER_A, SERVER_C), serverDeprioritization.getServerSelector().select(SHARDED_CLUSTER)), - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(REPLICA_SET)) + () -> assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(wrappedSelector).select(cluster)), + () -> assertEquals(emptyList(), serverDeprioritization.apply(emptyListWrappedSelector).select(cluster)) ); } - @Test - void selectAllDeprioritized() { - deprioritize(SERVER_A); - deprioritize(SERVER_B); - deprioritize(SERVER_C); - assertAll( - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(SHARDED_CLUSTER)), - () -> assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(REPLICA_SET)) + private static Stream deprioritizableClusters() { + return Stream.of( + of(SHARDED_CLUSTER, RUNTIME_EXCEPTION), + of(SHARDED_CLUSTER, MONGO_EXCEPTION_NO_LABEL), + of(REPLICA_SET_CLUSTER, createSystemOverloadedError()), + of(UNKNOWN_CLUSTER, createSystemOverloadedError()) ); } + private static Stream selectSomeDeprioritized() { + return deprioritizableClusters().flatMap(args -> { + ClusterDescription clusterDescription = (ClusterDescription) args.get()[0]; + Throwable exception = (Throwable) args.get()[1]; + return Stream.of( + namedArguments(clusterDescription, exception, SERVER_A), + namedArguments(clusterDescription, exception, SERVER_C), + namedArguments(clusterDescription, exception, SERVER_A, SERVER_C), + namedArguments(clusterDescription, exception, SERVER_C, SERVER_A) + ); + }); + } + @ParameterizedTest - @EnumSource(value = ClusterType.class, mode = EXCLUDE, names = {"SHARDED"}) - void serverSelectorSelectsAllIfNotShardedCluster(final ClusterType clusterType) { - serverDeprioritization.updateCandidate(SERVER_A.getAddress()); - serverDeprioritization.onAttemptFailure(new RuntimeException()); - assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(clusterDescription(clusterType))); + @MethodSource + void selectSomeDeprioritized(final ClusterDescription clusterDescription, final Throwable exception, + final List selectorResult) { + deprioritize(clusterDescription.getType(), exception, SERVER_B); + List expectedWrappedSelectorFilteredInput = asList(SERVER_A, SERVER_C); + ServerSelector wrappedSelector = createAssertingSelector(expectedWrappedSelectorFilteredInput, selectorResult); + assertEquals(selectorResult, serverDeprioritization.apply(wrappedSelector).select(clusterDescription)); + } + + private static Stream selectAllDeprioritized() { + return deprioritizableClusters().flatMap(args -> { + ClusterDescription clusterDescription = (ClusterDescription) args.get()[0]; + Throwable exception = (Throwable) args.get()[1]; + return Stream.of( + namedArguments(clusterDescription, exception), + namedArguments(clusterDescription, exception, SERVER_A), + namedArguments(clusterDescription, exception, SERVER_B), + namedArguments(clusterDescription, exception, SERVER_C), + namedArguments(clusterDescription, exception, SERVER_A, SERVER_B), + namedArguments(clusterDescription, exception, SERVER_B, SERVER_A), + namedArguments(clusterDescription, exception, SERVER_A, SERVER_C), + namedArguments(clusterDescription, exception, SERVER_C, SERVER_A), + namedArguments(clusterDescription, exception, SERVER_A, SERVER_B, SERVER_C) + ); + }); + } + + @ParameterizedTest + @MethodSource + void selectAllDeprioritized(final ClusterDescription clusterDescription, final Throwable exception, + final List selectorResult) { + deprioritize(clusterDescription.getType(), exception, SERVER_A); + deprioritize(clusterDescription.getType(), exception, SERVER_B); + deprioritize(clusterDescription.getType(), exception, SERVER_C); + ServerSelector selector = createAssertingSelector(ALL_SERVERS, selectorResult); + assertEquals(selectorResult, serverDeprioritization.apply(selector).select(clusterDescription)); + } + + @ParameterizedTest + @EnumSource(value = ClusterType.class, names = {"STANDALONE", "LOAD_BALANCED"}) + void selectAllDeprioritizedSingleServerCluster(final ClusterType clusterType) { + ClusterDescription cluster = singleModeClusterDescription(clusterType); + deprioritize(clusterType, createSystemOverloadedError(), SERVER_A); + ServerSelector selector = createAssertingSelector(singletonList(SERVER_A), singletonList(SERVER_A)); + assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster)); + } + + @ParameterizedTest + @MethodSource("selectSomeDeprioritized") + void selectWithRetryWhenWrappedReturnsEmpty(final ClusterDescription clusterDescription, + final Throwable exception, + final List selectorResult) { + deprioritize(clusterDescription.getType(), exception, SERVER_B); + ServerSelector selector = MongoMockito.mock(ServerSelector.class, tuner -> + Mockito.when(tuner.select(any(ClusterDescription.class))) + .thenAnswer(invocation -> { + assertEquals(asList(SERVER_A, SERVER_C), invocation.getArgument(0).getServerDescriptions()); + return emptyList(); + }) + .thenAnswer(invocation -> { + assertEquals(ALL_SERVERS, invocation.getArgument(0).getServerDescriptions()); + return selectorResult; + }) + ); + assertEquals(selectorResult, serverDeprioritization.apply(selector).select(clusterDescription)); } @Test void onAttemptFailureIgnoresIfPoolClearedException() { - serverDeprioritization.updateCandidate(SERVER_A.getAddress()); + serverDeprioritization.updateCandidate(SERVER_A.getAddress(), ClusterType.SHARDED); serverDeprioritization.onAttemptFailure( new MongoConnectionPoolClearedException(new ServerId(new ClusterId(), new ServerAddress()), null)); - assertEquals(ALL_SERVERS, serverDeprioritization.getServerSelector().select(SHARDED_CLUSTER)); + ServerSelector selector = createAssertingSelector(ALL_SERVERS, ALL_SERVERS); + assertEquals(ALL_SERVERS, serverDeprioritization.apply(selector).select(SHARDED_CLUSTER)); } @Test void onAttemptFailureDoesNotThrowIfNoCandidate() { - assertDoesNotThrow(() -> serverDeprioritization.onAttemptFailure(new RuntimeException())); + assertDoesNotThrow(() -> serverDeprioritization.onAttemptFailure(RUNTIME_EXCEPTION)); } - private void deprioritize(final ServerDescription... serverDescriptions) { + @ParameterizedTest + @EnumSource(value = ClusterType.class, names = "SHARDED", mode = EnumSource.Mode.EXCLUDE) + void onAttemptFailureIgnoresIfNonShardedWithoutOverloadError(final ClusterType clusterType) { + ClusterDescription cluster = multipleModeClusterDescription(clusterType); + ServerSelector selector = createAssertingSelector(ALL_SERVERS, singletonList(SERVER_A)); + + assertAll(() -> { + deprioritize(clusterType, RUNTIME_EXCEPTION, SERVER_B); + assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), + 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), + 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()); - serverDeprioritization.onAttemptFailure(new RuntimeException()); + serverDeprioritization.updateCandidate(serverDescription.getAddress(), clusterType); + serverDeprioritization.onAttemptFailure(exception); } } + private static ServerSelector createAssertingSelector( + final List expectedInput, + final List selectorResult) { + return clusterDescription -> { + assertEquals(expectedInput, clusterDescription.getServerDescriptions()); + return selectorResult; + }; + } + private static ServerDescription serverDescription(final String host) { return ServerDescription.builder() .state(ServerConnectionState.CONNECTED) @@ -120,7 +265,39 @@ private static ServerDescription serverDescription(final String host) { .build(); } - private static ClusterDescription clusterDescription(final ClusterType clusterType) { + private static ClusterDescription multipleModeClusterDescription(final ClusterType clusterType) { return new ClusterDescription(ClusterConnectionMode.MULTIPLE, clusterType, ALL_SERVERS); } + + private static ClusterDescription singleModeClusterDescription(final ClusterType clusterType) { + return new ClusterDescription(ClusterConnectionMode.SINGLE, clusterType, singletonList(SERVER_A)); + } + + private static MongoException createSystemOverloadedError() { + MongoException e = new MongoException(6, "overloaded"); + e.addLabel("SystemOverloadedError"); + return e; + } + + private static Arguments namedArguments(final ClusterDescription clusterDescription, final ServerDescription... serverDescriptions) { + return of(Named.of(generateArgumentName(clusterDescription), clusterDescription), + Named.of(generateArgumentName(asList(serverDescriptions)), asList(serverDescriptions))); + } + + private static Arguments namedArguments(final ClusterDescription clusterDescription, final Throwable exception, final ServerDescription... serverDescriptions) { + return of(Named.of(generateArgumentName(clusterDescription), clusterDescription), + exception, + Named.of(generateArgumentName(asList(serverDescriptions)), asList(serverDescriptions))); + } + + private static String generateArgumentName(final List servers) { + return "[" + servers.stream() + .map(ServerDescription::getAddress) + .map(ServerAddress::getHost) + .collect(Collectors.joining(", ")) + "]"; + } + + private static String generateArgumentName(final ClusterDescription clusterDescription) { + return "[" + clusterDescription.getType() + ", " + generateArgumentName(clusterDescription.getServerDescriptions()) + "]"; + } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java index dc81e5071e1..0406a8a9bbb 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.ServerAddress; import com.mongodb.connection.ClusterType; import com.mongodb.connection.ServerDescription; @@ -32,7 +33,6 @@ import java.util.Collection; import java.util.stream.Collectors; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getClusterDescription; import static com.mongodb.internal.connection.ClusterDescriptionHelper.getPrimaries; import static com.mongodb.internal.event.EventListenerHelper.NO_OP_CLUSTER_LISTENER; @@ -54,9 +54,6 @@ public class ServerDiscoveryAndMonitoringTest extends AbstractServerDiscoveryAnd public ServerDiscoveryAndMonitoringTest(final String description, final BsonDocument definition) { super(definition); - assumeFalse("https://jira.mongodb.org/browse/JAVA-5949", - description.equals("error_handling_handshake.json: Network timeouts before and after the handshake completes")); - this.description = description; init(serverAddress -> NO_OP_SERVER_LISTENER, NO_OP_CLUSTER_LISTENER); } @@ -154,9 +151,10 @@ private void assertServer(final String serverName, final BsonDocument expectedSe if (expectedServerDescriptionDocument.isDocument("pool")) { int expectedGeneration = expectedServerDescriptionDocument.getDocument("pool").getNumber("generation").intValue(); - Timeout serverSelectionTimeout = OPERATION_CONTEXT.getTimeoutContext().computeServerSelectionTimeout(); + OperationContext operationContext = ClusterFixture.createOperationContext(); + Timeout serverSelectionTimeout = operationContext.getTimeoutContext().computeServerSelectionTimeout(); DefaultServer server = (DefaultServer) getCluster() - .getServersSnapshot(serverSelectionTimeout, OPERATION_CONTEXT.getTimeoutContext()) + .getServersSnapshot(serverSelectionTimeout, operationContext.getTimeoutContext()) .getServer(new ServerAddress(serverName)); assertEquals(expectedGeneration, server.getConnectionPool().getGeneration()); } diff --git a/driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java similarity index 55% rename from driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java rename to driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java index 8b878fa77c5..5abcfbc1bb7 100644 --- a/driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java @@ -14,19 +14,37 @@ * limitations under the License. */ -package com.mongodb.connection; +package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoConfigurationException; +import com.mongodb.MongoException; +import com.mongodb.MongoTimeoutException; import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; import com.mongodb.Tag; import com.mongodb.TagSet; -import com.mongodb.internal.selector.LatencyMinimizingServerSelector; +import com.mongodb.assertions.Assertions; +import com.mongodb.connection.ClusterConnectionMode; +import com.mongodb.connection.ClusterDescription; +import com.mongodb.connection.ClusterId; +import com.mongodb.connection.ClusterSettings; +import com.mongodb.connection.ClusterType; +import com.mongodb.connection.ServerConnectionState; +import com.mongodb.connection.ServerDescription; +import com.mongodb.connection.ServerSettings; +import com.mongodb.connection.ServerType; +import com.mongodb.event.ServerDescriptionChangedEvent; +import com.mongodb.internal.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.thread.AsyncClientExecutor; +import com.mongodb.internal.time.Timeout; import com.mongodb.lang.NonNull; import com.mongodb.lang.Nullable; -import com.mongodb.selector.CompositeServerSelector; import com.mongodb.selector.ServerSelector; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -34,36 +52,54 @@ import org.bson.BsonInt64; import org.bson.BsonString; import org.bson.BsonValue; +import org.bson.json.JsonWriterSettings; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import util.JsonPoweredTestHelper; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; +import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; +import static com.mongodb.connection.ServerDescription.MIN_DRIVER_WIRE_VERSION; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; +import static org.mockito.Mockito.when; -// See https://github.com/mongodb/specifications/tree/master/source/server-selection/tests +/** + * See Server Selection Tests. + */ @RunWith(Parameterized.class) public class ServerSelectionSelectionTest { private final String description; private final BsonDocument definition; private final ClusterDescription clusterDescription; - private final long heartbeatFrequencyMS; private final boolean error; + private static final Set TOPOLOGY_DESCRIPTION_FIELDS = new HashSet<>(Arrays.asList("type", "servers")); + private static final Set SERVER_DESCRIPTION_FIELDS = new HashSet<>(Arrays.asList( + "address", "type", "tags", "avg_rtt_ms", "lastWrite", "lastUpdateTime", "maxWireVersion")); + private static final Set READ_PREFERENCE_FIELDS = new HashSet<>( + Arrays.asList("mode", "tag_sets", "maxStalenessSeconds")); + public ServerSelectionSelectionTest(final String description, final BsonDocument definition) { this.description = description; this.definition = definition; - this.heartbeatFrequencyMS = definition.getNumber("heartbeatFrequencyMS", new BsonInt64(10000)).longValue(); + + long heartbeatFrequencyMS = definition.getNumber("heartbeatFrequencyMS", new BsonInt64(10000)).longValue(); this.error = definition.getBoolean("error", BsonBoolean.FALSE).getValue(); this.clusterDescription = buildClusterDescription(definition.getDocument("topology_description"), ServerSettings.builder().heartbeatFrequency(heartbeatFrequencyMS, TimeUnit.MILLISECONDS).build()); @@ -73,37 +109,38 @@ public ServerSelectionSelectionTest(final String description, final BsonDocument public void shouldPassAllOutcomes() { // skip this test because the driver prohibits maxStaleness or tagSets with mode of primary at a much lower level assumeFalse(description.endsWith("/max-staleness/tests/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.json")); - assumeFalse(description.contains("Deprioritized")); // TODO JAVA-6021 deprioritized server selection" - - ServerSelector serverSelector = null; - List suitableServers = buildServerDescriptions(definition.getArray("suitable_servers", new BsonArray())); - List selectedServers = null; - try { - serverSelector = getServerSelector(); - selectedServers = serverSelector.select(clusterDescription); + ServerTuple serverTuple; + ServerSelector serverSelector = getServerSelector(); + OperationContext operationContext = createOperationContext(); + Cluster.ServersSnapshot serversSnapshot = createServersSnapshot(clusterDescription); + List inLatencyWindowServers = buildServerDescriptions(definition.getArray("in_latency_window", new BsonArray())); + + try (BaseCluster cluster = new TestCluster(clusterDescription, serversSnapshot)) { + serverTuple = cluster.selectServer(serverSelector, operationContext); if (error) { - fail("Should have thrown exception"); + fail(format("Should have thrown exception")); } } catch (MongoConfigurationException e) { if (!error) { - fail("Should not have thrown exception: " + e); + fail(format("Should not have thrown exception: %s", e)); } return; + } catch (MongoTimeoutException mongoTimeoutException) { + assertTrue(format("Expected empty but was %s", inLatencyWindowServers.size()), + inLatencyWindowServers.isEmpty()); + return; } - assertServers(selectedServers, suitableServers); - - ServerSelector latencyBasedServerSelector = new CompositeServerSelector(asList(serverSelector, - new LatencyMinimizingServerSelector(15, TimeUnit.MILLISECONDS))); - List inLatencyWindowServers = buildServerDescriptions(definition.getArray("in_latency_window")); - List latencyBasedSelectedServers = latencyBasedServerSelector.select(clusterDescription); - assertServers(latencyBasedSelectedServers, inLatencyWindowServers); + assertNotNull(format("Server tuple should not be null"), serverTuple); + assertTrue(format("Selected server should be in latency window. Selected server: %s", serverTuple.getServerDescription()), + inLatencyWindowServers.stream().anyMatch(s -> s.equals(serverTuple.getServerDescription()))); } @Parameterized.Parameters(name = "{0}") public static Collection data() { List data = new ArrayList<>(); for (BsonDocument testDocument : JsonPoweredTestHelper.getSpecTestDocuments("server-selection/tests/server_selection")) { - data.add(new Object[]{testDocument.getString("resourcePath").getValue(), testDocument}); + String resourcePath = testDocument.getString("resourcePath").getValue(); + data.add(new Object[]{resourcePath, testDocument}); } for (BsonDocument testDocument : JsonPoweredTestHelper.getSpecTestDocuments("max-staleness/tests")) { data.add(new Object[]{testDocument.getString("resourcePath").getValue(), testDocument}); @@ -112,11 +149,12 @@ public static Collection data() { } public static ClusterDescription buildClusterDescription(final BsonDocument topologyDescription, - @Nullable final ServerSettings serverSettings) { + @Nullable final ServerSettings serverSettings) { + validateTestDescriptionFields(topologyDescription.keySet(), TOPOLOGY_DESCRIPTION_FIELDS); ClusterType clusterType = getClusterType(topologyDescription.getString("type").getValue()); ClusterConnectionMode connectionMode = getClusterConnectionMode(clusterType); List servers = buildServerDescriptions(topologyDescription.getArray("servers")); - return new ClusterDescription(connectionMode, clusterType, servers, null, + return new ClusterDescription(connectionMode, clusterType, servers, ClusterSettings.builder().build(), serverSettings == null ? ServerSettings.builder().build() : serverSettings); } @@ -153,6 +191,7 @@ private static List buildServerDescriptions(final BsonArray s } private static ServerDescription buildServerDescription(final BsonDocument serverDescription) { + validateTestDescriptionFields(serverDescription.keySet(), SERVER_DESCRIPTION_FIELDS); ServerDescription.Builder builder = ServerDescription.builder(); builder.address(new ServerAddress(serverDescription.getString("address").getValue())); ServerType serverType = getServerType(serverDescription.getString("type").getValue()); @@ -175,6 +214,8 @@ private static ServerDescription buildServerDescription(final BsonDocument serve } if (serverDescription.containsKey("maxWireVersion")) { builder.maxWireVersion(serverDescription.getNumber("maxWireVersion").intValue()); + } else { + builder.maxWireVersion(MIN_DRIVER_WIRE_VERSION); } return builder.build(); } @@ -229,6 +270,7 @@ private ServerSelector getServerSelector() { return new WritableServerSelector(); } else { BsonDocument readPreferenceDefinition = definition.getDocument("read_preference"); + validateTestDescriptionFields(readPreferenceDefinition.keySet(), READ_PREFERENCE_FIELDS); ReadPreference readPreference; if (readPreferenceDefinition.getString("mode").getValue().equals("Primary")) { readPreference = ReadPreference.valueOf("Primary"); @@ -244,8 +286,89 @@ private ServerSelector getServerSelector() { } } - private void assertServers(final List actual, final List expected) { - assertEquals(expected.size(), actual.size()); - assertTrue(actual.containsAll(expected)); + private static List extractDeprioritizedServerAddresses(final BsonDocument definition) { + if (!definition.containsKey("deprioritized_servers")) { + return Collections.emptyList(); + } + return definition.getArray("deprioritized_servers") + .stream() + .map(BsonValue::asDocument) + .map(ServerSelectionSelectionTest::buildServerDescription) + .map(ServerDescription::getAddress) + .collect(Collectors.toList()); + } + + private OperationContext createOperationContext() { + OperationContext operationContext = + new OperationContext( + IgnorableRequestContext.INSTANCE, + NoOpSessionContext.INSTANCE, + new TimeoutContext(TIMEOUT_SETTINGS.withServerSelectionTimeoutMS(0)), + AsyncClientExecutor.NO_OP, + TracingManager.NO_OP, + null, + null, + new OperationContext.ServerDeprioritization(true)); + OperationContext.ServerDeprioritization serverDeprioritization = operationContext.getServerDeprioritization(); + for (ServerAddress address : extractDeprioritizedServerAddresses(definition)) { + serverDeprioritization.updateCandidate(address, clusterDescription.getType()); + // The spec defines deprioritized_servers as a pre-populated list to feed into the selection mechanism - not as "simulate the + // failure that caused deprioritization." Thus, SystemOverloadedError used unconditionally regardless of the cluster type. + MongoException error = new MongoException("test"); + error.addLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL); + serverDeprioritization.onAttemptFailure(error); + } + return operationContext; + } + + private static Cluster.ServersSnapshot createServersSnapshot( + final ClusterDescription clusterDescription) { + Map serverMap = new HashMap<>(); + for (ServerDescription desc : clusterDescription.getServerDescriptions()) { + serverMap.put(desc.getAddress(), MongoMockito.mock(Server.class, server -> { + // `MinimumOperationCountServerSelector` should select any server since they all have 0 operation count. + when(server.operationCount()).thenReturn(0); + })); + } + return serverMap::get; + } + + private static void validateTestDescriptionFields(final Set actualFields, final Set knownFields) { + Set unknownFields = new HashSet<>(actualFields); + unknownFields.removeAll(knownFields); + if (!unknownFields.isEmpty()) { + throw new UnsupportedOperationException("Unknown fields: " + unknownFields); + } + } + + private static class TestCluster extends BaseCluster { + private final ServersSnapshot serversSnapshot; + + TestCluster(final ClusterDescription clusterDescription, final ServersSnapshot serversSnapshot) { + super(new ClusterId(), clusterDescription.getClusterSettings(), new TestClusterableServerFactory(), + ClusterFixture.CLIENT_METADATA); + this.serversSnapshot = serversSnapshot; + updateDescription(clusterDescription); + } + + @Override + protected void connect() { + // NOOP: this method may be invoked in test cases where no server is expected to be selected. + } + + @Override + public ServersSnapshot getServersSnapshot(final Timeout serverSelectionTimeout, final TimeoutContext timeoutContext) { + return serversSnapshot; + } + + @Override + public void onChange(final ServerDescriptionChangedEvent event) { + Assertions.fail(); + } + } + + private String format(final String messageFormat, final Object... args) { + String message = String.format(messageFormat, args); + return message + "\nTest Definition:\n" + definition.toJson(JsonWriterSettings.builder().indent(true).build()); } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionWithinLatencyWindowTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionWithinLatencyWindowTest.java index 14d6c59b0c6..23e1f59d3a7 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionWithinLatencyWindowTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionWithinLatencyWindowTest.java @@ -43,7 +43,7 @@ import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.ClusterFixture.createOperationContext; -import static com.mongodb.connection.ServerSelectionSelectionTest.buildClusterDescription; +import static com.mongodb.internal.connection.ServerSelectionSelectionTest.buildClusterDescription; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toMap; import static org.junit.Assert.assertEquals; diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy index faa04a188f9..126cadce0c0 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy @@ -29,7 +29,7 @@ import com.mongodb.internal.selector.WritableServerSelector import spock.lang.Specification import static com.mongodb.ClusterFixture.CLIENT_METADATA -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.connection.ClusterConnectionMode.SINGLE import static com.mongodb.connection.ClusterType.REPLICA_SET import static com.mongodb.connection.ClusterType.UNKNOWN @@ -78,10 +78,9 @@ class SingleServerClusterSpecification extends Specification { sendNotification(firstServer, STANDALONE) then: - cluster.getServersSnapshot(OPERATION_CONTEXT - .getTimeoutContext() - .computeServerSelectionTimeout(), - OPERATION_CONTEXT.getTimeoutContext()).getServer(firstServer) == factory.getServer(firstServer) + def operationContext = createOperationContext() + cluster.getServersSnapshot(operationContext.getTimeoutContext().computeServerSelectionTimeout(), + operationContext.getTimeoutContext()).getServer(firstServer) == factory.getServer(firstServer) cleanup: cluster?.close() @@ -95,8 +94,9 @@ class SingleServerClusterSpecification extends Specification { cluster.close() when: - cluster.getServersSnapshot(OPERATION_CONTEXT.getTimeoutContext().computeServerSelectionTimeout(), - OPERATION_CONTEXT.getTimeoutContext()) + def operationContext = createOperationContext() + cluster.getServersSnapshot(operationContext.getTimeoutContext().computeServerSelectionTimeout(), + operationContext.getTimeoutContext()) then: thrown(IllegalStateException) @@ -146,7 +146,7 @@ class SingleServerClusterSpecification extends Specification { sendNotification(firstServer, getBuilder(firstServer).minWireVersion(1000).maxWireVersion(1000).build()) when: - cluster.selectServer(new WritableServerSelector(), OPERATION_CONTEXT) + cluster.selectServer(new WritableServerSelector(), createOperationContext()) then: thrown(MongoIncompatibleDriverException) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/SocksSocketFunctionalTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/SocksSocketFunctionalTest.java new file mode 100644 index 00000000000..6d34f771e2f --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/SocksSocketFunctionalTest.java @@ -0,0 +1,203 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.connection; + +import com.mongodb.MongoSocksProxyException; +import com.mongodb.connection.ProxySettings; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Verifies that SocksSocket surfaces each SOCKS5 protocol failure as a MongoSocksProxyException + * and, for parsed non-success CONNECT replies, exposes the correct RFC 1928 reply code via + * {@link MongoSocksProxyException#getProxyReplyCode()}. Uses a local mini-server; no real SOCKS5 + * proxy required. + */ +class SocksSocketFunctionalTest { + + private static final InetSocketAddress TARGET = + InetSocketAddress.createUnresolved("mongo.example.com", 27017); + private static final int CONNECT_TIMEOUT_MS = 5000; + + private void connectWithMiniServer(final byte[] serverBytes, final boolean withCredentials) + throws Exception { + connectWithMiniServer(serverBytes, withCredentials, false); + } + + private void connectWithMiniServer(final byte[] serverBytes, final boolean withCredentials, + final boolean eofAfterWrite) + throws Exception { + try (ServerSocket server = new ServerSocket(0)) { + int port = server.getLocalPort(); + + Thread t = new Thread(() -> { + try (Socket client = server.accept()) { + OutputStream out = client.getOutputStream(); + out.write(serverBytes); + out.flush(); + if (eofAfterWrite) { + // Half-close: send TCP FIN so the client sees EOF on its next read. + client.shutdownOutput(); + } + // Drain anything the client writes until it closes its end, so the server thread + // does not tear down the socket while the client is still reading canned bytes. + InputStream in = client.getInputStream(); + byte[] discard = new byte[1024]; + //noinspection StatementWithEmptyBody + while (in.read(discard) != -1) { + // discard + } + } catch (Exception ignored) { + } + }); + t.setDaemon(true); + t.start(); + + try (SocksSocket socksSocket = new SocksSocket(buildProxySettings("127.0.0.1", port, withCredentials))) { + socksSocket.connect(TARGET, CONNECT_TIMEOUT_MS); + } finally { + try { + t.join(CONNECT_TIMEOUT_MS); + } catch (InterruptedException ie) { + // Don't mask the primary exception (if any) with the join interruption; + // just preserve the thread's interrupted status and continue. + Thread.currentThread().interrupt(); + } + } + } + } + + private static ProxySettings buildProxySettings(final String host, final int port, final boolean withCredentials) { + ProxySettings.Builder b = ProxySettings.builder().host(host).port(port); + if (withCredentials) { + b.username("user").password("pass"); + } + return b.build(); + } + + // ----------------------------------------------------------------------- + // CONNECT relay — RFC 1928 server reply codes + // ----------------------------------------------------------------------- + + static Stream connectRelayReplyCodes() { + return Stream.of( + SocksSocket.ServerReply.GENERAL_FAILURE, + SocksSocket.ServerReply.NOT_ALLOWED, + SocksSocket.ServerReply.NET_UNREACHABLE, + SocksSocket.ServerReply.HOST_UNREACHABLE, + SocksSocket.ServerReply.CONN_REFUSED, + SocksSocket.ServerReply.TTL_EXPIRED, + SocksSocket.ServerReply.CMD_NOT_SUPPORTED, + SocksSocket.ServerReply.ADDR_TYPE_NOT_SUP + ); + } + + @ParameterizedTest + @MethodSource + void connectRelayReplyCodes(final SocksSocket.ServerReply reply) { + byte[] bytes = { + 0x05, 0x00, // negotiation OK, no auth + 0x05, (byte) reply.getReplyNumber(), 0x00, 0x01, 0, 0, 0, 0, 0, 0 // CONNECT reply + }; + MongoSocksProxyException ex = assertThrows(MongoSocksProxyException.class, + () -> connectWithMiniServer(bytes, false)); + assertEquals(reply.getReplyNumber(), ex.getProxyReplyCode()); + } + + // ----------------------------------------------------------------------- + // Authentication + // ----------------------------------------------------------------------- + + @Test + void authRejectedNoReplyCode() { + byte[] bytes = { + 0x05, 0x02, // negotiation OK, needs username/password + 0x01, 0x01 // auth rejected + }; + MongoSocksProxyException ex = assertThrows(MongoSocksProxyException.class, + () -> connectWithMiniServer(bytes, true)); + assertNull(ex.getProxyReplyCode()); + } + + // ----------------------------------------------------------------------- + // Negotiation + // ----------------------------------------------------------------------- + + @Test + void noAcceptableMethodNoReplyCode() { + byte[] bytes = {0x05, (byte) 0xFF}; + MongoSocksProxyException ex = assertThrows(MongoSocksProxyException.class, + () -> connectWithMiniServer(bytes, false)); + assertNull(ex.getProxyReplyCode()); + } + + @Test + void wrongSocksVersionNoReplyCode() { + byte[] bytes = {0x04, 0x00}; + MongoSocksProxyException ex = assertThrows(MongoSocksProxyException.class, + () -> connectWithMiniServer(bytes, false)); + assertNull(ex.getProxyReplyCode()); + } + + // ----------------------------------------------------------------------- + // IO failure mid-handshake → surfaces as MongoSocksProxyException with null replyCode + // ----------------------------------------------------------------------- + + @Test + void ioFailureDuringNegotiationNoReplyCode() { + // Mini-server half-closes before sending any method-selection bytes, so the client sees + // EOF while reading the negotiation reply. + byte[] noReply = new byte[0]; + MongoSocksProxyException ex = assertThrows(MongoSocksProxyException.class, + () -> connectWithMiniServer(noReply, false, true)); + assertTrue(ex.getMessage().contains("Malformed reply from SOCKS proxy server")); + assertNull(ex.getProxyReplyCode()); + } + + @Test + void unknownReplyCodeDuringConnectRelayNoReplyCode() { + byte[] bytes = { + 0x05, 0x00, // negotiation OK + 0x05, 0x09, 0x00, 0x01, 0, 0, 0, 0, 0, 0 // reply code 0x09 is not a known RFC 1928 code + }; + MongoSocksProxyException ex = assertThrows(MongoSocksProxyException.class, + () -> connectWithMiniServer(bytes, false)); + assertNull(ex.getProxyReplyCode()); + } + + @Test + void ioFailureDuringAuthenticationNoReplyCode() { + byte[] bytes = {0x05, 0x02}; + MongoSocksProxyException ex = assertThrows(MongoSocksProxyException.class, + () -> connectWithMiniServer(bytes, true, true)); + assertTrue(ex.getMessage().contains("Malformed reply from SOCKS proxy server")); + assertNull(ex.getProxyReplyCode()); + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/TestClusterListener.java b/driver-core/src/test/unit/com/mongodb/internal/connection/TestClusterListener.java index edf1babd028..e1abb2c42f9 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/TestClusterListener.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/TestClusterListener.java @@ -16,6 +16,8 @@ package com.mongodb.internal.connection; +import com.mongodb.connection.ServerDescription; +import com.mongodb.connection.ServerType; import com.mongodb.event.ClusterClosedEvent; import com.mongodb.event.ClusterDescriptionChangedEvent; import com.mongodb.event.ClusterListener; @@ -115,6 +117,14 @@ public void waitForClusterDescriptionChangedEvents( } } + public void waitForAllServersDiscovered(final Duration duration) throws InterruptedException, TimeoutException { + waitForClusterDescriptionChangedEvents( + event -> event.getNewDescription().getServerDescriptions().stream() + .map(ServerDescription::getType) + .noneMatch(ServerType.UNKNOWN::equals), + 1, duration); + } + /** * Waits for the cluster to be closed, which is signaled by a {@link ClusterClosedEvent}. */ diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/TestSessionContext.java b/driver-core/src/test/unit/com/mongodb/internal/connection/TestSessionContext.java index 4e69b5fc9b9..65af5785f3c 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/TestSessionContext.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/TestSessionContext.java @@ -17,11 +17,14 @@ package com.mongodb.internal.connection; import com.mongodb.ReadConcern; +import com.mongodb.internal.session.BaseClientSessionImpl.OverloadRetryPolicyState; import com.mongodb.internal.session.SessionContext; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.BsonTimestamp; +import static com.mongodb.assertions.Assertions.fail; + class TestSessionContext implements SessionContext { private BsonDocument clusterTime; @@ -129,4 +132,9 @@ public void markSessionDirty() { public boolean isSessionMarkedDirty() { return false; } + + @Override + public OverloadRetryPolicyState getOverloadRetryPolicyState() { + throw fail(); + } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/UsageTrackingConnectionSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/UsageTrackingConnectionSpecification.groovy index 78d79fba8b2..379d60a2fe7 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/UsageTrackingConnectionSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/UsageTrackingConnectionSpecification.groovy @@ -26,7 +26,7 @@ import org.bson.BsonInt32 import org.bson.codecs.BsonDocumentCodec import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ReadPreference.primary import static com.mongodb.connection.ClusterConnectionMode.SINGLE @@ -49,7 +49,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.openedAt == Long.MAX_VALUE when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: connection.openedAt <= System.currentTimeMillis() @@ -65,7 +65,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.openedAt == Long.MAX_VALUE when: - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -80,7 +80,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.lastUsedAt == Long.MAX_VALUE when: - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) then: connection.lastUsedAt <= System.currentTimeMillis() @@ -96,7 +96,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.lastUsedAt == Long.MAX_VALUE when: - connection.openAsync(OPERATION_CONTEXT, futureResultCallback) + connection.openAsync(createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -106,11 +106,11 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on sendMessage'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt when: - connection.sendMessage([], 1, OPERATION_CONTEXT) + connection.sendMessage([], 1, createOperationContext()) then: connection.lastUsedAt >= openedLastUsedAt @@ -121,12 +121,12 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on sendMessage asynchronously'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt def futureResultCallback = new FutureResultCallback() when: - connection.sendMessageAsync([], 1, OPERATION_CONTEXT, futureResultCallback) + connection.sendMessageAsync([], 1, createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -137,10 +137,10 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on receiveMessage'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt when: - connection.receiveMessage(1, OPERATION_CONTEXT) + connection.receiveMessage(1, createOperationContext()) then: connection.lastUsedAt >= openedLastUsedAt @@ -150,12 +150,12 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on receiveMessage asynchronously'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt def futureResultCallback = new FutureResultCallback() when: - connection.receiveMessageAsync(1, OPERATION_CONTEXT, futureResultCallback) + connection.receiveMessageAsync(1, createOperationContext(), futureResultCallback) futureResultCallback.get() then: @@ -166,13 +166,13 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on sendAndReceive'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt when: connection.sendAndReceive(new CommandMessage('test', new BsonDocument('ping', new BsonInt32(1)), NoOpFieldNameValidator.INSTANCE, primary(), - MessageSettings.builder().build(), SINGLE, null), new BsonDocumentCodec(), OPERATION_CONTEXT) + MessageSettings.builder().build(), SINGLE, null), new BsonDocumentCodec(), createOperationContext()) then: connection.lastUsedAt >= openedLastUsedAt @@ -182,7 +182,7 @@ class UsageTrackingConnectionSpecification extends Specification { def 'lastUsedAt should be set on sendAndReceive asynchronously'() { given: def connection = createConnection() - connection.open(OPERATION_CONTEXT) + connection.open(createOperationContext()) def openedLastUsedAt = connection.lastUsedAt def futureResultCallback = new FutureResultCallback() @@ -190,7 +190,7 @@ class UsageTrackingConnectionSpecification extends Specification { connection.sendAndReceiveAsync(new CommandMessage('test', new BsonDocument('ping', new BsonInt32(1)), NoOpFieldNameValidator.INSTANCE, primary(), MessageSettings.builder().build(), SINGLE, null), - new BsonDocumentCodec(), OPERATION_CONTEXT, futureResultCallback) + new BsonDocumentCodec(), createOperationContext(), futureResultCallback) futureResultCallback.get() then: diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorNoUserNameTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorNoUserNameTest.java index 5326c8c723d..0259b41930a 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorNoUserNameTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorNoUserNameTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import com.mongodb.async.FutureResultCallback; @@ -32,7 +33,6 @@ import java.util.List; import java.util.concurrent.ExecutionException; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getServerApi; import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE; import static com.mongodb.internal.connection.MessageHelper.buildSuccessfulReply; @@ -58,7 +58,7 @@ public void testSuccessfulAuthentication() { enqueueSuccessfulAuthenticationReply(); new X509Authenticator(getCredentialWithCache(), MULTIPLE, getServerApi()) - .authenticate(connection, connectionDescriptionThreeSix, OPERATION_CONTEXT); + .authenticate(connection, connectionDescriptionThreeSix, ClusterFixture.createOperationContext()); validateMessages(); } @@ -69,7 +69,7 @@ public void testSuccessfulAuthenticationAsync() throws ExecutionException, Inter FutureResultCallback futureCallback = new FutureResultCallback<>(); new X509Authenticator(getCredentialWithCache(), MULTIPLE, getServerApi()).authenticateAsync(connection, - connectionDescriptionThreeSix, OPERATION_CONTEXT, futureCallback); + connectionDescriptionThreeSix, ClusterFixture.createOperationContext(), futureCallback); futureCallback.get(); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorUnitTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorUnitTest.java index a8b2d7b71d5..80d0b2c0411 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorUnitTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/X509AuthenticatorUnitTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.ClusterFixture; import com.mongodb.MongoCredential; import com.mongodb.MongoSecurityException; import com.mongodb.ServerAddress; @@ -31,7 +32,6 @@ import java.util.List; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getServerApi; import static com.mongodb.internal.connection.MessageHelper.buildSuccessfulReply; import static com.mongodb.internal.connection.MessageHelper.getApiVersionField; @@ -58,7 +58,7 @@ public void testFailedAuthentication() { enqueueFailedAuthenticationReply(); try { - subject.authenticate(connection, connectionDescription, OPERATION_CONTEXT); + subject.authenticate(connection, connectionDescription, ClusterFixture.createOperationContext()); fail(); } catch (MongoSecurityException e) { // all good @@ -70,7 +70,7 @@ public void testFailedAuthenticationAsync() { enqueueFailedAuthenticationReply(); FutureResultCallback futureCallback = new FutureResultCallback<>(); - subject.authenticateAsync(connection, connectionDescription, OPERATION_CONTEXT, futureCallback); + subject.authenticateAsync(connection, connectionDescription, ClusterFixture.createOperationContext(), futureCallback); try { futureCallback.get(); @@ -92,7 +92,7 @@ private void enqueueFailedAuthenticationReply() { public void testSuccessfulAuthentication() { enqueueSuccessfulAuthenticationReply(); - subject.authenticate(connection, connectionDescription, OPERATION_CONTEXT); + subject.authenticate(connection, connectionDescription, ClusterFixture.createOperationContext()); validateMessages(); } @@ -102,7 +102,7 @@ public void testSuccessfulAuthenticationAsync() { enqueueSuccessfulAuthenticationReply(); FutureResultCallback futureCallback = new FutureResultCallback<>(); - subject.authenticateAsync(connection, connectionDescription, OPERATION_CONTEXT, futureCallback); + subject.authenticateAsync(connection, connectionDescription, ClusterFixture.createOperationContext(), futureCallback); futureCallback.get(); @@ -117,7 +117,7 @@ public void testSpeculativeAuthentication() { + "user: \"CN=client,OU=kerneluser,O=10Gen,L=New York City,ST=New York,C=US\", " + "mechanism: \"MONGODB-X509\", db: \"$external\"}"); subject.setSpeculativeAuthenticateResponse(BsonDocument.parse(speculativeAuthenticateResponse)); - subject.authenticate(connection, connectionDescription, OPERATION_CONTEXT); + subject.authenticate(connection, connectionDescription, ClusterFixture.createOperationContext()); assertEquals(connection.getSent().size(), 0); assertEquals(expectedSpeculativeAuthenticateCommand, subject.createSpeculativeAuthenticateCommand(connection)); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/netty/NettyStreamCloseFutureListenerTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/netty/NettyStreamCloseFutureListenerTest.java index 2287b478938..305e1d53624 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/netty/NettyStreamCloseFutureListenerTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/netty/NettyStreamCloseFutureListenerTest.java @@ -20,6 +20,7 @@ import com.mongodb.connection.AsyncCompletionHandler; import com.mongodb.connection.SocketSettings; import com.mongodb.connection.SslSettings; +import com.mongodb.internal.connection.OperationContext; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; @@ -39,7 +40,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; +import static com.mongodb.ClusterFixture.createOperationContext; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -99,7 +100,7 @@ public void failed(final Throwable t) { }; WeakReference> canary = new WeakReference<>(handler); - stream.openAsync(OPERATION_CONTEXT, handler); + stream.openAsync(createOperationContext(), handler); assertTrue(opened.await(10, TimeUnit.SECONDS), "open did not complete"); // Nullify the test's own reference so the driver is the only thing that could still retain the handler. @@ -117,12 +118,13 @@ public void failed(final Throwable t) { @Test @DisplayName("pending read should be failed when the channel is closed") public void shouldFailPendingReadWhenChannelIsClosed() throws Exception { - Socket acceptedSocket = openAndAcceptConnection(); + OperationContext operationContext = createOperationContext(); + Socket acceptedSocket = openAndAcceptConnection(operationContext); // Create a read that 127.0.0.1 will never satisfy, then close the connection from the server side CountDownLatch readCompleted = new CountDownLatch(1); AtomicReference readFailure = new AtomicReference<>(); - stream.readAsync(4, OPERATION_CONTEXT, new AsyncCompletionHandler() { + stream.readAsync(4, operationContext, new AsyncCompletionHandler() { @Override public void completed(final ByteBuf result) { readCompleted.countDown(); @@ -166,7 +168,7 @@ private static void assertRefUnreachable(final WeakReference ref, final Strin * Opens {@link #stream} against the local {@link #serverSocket} and returns the server side of the accepted * connection, so the test can later close it to simulate the server dropping the connection. */ - private Socket openAndAcceptConnection() throws Exception { + private Socket openAndAcceptConnection(final OperationContext operationContext) throws Exception { AtomicReference acceptedSocket = new AtomicReference<>(); Thread acceptor = new Thread(() -> { try { @@ -177,7 +179,7 @@ private Socket openAndAcceptConnection() throws Exception { }); acceptor.start(); - stream.open(OPERATION_CONTEXT); + stream.open(operationContext); acceptor.join(TimeUnit.SECONDS.toMillis(10)); assertNotNull(acceptedSocket.get(), "the server never accepted the connection"); return acceptedSocket.get(); diff --git a/driver-core/src/test/unit/com/mongodb/internal/mockito/InsufficientStubbingDetectorDemoTest.java b/driver-core/src/test/unit/com/mongodb/internal/mockito/InsufficientStubbingDetectorDemoTest.java index 5d8bd8e61b1..9e31f1bae16 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/mockito/InsufficientStubbingDetectorDemoTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/mockito/InsufficientStubbingDetectorDemoTest.java @@ -15,6 +15,7 @@ */ package com.mongodb.internal.mockito; +import com.mongodb.ClusterFixture; import com.mongodb.internal.binding.ReadBinding; import com.mongodb.internal.operation.ListCollectionsOperation; import org.bson.BsonDocument; @@ -24,7 +25,6 @@ import org.mockito.Mockito; import org.mockito.internal.stubbing.answers.ThrowsException; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; @@ -34,39 +34,39 @@ final class InsufficientStubbingDetectorDemoTest { @BeforeEach void beforeEach() { - operation = new ListCollectionsOperation<>("db", new BsonDocumentCodec()); + operation = new ListCollectionsOperation<>("db", new BsonDocumentCodec(), null); } @Test void mockObjectWithDefaultAnswer() { ReadBinding binding = Mockito.mock(ReadBinding.class); - assertThrows(NullPointerException.class, () -> operation.execute(binding, OPERATION_CONTEXT)); + assertThrows(NullPointerException.class, () -> operation.execute(binding, ClusterFixture.createOperationContext())); } @Test void mockObjectWithThrowsException() { ReadBinding binding = Mockito.mock(ReadBinding.class, new ThrowsException(new AssertionError("Insufficient stubbing for " + ReadBinding.class))); - assertThrows(AssertionError.class, () -> operation.execute(binding, OPERATION_CONTEXT)); + assertThrows(AssertionError.class, () -> operation.execute(binding, ClusterFixture.createOperationContext())); } @Test void mockObjectWithInsufficientStubbingDetector() { ReadBinding binding = MongoMockito.mock(ReadBinding.class); - assertThrows(AssertionError.class, () -> operation.execute(binding, OPERATION_CONTEXT)); + assertThrows(AssertionError.class, () -> operation.execute(binding, ClusterFixture.createOperationContext())); } @Test void stubbingWithThrowsException() { ReadBinding binding = Mockito.mock(ReadBinding.class, new ThrowsException(new AssertionError("Unfortunately, you cannot do stubbing"))); - assertThrows(AssertionError.class, () -> when(binding.getReadConnectionSource(OPERATION_CONTEXT)).thenReturn(null)); + assertThrows(AssertionError.class, () -> when(binding.getReadConnectionSource(ClusterFixture.createOperationContext())).thenReturn(null)); } @Test void stubbingWithInsufficientStubbingDetector() { MongoMockito.mock(ReadBinding.class, bindingMock -> - when(bindingMock.getReadConnectionSource(OPERATION_CONTEXT)).thenReturn(null) + when(bindingMock.getReadConnectionSource(ClusterFixture.createOperationContext())).thenReturn(null) ); } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/mockito/MongoMockito.java b/driver-core/src/test/unit/com/mongodb/internal/mockito/MongoMockito.java index 7b6c08a2efb..739aaf996a0 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/mockito/MongoMockito.java +++ b/driver-core/src/test/unit/com/mongodb/internal/mockito/MongoMockito.java @@ -23,7 +23,7 @@ import java.util.function.Consumer; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.withSettings; /** @@ -56,18 +56,19 @@ public static T mock(final Class classToMock) { * Moreover, a mock object created with {@link ThrowsException} as its default answer cannot be stubbed: * stubbing requires calling methods of the mock object, but they all complete abruptly * (see {@link InsufficientStubbingDetectorDemoTest#stubbingWithThrowsException()}). - * Therefore, {@link ThrowsException} is not suitable for detecting insufficient stubbing.

+ * Therefore, {@link ThrowsException} is not suitable for detecting insufficient stubbing. *

* This method overcomes both of the aforementioned limitations by using {@link InsufficientStubbingDetector} as the default answer * (see {@link InsufficientStubbingDetectorDemoTest#mockObjectWithInsufficientStubbingDetector()}, * {@link InsufficientStubbingDetectorDemoTest#stubbingWithInsufficientStubbingDetector()}). - * Note also that for convenience, {@link InsufficientStubbingDetector} stubs the {@link Object#toString()} method by using - * {@link OngoingStubbing#thenCallRealMethod()}, unless this stubbing is overwritten by the {@code tuner}.

+ * Note also that for convenience, {@link InsufficientStubbingDetector} {@linkplain Mockito#lenient() leniently} + * stubs the {@link Object#toString()} method by using + * {@link OngoingStubbing#thenCallRealMethod()}, unless this stubbing is overwritten by the {@code tuner}. */ public static T mock(final Class classToMock, @Nullable final Consumer tuner) { final InsufficientStubbingDetector insufficientStubbingDetector = new InsufficientStubbingDetector(); final T mock = Mockito.mock(classToMock, withSettings().defaultAnswer(insufficientStubbingDetector)); - when(mock.toString()).thenCallRealMethod(); + lenient().when(mock.toString()).thenCallRealMethod(); if (tuner != null) { tuner.accept(mock); } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncCommandBatchCursorSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncCommandBatchCursorSpecification.groovy index b9884e3481c..d268d2df6c1 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncCommandBatchCursorSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncCommandBatchCursorSpecification.groovy @@ -72,7 +72,14 @@ class AsyncCommandBatchCursorSpecification extends Specification { def reply = getMoreResponse([], 0) when: - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, batchSize, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>( + firstBatch, batchSize, + CODEC, + null, + connectionSource, + initialConnection, + false, + null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, operationContext, commandCoreCursor) then: 1 * timeoutContext.withMaxTimeOverride(*_) @@ -111,7 +118,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { def connection = referenceCountedAsyncConnection(serverVersion) def connectionSource = getAsyncConnectionSource(connection) def operationContext = getOperationContext() - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) when: @@ -142,7 +149,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { when: def firstBatch = createCommandResult(FIRST_BATCH, 0) - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) then: @@ -173,7 +180,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { when: def firstBatch = createCommandResult([], CURSOR_ID) - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) def batch = nextBatch(cursor) @@ -223,7 +230,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { def firstBatch = createCommandResult() when: - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) def batch = nextBatch(cursor) @@ -278,7 +285,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { when: def commandCoreCursor = new AsyncCommandCursor<>(createCommandResult(FIRST_BATCH, 42), 0, - CODEC, null, connectionSource, initialConnection) + CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) def batch = nextBatch(cursor) @@ -314,7 +321,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { def firstBatch = createCommandResult() when: - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) def batch = nextBatch(cursor) @@ -354,7 +361,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { def initialConnection = referenceCountedAsyncConnection() def connectionSource = getAsyncConnectionSourceWithResult(ServerType.STANDALONE) { [null, MONGO_EXCEPTION] } def firstBatch = createCommandResult() - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) when: @@ -374,7 +381,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { when: def firstBatch = createCommandResult() - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) then: @@ -401,7 +408,7 @@ class AsyncCommandBatchCursorSpecification extends Specification { when: def firstBatch = createCommandResult() - def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new AsyncCommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new AsyncCommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) then: diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncOperationHelperSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncOperationHelperSpecification.groovy index d573822cab7..a492a53ea01 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncOperationHelperSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncOperationHelperSpecification.groovy @@ -36,7 +36,7 @@ import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.Decoder import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ReadPreference.primary import static com.mongodb.internal.operation.AsyncOperationHelper.CommandReadTransformerAsync import static com.mongodb.internal.operation.AsyncOperationHelper.executeCommandAsync @@ -74,7 +74,7 @@ class AsyncOperationHelperSpecification extends Specification { _ * getDescription() >> connectionDescription } - def operationContext = OPERATION_CONTEXT.withSessionContext( + def operationContext = createOperationContext().withSessionContext( Stub(SessionContext) { hasSession() >> true hasActiveTransaction() >> false @@ -91,7 +91,7 @@ class AsyncOperationHelperSpecification extends Specification { when: executeRetryableWriteAsync(asyncWriteBinding, operationContext, dbName, primary(), NoOpFieldNameValidator.INSTANCE, decoder, commandCreator, FindAndModifyHelper.asyncTransformer(), - { cmd -> cmd }, callback) + { cmd -> cmd }, true, null, callback) then: 2 * connection.commandAsync(dbName, command, _, primary(), decoder, *_) >> { it.last().onResult(results.poll(), null) } @@ -116,7 +116,7 @@ class AsyncOperationHelperSpecification extends Specification { def connectionDescription = Stub(ConnectionDescription) when: - executeCommandAsync(asyncWriteBinding, OPERATION_CONTEXT, dbName, command, connection, { t, conn -> t }, callback) + executeCommandAsync(asyncWriteBinding, createOperationContext(), dbName, command, connection, { t, conn -> t }, callback) then: _ * connection.getDescription() >> connectionDescription @@ -143,7 +143,8 @@ class AsyncOperationHelperSpecification extends Specification { def connectionDescription = Stub(ConnectionDescription) when: - executeRetryableReadAsync(asyncReadBinding, OPERATION_CONTEXT, dbName, commandCreator, decoder, function, false, callback) + executeRetryableReadAsync(asyncReadBinding, createOperationContext(), dbName, commandCreator, decoder, function, + false, null, callback) then: _ * connection.getDescription() >> connectionDescription diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/BulkWriteBatchSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/BulkWriteBatchSpecification.groovy index 2ccd3513cf7..a7eaacda36c 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/BulkWriteBatchSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/BulkWriteBatchSpecification.groovy @@ -309,7 +309,7 @@ class BulkWriteBatchSpecification extends Specification { [new DeleteRequest(new BsonDocument()).multi(true), new InsertRequest(new BsonDocument())], operationContext, null, null) then: - !bulkWriteBatch.getRetryWrites() + !bulkWriteBatch.isWriteRetryRequirementsMet() } def 'should handle operation responses'() { diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java index 5de1992b69d..2a42b3d3b8a 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java @@ -122,9 +122,11 @@ void shouldIgnoreSuccessfulCursorResultWhenVerboseResultIsFalse() { options, WriteConcern.ACKNOWLEDGED, false, + false, + null, getDefaultCodecRegistry()); //when - ClientBulkWriteResult result = op.execute(binding, ClusterFixture.OPERATION_CONTEXT); + ClientBulkWriteResult result = op.execute(binding, ClusterFixture.createOperationContext()); //then assertEquals( @@ -174,9 +176,11 @@ void shouldUseDefaultNumberOfModifiedDocumentsWhenMissingInCursor() { options, WriteConcern.ACKNOWLEDGED, false, + false, + null, getDefaultCodecRegistry()); //when - ClientBulkWriteResult result = op.execute(binding, ClusterFixture.OPERATION_CONTEXT); + ClientBulkWriteResult result = op.execute(binding, ClusterFixture.createOperationContext()); //then assertEquals(1, result.getInsertedCount()); diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorSpecification.groovy index ccbf1b53efd..d9375727bc3 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorSpecification.groovy @@ -73,7 +73,7 @@ class CommandBatchCursorSpecification extends Specification { def reply = getMoreResponse([], 0) when: - def commandCoreCursor = new CommandCursor<>(firstBatch, batchSize, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, batchSize, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, maxTimeMS, operationContext, commandCoreCursor) then: @@ -109,7 +109,7 @@ class CommandBatchCursorSpecification extends Specification { def serverVersion = new ServerVersion([3, 6, 0]) def connection = referenceCountedConnection(serverVersion) def connectionSource = getConnectionSource(connection) - def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) when: @@ -137,7 +137,7 @@ class CommandBatchCursorSpecification extends Specification { when: def firstBatch = createCommandResult(FIRST_BATCH, 0) - def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) then: @@ -161,7 +161,7 @@ class CommandBatchCursorSpecification extends Specification { when: def firstBatch = createCommandResult([], CURSOR_ID) - def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) def batch = cursor.next() @@ -215,7 +215,7 @@ class CommandBatchCursorSpecification extends Specification { def firstBatch = createCommandResult() when: - def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) List batch = cursor.next() @@ -268,7 +268,7 @@ class CommandBatchCursorSpecification extends Specification { when: def commandCoreCursor = new CommandCursor<>(createCommandResult(FIRST_BATCH, 42), 0, CODEC, - null, connectionSource, initialConnection) + null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) def batch = cursor.next() @@ -304,7 +304,7 @@ class CommandBatchCursorSpecification extends Specification { def firstBatch = createCommandResult() when: - def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) def batch = cursor.next() @@ -343,7 +343,7 @@ class CommandBatchCursorSpecification extends Specification { def initialConnection = referenceCountedConnection() def connectionSource = getConnectionSourceWithResult(ServerType.STANDALONE) { throw MONGO_EXCEPTION } def firstBatch = createCommandResult() - def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) when: @@ -364,7 +364,7 @@ class CommandBatchCursorSpecification extends Specification { when: def firstBatch = createCommandResult() - def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) then: @@ -391,7 +391,7 @@ class CommandBatchCursorSpecification extends Specification { when: def firstBatch = createCommandResult() - def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(firstBatch, 0, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 0, operationContext, commandCoreCursor) then: connectionSource.getCount() == 1 @@ -450,7 +450,7 @@ class CommandBatchCursorSpecification extends Specification { connectionSource.retain() >> connectionSource def initialResults = createCommandResult([]) - def commandCoreCursor = new CommandCursor<>(initialResults, 2, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(initialResults, 2, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 100, operationContext, commandCoreCursor) when: @@ -476,7 +476,7 @@ class CommandBatchCursorSpecification extends Specification { connectionSource.retain() >> connectionSource def initialResults = createCommandResult([]) - def commandCoreCursor = new CommandCursor<>(initialResults, 2, CODEC, null, connectionSource, initialConnection) + def commandCoreCursor = new CommandCursor<>(initialResults, 2, CODEC, null, connectionSource, initialConnection, false, null) def cursor = new CommandBatchCursor(TimeoutMode.CURSOR_LIFETIME, 100, operationContext, commandCoreCursor) when: diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/CommandCursorTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/CommandCursorTest.java index b59c47f7b60..2f75bba9821 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/CommandCursorTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/CommandCursorTest.java @@ -172,6 +172,8 @@ private Cursor createCoreCursor() { DOCUMENT_CODEC, null, connectionSource, - mockConnection); + mockConnection, + false, + null); } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/CommitTransactionOperationUnitSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/CommitTransactionOperationUnitSpecification.groovy index 75ed9e6c5f3..0fbfe927a49 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/CommitTransactionOperationUnitSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/CommitTransactionOperationUnitSpecification.groovy @@ -27,7 +27,7 @@ import com.mongodb.internal.binding.WriteBinding import com.mongodb.internal.connection.OperationContext import com.mongodb.internal.session.SessionContext -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class CommitTransactionOperationUnitSpecification extends OperationUnitSpecification { def 'should add UnknownTransactionCommitResult error label to MongoTimeoutException'() { @@ -39,10 +39,10 @@ class CommitTransactionOperationUnitSpecification extends OperationUnitSpecifica def writeBinding = Stub(WriteBinding) { getWriteConnectionSource(_) >> { throw new MongoTimeoutException('Time out!') } } - def operation = new CommitTransactionOperation(WriteConcern.ACKNOWLEDGED) + def operation = new CommitTransactionOperation(WriteConcern.ACKNOWLEDGED, null, false) when: - operation.execute(writeBinding, OPERATION_CONTEXT.withSessionContext(sessionContext)) + operation.execute(writeBinding, createOperationContext().withSessionContext(sessionContext)) then: def e = thrown(MongoTimeoutException) @@ -60,11 +60,11 @@ class CommitTransactionOperationUnitSpecification extends OperationUnitSpecifica it[1].onResult(null, new MongoTimeoutException('Time out!')) } } - def operation = new CommitTransactionOperation(WriteConcern.ACKNOWLEDGED) + def operation = new CommitTransactionOperation(WriteConcern.ACKNOWLEDGED, null, false) def callback = new FutureResultCallback() when: - operation.executeAsync(writeBinding, OPERATION_CONTEXT.withSessionContext(sessionContext), callback) + operation.executeAsync(writeBinding, createOperationContext().withSessionContext(sessionContext), callback) callback.get() then: diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/CursorResourceManagerTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/CursorResourceManagerTest.java index 68b3bf7f606..26fd6e6eab5 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/CursorResourceManagerTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/CursorResourceManagerTest.java @@ -15,6 +15,7 @@ */ package com.mongodb.internal.operation; +import com.mongodb.ClusterFixture; import com.mongodb.MongoNamespace; import com.mongodb.ServerCursor; import com.mongodb.internal.binding.AsyncConnectionSource; @@ -24,7 +25,6 @@ import com.mongodb.internal.mockito.MongoMockito; import org.junit.jupiter.api.Test; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.Mockito.when; @@ -50,12 +50,12 @@ void doClose(final OperationContext operationContext) { cursorResourceManager.tryStartOperation(); try { assertDoesNotThrow(() -> { - cursorResourceManager.close(OPERATION_CONTEXT); - cursorResourceManager.close(OPERATION_CONTEXT); + cursorResourceManager.close(ClusterFixture.createOperationContext()); + cursorResourceManager.close(ClusterFixture.createOperationContext()); cursorResourceManager.setServerCursor(null); }); } finally { - cursorResourceManager.endOperation(OPERATION_CONTEXT); + cursorResourceManager.endOperation(ClusterFixture.createOperationContext()); } } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/FindOperationUnitSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/FindOperationUnitSpecification.groovy index dd843985bbb..41ccf40eb4b 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/FindOperationUnitSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/FindOperationUnitSpecification.groovy @@ -33,14 +33,14 @@ class FindOperationUnitSpecification extends OperationUnitSpecification { def 'should find with correct command'() { when: - def operation = new FindOperation(namespace, new BsonDocumentCodec()) + def operation = new FindOperation(namespace, new BsonDocumentCodec(), null) def expectedCommand = new BsonDocument('find', new BsonString(namespace.getCollectionName())) then: testOperation(operation, [3, 2, 0], expectedCommand, async, commandResult) // Overrides when: - operation = new FindOperation(namespace, new BsonDocumentCodec()) + operation = new FindOperation(namespace, new BsonDocumentCodec(), null) .filter(new BsonDocument('a', BsonBoolean.TRUE)) .projection(new BsonDocument('x', new BsonInt32(1))) .skip(2) @@ -104,7 +104,7 @@ class FindOperationUnitSpecification extends OperationUnitSpecification { def 'should find with correct command with effective batch size'() { when: - def operation = new FindOperation(namespace, new BsonDocumentCodec()) + def operation = new FindOperation(namespace, new BsonDocumentCodec(), null) .batchSize(batchSize) .limit(limit) @@ -125,7 +125,7 @@ class FindOperationUnitSpecification extends OperationUnitSpecification { def 'should use the readPreference to set secondaryOk for commands'() { when: - def operation = new FindOperation(namespace, new DocumentCodec()) + def operation = new FindOperation(namespace, new DocumentCodec(), null) then: testOperationSecondaryOk(operation, [3, 2, 0], readPreference, async, commandResult) @@ -135,7 +135,6 @@ class FindOperationUnitSpecification extends OperationUnitSpecification { } def namespace = new MongoNamespace('db', 'coll') - def decoder = new BsonDocumentCodec() def readPreference = ReadPreference.secondary() def commandResult = new BsonDocument('cursor', new BsonDocument('id', new BsonInt64(0)) .append('ns', new BsonString('db.coll')) diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/ListCollectionsOperationTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/ListCollectionsOperationTest.java index de1bfe405ed..952d5b239f7 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/ListCollectionsOperationTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/ListCollectionsOperationTest.java @@ -15,6 +15,7 @@ */ package com.mongodb.internal.operation; +import com.mongodb.ClusterFixture; import com.mongodb.MongoNamespace; import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; @@ -39,7 +40,6 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.assertions.Assertions.assertNotNull; import static com.mongodb.internal.mockito.MongoMockito.mock; import static java.util.Collections.emptyList; @@ -57,7 +57,7 @@ final class ListCollectionsOperationTest { @BeforeEach void beforeEach() { MongoNamespace namespace = new MongoNamespace("db", "coll"); - operation = new ListCollectionsOperation<>(namespace.getDatabaseName(), new BsonDocumentCodec()); + operation = new ListCollectionsOperation<>(namespace.getDatabaseName(), new BsonDocumentCodec(), null); mocks = mocks(namespace); } @@ -99,7 +99,7 @@ void authorizedCollectionsIsFalseByDefault() { } private BsonDocument executeOperationAndCaptureCommand() { - operation.execute(mocks.readBinding(), OPERATION_CONTEXT); + operation.execute(mocks.readBinding(), ClusterFixture.createOperationContext()); ArgumentCaptor commandCaptor = forClass(BsonDocument.class); verify(mocks.connection()).command(any(), commandCaptor.capture(), any(), any(), any(), any()); return commandCaptor.getValue(); diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy index 6c24c69673d..6dcb6caf34f 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationHelperSpecification.groovy @@ -31,13 +31,13 @@ import org.bson.BsonArray import org.bson.BsonDocument import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.WriteConcern.ACKNOWLEDGED import static com.mongodb.WriteConcern.UNACKNOWLEDGED import static com.mongodb.connection.ServerType.REPLICA_SET_PRIMARY import static com.mongodb.connection.ServerType.STANDALONE -import static com.mongodb.internal.operation.OperationHelper.canRetryRead -import static com.mongodb.internal.operation.OperationHelper.isRetryableWrite +import static com.mongodb.internal.operation.OperationHelper.isReadRetryRequirementsMet +import static com.mongodb.internal.operation.OperationHelper.isNonCommandWriteRetryRequirementsMet import static com.mongodb.internal.operation.OperationHelper.validateWriteRequests class OperationHelperSpecification extends Specification { @@ -81,8 +81,8 @@ class OperationHelperSpecification extends Specification { } expect: - isRetryableWrite(retryWrites, writeConcern, connectionDescription, noTransactionSessionContext) == expected - !isRetryableWrite(retryWrites, writeConcern, connectionDescription, activeTransactionSessionContext) + isNonCommandWriteRetryRequirementsMet(retryWrites, writeConcern, connectionDescription, noTransactionSessionContext) == expected + !isNonCommandWriteRetryRequirementsMet(retryWrites, writeConcern, connectionDescription, activeTransactionSessionContext) where: retryWrites | writeConcern | connectionDescription | expected @@ -106,8 +106,8 @@ class OperationHelperSpecification extends Specification { } expect: - canRetryRead(OPERATION_CONTEXT.withSessionContext(noTransactionSessionContext)) - !canRetryRead(OPERATION_CONTEXT.withSessionContext(activeTransactionSessionContext)) + isReadRetryRequirementsMet(true, createOperationContext().withSessionContext(noTransactionSessionContext)) + !isReadRetryRequirementsMet(true, createOperationContext().withSessionContext(activeTransactionSessionContext)) } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationUnitSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationUnitSpecification.groovy index ec5cb74156f..52934c3bfad 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/OperationUnitSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/OperationUnitSpecification.groovy @@ -41,7 +41,7 @@ import spock.lang.Specification import java.util.concurrent.TimeUnit -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class OperationUnitSpecification extends Specification { @@ -97,7 +97,7 @@ class OperationUnitSpecification extends Specification { def testSyncOperation(operation, List serverVersion, result, Boolean checkCommand=true, BsonDocument expectedCommand=null, Boolean checkSecondaryOk=false, ReadPreference readPreference=ReadPreference.primary()) { - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasActiveTransaction() >> false getReadConcern() >> ReadConcern.DEFAULT @@ -151,7 +151,7 @@ class OperationUnitSpecification extends Specification { Boolean checkCommand=true, BsonDocument expectedCommand=null, Boolean checkSecondaryOk=false, ReadPreference readPreference=ReadPreference.primary()) { - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasActiveTransaction() >> false getReadConcern() >> ReadConcern.DEFAULT diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/SyncOperationHelperSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/SyncOperationHelperSpecification.groovy index bd9bd2f2578..77161b33030 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/SyncOperationHelperSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/SyncOperationHelperSpecification.groovy @@ -34,7 +34,7 @@ import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.Decoder import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ReadPreference.primary import static com.mongodb.internal.operation.OperationUnitSpecification.getMaxWireVersionForServerVersion import static com.mongodb.internal.operation.SyncOperationHelper.CommandReadTransformer @@ -61,7 +61,7 @@ class SyncOperationHelperSpecification extends Specification { def connectionDescription = Stub(ConnectionDescription) when: - executeCommand(writeBinding, OPERATION_CONTEXT, dbName, command, decoder, function) + executeCommand(writeBinding, createOperationContext(), dbName, command, decoder, function) then: _ * connection.getDescription() >> connectionDescription @@ -71,7 +71,7 @@ class SyncOperationHelperSpecification extends Specification { def 'should retry with retryable exception'() { given: - def operationContext = OPERATION_CONTEXT + def operationContext = createOperationContext() .withSessionContext(Stub(SessionContext) { hasSession() >> true hasActiveTransaction() >> false @@ -103,8 +103,8 @@ class SyncOperationHelperSpecification extends Specification { when: executeRetryableWrite(writeBinding, operationContext, dbName, primary(), - NoOpFieldNameValidator.INSTANCE, decoder, commandCreator, FindAndModifyHelper.transformer()) - { cmd -> cmd } + NoOpFieldNameValidator.INSTANCE, decoder, commandCreator, FindAndModifyHelper.transformer(), + { cmd -> cmd }, true, null) then: 2 * connection.command(dbName, command, _, primary(), decoder, _) >> { results.poll() } @@ -132,7 +132,7 @@ class SyncOperationHelperSpecification extends Specification { def connectionDescription = Stub(ConnectionDescription) when: - executeRetryableRead(readBinding, OPERATION_CONTEXT, dbName, commandCreator, decoder, function, false) + executeRetryableRead(readBinding, createOperationContext(), dbName, commandCreator, decoder, function, false, null) then: _ * connection.getDescription() >> connectionDescription diff --git a/driver-core/src/test/unit/com/mongodb/internal/session/BaseClientSessionImplTest.java b/driver-core/src/test/unit/com/mongodb/internal/session/BaseClientSessionImplTest.java index c7fc1d73e20..495523f90a3 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/session/BaseClientSessionImplTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/session/BaseClientSessionImplTest.java @@ -17,10 +17,10 @@ package com.mongodb.internal.session; import com.mongodb.ClientSessionOptions; +import com.mongodb.ClusterFixture; import com.mongodb.session.ClientSession; import org.junit.jupiter.api.Test; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getCluster; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -28,7 +28,7 @@ class BaseClientSessionImplTest { @Test void shouldNotCheckoutServerSessionIfNeverRequested() { - ServerSessionPool serverSessionPool = new ServerSessionPool(getCluster(), OPERATION_CONTEXT); + ServerSessionPool serverSessionPool = new ServerSessionPool(getCluster(), ClusterFixture.createOperationContext()); ClientSession clientSession = new BaseClientSessionImpl(serverSessionPool, new Object(), ClientSessionOptions.builder().build()); assertEquals(0, serverSessionPool.getInUseCount()); @@ -40,7 +40,7 @@ void shouldNotCheckoutServerSessionIfNeverRequested() { @Test void shouldDelayServerSessionCheckoutUntilRequested() { - ServerSessionPool serverSessionPool = new ServerSessionPool(getCluster(), OPERATION_CONTEXT); + ServerSessionPool serverSessionPool = new ServerSessionPool(getCluster(), ClusterFixture.createOperationContext()); ClientSession clientSession = new BaseClientSessionImpl(serverSessionPool, new Object(), ClientSessionOptions.builder().build()); assertEquals(0, serverSessionPool.getInUseCount()); diff --git a/driver-core/src/test/unit/com/mongodb/internal/session/ServerSessionPoolSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/session/ServerSessionPoolSpecification.groovy index 19bfa994200..60ec62b2faf 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/session/ServerSessionPoolSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/session/ServerSessionPoolSpecification.groovy @@ -25,6 +25,7 @@ import com.mongodb.internal.connection.Cluster import com.mongodb.internal.connection.Connection import com.mongodb.internal.connection.Server import com.mongodb.internal.connection.ServerTuple +import com.mongodb.internal.thread.AsyncClientExecutor import com.mongodb.internal.validator.NoOpFieldNameValidator import org.bson.BsonArray import org.bson.BsonBinarySubType @@ -32,7 +33,7 @@ import org.bson.BsonDocument import org.bson.codecs.BsonDocumentCodec import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.getServerApi import static com.mongodb.ReadPreference.primaryPreferred @@ -71,7 +72,7 @@ class ServerSessionPoolSpecification extends Specification { def cluster = Stub(Cluster) { getCurrentDescription() >> connectedDescription } - def pool = new ServerSessionPool(cluster, TIMEOUT_SETTINGS, getServerApi()) + def pool = createServerSessionPool(cluster) when: def session = pool.get() @@ -85,7 +86,7 @@ class ServerSessionPoolSpecification extends Specification { def cluster = Stub(Cluster) { getCurrentDescription() >> connectedDescription } - def pool = new ServerSessionPool(cluster, TIMEOUT_SETTINGS, getServerApi()) + def pool = createServerSessionPool(cluster) pool.close() when: @@ -100,7 +101,7 @@ class ServerSessionPoolSpecification extends Specification { def cluster = Stub(Cluster) { getCurrentDescription() >> connectedDescription } - def pool = new ServerSessionPool(cluster, TIMEOUT_SETTINGS, getServerApi()) + def pool = createServerSessionPool(cluster) def session = pool.get() when: @@ -120,7 +121,7 @@ class ServerSessionPoolSpecification extends Specification { millis() >>> [0, MINUTES.toMillis(29) + 1, ] } - def pool = new ServerSessionPool(cluster, OPERATION_CONTEXT, clock) + def pool = new ServerSessionPool(cluster, createOperationContext(), clock) def sessionOne = pool.get() when: @@ -146,7 +147,7 @@ class ServerSessionPoolSpecification extends Specification { def clock = Stub(ServerSessionPool.Clock) { millis() >>> [0, 0, 0] } - def pool = new ServerSessionPool(cluster, OPERATION_CONTEXT, clock) + def pool = new ServerSessionPool(cluster, createOperationContext(), clock) def session = pool.get() when: @@ -165,7 +166,7 @@ class ServerSessionPoolSpecification extends Specification { def clock = Stub(ServerSessionPool.Clock) { millis() >> 42 } - def pool = new ServerSessionPool(cluster, OPERATION_CONTEXT, clock) + def pool = new ServerSessionPool(cluster, createOperationContext(), clock) when: def session = pool.get() as ServerSessionPool.ServerSessionImpl @@ -187,7 +188,7 @@ class ServerSessionPoolSpecification extends Specification { def clock = Stub(ServerSessionPool.Clock) { millis() >> 42 } - def pool = new ServerSessionPool(cluster, OPERATION_CONTEXT, clock) + def pool = new ServerSessionPool(cluster, createOperationContext(), clock) when: def session = pool.get() as ServerSessionPool.ServerSessionImpl @@ -207,7 +208,7 @@ class ServerSessionPoolSpecification extends Specification { def cluster = Mock(Cluster) { getCurrentDescription() >> connectedDescription } - def pool = new ServerSessionPool(cluster, TIMEOUT_SETTINGS, getServerApi()) + def pool = createServerSessionPool(cluster) def sessions = [] 10.times { sessions.add(pool.get()) } @@ -226,4 +227,8 @@ class ServerSessionPoolSpecification extends Specification { { it instanceof BsonDocumentCodec }, _) >> new BsonDocument() 1 * connection.release() } + + static createServerSessionPool(Cluster cluster) { + new ServerSessionPool(cluster, AsyncClientExecutor.NO_OP, TIMEOUT_SETTINGS, getServerApi()) + } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/thread/CommonExecutorTest.java b/driver-core/src/test/unit/com/mongodb/internal/thread/CommonExecutorTest.java new file mode 100644 index 00000000000..e3690ddc077 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/thread/CommonExecutorTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.thread; + +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import static com.mongodb.internal.thread.CommonExecutor.commonExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +class CommonExecutorTest { + private static final long TIMEOUT_MILLIS = 400; + /** + * This test verifies that even in the unlikely event that the single scheduling thread is terminated, + * it is replaced with another one to execute a previously scheduled task. + */ + @Test + void singleSchedulingThreadIsReplacedIfTerminated() throws Exception { + Executor sameThreadExecutor = Runnable::run; + CompletableFuture newSchedulingThread = new CompletableFuture<>(); + commonExecutor().schedule( + () -> newSchedulingThread.complete(Thread.currentThread()), + Duration.ofMillis(TIMEOUT_MILLIS / 2), + sameThreadExecutor); + CompletableFuture terminatedSchedulingThread = new CompletableFuture<>(); + commonExecutor().schedule( + () -> { + terminatedSchedulingThread.complete(Thread.currentThread()); + throw new Error("This error is thrown in the single scheduling thread, causing its termination"); + }, + Duration.ZERO, + sameThreadExecutor); + assertNotSame(terminatedSchedulingThread.get(TIMEOUT_MILLIS, MILLISECONDS), newSchedulingThread.get(TIMEOUT_MILLIS, MILLISECONDS)); + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/thread/DefaultAsyncClientExecutorTest.java b/driver-core/src/test/unit/com/mongodb/internal/thread/DefaultAsyncClientExecutorTest.java new file mode 100644 index 00000000000..efcb4b67dba --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/thread/DefaultAsyncClientExecutorTest.java @@ -0,0 +1,278 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.thread; + +import com.mongodb.internal.thread.AsyncClientExecutor.RejectableRunnable; +import com.mongodb.internal.time.StartTime; +import io.netty.channel.EventLoopGroup; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.mongodb.internal.thread.InterruptionUtil.interruptAndCreateMongoInterruptedException; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class DefaultAsyncClientExecutorTest { + private static final long SCHEDULE_DELAY_MILLIS = 200; + + private ExecutorService executorService; + private ScheduledExecutorService scheduledExecutorService; + + @BeforeEach + void beforeEach() { + executorService = Executors.newSingleThreadExecutor(); + scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + } + + @AfterEach + void afterEach() { + if (executorService != null) { + executorService.shutdownNow(); + } + if (scheduledExecutorService != null) { + scheduledExecutorService.shutdownNow(); + } + } + + @ParameterizedTest + @ValueSource(longs = {0, SCHEDULE_DELAY_MILLIS}) + void schedule(final long delayMs) { + Duration delay = Duration.ofMillis(delayMs); + try (DefaultAsyncClientExecutor backedByExecutorService = new DefaultAsyncClientExecutor(executorService); + DefaultAsyncClientExecutor backedByScheduledExecutorService = new DefaultAsyncClientExecutor(scheduledExecutorService)) { + assertAll( + () -> assertSchedule(backedByExecutorService, delay), + () -> assertSchedule(backedByScheduledExecutorService, delay) + ); + } + } + + private static void assertSchedule( + final DefaultAsyncClientExecutor clientExecutor, final Duration delay) throws Exception { + StartTime startTime = StartTime.now(); + CompletableFuture callbackDelayFuture = new CompletableFuture<>(); + CompletableFuture callbackThreadFuture = new CompletableFuture<>(); + clientExecutor.schedule(RejectableRunnable.from((result, t) -> { + if (t != null) { + callbackDelayFuture.completeExceptionally(t); + } else { + callbackDelayFuture.complete(startTime.elapsed()); + } + callbackThreadFuture.complete(Thread.currentThread()); + }), delay); + long timeoutMs = delay.isZero() ? SCHEDULE_DELAY_MILLIS : delay.toMillis() * 2; + Duration actualCallbackDelay = callbackDelayFuture.get(timeoutMs, MILLISECONDS); + Thread actualCallbackThread = callbackThreadFuture.get(timeoutMs, MILLISECONDS); + assertTrue(actualCallbackDelay.compareTo(delay) >= 0); + Duration expectedMaxDelay = delay.isZero() ? Duration.ofMillis(SCHEDULE_DELAY_MILLIS) : delay.multipliedBy(2); + assertTrue(actualCallbackDelay.compareTo(expectedMaxDelay) < 0); + assertNotSame(Thread.currentThread(), actualCallbackThread); + } + + @ParameterizedTest + @ValueSource(longs = {0, SCHEDULE_DELAY_MILLIS}) + void closeBeforeSchedule(final long delayMs) { + Duration delay = Duration.ofMillis(delayMs); + try (DefaultAsyncClientExecutor backedByExecutorService = new DefaultAsyncClientExecutor(executorService); + DefaultAsyncClientExecutor backedByScheduledExecutorService = new DefaultAsyncClientExecutor(scheduledExecutorService)) { + assertAll( + () -> assertCloseOrBackingExecutorShutdownBeforeSchedule(backedByExecutorService, delay, backedByExecutorService::close), + () -> assertCloseOrBackingExecutorShutdownBeforeSchedule(backedByScheduledExecutorService, delay, backedByScheduledExecutorService::close) + ); + } + } + + /** + * {@link AsyncClientExecutor#close()} and {@link com.mongodb.connection.NettyTransportSettings.Builder#eventLoopGroup(EventLoopGroup)} + * forbit this scenario, but we still handle it. + */ + @Test + void backingExecutorShutdownBeforeSchedule() { + Duration delay = Duration.ofMillis(SCHEDULE_DELAY_MILLIS); + try (DefaultAsyncClientExecutor backedByExecutorService = new DefaultAsyncClientExecutor(executorService); + DefaultAsyncClientExecutor backedByScheduledExecutorService = new DefaultAsyncClientExecutor(scheduledExecutorService)) { + assertAll( + () -> assertCloseOrBackingExecutorShutdownBeforeSchedule(backedByExecutorService, delay, executorService::shutdownNow), + () -> assertCloseOrBackingExecutorShutdownBeforeSchedule(backedByScheduledExecutorService, delay, scheduledExecutorService::shutdownNow) + ); + } + } + + private static void assertCloseOrBackingExecutorShutdownBeforeSchedule( + final DefaultAsyncClientExecutor clientExecutor, final Duration delay, final Runnable doBeforeSchedule) throws Exception { + doBeforeSchedule.run(); + AtomicInteger completionCount = new AtomicInteger(); + CompletableFuture callbackFuture = new CompletableFuture<>(); + clientExecutor.schedule(RejectableRunnable.from((result, t) -> { + completionCount.incrementAndGet(); + if (t != null) { + callbackFuture.completeExceptionally(t); + } else { + callbackFuture.complete(result); + } + }), delay); + Throwable callbackException = assertThrows(CompletionException.class, () -> callbackFuture.getNow(null)).getCause(); + assertInstanceOf(RejectedExecutionException.class, callbackException); + Thread.sleep(delay.isZero() ? SCHEDULE_DELAY_MILLIS : delay.toMillis() * 2); + assertEquals(1, completionCount.get()); + } + + @Test + void closeWhileTaskIsWaitingToBeExecutedAfterSchedule() { + Duration delay = Duration.ofMillis(SCHEDULE_DELAY_MILLIS); + try (DefaultAsyncClientExecutor backedByExecutorService = new DefaultAsyncClientExecutor(executorService); + DefaultAsyncClientExecutor backedByScheduledExecutorService = new DefaultAsyncClientExecutor(scheduledExecutorService)) { + assertAll( + () -> assertCloseWhileTaskIsWaitingToBeExecutedAfterSchedule(backedByExecutorService, delay), + () -> assertCloseWhileTaskIsWaitingToBeExecutedAfterSchedule(backedByScheduledExecutorService, delay) + ); + } + } + + private static void assertCloseWhileTaskIsWaitingToBeExecutedAfterSchedule( + final DefaultAsyncClientExecutor clientExecutor, final Duration delay) throws Exception { + AtomicInteger completionCount = new AtomicInteger(); + CompletableFuture callbackFuture = new CompletableFuture<>(); + clientExecutor.schedule(RejectableRunnable.from((result, t) -> { + completionCount.incrementAndGet(); + if (t != null) { + callbackFuture.completeExceptionally(t); + } else { + callbackFuture.complete(result); + } + }), delay); + clientExecutor.close(); + long timeoutMs = delay.toMillis() * 2; + Throwable callbackException = assertThrows(ExecutionException.class, () -> callbackFuture.get(timeoutMs, MILLISECONDS)).getCause(); + assertInstanceOf(RejectedExecutionException.class, callbackException); + Thread.sleep(timeoutMs); + assertEquals(1, completionCount.get()); + } + + @Test + void closeWhileTaskIsBeingExecutedAfterSchedule() { + Duration delay = Duration.ofMillis(SCHEDULE_DELAY_MILLIS); + try (DefaultAsyncClientExecutor backedByExecutorService = new DefaultAsyncClientExecutor(executorService); + DefaultAsyncClientExecutor backedByScheduledExecutorService = new DefaultAsyncClientExecutor(scheduledExecutorService)) { + assertAll( + () -> assertCloseWhileTaskIsBeingExecutedAfterSchedule(backedByExecutorService, delay), + () -> assertCloseWhileTaskIsBeingExecutedAfterSchedule(backedByScheduledExecutorService, delay) + ); + } + } + + private static void assertCloseWhileTaskIsBeingExecutedAfterSchedule( + final DefaultAsyncClientExecutor clientExecutor, final Duration delay) throws Exception { + AtomicInteger completionCount = new AtomicInteger(); + CompletableFuture callbackFuture = new CompletableFuture<>(); + CompletableFuture closeFuture = new CompletableFuture<>(); + clientExecutor.schedule(RejectableRunnable.from((result, t) -> { + completionCount.incrementAndGet(); + if (t != null) { + callbackFuture.completeExceptionally(t); + } else { + callbackFuture.complete(result); + } + waitForCompletion(closeFuture, delay); + }), delay); + waitForCompletion(callbackFuture, delay.multipliedBy(2)); + clientExecutor.close(); + closeFuture.complete(null); + long timeoutMs = delay.toMillis() * 2; + assertDoesNotThrow(() -> callbackFuture.get(timeoutMs, MILLISECONDS)); + Thread.sleep(timeoutMs); + assertEquals(1, completionCount.get()); + } + + @Test + void closeWhileTaskIsWaitingToBeExecutedAfterScheduleExecutesAllTasksDespiteFailures() { + Duration delay = Duration.ofMillis(SCHEDULE_DELAY_MILLIS); + try (DefaultAsyncClientExecutor backedByExecutorService = new DefaultAsyncClientExecutor(executorService); + DefaultAsyncClientExecutor backedByScheduledExecutorService = new DefaultAsyncClientExecutor(scheduledExecutorService)) { + assertAll( + () -> assertCloseWhileTaskIsWaitingToBeExecutedAfterScheduleExecutesAllTasksDespiteFailures(backedByExecutorService, delay), + () -> assertCloseWhileTaskIsWaitingToBeExecutedAfterScheduleExecutesAllTasksDespiteFailures(backedByScheduledExecutorService, delay) + ); + } + } + + private static void assertCloseWhileTaskIsWaitingToBeExecutedAfterScheduleExecutesAllTasksDespiteFailures( + final DefaultAsyncClientExecutor clientExecutor, final Duration delay) { + AtomicInteger completionCount = new AtomicInteger(); + RuntimeException exception = new RuntimeException("must not prevent task execution caused by `close`"); + Error error = new Error("must not prevent task execution caused by `close`"); + clientExecutor.schedule(RejectableRunnable.from((result, t) -> { + completionCount.incrementAndGet(); + throw exception; + }), delay); + clientExecutor.schedule(RejectableRunnable.from((result, t) -> { + completionCount.incrementAndGet(); + throw exception; + }), delay); + clientExecutor.schedule(RejectableRunnable.from((result, t) -> { + completionCount.incrementAndGet(); + throw error; + }), delay); + Throwable actualThrowable = assertThrows(Throwable.class, clientExecutor::close); + // the order of task execution caused by `close` is indeterministic, so we handle all possibilities + if (actualThrowable instanceof RuntimeException) { + assertSame(exception, actualThrowable); + assertSame(error, actualThrowable.getSuppressed()[0]); + } else if (actualThrowable instanceof Error) { + assertSame(error, actualThrowable); + assertSame(exception, actualThrowable.getSuppressed()[0]); + } else { + fail(actualThrowable); + } + assertEquals(3, completionCount.get()); + } + + private static void waitForCompletion(final Future future, final Duration duration) { + try { + future.get(duration.toNanos(), NANOSECONDS); + } catch (InterruptedException e) { + throw interruptAndCreateMongoInterruptedException(null, e); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + // nothing to do + } + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/thread/MongoScheduledThreadPoolExecutorTest.java b/driver-core/src/test/unit/com/mongodb/internal/thread/MongoScheduledThreadPoolExecutorTest.java new file mode 100644 index 00000000000..14d2a7ce2ef --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/thread/MongoScheduledThreadPoolExecutorTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.thread; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.concurrent.Callable; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +class MongoScheduledThreadPoolExecutorTest extends MongoThreadPoolExecutorTest { + @ParameterizedTest + @CsvSource({ + "false, false", + "false, true", + "true, false", + "true, true"}) + @Override + void delegateErrorToDefaultUncaughtExceptionHandlerOrLog( + final boolean taskCompletesAbruptlyWithError, + final boolean setDefaultUncaughtExceptionHandler) throws Exception { + MongoScheduledThreadPoolExecutor executor = new MongoScheduledThreadPoolExecutor(1, new DaemonThreadFactory("test")); + try { + Error error = new Error("expected error"); + RuntimeException exception = new RuntimeException("expected exception"); + Throwable expectedThrowable = taskCompletesAbruptlyWithError ? error : exception; + Runnable runnable = () -> { + if (taskCompletesAbruptlyWithError) { + throw error; + } else { + throw exception; + } + }; + Callable callable = () -> { + runnable.run(); + return null; + }; + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.execute(runnable)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.submit(runnable)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.submit(runnable, null)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.submit(callable)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.schedule(runnable, 0, MILLISECONDS)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.schedule(callable, 0, MILLISECONDS)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.scheduleAtFixedRate(runnable, 0, 1, MILLISECONDS)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.scheduleWithFixedDelay(runnable, 0, 1, MILLISECONDS)); + } finally { + executor.shutdownNow(); + } + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/thread/MongoThreadPoolExecutorTest.java b/driver-core/src/test/unit/com/mongodb/internal/thread/MongoThreadPoolExecutorTest.java new file mode 100644 index 00000000000..f17c87a4bce --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/thread/MongoThreadPoolExecutorTest.java @@ -0,0 +1,143 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.thread; + +import com.mongodb.lang.Nullable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.lang.Thread.UncaughtExceptionHandler; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MongoThreadPoolExecutorTest { + private static final long TIMEOUT_MILLIS = 100; + + @Nullable + private PrintStream originalStderr; + private ByteArrayOutputStream stderrTap; + @Nullable + private UncaughtExceptionHandler originalUncaughtExceptionHandler; + + @BeforeEach + void beforeEach() throws UnsupportedEncodingException { + originalUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + originalStderr = System.err; + stderrTap = new ByteArrayOutputStream(); + System.setErr(new PrintStream(stderrTap, true, StandardCharsets.UTF_8.name())); + } + + @AfterEach + void afterEach() throws IOException { + try { + Thread.setDefaultUncaughtExceptionHandler(originalUncaughtExceptionHandler); + } finally { + try { + if (originalStderr != null) { + System.setErr(originalStderr); + } + } finally { + if (stderrTap != null) { + stderrTap.close(); + } + } + } + } + + @ParameterizedTest + @CsvSource({ + "false, false", + "false, true", + "true, false", + "true, true" + }) + void delegateErrorToDefaultUncaughtExceptionHandlerOrLog( + final boolean taskCompletesAbruptlyWithError, + final boolean setDefaultUncaughtExceptionHandler) throws Exception { + MongoThreadPoolExecutor executor = new MongoThreadPoolExecutor( + 1, 1, Duration.ofMillis(TIMEOUT_MILLIS), new LinkedBlockingQueue<>(), new DaemonThreadFactory("test")); + try { + Error error = new Error("expected error"); + RuntimeException exception = new RuntimeException("expected exception"); + Throwable expectedThrowable = taskCompletesAbruptlyWithError ? error : exception; + Runnable runnable = () -> { + if (taskCompletesAbruptlyWithError) { + throw error; + } else { + throw exception; + } + }; + Callable callable = () -> { + runnable.run(); + return null; + }; + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.execute(runnable)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.submit(runnable)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.submit(runnable, null)); + assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog(expectedThrowable, setDefaultUncaughtExceptionHandler, () -> executor.submit(callable)); + } finally { + executor.shutdownNow(); + } + } + + void assertDelegateErrorToDefaultUncaughtExceptionHandlerOrLog( + final Throwable expectedThrowable, + final boolean setDefaultUncaughtExceptionHandler, + final Runnable submitThrowingTask) throws Exception { + CompletableFuture uncaughtExceptionFuture = new CompletableFuture<>(); + if (setDefaultUncaughtExceptionHandler) { + Thread.setDefaultUncaughtExceptionHandler((t, e) -> { + uncaughtExceptionFuture.complete(e); + }); + } else { + // we remove the original `UncaughtExceptionHandler` to guarantee that uncaught exceptions are printed to `System.err` + Thread.setDefaultUncaughtExceptionHandler(null); + } + stderrTap.reset(); + submitThrowingTask.run(); + Thread.sleep(TIMEOUT_MILLIS); + if (setDefaultUncaughtExceptionHandler) { + Throwable actualUncaughtException = uncaughtExceptionFuture.get(TIMEOUT_MILLIS, MILLISECONDS); + if (expectedThrowable instanceof Error) { + assertSame(expectedThrowable, actualUncaughtException); + } else { + assertInstanceOf(Error.class, actualUncaughtException); + assertSame(expectedThrowable, actualUncaughtException.getCause()); + } + } else { + String actualLoggedMessage = stderrTap.toString(StandardCharsets.UTF_8.name()); + assertTrue(actualLoggedMessage.contains(expectedThrowable.getClass().getName()) && actualLoggedMessage.contains(expectedThrowable.getMessage()), + () -> { + return String.format("actualLoggedMessage=%s does not contain information about expectedThrowable=%s", + actualLoggedMessage, expectedThrowable); + }); + } + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/thread/ThreadUtilTest.java b/driver-core/src/test/unit/com/mongodb/internal/thread/ThreadUtilTest.java new file mode 100644 index 00000000000..5178e4dfe07 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/thread/ThreadUtilTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.thread; + +import com.mongodb.internal.async.MutableValue; +import com.mongodb.internal.mockito.MongoMockito; +import com.mongodb.internal.thread.AsyncClientExecutor.RejectableRunnable; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.doAnswer; + +class ThreadUtilTest { + @Test + void sleepAsyncCompletesInCallingThreadIfNoDelay() { + AsyncClientExecutor failingClientExecutor = MongoMockito.mock(AsyncClientExecutor.class); + Thread expectedThread = Thread.currentThread(); + // we use `AtomicReference` instead of `MutableValue` in case completion incorrectly happens in a different thread + AtomicReference actualThread = new AtomicReference<>(); + AtomicReference actualThrowable = new AtomicReference<>(); + ThreadUtil.sleepAsync(Duration.ZERO, failingClientExecutor, (result, t) -> { + actualThread.set(Thread.currentThread()); + actualThrowable.set(t); + }); + assertSame(expectedThread, actualThread.get()); + assertNull(actualThrowable.get()); + } + + @Test + void sleepAsyncSchedulesIfDelay() { + RejectedExecutionException expectedRejectionCauseFromClientExecutor = new RejectedExecutionException(); + Duration delay = Duration.ofNanos(1); + AsyncClientExecutor clientExecutor = MongoMockito.mock(AsyncClientExecutor.class, mock -> { + doAnswer(invocation -> { + RejectableRunnable task = invocation.getArgument(0); + task.reject(expectedRejectionCauseFromClientExecutor); + return null; + }).when(mock).schedule(any(), same(delay)); + }); + MutableValue completedByClientExecutor = new MutableValue<>(); + ThreadUtil.sleepAsync(delay, clientExecutor, (result, t) -> { + completedByClientExecutor.set(t); + }); + assertSame(expectedRejectionCauseFromClientExecutor, completedByClientExecutor.getNullable()); + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java new file mode 100644 index 00000000000..504a9840e73 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.time; + +import org.junit.jupiter.api.Test; + +import java.util.function.DoubleSupplier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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}; + + @Test + void testCalculateTransactionBackoffMs() { + for (int attemptNumber = 1; attemptNumber <= EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) { + long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); + long expectedBackoff = Math.round(EXPECTED_BACKOFFS_MAX_VALUES[attemptNumber - 1]); + assertTrue(backoff >= 0 && backoff <= expectedBackoff, + String.format("Attempt %d: backoff should be between 0 ms and %d ms, got: %d", attemptNumber, + expectedBackoff, backoff)); + } + } + + @Test + void testCalculateTransactionBackoffMsRespectsMaximum() { + for (int attemptNumber = 1; attemptNumber < EXPECTED_BACKOFFS_MAX_VALUES.length * 2; attemptNumber++) { + long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); + assertTrue(backoff >= 0 && backoff <= ExponentialBackoff.TRANSACTION_MAX_MS, + String.format("Attempt %d: backoff should be capped at %f ms, got: %d ms", + attemptNumber, ExponentialBackoff.TRANSACTION_MAX_MS, backoff)); + } + } + + @Test + void testCalculateTransactionBackoffMsWithJitterOne() { + ExponentialBackoff.setTestJitterSupplier(() -> 1.0); + try { + for (int attemptNumber = 1; attemptNumber <= EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) { + long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); + long expected = Math.round(EXPECTED_BACKOFFS_MAX_VALUES[attemptNumber - 1]); + assertEquals(expected, backoff, + String.format("Attempt %d: with jitter=1.0, backoff should be %d ms", attemptNumber, expected)); + } + } finally { + ExponentialBackoff.clearTestJitterSupplier(); + } + } + + @Test + void testCalculateTransactionBackoffMsWithJitterZero() { + ExponentialBackoff.setTestJitterSupplier(() -> 0.0); + try { + for (int attemptNumber = 1; attemptNumber <= EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) { + long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); + assertEquals(0, backoff, String.format("Attempt %d: with jitter=0, backoff should always be 0 ms", attemptNumber)); + } + } finally { + ExponentialBackoff.clearTestJitterSupplier(); + } + } +} diff --git a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt index 1867a15468c..e14d4086230 100644 --- a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt +++ b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt @@ -91,5 +91,7 @@ class SyncClientSession(internal val wrapped: ClientSession, private val origina override fun getTimeoutContext(): TimeoutContext? = wrapped.getTimeoutContext() + override fun getOverloadRetryPolicyState(): Any = wrapped.getOverloadRetryPolicyState() + override fun getTransactionSpan(): TransactionSpan? = null } diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt index cbe308eece0..ceefd8e2ac1 100644 --- a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/ClientSession.kt @@ -18,6 +18,7 @@ package com.mongodb.kotlin.client.coroutine import com.mongodb.ClientSessionOptions import com.mongodb.ServerAddress import com.mongodb.TransactionOptions +import com.mongodb.annotations.Internal import com.mongodb.internal.TimeoutContext import com.mongodb.internal.observability.micrometer.TransactionSpan import com.mongodb.reactivestreams.client.ClientSession as reactiveClientSession @@ -41,13 +42,13 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS public fun hasActiveTransaction(): Boolean = wrapped.hasActiveTransaction() /** - * Notify the client session that a message has been sent. + * Notify that a message is about to be sent. * * For internal use only * - * @return true if this is the first message sent, false otherwise + * @return true Iff the message must bear {@code startTransaction: true}. */ - public fun notifyMessageSent(): Boolean = wrapped.notifyMessageSent() + @Internal public fun notifyMessageSent(): Boolean = wrapped.notifyMessageSent() /** * Notify the client session that command execution is being initiated. This should be called before server @@ -57,7 +58,7 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * * @param operation the operation */ - public fun notifyOperationInitiated(operation: Any): Unit = wrapped.notifyOperationInitiated(operation) + @Internal public fun notifyOperationInitiated(operation: Any): Unit = wrapped.notifyOperationInitiated(operation) /** Get the transaction span (if started). */ public fun getTransactionSpan(): TransactionSpan? = wrapped.transactionSpan @@ -67,7 +68,7 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * * @return the server address of the pinned mongos */ - public override fun getPinnedServerAddress(): ServerAddress? = wrapped.pinnedServerAddress + @Internal public override fun getPinnedServerAddress(): ServerAddress? = wrapped.pinnedServerAddress /** * Gets the transaction context. @@ -76,7 +77,7 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * * @return the transaction context */ - public override fun getTransactionContext(): Any? = wrapped.transactionContext + @Internal public override fun getTransactionContext(): Any? = wrapped.transactionContext /** * Sets the transaction context. @@ -88,6 +89,7 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * @param address the server address * @param transactionContext the transaction context */ + @Internal public override fun setTransactionContext(address: ServerAddress, transactionContext: Any): Unit = wrapped.setTransactionContext(address, transactionContext) @@ -96,21 +98,21 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * * For internal use only */ - public override fun clearTransactionContext(): Unit = wrapped.clearTransactionContext() + @Internal public override fun clearTransactionContext(): Unit = wrapped.clearTransactionContext() /** * Get the recovery token from the latest outcome in a sharded transaction. For internal use only. * - * @return the recovery token @mongodb.server.release 4.2 - * @since 3.11 + * @return the recovery token */ - public override fun getRecoveryToken(): BsonDocument? = wrapped.recoveryToken + @Internal public override fun getRecoveryToken(): BsonDocument? = wrapped.recoveryToken /** * Set the recovery token. For internal use only. * * @param recoveryToken the recovery token */ + @Internal public override fun setRecoveryToken(recoveryToken: BsonDocument) { wrapped.recoveryToken = recoveryToken } @@ -164,6 +166,7 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * * @param snapshotTimestamp the snapshot timestamp */ + @Internal public override fun setSnapshotTimestamp(snapshotTimestamp: BsonTimestamp?) { wrapped.snapshotTimestamp = snapshotTimestamp } @@ -173,7 +176,7 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * * @return the snapshot timestamp */ - public override fun getSnapshotTimestamp(): BsonTimestamp? = wrapped.snapshotTimestamp + @Internal public override fun getSnapshotTimestamp(): BsonTimestamp? = wrapped.snapshotTimestamp /** @return the latest cluster time seen by this session */ public override fun getClusterTime(): BsonDocument = wrapped.clusterTime @@ -188,6 +191,8 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS /** * Start a transaction in the context of this session with default transaction options. A transaction can not be * started if there is already an active transaction on this session. + * + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL */ public fun startTransaction(): Unit = wrapped.startTransaction() @@ -196,15 +201,17 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * started if there is already an active transaction on this session. * * @param transactionOptions the options to apply to the transaction + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL */ public fun startTransaction(transactionOptions: TransactionOptions): Unit = wrapped.startTransaction(transactionOptions) /** - * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been + * Commit a transaction in the context of this session. A transaction can only be committed if one has first been * started. * * @return an empty publisher that indicates when the operation has completed + * @see com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL */ public suspend fun commitTransaction() { wrapped.commitTransaction().awaitFirstOrNull() @@ -228,9 +235,11 @@ public class ClientSession(public val wrapped: reactiveClientSession) : jClientS * Note: For internal use only * * @return the timeout to use - * @since 5.2 */ - public override fun getTimeoutContext(): TimeoutContext? = wrapped.timeoutContext + @Internal public override fun getTimeoutContext(): TimeoutContext? = wrapped.timeoutContext + + /** For internal use only. */ + @Internal public override fun getOverloadRetryPolicyState(): Any = wrapped.overloadRetryPolicyState } /** diff --git a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt index f995d1a6125..0692348d7dd 100644 --- a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt +++ b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt @@ -95,5 +95,7 @@ internal class SyncClientSession(internal val wrapped: ClientSession, private va override fun getTimeoutContext(): TimeoutContext = throw UnsupportedOperationException() + override fun getOverloadRetryPolicyState(): Any = delegate.getOverloadRetryPolicyState() + override fun getTransactionSpan(): TransactionSpan? = null } diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt index 5656feb4523..0f49d667995 100644 --- a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt @@ -50,6 +50,8 @@ public class ClientSession(public val wrapped: JClientSession) : Closeable { /** * Start a transaction in the context of this session with default transaction options. A transaction can not be * started if there is already an active transaction on this session. + * + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL */ public fun startTransaction(): Unit = wrapped.startTransaction() @@ -58,13 +60,16 @@ public class ClientSession(public val wrapped: JClientSession) : Closeable { * started if there is already an active transaction on this session. * * @param transactionOptions the options to apply to the transaction + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL */ public fun startTransaction(transactionOptions: TransactionOptions): Unit = wrapped.startTransaction(transactionOptions) /** - * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been + * Commit a transaction in the context of this session. A transaction can only be committed if one has first been * started. + * + * @see com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL */ public fun commitTransaction(): Unit = wrapped.commitTransaction() @@ -82,6 +87,8 @@ public class ClientSession(public val wrapped: JClientSession) : Closeable { * @param transactionBody the body of the transaction * @param options the transaction options * @return the return value of the transaction body + * @see com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL + * @see com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL */ public fun withTransaction( transactionBody: () -> T, diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ClientSessionTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ClientSessionTest.kt index c3c4772f9d6..c38acdd5fef 100644 --- a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ClientSessionTest.kt +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/ClientSessionTest.kt @@ -50,7 +50,8 @@ class ClientSessionTest { "notifyOperationInitiated", "setRecoveryToken", "setSnapshotTimestamp", - "setTransactionContext") + "setTransactionContext", + "getOverloadRetryPolicyState") val jClientSessionFunctions = JClientSession::class.functions.map { it.name }.toSet() - internalFunctions val kClientSessionFunctions = diff --git a/driver-legacy/src/main/com/mongodb/DB.java b/driver-legacy/src/main/com/mongodb/DB.java index b5aa60a58e1..494970e2610 100644 --- a/driver-legacy/src/main/com/mongodb/DB.java +++ b/driver-legacy/src/main/com/mongodb/DB.java @@ -195,7 +195,8 @@ public DBCollection getCollection(final String name) { */ public void dropDatabase() { try { - getExecutor().execute(new DropDatabaseOperation(getName(), getWriteConcern()), getReadConcern()); + getExecutor().execute(new DropDatabaseOperation(getName(), getWriteConcern(), + mongo.getMongoClientOptions().getRetryWrites(), mongo.getMongoClientOptions().getMaxAdaptiveRetries()), getReadConcern()); } catch (MongoWriteConcernException e) { throw createWriteConcernException(e); } @@ -218,12 +219,13 @@ public String getName() { * @mongodb.driver.manual reference/method/db.getCollectionNames/ getCollectionNames() */ public Set getCollectionNames() { + MongoClientOptions options = mongo.getMongoClientOptions(); List collectionNames = new MongoIterableImpl(null, executor, ReadConcern.DEFAULT, primary(), - mongo.getMongoClientOptions().getRetryReads(), DB.this.getTimeoutSettings()) { + options.getRetryReads(), DB.this.getTimeoutSettings()) { @Override public ReadOperationCursor asReadOperation() { - return new ListCollectionsOperation<>(name, commandCodec).nameOnly(true); + return new ListCollectionsOperation<>(name, commandCodec, options.getMaxAdaptiveRetries()).nameOnly(true); } @Override @@ -309,7 +311,8 @@ public DBCollection createView(final String viewName, final String viewOn, final notNull("options", options); DBCollection view = getCollection(viewName); executor.execute(new CreateViewOperation(name, viewName, viewOn, - view.preparePipeline(pipeline), writeConcern) + view.preparePipeline(pipeline), writeConcern, + mongo.getMongoClientOptions().getRetryWrites(), mongo.getMongoClientOptions().getMaxAdaptiveRetries()) .collation(options.getCollation()), getReadConcern()); return view; } catch (MongoWriteConcernException e) { @@ -386,7 +389,7 @@ private CreateCollectionOperation getCreateCollectionOperation(final String coll } Collation collation = DBObjectCollationHelper.createCollationFromOptions(options); return new CreateCollectionOperation(getName(), collectionName, - getWriteConcern()) + getWriteConcern(), mongo.getMongoClientOptions().getRetryWrites(), mongo.getMongoClientOptions().getMaxAdaptiveRetries()) .capped(capped) .collation(collation) .sizeInBytes(sizeInBytes) diff --git a/driver-legacy/src/main/com/mongodb/DBCollection.java b/driver-legacy/src/main/com/mongodb/DBCollection.java index 7e460af74ba..fdfa0767b82 100644 --- a/driver-legacy/src/main/com/mongodb/DBCollection.java +++ b/driver-legacy/src/main/com/mongodb/DBCollection.java @@ -32,6 +32,7 @@ import com.mongodb.internal.bulk.InsertRequest; import com.mongodb.internal.bulk.UpdateRequest; import com.mongodb.internal.bulk.WriteRequest.Type; +import com.mongodb.internal.client.model.AggregationLevel; import com.mongodb.internal.connection.PowerOfTwoBufferPool; import com.mongodb.internal.operation.AggregateOperation; import com.mongodb.internal.operation.AggregateToCollectionOperation; @@ -139,6 +140,8 @@ public class DBCollection { private final OperationExecutor executor; private final boolean retryWrites; private final boolean retryReads; + @Nullable + private final Integer maxAdaptiveRetriesSetting; private volatile ReadPreference readPreference; private volatile WriteConcern writeConcern; private volatile ReadConcern readConcern; @@ -161,8 +164,10 @@ public class DBCollection { this.executor = executor; this.objectFactory = new DBCollectionObjectFactory(); this.objectCodec = new CompoundDBObjectCodec(getDefaultDBObjectCodec()); - this.retryWrites = database.getMongoClient().getMongoClientOptions().getRetryWrites(); - this.retryReads = database.getMongoClient().getMongoClientOptions().getRetryReads(); + MongoClientOptions options = database.getMongoClient().getMongoClientOptions(); + this.retryWrites = options.getRetryWrites(); + this.retryReads = options.getRetryReads(); + this.maxAdaptiveRetriesSetting = options.getMaxAdaptiveRetries(); } /** @@ -348,7 +353,7 @@ private Encoder toEncoder(@Nullable final DBEncoder dbEncoder) { private WriteResult insert(final List insertRequestList, final WriteConcern writeConcern, final boolean continueOnError, @Nullable final Boolean bypassDocumentValidation) { return executeWriteOperation(createBulkWriteOperationForInsert(getNamespace(), - !continueOnError, writeConcern, retryWrites, insertRequestList).bypassDocumentValidation(bypassDocumentValidation)); + !continueOnError, writeConcern, retryWrites, maxAdaptiveRetriesSetting, insertRequestList).bypassDocumentValidation(bypassDocumentValidation)); } WriteResult executeWriteOperation(final LegacyMixedBulkWriteOperation operation) { @@ -432,7 +437,7 @@ private WriteResult replaceOrInsert(final DBObject obj, final Object id, final W Type.REPLACE).upsert(true); return executeWriteOperation(createBulkWriteOperationForReplace(getNamespace(), false, - writeConcern, retryWrites, singletonList(replaceRequest))); + writeConcern, retryWrites, maxAdaptiveRetriesSetting, singletonList(replaceRequest))); } /** @@ -584,9 +589,9 @@ public WriteResult update(final DBObject query, final DBObject update, final DBC .collation(options.getCollation()) .arrayFilters(wrapAllowNull(options.getArrayFilters(), options.getEncoder())); LegacyMixedBulkWriteOperation operation = (updateType == UPDATE - ? createBulkWriteOperationForUpdate(getNamespace(), true, writeConcern, retryWrites, + ? createBulkWriteOperationForUpdate(getNamespace(), true, writeConcern, retryWrites, maxAdaptiveRetriesSetting, singletonList(updateRequest)) - : createBulkWriteOperationForReplace(getNamespace(), true, writeConcern, retryWrites, + : createBulkWriteOperationForReplace(getNamespace(), true, writeConcern, retryWrites, maxAdaptiveRetriesSetting, singletonList(updateRequest))) .bypassDocumentValidation(options.getBypassDocumentValidation()); return executeWriteOperation(operation); @@ -660,7 +665,7 @@ public WriteResult remove(final DBObject query, final DBCollectionRemoveOptions WriteConcern writeConcern = optionsWriteConcern != null ? optionsWriteConcern : getWriteConcern(); DeleteRequest deleteRequest = new DeleteRequest(wrap(query, options.getEncoder())).collation(options.getCollation()); return executeWriteOperation(createBulkWriteOperationForDelete(getNamespace(), false, - writeConcern, retryWrites, singletonList(deleteRequest))); + writeConcern, retryWrites, maxAdaptiveRetriesSetting, singletonList(deleteRequest))); } /** @@ -672,7 +677,11 @@ public WriteResult remove(final DBObject query, final DBCollectionRemoveOptions * @mongodb.driver.manual tutorial/query-documents/ Querying */ public DBCursor find(final DBObject query) { - return new DBCursor(this, query, null, getReadPreference()); + return new DBCursor( + this, + query, + new DBCollectionFindOptions().readPreference(getReadPreference()), + retryReads); } /** @@ -685,7 +694,11 @@ public DBCursor find(final DBObject query) { * @mongodb.driver.manual tutorial/query-documents/ Querying */ public DBCursor find(final DBObject query, final DBObject projection) { - return new DBCursor(this, query, projection, getReadPreference()); + return new DBCursor( + this, + query, + new DBCollectionFindOptions().projection(projection).readPreference(getReadPreference()), + retryReads); } /** @@ -709,7 +722,7 @@ public DBCursor find() { * @since 3.4 */ public DBCursor find(@Nullable final DBObject query, final DBCollectionFindOptions options) { - return new DBCursor(this, query, options); + return new DBCursor(this, query, options, retryReads); } /** @@ -918,7 +931,8 @@ public long getCount(@Nullable final DBObject query) { public long getCount(@Nullable final DBObject query, final DBCollectionCountOptions options) { notNull("countOptions", options); CountOperation operation = new CountOperation( - getNamespace()) + getNamespace(), + maxAdaptiveRetriesSetting) .skip(options.getSkip()) .limit(options.getLimit()) .collation(options.getCollation()) @@ -966,7 +980,8 @@ public DBCollection rename(final String newName) { public DBCollection rename(final String newName, final boolean dropTarget) { try { executor.execute(new RenameCollectionOperation(getNamespace(), - new MongoNamespace(getNamespace().getDatabaseName(), newName), getWriteConcern()) + new MongoNamespace(getNamespace().getDatabaseName(), newName), getWriteConcern(), + retryWrites, maxAdaptiveRetriesSetting) .dropTarget(dropTarget), getReadConcern()); return getDB().getCollection(newName); } catch (MongoWriteConcernException e) { @@ -1039,7 +1054,7 @@ public List distinct(final String fieldName, final DBCollectionDistinctOptions o retryReads, DBCollection.this.getTimeoutSettings()) { @Override public ReadOperationCursor asReadOperation() { - return new DistinctOperation<>(getNamespace(), fieldName, new BsonValueCodec()) + return new DistinctOperation<>(getNamespace(), fieldName, new BsonValueCodec(), maxAdaptiveRetriesSetting) .filter(wrapAllowNull(options.getFilter())) .collation(options.getCollation()) .retryReads(retryReads); @@ -1236,7 +1251,8 @@ public Cursor aggregate(final List pipeline, final Aggregati if (outCollection != null) { AggregateToCollectionOperation operation = new AggregateToCollectionOperation( - getNamespace(), stages, getReadConcern(), getWriteConcern()) + getNamespace(), stages, getReadConcern(), getWriteConcern(), AggregationLevel.COLLECTION, + retryWrites, maxAdaptiveRetriesSetting) .allowDiskUse(options.getAllowDiskUse()) .bypassDocumentValidation(options.getBypassDocumentValidation()) .collation(options.getCollation()); @@ -1244,14 +1260,16 @@ public Cursor aggregate(final List pipeline, final Aggregati getExecutor(createTimeoutSettings(getTimeoutSettings(), options)) .execute(operation, getReadPreference(), getReadConcern(), null); result = new DBCursor(database.getCollection(outCollection.asString().getValue()), new BasicDBObject(), - new DBCollectionFindOptions().readPreference(primary()).collation(options.getCollation())); + new DBCollectionFindOptions().readPreference(primary()).collation(options.getCollation()), + retryReads); } catch (MongoWriteConcernException e) { throw createWriteConcernException(e); } } else { AggregateOperation operation = new AggregateOperation<>( getNamespace(), stages, - getDefaultDBObjectCodec()) + getDefaultDBObjectCodec(), + maxAdaptiveRetriesSetting) .allowDiskUse(options.getAllowDiskUse()) .batchSize(options.getBatchSize()) .collation(options.getCollation()) @@ -1277,7 +1295,8 @@ public Cursor aggregate(final List pipeline, final Aggregati public CommandResult explainAggregate(final List pipeline, final AggregationOptions options) { AggregateOperation operation = new AggregateOperation<>( getNamespace(), - preparePipeline(pipeline), new BsonDocumentCodec()) + preparePipeline(pipeline), new BsonDocumentCodec(), + maxAdaptiveRetriesSetting) .allowDiskUse(options.getAllowDiskUse()) .collation(options.getCollation()) .retryReads(retryReads); @@ -1666,7 +1685,7 @@ public DBObject findAndModify(final DBObject query, final DBCollectionFindAndMod WriteConcern writeConcern = optionsWriteConcern != null ? optionsWriteConcern : getWriteConcern(); WriteOperation operation; if (options.isRemove()) { - operation = new FindAndDeleteOperation<>(getNamespace(), writeConcern, retryWrites, objectCodec) + operation = new FindAndDeleteOperation<>(getNamespace(), writeConcern, retryWrites, maxAdaptiveRetriesSetting, objectCodec) .filter(wrapAllowNull(query)) .projection(wrapAllowNull(options.getProjection())) .sort(wrapAllowNull(options.getSort())) @@ -1677,7 +1696,7 @@ public DBObject findAndModify(final DBObject query, final DBCollectionFindAndMod throw new IllegalArgumentException("update can not be null unless it's a remove"); } if (!update.keySet().isEmpty() && update.keySet().iterator().next().charAt(0) == '$') { - operation = new FindAndUpdateOperation<>(getNamespace(), writeConcern, retryWrites, + operation = new FindAndUpdateOperation<>(getNamespace(), writeConcern, retryWrites, maxAdaptiveRetriesSetting, objectCodec, wrap(update)) .filter(wrap(query)) .projection(wrapAllowNull(options.getProjection())) @@ -1688,7 +1707,7 @@ objectCodec, wrap(update)) .collation(options.getCollation()) .arrayFilters(wrapAllowNull(options.getArrayFilters(), (Encoder) null)); } else { - operation = new FindAndReplaceOperation<>(getNamespace(), writeConcern, retryWrites, + operation = new FindAndReplaceOperation<>(getNamespace(), writeConcern, retryWrites, maxAdaptiveRetriesSetting, objectCodec, wrap(update)) .filter(wrap(query)) .projection(wrapAllowNull(options.getProjection())) @@ -1802,7 +1821,7 @@ public ReadConcern getReadConcern() { public void drop() { try { executor.execute(new DropCollectionOperation(getNamespace(), - getWriteConcern()), getReadConcern()); + getWriteConcern(), retryWrites, maxAdaptiveRetriesSetting), getReadConcern()); } catch (MongoWriteConcernException e) { throw createWriteConcernException(e); } @@ -1874,7 +1893,8 @@ public List getIndexInfo() { DBCollection.this.getTimeoutSettings()) { @Override public ReadOperationCursor asReadOperation() { - return new ListIndexesOperation<>(getNamespace(), getDefaultDBObjectCodec()) + return new ListIndexesOperation<>(getNamespace(), getDefaultDBObjectCodec(), + maxAdaptiveRetriesSetting) .retryReads(retryReads); } @@ -1896,7 +1916,7 @@ public OperationExecutor getExecutor() { public void dropIndex(final DBObject index) { try { executor.execute(new DropIndexOperation(getNamespace(), wrap(index), - getWriteConcern()), getReadConcern()); + getWriteConcern(), retryWrites, maxAdaptiveRetriesSetting), getReadConcern()); } catch (MongoWriteConcernException e) { throw createWriteConcernException(e); } @@ -1912,7 +1932,7 @@ public void dropIndex(final DBObject index) { public void dropIndex(final String indexName) { try { executor.execute(new DropIndexOperation(getNamespace(), indexName, - getWriteConcern()), getReadConcern()); + getWriteConcern(), retryWrites, maxAdaptiveRetriesSetting), getReadConcern()); } catch (MongoWriteConcernException e) { throw createWriteConcernException(e); } @@ -2027,7 +2047,7 @@ BulkWriteResult executeBulkWriteOperation(final boolean ordered, final Boolean b final WriteConcern writeConcern) { try { return translateBulkWriteResult(executor.execute(new MixedBulkWriteOperation( - getNamespace(), translateWriteRequestsToNew(writeRequests), ordered, writeConcern, false) + getNamespace(), translateWriteRequestsToNew(writeRequests), ordered, writeConcern, false, maxAdaptiveRetriesSetting) .bypassDocumentValidation(bypassDocumentValidation), getReadConcern()), getObjectCodec()); } catch (MongoBulkWriteException e) { throw BulkWriteHelper.translateBulkWriteException(e, MongoClient.getDefaultCodecRegistry().get(DBObject.class)); @@ -2139,7 +2159,7 @@ private CreateIndexesOperation createIndexOperation(final DBObject key, final DB if (options.containsField("collation")) { request.collation(DBObjectCollationHelper.createCollationFromOptions(options)); } - return new CreateIndexesOperation(getNamespace(), singletonList(request), writeConcern); + return new CreateIndexesOperation(getNamespace(), singletonList(request), writeConcern, retryWrites, maxAdaptiveRetriesSetting); } Codec getObjectCodec() { @@ -2204,6 +2224,11 @@ TimeoutSettings getTimeoutSettings(){ return database.getTimeoutSettings(); } + @Nullable + Integer getMaxAdaptiveRetriesSetting() { + return maxAdaptiveRetriesSetting; + } + static WriteConcernException createWriteConcernException(final MongoWriteConcernException e) { return new WriteConcernException(new BsonDocument("code", new BsonInt32(e.getWriteConcernError().getCode())) .append("errmsg", new BsonString(e.getWriteConcernError().getMessage())), diff --git a/driver-legacy/src/main/com/mongodb/DBCursor.java b/driver-legacy/src/main/com/mongodb/DBCursor.java index 9b91bad5984..f824fa0d749 100644 --- a/driver-legacy/src/main/com/mongodb/DBCursor.java +++ b/driver-legacy/src/main/com/mongodb/DBCursor.java @@ -110,10 +110,6 @@ public DBCursor(final DBCollection collection, final DBObject query, @Nullable f this(collection, query, new DBCollectionFindOptions().projection(fields).readPreference(readPreference), retryReads); } - DBCursor(final DBCollection collection, @Nullable final DBObject filter, final DBCollectionFindOptions findOptions) { - this(collection, filter, findOptions, true); - } - DBCursor(final DBCollection collection, @Nullable final DBObject filter, final DBCollectionFindOptions findOptions, final boolean retryReads) { this(collection, filter, findOptions, collection.getExecutor(), collection.getDBDecoderFactory(), @@ -415,7 +411,8 @@ public DBCursor partial(final boolean partial) { private FindOperation getQueryOperation(final Decoder decoder) { return new FindOperation<>( - collection.getNamespace(), decoder) + collection.getNamespace(), decoder, + collection.getMaxAdaptiveRetriesSetting()) .filter(collection.wrapAllowNull(filter)) .batchSize(findOptions.getBatchSize()) .skip(findOptions.getSkip()) diff --git a/driver-legacy/src/main/com/mongodb/LegacyMixedBulkWriteOperation.java b/driver-legacy/src/main/com/mongodb/LegacyMixedBulkWriteOperation.java index 1538324b861..3a5e135dc43 100644 --- a/driver-legacy/src/main/com/mongodb/LegacyMixedBulkWriteOperation.java +++ b/driver-legacy/src/main/com/mongodb/LegacyMixedBulkWriteOperation.java @@ -55,33 +55,48 @@ final class LegacyMixedBulkWriteOperation implements WriteOperation insertRequests) { - return new LegacyMixedBulkWriteOperation(namespace, ordered, writeConcern, retryWrites, insertRequests, INSERT); + final WriteConcern writeConcern, + final boolean retryWrites, + @Nullable final Integer maxAdaptiveRetriesSetting, + final List insertRequests) { + return new LegacyMixedBulkWriteOperation(namespace, ordered, writeConcern, retryWrites, maxAdaptiveRetriesSetting, insertRequests, INSERT); } static LegacyMixedBulkWriteOperation createBulkWriteOperationForUpdate(final MongoNamespace namespace, final boolean ordered, - final WriteConcern writeConcern, final boolean retryWrites, final List updateRequests) { + final WriteConcern writeConcern, + final boolean retryWrites, + @Nullable final Integer maxAdaptiveRetriesSetting, + final List updateRequests) { assertTrue(updateRequests.stream().allMatch(updateRequest -> updateRequest.getType() == UPDATE)); - return new LegacyMixedBulkWriteOperation(namespace, ordered, writeConcern, retryWrites, updateRequests, UPDATE); + return new LegacyMixedBulkWriteOperation(namespace, ordered, writeConcern, retryWrites, maxAdaptiveRetriesSetting, updateRequests, UPDATE); } static LegacyMixedBulkWriteOperation createBulkWriteOperationForReplace(final MongoNamespace namespace, final boolean ordered, - final WriteConcern writeConcern, final boolean retryWrites, final List replaceRequests) { + final WriteConcern writeConcern, + final boolean retryWrites, + @Nullable final Integer maxAdaptiveRetriesSetting, + final List replaceRequests) { assertTrue(replaceRequests.stream().allMatch(updateRequest -> updateRequest.getType() == REPLACE)); - return new LegacyMixedBulkWriteOperation(namespace, ordered, writeConcern, retryWrites, replaceRequests, REPLACE); + return new LegacyMixedBulkWriteOperation(namespace, ordered, writeConcern, retryWrites, maxAdaptiveRetriesSetting, replaceRequests, REPLACE); } static LegacyMixedBulkWriteOperation createBulkWriteOperationForDelete(final MongoNamespace namespace, final boolean ordered, - final WriteConcern writeConcern, final boolean retryWrites, final List deleteRequests) { - return new LegacyMixedBulkWriteOperation(namespace, ordered, writeConcern, retryWrites, deleteRequests, DELETE); + final WriteConcern writeConcern, + final boolean retryWrites, + @Nullable final Integer maxAdaptiveRetriesSetting, + final List deleteRequests) { + return new LegacyMixedBulkWriteOperation(namespace, ordered, writeConcern, retryWrites, maxAdaptiveRetriesSetting, deleteRequests, DELETE); } private LegacyMixedBulkWriteOperation(final MongoNamespace namespace, final boolean ordered, final WriteConcern writeConcern, - final boolean retryWrites, final List writeRequests, final WriteRequest.Type type) { + final boolean retryWrites, + @Nullable final Integer maxAdaptiveRetriesSetting, + final List writeRequests, + final WriteRequest.Type type) { notNull("writeRequests", writeRequests); isTrueArgument("writeRequests is not an empty list", !writeRequests.isEmpty()); this.type = type; - this.wrappedOperation = new MixedBulkWriteOperation(namespace, writeRequests, ordered, writeConcern, retryWrites); + this.wrappedOperation = new MixedBulkWriteOperation(namespace, writeRequests, ordered, writeConcern, retryWrites, maxAdaptiveRetriesSetting); } List getWriteRequests() { diff --git a/driver-legacy/src/main/com/mongodb/MongoClient.java b/driver-legacy/src/main/com/mongodb/MongoClient.java index 06dac49c671..fbbbab0f817 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClient.java +++ b/driver-legacy/src/main/com/mongodb/MongoClient.java @@ -41,7 +41,9 @@ import com.mongodb.internal.connection.StreamFactoryFactory; import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; +import com.mongodb.internal.observability.micrometer.TracingManager; import com.mongodb.internal.session.ServerSessionPool; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.internal.thread.DaemonThreadFactory; import com.mongodb.internal.validator.NoOpFieldNameValidator; import com.mongodb.lang.Nullable; @@ -255,13 +257,14 @@ private MongoClient(final MongoClientSettings settings, StreamFactoryFactory syncStreamFactoryFactory = getSyncStreamFactoryFactory( settings.getTransportSettings(), getInetAddressResolver(settings)); - + AsyncClientExecutor clientExecutor = AsyncClientExecutor.NO_OP; Cluster cluster = Clusters.createCluster( settings, wrappedMongoDriverInformation, - syncStreamFactoryFactory); + syncStreamFactoryFactory, + clientExecutor); - delegate = new MongoClientImpl(cluster, settings, wrappedMongoDriverInformation, syncStreamFactoryFactory); + delegate = new MongoClientImpl(cluster, wrappedMongoDriverInformation, settings, syncStreamFactoryFactory, clientExecutor); this.options = options != null ? options : MongoClientOptions.builder(settings).build(); cursorCleaningService = this.options.isCursorFinalizerEnabled() ? createCursorCleaningService() : null; this.closed = new AtomicBoolean(); @@ -860,7 +863,7 @@ private void cleanCursors() { ServerCursorAndNamespace cur; while ((cur = orphanedCursors.poll()) != null) { OperationContext operationContext = new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE, - new TimeoutContext(getTimeoutSettings()), options.getServerApi()); + new TimeoutContext(getTimeoutSettings()), delegate.getClientExecutor(), TracingManager.NO_OP, options.getServerApi(), null); ReadWriteBinding binding = new SingleServerBinding(delegate.getCluster(), cur.serverCursor.getAddress()); try { diff --git a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java index 3b049eb85de..1873bbdb759 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java @@ -17,6 +17,7 @@ package com.mongodb; import com.mongodb.annotations.Alpha; +import com.mongodb.annotations.Beta; import com.mongodb.annotations.Immutable; import com.mongodb.annotations.NotThreadSafe; import com.mongodb.annotations.Reason; @@ -442,11 +443,14 @@ public WriteConcern getWriteConcern() { } /** - * Returns true if writes should be retried if they fail due to a network error or other retryable error. + * Returns whether attempts to execute write commands should be retried if they fail due to a retryable error. + * See {@link MongoClientSettings.Builder#retryWrites(boolean)} for more information. * *

Starting with the 3.11.0 release, the default value is true

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

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

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

Starting with the 3.11.0 release, the default value is true

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

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

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

    General configuration:

    *
      - *
    • {@code retryWrites=true|false}. If true the driver will retry supported write operations if they fail due to a network error. - * Defaults to true.
    • - *
    • {@code retryReads=true|false}. If true the driver will retry supported read operations if they fail due to a network error. - * Defaults to true.
    • + *
    • {@code retryWrites=true|false}: Whether attempts to execute write commands should be retried if they fail due to a retryable error. + * Defaults to true. See also {@code maxAdaptiveRetries}.
    • + *
    • {@code retryReads=true|false}: Whether attempts to execute read commands should be retried if they fail due to a retryable error. + * Defaults to true. See also {@code maxAdaptiveRetries}.
    • + *
    • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. + * The maximum number of retry attempts when encountering a retryable overload error. + * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
    • +*
    • {@code 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.
    • @@ -381,11 +383,19 @@ public MongoClientOptions getOptions() { if (retryWritesValue != null) { builder.retryWrites(retryWritesValue); } - Boolean retryReads = proxied.getRetryReads(); if (retryReads != null) { builder.retryReads(retryReads); } + Integer maxAdaptiveRetries = proxied.getMaxAdaptiveRetries(); + if (maxAdaptiveRetries != null) { + builder.maxAdaptiveRetries(maxAdaptiveRetries); + } + + Boolean enableOverloadRetargeting = proxied.getEnableOverloadRetargeting(); + if (enableOverloadRetargeting != null) { + builder.enableOverloadRetargeting(enableOverloadRetargeting); + } Integer maxConnectionPoolSize = proxied.getMaxConnectionPoolSize(); if (maxConnectionPoolSize != null) { diff --git a/driver-legacy/src/test/functional/com/mongodb/DBCollectionSpecification.groovy b/driver-legacy/src/test/functional/com/mongodb/DBCollectionSpecification.groovy index 98cb8282c17..c546017f5b1 100644 --- a/driver-legacy/src/test/functional/com/mongodb/DBCollectionSpecification.groovy +++ b/driver-legacy/src/test/functional/com/mongodb/DBCollectionSpecification.groovy @@ -32,6 +32,7 @@ import com.mongodb.internal.bulk.DeleteRequest import com.mongodb.internal.bulk.IndexRequest import com.mongodb.internal.bulk.InsertRequest import com.mongodb.internal.bulk.UpdateRequest +import com.mongodb.internal.client.model.AggregationLevel import com.mongodb.internal.operation.AggregateOperation import com.mongodb.internal.operation.AggregateToCollectionOperation import com.mongodb.internal.operation.BatchCursor @@ -272,7 +273,8 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), - collection.getObjectCodec()) + collection.getObjectCodec(), + null) .filter(new BsonDocument()) .retryReads(true)) @@ -282,7 +284,8 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), - collection.getObjectCodec()) + collection.getObjectCodec(), + null) .filter(new BsonDocument()) .retryReads(true)) @@ -292,7 +295,8 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), - collection.getObjectCodec()) + collection.getObjectCodec(), + null) .filter(new BsonDocument()) .collation(collation) .retryReads(true)) @@ -315,7 +319,8 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), - collection.getObjectCodec()) + collection.getObjectCodec(), + null) .filter(new BsonDocument()) .limit(-1) .retryReads(true)) @@ -326,7 +331,8 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), - collection.getObjectCodec()) + collection.getObjectCodec(), + null) .filter(new BsonDocument()) .limit(-1) .retryReads(true)) @@ -337,7 +343,8 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), - collection.getObjectCodec()) + collection.getObjectCodec(), + null) .filter(new BsonDocument()) .limit(-1) .collation(collation) @@ -358,7 +365,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(new FindAndDeleteOperation(collection. - getNamespace(), WriteConcern.ACKNOWLEDGED, retryWrites, collection.getObjectCodec()).filter(new BsonDocument())) + getNamespace(), WriteConcern.ACKNOWLEDGED, retryWrites, null, collection.getObjectCodec()).filter(new BsonDocument())) } def 'findAndModify should create the correct FindAndUpdateOperation'() { @@ -378,7 +385,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(new FindAndUpdateOperation(collection.getNamespace(), - WriteConcern.ACKNOWLEDGED, retryWrites, collection.getObjectCodec(), bsonUpdate) + WriteConcern.ACKNOWLEDGED, retryWrites, null, collection.getObjectCodec(), bsonUpdate) .filter(new BsonDocument())) when: // With options @@ -387,7 +394,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(new FindAndUpdateOperation(collection.getNamespace(), WriteConcern.W3, - retryWrites, collection.getObjectCodec(), bsonUpdate) + retryWrites, null, collection.getObjectCodec(), bsonUpdate) .filter(new BsonDocument()) .collation(collation) .arrayFilters(bsonDocumentWrapperArrayFilters)) @@ -415,7 +422,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(new FindAndReplaceOperation(collection. - getNamespace(), WriteConcern.ACKNOWLEDGED, retryWrites, collection.getObjectCodec(), bsonReplace) + getNamespace(), WriteConcern.ACKNOWLEDGED, retryWrites, null, collection.getObjectCodec(), bsonReplace) .filter(new BsonDocument())) when: // With options @@ -424,7 +431,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(new FindAndReplaceOperation(collection.getNamespace(), WriteConcern.W3, - retryWrites, collection.getObjectCodec(), bsonReplace) + retryWrites, null, collection.getObjectCodec(), bsonReplace) .filter(new BsonDocument()) .collation(collation)) } @@ -439,7 +446,7 @@ class DBCollectionSpecification extends Specification { collection.count() then: - expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace()) + expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace(), null) .filter(new BsonDocument()).retryReads(true)) when: // Inherits from DB @@ -448,7 +455,7 @@ class DBCollectionSpecification extends Specification { executor.getReadConcern() == ReadConcern.MAJORITY then: - expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace()) + expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace(), null) .filter(new BsonDocument()).retryReads(true)) executor.getReadConcern() == ReadConcern.MAJORITY @@ -457,7 +464,7 @@ class DBCollectionSpecification extends Specification { collection.count(new BasicDBObject(), new DBCollectionCountOptions().collation(collation)) then: - expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace()) + expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace(), null) .filter(new BsonDocument()).retryReads(true) .collation(collation)) executor.getReadConcern() == ReadConcern.LOCAL @@ -485,7 +492,7 @@ class DBCollectionSpecification extends Specification { then: distinctFieldValues == [1, 2] expect executor.getReadOperation(), isTheSameAs(new DistinctOperation(collection.getNamespace(), 'field1', - new BsonValueCodec()).filter(new BsonDocument()).retryReads(true)) + new BsonValueCodec(), null).filter(new BsonDocument()).retryReads(true)) executor.getReadConcern() == ReadConcern.DEFAULT when: // Inherits from DB @@ -494,7 +501,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new DistinctOperation(collection.getNamespace(), 'field1', - new BsonValueCodec()) + new BsonValueCodec(), null) .filter(new BsonDocument()).retryReads(true)) executor.getReadConcern() == ReadConcern.MAJORITY @@ -504,7 +511,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new DistinctOperation(collection.getNamespace(), 'field1', - new BsonValueCodec()).collation(collation).retryReads(true)) + new BsonValueCodec(), null).collation(collation).retryReads(true)) executor.getReadConcern() == ReadConcern.LOCAL } @@ -620,7 +627,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new AggregateOperation(collection.getNamespace(), - bsonPipeline, collection.getDefaultDBObjectCodec()).retryReads(true)) + bsonPipeline, collection.getDefaultDBObjectCodec(), null).retryReads(true)) executor.getReadConcern() == ReadConcern.DEFAULT when: // Inherits from DB @@ -629,7 +636,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new AggregateOperation(collection.getNamespace(), - bsonPipeline, collection.getDefaultDBObjectCodec()).retryReads(true)) + bsonPipeline, collection.getDefaultDBObjectCodec(), null).retryReads(true)) executor.getReadConcern() == ReadConcern.MAJORITY when: @@ -638,7 +645,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new AggregateOperation(collection.getNamespace(), - bsonPipeline, collection.getDefaultDBObjectCodec()).collation(collation).retryReads(true)) + bsonPipeline, collection.getDefaultDBObjectCodec(), null).collation(collation).retryReads(true)) executor.getReadConcern() == ReadConcern.LOCAL } @@ -655,21 +662,22 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new AggregateToCollectionOperation(collection.getNamespace(), - bsonPipeline, collection.getReadConcern(), collection.getWriteConcern())) + bsonPipeline, collection.getReadConcern(), collection.getWriteConcern(), AggregationLevel.COLLECTION, true, null)) when: // Inherits from DB collection.aggregate(pipeline, AggregationOptions.builder().build()) then: expect executor.getReadOperation(), isTheSameAs(new AggregateToCollectionOperation(collection.getNamespace(), - bsonPipeline, collection.getReadConcern(), collection.getWriteConcern())) + bsonPipeline, collection.getReadConcern(), collection.getWriteConcern(), AggregationLevel.COLLECTION, true, null)) when: collection.aggregate(pipeline, AggregationOptions.builder().collation(collation).build()) then: expect executor.getReadOperation(), isTheSameAs(new AggregateToCollectionOperation(collection.getNamespace(), - bsonPipeline, collection.getReadConcern(), collection.getWriteConcern()).collation(collation)) + bsonPipeline, collection.getReadConcern(), collection.getWriteConcern(), + AggregationLevel.COLLECTION, true, null).collation(collation)) } def 'explainAggregate should create the correct AggregateOperation'() { @@ -687,7 +695,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new AggregateOperation(collection.getNamespace(), - bsonPipeline, collection.getDefaultDBObjectCodec()).retryReads(true).collation(collation) + bsonPipeline, collection.getDefaultDBObjectCodec(), null).retryReads(true).collation(collation) .asExplainableOperation(ExplainVerbosity.QUERY_PLANNER, new BsonDocumentCodec())) when: // Inherits from DB @@ -696,7 +704,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new AggregateOperation(collection.getNamespace(), - bsonPipeline, collection.getDefaultDBObjectCodec()).retryReads(true).collation(collation) + bsonPipeline, collection.getDefaultDBObjectCodec(), null).retryReads(true).collation(collation) .asExplainableOperation(ExplainVerbosity.QUERY_PLANNER, new BsonDocumentCodec())) when: @@ -705,7 +713,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new AggregateOperation(collection.getNamespace(), - bsonPipeline, collection.getDefaultDBObjectCodec()).retryReads(true).collation(collation) + bsonPipeline, collection.getDefaultDBObjectCodec(), null).retryReads(true).collation(collation) .asExplainableOperation(ExplainVerbosity.QUERY_PLANNER, new BsonDocumentCodec())) } @@ -726,7 +734,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(createBulkWriteOperationForUpdate(collection.getNamespace(), - true, WriteConcern.ACKNOWLEDGED, retryWrites, asList(updateRequest))) + true, WriteConcern.ACKNOWLEDGED, retryWrites, null, asList(updateRequest))) when: // Inherits from DB db.setWriteConcern(WriteConcern.W3) @@ -735,7 +743,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(createBulkWriteOperationForUpdate(collection.getNamespace(), - true, WriteConcern.W3, retryWrites, asList(updateRequest))) + true, WriteConcern.W3, retryWrites, null, asList(updateRequest))) when: collection.setWriteConcern(WriteConcern.W1) @@ -745,7 +753,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(createBulkWriteOperationForUpdate(collection.getNamespace(), - true, WriteConcern.W1, retryWrites, asList(updateRequest.arrayFilters(bsonDocumentWrapperArrayFilters)))) + true, WriteConcern.W1, retryWrites, null, asList(updateRequest.arrayFilters(bsonDocumentWrapperArrayFilters)))) where: dbObjectArrayFilters << [null, [], [new BasicDBObject('i.b', 1)]] @@ -768,7 +776,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(createBulkWriteOperationForDelete(collection.getNamespace(), - false, WriteConcern.ACKNOWLEDGED, retryWrites, asList(deleteRequest))) + false, WriteConcern.ACKNOWLEDGED, retryWrites, null, asList(deleteRequest))) when: // Inherits from DB db.setWriteConcern(WriteConcern.W3) @@ -776,7 +784,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(createBulkWriteOperationForDelete(collection.getNamespace(), - false, WriteConcern.W3, retryWrites, asList(deleteRequest))) + false, WriteConcern.W3, retryWrites, null, asList(deleteRequest))) when: collection.setWriteConcern(WriteConcern.W1) @@ -785,7 +793,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(createBulkWriteOperationForDelete(collection.getNamespace(), - false, WriteConcern.W1, retryWrites, asList(deleteRequest))) + false, WriteConcern.W1, retryWrites, null, asList(deleteRequest))) } def 'should create the correct MixedBulkWriteOperation'() { @@ -818,7 +826,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(new MixedBulkWriteOperation(collection.getNamespace(), writeRequests, ordered, - WriteConcern.ACKNOWLEDGED, false)) + WriteConcern.ACKNOWLEDGED, false, null)) when: // Inherits from DB db.setWriteConcern(WriteConcern.W3) @@ -826,7 +834,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(new MixedBulkWriteOperation(collection.getNamespace(), - writeRequests, ordered, WriteConcern.W3, false)) + writeRequests, ordered, WriteConcern.W3, false, null)) when: collection.setWriteConcern(WriteConcern.W1) @@ -834,7 +842,7 @@ class DBCollectionSpecification extends Specification { then: expect executor.getWriteOperation(), isTheSameAs(new MixedBulkWriteOperation(collection.getNamespace(), - writeRequests, ordered, WriteConcern.W1, false)) + writeRequests, ordered, WriteConcern.W1, false, null)) where: ordered << [true, false, true] diff --git a/driver-legacy/src/test/functional/com/mongodb/DBTest.java b/driver-legacy/src/test/functional/com/mongodb/DBTest.java index cf44573a2b4..5b92651fbcf 100644 --- a/driver-legacy/src/test/functional/com/mongodb/DBTest.java +++ b/driver-legacy/src/test/functional/com/mongodb/DBTest.java @@ -31,7 +31,6 @@ import java.util.Locale; import java.util.UUID; -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.disableMaxTimeFailPoint; import static com.mongodb.ClusterFixture.enableMaxTimeFailPoint; import static com.mongodb.ClusterFixture.getBinding; @@ -344,8 +343,8 @@ public void shouldApplyUuidRepresentationToCommandEncodingAndDecoding() { } BsonDocument getCollectionInfo(final String collectionName) { - return new ListCollectionsOperation<>(getDefaultDatabaseName(), new BsonDocumentCodec()) - .filter(new BsonDocument("name", new BsonString(collectionName))).execute(getBinding(), OPERATION_CONTEXT).next().get(0); + return new ListCollectionsOperation<>(getDefaultDatabaseName(), new BsonDocumentCodec(), null) + .filter(new BsonDocument("name", new BsonString(collectionName))).execute(getBinding(), ClusterFixture.createOperationContext()).next().get(0); } private boolean isCapped(final DBCollection collection) { diff --git a/driver-legacy/src/test/functional/com/mongodb/LegacyMixedBulkWriteOperationSpecification.groovy b/driver-legacy/src/test/functional/com/mongodb/LegacyMixedBulkWriteOperationSpecification.groovy index 6a9c511c3bc..2eb508fc49a 100644 --- a/driver-legacy/src/test/functional/com/mongodb/LegacyMixedBulkWriteOperationSpecification.groovy +++ b/driver-legacy/src/test/functional/com/mongodb/LegacyMixedBulkWriteOperationSpecification.groovy @@ -45,7 +45,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def 'should throw IllegalArgumentException for empty list of requests'() { when: - createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, true, []) + createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, true, null, []) then: thrown(IllegalArgumentException) @@ -55,7 +55,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec given: def inserts = [new InsertRequest(new BsonDocument('_id', new BsonInt32(1))), new InsertRequest(new BsonDocument('_id', new BsonInt32(2)))] - def operation = createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, inserts) + def operation = createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, null, inserts) when: def result = execute(operation) @@ -72,7 +72,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def 'should insert a single document'() { given: def insert = new InsertRequest(new BsonDocument('_id', new BsonInt32(1))) - def operation = createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, asList(insert)) + def operation = createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, null, asList(insert)) when: execute(operation) @@ -84,7 +84,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def 'should execute unacknowledged write'() { given: def binding = getSingleConnectionBinding() - def operation = createBulkWriteOperationForInsert(getNamespace(), true, UNACKNOWLEDGED, false, + def operation = createBulkWriteOperationForInsert(getNamespace(), true, UNACKNOWLEDGED, false, null, [new InsertRequest(new BsonDocument('_id', new BsonInt32(1))), new InsertRequest(new BsonDocument('_id', new BsonInt32(2)))]) @@ -106,7 +106,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec new InsertRequest(new BsonDocument('_id', new BsonInt32(1))), new InsertRequest(new BsonDocument('_id', new BsonInt32(2))), ] - def operation = createBulkWriteOperationForInsert(getNamespace(), false, ACKNOWLEDGED, false, documents) + def operation = createBulkWriteOperationForInsert(getNamespace(), false, ACKNOWLEDGED, false, null, documents) when: execute(operation) @@ -123,7 +123,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec new InsertRequest(new BsonDocument('_id', new BsonInt32(1))), new InsertRequest(new BsonDocument('_id', new BsonInt32(2))), ] - def operation = createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, documents) + def operation = createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, null, documents) when: execute(operation) @@ -137,7 +137,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def 'should support retryable writes'() { given: def insert = new InsertRequest(new BsonDocument('_id', new BsonInt32(1))) - def operation = createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, true, asList(insert)) + def operation = createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, true, null, asList(insert)) when: executeWithSession(operation, false) @@ -149,7 +149,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def 'should remove a document'() { given: getCollectionHelper().insertDocuments(new DocumentCodec(), new Document('_id', 1)) - def operation = createBulkWriteOperationForDelete(getNamespace(), true, ACKNOWLEDGED, false, + def operation = createBulkWriteOperationForDelete(getNamespace(), true, ACKNOWLEDGED, false, null, [new DeleteRequest(new BsonDocument('_id', new BsonInt32(1)))]) when: @@ -167,7 +167,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec given: def replacement = new UpdateRequest(new BsonDocument(), new BsonDocument('_id', new BsonInt32(1)), REPLACE) def operation = createBulkWriteOperationForReplace(getNamespace(), true, ACKNOWLEDGED, - false, asList(replacement)) + false, null, asList(replacement)) when: def result = execute(operation) @@ -183,13 +183,13 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec given: def insert = new InsertRequest(new BsonDocument('_id', new BsonInt32(1))) def binding = getBinding() - createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, asList(insert)) - .execute(binding, ClusterFixture.getOperationContext(binding.getReadPreference())) + createBulkWriteOperationForInsert(getNamespace(), true, ACKNOWLEDGED, false, null, asList(insert)) + .execute(binding, ClusterFixture.createOperationContext(binding.getReadPreference())) def replacement = new UpdateRequest(new BsonDocument('_id', new BsonInt32(1)), new BsonDocument('_id', new BsonInt32(1)).append('x', new BsonInt32(1)), REPLACE) def operation = createBulkWriteOperationForReplace(getNamespace(), true, ACKNOWLEDGED, - false, asList(replacement)) + false, null, asList(replacement)) when: def result = execute(operation) @@ -209,7 +209,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec new BsonDocument('_id', new BsonInt32(1)).append('x', new BsonInt32(1)), REPLACE) .upsert(true) def operation = createBulkWriteOperationForReplace(getNamespace(), true, ACKNOWLEDGED, - false, asList(replacement)) + false, null, asList(replacement)) when: execute(operation) @@ -221,7 +221,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def 'should update nothing if no documents match'() { given: def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, - false, asList(new UpdateRequest(new BsonDocument('x', new BsonInt32(1)), + false, null, asList(new UpdateRequest(new BsonDocument('x', new BsonInt32(1)), new BsonDocument('$set', new BsonDocument('y', new BsonInt32(2))), UPDATE).multi(false))) when: @@ -240,7 +240,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec getCollectionHelper().insertDocuments(new DocumentCodec(), new Document('x', 1), new Document('x', 1)) - def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, false, + def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, false, null, asList(new UpdateRequest(new BsonDocument('x', new BsonInt32(1)), new BsonDocument('$set', new BsonDocument('y', new BsonInt32(2))), UPDATE).multi(false))) @@ -260,7 +260,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec getCollectionHelper().insertDocuments(new DocumentCodec(), new Document('x', 1), new Document('x', 1)) - def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, false, + def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, false, null, asList(new UpdateRequest(new BsonDocument('x', new BsonInt32(1)), new BsonDocument('$set', new BsonDocument('y', new BsonInt32(2))), UPDATE).multi(true))) @@ -277,7 +277,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def 'when upsert is true should insert a document if there are no matching documents'() { given: - def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, false, + def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, false, null, asList(new UpdateRequest(new BsonDocument('_id', new BsonInt32(1)), new BsonDocument('$set', new BsonDocument('y', new BsonInt32(2))), UPDATE).upsert(true))) @@ -295,7 +295,7 @@ class LegacyMixedBulkWriteOperationSpecification extends OperationFunctionalSpec def 'should return correct result for upsert'() { given: def id = new ObjectId() - def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, false, + def operation = createBulkWriteOperationForUpdate(getNamespace(), true, ACKNOWLEDGED, false, null, asList(new UpdateRequest(new BsonDocument('_id', new BsonObjectId(id)), new BsonDocument('$set', new BsonDocument('x', new BsonInt32(1))), UPDATE).upsert(true))) diff --git a/driver-legacy/src/test/unit/com/mongodb/DBCursorSpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/DBCursorSpecification.groovy index 59dceb6478a..37e6ac343a5 100644 --- a/driver-legacy/src/test/unit/com/mongodb/DBCursorSpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/DBCursorSpecification.groovy @@ -123,7 +123,8 @@ class DBCursorSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), - collection.getObjectCodec()) + collection.getObjectCodec(), + null) .filter(new BsonDocument()) .projection(new BsonDocument()) .retryReads(true)) @@ -142,7 +143,7 @@ class DBCursorSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs( - new FindOperation(collection.getNamespace(), collection.getObjectCodec()) + new FindOperation(collection.getNamespace(), collection.getObjectCodec(), null) .limit(-1) .filter(new BsonDocument()) .projection(new BsonDocument()) @@ -163,7 +164,7 @@ class DBCursorSpecification extends Specification { def readConcern = ReadConcern.LOCAL def readPreference = ReadPreference.nearest() def findOptions = new DBCollectionFindOptions() - def cursor = new DBCursor(collection, filter, findOptions) + def cursor = new DBCursor(collection, filter, findOptions, true) .setReadConcern(readConcern) .setReadPreference(readPreference) .setCollation(collation) @@ -181,7 +182,7 @@ class DBCursorSpecification extends Specification { then: expect executor.getReadOperation(), isTheSameAs( - new FindOperation(collection.getNamespace(), collection.getObjectCodec()) + new FindOperation(collection.getNamespace(), collection.getObjectCodec(), null) .batchSize(1) .collation(collation) .cursorType(cursorType) @@ -240,13 +241,13 @@ class DBCursorSpecification extends Specification { .returnKey(true) .showRecordId(true) - def cursor = new DBCursor(collection, filter, findOptions) + def cursor = new DBCursor(collection, filter, findOptions, true) when: cursor.toArray() then: - expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), collection.getObjectCodec()) + expect executor.getReadOperation(), isTheSameAs(new FindOperation(collection.getNamespace(), collection.getObjectCodec(), null) .batchSize(1) .collation(collation) .cursorType(cursorType) @@ -280,7 +281,7 @@ class DBCursorSpecification extends Specification { then: result == 42 - expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace()) + expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace(), null) .filter(new BsonDocument()).retryReads(true)) executor.getReadConcern() == ReadConcern.MAJORITY } @@ -296,7 +297,7 @@ class DBCursorSpecification extends Specification { then: result == 42 - expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace()) + expect executor.getReadOperation(), isTheSameAs(new CountOperation(collection.getNamespace(), null) .filter(new BsonDocument()).retryReads(true)) executor.getReadConcern() == ReadConcern.MAJORITY } diff --git a/driver-legacy/src/test/unit/com/mongodb/DBSpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/DBSpecification.groovy index 5f0c81f28cc..832d75f520c 100644 --- a/driver-legacy/src/test/unit/com/mongodb/DBSpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/DBSpecification.groovy @@ -88,7 +88,7 @@ class DBSpecification extends Specification { then: def operation = executor.getWriteOperation() as CreateCollectionOperation - expect operation, isTheSameAs(new CreateCollectionOperation('test', 'ctest', db.getWriteConcern())) + expect operation, isTheSameAs(new CreateCollectionOperation('test', 'ctest', db.getWriteConcern(), true, null)) executor.getReadConcern() == ReadConcern.MAJORITY when: @@ -108,7 +108,7 @@ class DBSpecification extends Specification { operation = executor.getWriteOperation() as CreateCollectionOperation then: - expect operation, isTheSameAs(new CreateCollectionOperation('test', 'ctest', db.getWriteConcern()) + expect operation, isTheSameAs(new CreateCollectionOperation('test', 'ctest', db.getWriteConcern(), true, null) .sizeInBytes(100000) .maxDocuments(2000) .capped(true) @@ -136,7 +136,7 @@ class DBSpecification extends Specification { operation = executor.getWriteOperation() as CreateCollectionOperation then: - expect operation, isTheSameAs(new CreateCollectionOperation('test', 'ctest', db.getWriteConcern()) + expect operation, isTheSameAs(new CreateCollectionOperation('test', 'ctest', db.getWriteConcern(), true, null) .collation(collation)) executor.getReadConcern() == ReadConcern.MAJORITY } @@ -167,7 +167,7 @@ class DBSpecification extends Specification { then: def operation = executor.getWriteOperation() as CreateViewOperation expect operation, isTheSameAs(new CreateViewOperation(databaseName, viewName, viewOn, - [new BsonDocument('$match', new BsonDocument('x', BsonBoolean.TRUE))], writeConcern)) + [new BsonDocument('$match', new BsonDocument('x', BsonBoolean.TRUE))], writeConcern, true, null)) executor.getReadConcern() == ReadConcern.MAJORITY when: @@ -176,7 +176,7 @@ class DBSpecification extends Specification { then: expect operation, isTheSameAs(new CreateViewOperation(databaseName, viewName, viewOn, - [new BsonDocument('$match', new BsonDocument('x', BsonBoolean.TRUE))], writeConcern).collation(collation)) + [new BsonDocument('$match', new BsonDocument('x', BsonBoolean.TRUE))], writeConcern, true, null).collation(collation)) executor.getReadConcern() == ReadConcern.MAJORITY } @@ -197,7 +197,8 @@ class DBSpecification extends Specification { then: expect operation, isTheSameAs(new ListCollectionsOperation(databaseName, - new DBObjectCodec(getDefaultCodecRegistry())) + new DBObjectCodec(getDefaultCodecRegistry()), + null) .nameOnly(true)) when: @@ -206,7 +207,8 @@ class DBSpecification extends Specification { then: expect operation, isTheSameAs(new ListCollectionsOperation(databaseName, - new DBObjectCodec(getDefaultCodecRegistry())) + new DBObjectCodec(getDefaultCodecRegistry()), + null) .nameOnly(true)) } diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy index ae1d332674c..a386cd7f684 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy @@ -46,6 +46,8 @@ class MongoClientOptionsSpecification extends Specification { options.getWriteConcern() == WriteConcern.ACKNOWLEDGED options.getRetryWrites() options.getRetryReads() + options.getMaxAdaptiveRetries() == null + !options.getEnableOverloadRetargeting() options.getCodecRegistry() == MongoClientSettings.defaultCodecRegistry options.getUuidRepresentation() == UuidRepresentation.UNSPECIFIED options.getMinConnectionsPerHost() == 0 @@ -84,6 +86,11 @@ class MongoClientOptionsSpecification extends Specification { given: def builder = new MongoClientOptions.Builder() + when: + builder.maxAdaptiveRetries(-1) + then: + thrown(IllegalArgumentException) + when: builder.dbDecoderFactory(null) then: @@ -116,6 +123,8 @@ class MongoClientOptionsSpecification extends Specification { .readPreference(ReadPreference.secondary()) .retryWrites(true) .retryReads(false) + .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .writeConcern(WriteConcern.JOURNALED) .readConcern(ReadConcern.MAJORITY) .minConnectionsPerHost(30) @@ -162,6 +171,8 @@ class MongoClientOptionsSpecification extends Specification { options.getServerSelector() == serverSelector options.getRetryWrites() !options.getRetryReads() + options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getServerSelectionTimeout() == 150 options.getTimeout() == 10_000 options.getMaxWaitTime() == 200 @@ -207,6 +218,7 @@ class MongoClientOptionsSpecification extends Specification { settings.writeConcern == WriteConcern.JOURNALED settings.retryWrites !settings.retryReads + settings.getMaxAdaptiveRetries() == 42 settings.autoEncryptionSettings == autoEncryptionSettings settings.codecRegistry == codecRegistry settings.commandListeners == [commandListener] @@ -227,6 +239,7 @@ class MongoClientOptionsSpecification extends Specification { optionsFromSettings.getServerSelector() == serverSelector optionsFromSettings.getRetryWrites() !optionsFromSettings.getRetryReads() + optionsFromSettings.getMaxAdaptiveRetries() == 42 optionsFromSettings.getServerSelectionTimeout() == 150 optionsFromSettings.getServerSelectionTimeout() == 150 optionsFromSettings.getMaxWaitTime() == 200 @@ -318,6 +331,7 @@ class MongoClientOptionsSpecification extends Specification { .applicationName('appName') .readPreference(ReadPreference.secondary()) .retryReads(true) + .enableOverloadRetargeting(true) .uuidRepresentation(UuidRepresentation.STANDARD) .writeConcern(WriteConcern.JOURNALED) .minConnectionsPerHost(30) @@ -619,6 +633,8 @@ class MongoClientOptionsSpecification extends Specification { .writeConcern(WriteConcern.JOURNALED) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .uuidRepresentation(UuidRepresentation.STANDARD) .minConnectionsPerHost(30) .connectionsPerHost(500) @@ -663,6 +679,18 @@ class MongoClientOptionsSpecification extends Specification { MongoClientOptions.builder().connectionsPerHost(0).build().getConnectionsPerHost() == 0 } + def 'should allow null, 0 maxAdaptiveRetries'() { + when: + def options = MongoClientOptions.builder().maxAdaptiveRetries(null).build() + then: + options.getMaxAdaptiveRetries() == null + + when: + options = MongoClientOptions.builder().maxAdaptiveRetries(0).build() + then: + options.getMaxAdaptiveRetries() == 0 + } + private static class MyDBEncoderFactory implements DBEncoderFactory { @Override DBEncoder create() { diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientSpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientSpecification.groovy index 1389a41c760..60722472919 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientSpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientSpecification.groovy @@ -23,6 +23,8 @@ import com.mongodb.client.model.geojson.MultiPolygon import com.mongodb.connection.ClusterSettings import com.mongodb.internal.connection.ClientMetadata import com.mongodb.internal.connection.Cluster +import com.mongodb.internal.connection.StreamFactoryFactory +import com.mongodb.internal.thread.AsyncClientExecutor import org.bson.BsonDocument import org.bson.Document import org.bson.codecs.UuidCodec @@ -36,6 +38,7 @@ import static com.mongodb.CustomMatchers.isTheSameAs import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry import static com.mongodb.MongoCredential.createMongoX509Credential import static com.mongodb.ReadPreference.secondary +import static com.mongodb.assertions.Assertions.fail import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE import static com.mongodb.connection.ClusterConnectionMode.SINGLE import static java.util.concurrent.TimeUnit.MILLISECONDS @@ -314,7 +317,9 @@ class MongoClientSpecification extends Specification { def clusterStub = Stub(Cluster) clusterStub.getClientMetadata() >> new ClientMetadata("test", MongoDriverInformation.builder().build()) - def client = new MongoClientImpl(clusterStub, null, MongoClientSettings.builder().build(), null, executor) + def client = new MongoClientImpl( + clusterStub, null, MongoClientSettings.builder().build(), mockStreamFactoryFactory(), + AsyncClientExecutor.NO_OP, executor) when: client.watch((Class) null) @@ -345,7 +350,7 @@ class MongoClientSpecification extends Specification { then: expect database, isTheSameAs(new MongoDatabaseImpl('name', client.getCodecRegistry(), secondary(), - WriteConcern.MAJORITY, true, true, ReadConcern.MAJORITY, STANDARD, null, + WriteConcern.MAJORITY, true, true, null, ReadConcern.MAJORITY, STANDARD, null, TIMEOUT_SETTINGS.withMaxWaitTimeMS(120_000), client.getOperationExecutor())) } @@ -365,4 +370,12 @@ class MongoClientSpecification extends Specification { cleanup: client?.close() } + + def mockStreamFactoryFactory() { + Mock(StreamFactoryFactory) { + getExecutor() >> { + fail() + } + } + } } diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy index 241ac958c8a..3de1f77b6da 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy @@ -131,6 +131,8 @@ class MongoClientURISpecification extends Specification { + 'heartbeatFrequencyMS=20000&' + 'retryWrites=true&' + 'retryReads=true&' + + 'maxAdaptiveRetries=42&' + + 'enableOverloadRetargeting=true&' + 'uuidRepresentation=csharpLegacy&' + 'appName=app1&' + 'timeoutMS=10000') @@ -158,6 +160,8 @@ class MongoClientURISpecification extends Specification { options.getHeartbeatFrequency() == 20000 options.getRetryWrites() options.getRetryReads() + options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getUuidRepresentation() == UuidRepresentation.C_SHARP_LEGACY options.getApplicationName() == 'app1' } @@ -178,6 +182,8 @@ class MongoClientURISpecification extends Specification { !options.isSslEnabled() options.getRetryWrites() options.getRetryReads() + options.getMaxAdaptiveRetries() == null + !options.getEnableOverloadRetargeting() options.getUuidRepresentation() == UuidRepresentation.UNSPECIFIED } @@ -188,6 +194,8 @@ class MongoClientURISpecification extends Specification { .readPreference(ReadPreference.secondary()) .retryWrites(true) .retryReads(true) + .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .writeConcern(WriteConcern.JOURNALED) .minConnectionsPerHost(30) .connectionsPerHost(500) @@ -220,6 +228,8 @@ class MongoClientURISpecification extends Specification { options.getWriteConcern() == WriteConcern.JOURNALED options.getRetryWrites() options.getRetryReads() + options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getTimeout() == 10_000 options.getServerSelectionTimeout() == 150 options.getMaxWaitTime() == 200 @@ -314,24 +324,37 @@ class MongoClientURISpecification extends Specification { def 'should respect MongoClientOptions builder'() { given: - def uri = new MongoClientURI('mongodb://localhost/', MongoClientOptions.builder().connectionsPerHost(200)) + def uri = new MongoClientURI('mongodb://localhost/', MongoClientOptions.builder() + .connectionsPerHost(200) + .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true)) when: def options = uri.getOptions() then: options.getConnectionsPerHost() == 200 + options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() } def 'should override MongoClientOptions builder'() { given: - def uri = new MongoClientURI('mongodb://localhost/?maxPoolSize=250', MongoClientOptions.builder().connectionsPerHost(200)) + def uri = new MongoClientURI('mongodb://localhost/?' + + 'maxPoolSize=250' + + '&maxAdaptiveRetries=43' + + '&enableOverloadRetargeting=false', + MongoClientOptions.builder() + .connectionsPerHost(200) + .maxAdaptiveRetries(42)) when: def options = uri.getOptions() then: options.getConnectionsPerHost() == 250 + options.getMaxAdaptiveRetries() == 43 + !options.getEnableOverloadRetargeting() } def 'should be equal to another MongoClientURI with the same string values'() { @@ -371,7 +394,9 @@ class MongoClientURISpecification extends Specification { + 'socketTimeoutMS=5500;' + 'safe=false;w=1;wtimeout=2500;' + 'fsync=true;readPreference=primary;' - + 'ssl=true') | new MongoClientURI('mongodb://localhost/db.coll?minPoolSize=5;' + + 'ssl=true;' + + 'maxAdaptiveRetries=42') | new MongoClientURI('mongodb://localhost/db.coll?' + + 'minPoolSize=5;' + 'maxPoolSize=10;' + 'waitQueueTimeoutMS=150;' + 'maxIdleTimeMS=200&maxLifeTimeMS=300;' @@ -379,7 +404,8 @@ class MongoClientURISpecification extends Specification { + '&replicaSet=test;connectTimeoutMS=2500;' + 'socketTimeoutMS=5500&safe=false&w=1;' + 'wtimeout=2500;fsync=true' - + '&readPreference=primary;ssl=true') + + '&readPreference=primary;ssl=true;' + + 'maxAdaptiveRetries=42') } def 'should be not equal to another MongoClientURI with the different string values'() { @@ -401,12 +427,14 @@ class MongoClientURISpecification extends Specification { + '&readPreferenceTags=dc:ny,rack:1' + '&readPreferenceTags=dc:ny' + '&readPreferenceTags=' - + '&maxConnecting=1') | new MongoClientURI('mongodb://localhost/' + + '&maxConnecting=1' + + '&maxAdaptiveRetries=42') | new MongoClientURI('mongodb://localhost/' + '?readPreference=secondaryPreferred' + '&readPreferenceTags=dc:ny' + '&readPreferenceTags=dc:ny, rack:1' + '&readPreferenceTags=' - + '&maxConnecting=2') + + '&maxConnecting=2' + + '&maxAdaptiveRetries=43') new MongoClientURI('mongodb://ross:123@localhost/?' + 'authMechanism=SCRAM-SHA-1') | new MongoClientURI('mongodb://ross:123@localhost/?' + 'authMechanism=GSSAPI') @@ -419,7 +447,8 @@ class MongoClientURISpecification extends Specification { + 'minPoolSize=7;maxIdleTimeMS=1000;maxLifeTimeMS=2000;maxConnecting=1;' + 'replicaSet=test;' + 'connectTimeoutMS=2500;socketTimeoutMS=5500;autoConnectRetry=true;' - + 'readPreference=secondaryPreferred;safe=false;w=1;wtimeout=2600') + + 'readPreference=secondaryPreferred;safe=false;w=1;wtimeout=2600;' + + 'maxAdaptiveRetries=42') MongoClientOptions.Builder builder = MongoClientOptions.builder() .connectionsPerHost(10) @@ -433,6 +462,7 @@ class MongoClientURISpecification extends Specification { .socketTimeout(5500) .readPreference(secondaryPreferred()) .writeConcern(new WriteConcern(1, 2600)) + .maxAdaptiveRetries(42) MongoClientOptions options = builder.build() diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ClientSession.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ClientSession.java index fe58864fad0..2e7f5a4c1d8 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ClientSession.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ClientSession.java @@ -17,7 +17,9 @@ package com.mongodb.reactivestreams.client; +import com.mongodb.MongoException; import com.mongodb.TransactionOptions; +import com.mongodb.annotations.Internal; import com.mongodb.internal.observability.micrometer.TransactionSpan; import com.mongodb.lang.Nullable; import org.reactivestreams.Publisher; @@ -37,14 +39,14 @@ public interface ClientSession extends com.mongodb.session.ClientSession { boolean hasActiveTransaction(); /** - * Notify the client session that a message has been sent. + * Notify that a message is about to be sent. *

      * For internal use only *

      * - * @return true if this is the first message sent, false otherwise - * @since 4.0 + * @return true Iff the message must bear {@code startTransaction: true}. */ + @Internal boolean notifyMessageSent(); /** @@ -54,6 +56,7 @@ public interface ClientSession extends com.mongodb.session.ClientSession { *

      * @param operation the operation */ + @Internal void notifyOperationInitiated(Object operation); /** @@ -67,6 +70,7 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * Start a transaction in the context of this session with default transaction options. A transaction can not be started if there is * already an active transaction on this session. * + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL * @mongodb.server.release 4.0 */ void startTransaction(); @@ -77,14 +81,16 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * * @param transactionOptions the options to apply to the transaction * + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL * @mongodb.server.release 4.0 */ void startTransaction(TransactionOptions transactionOptions); /** - * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been started. + * Commit a transaction in the context of this session. A transaction can only be committed if one has first been started. * * @return an empty publisher that indicates when the operation has completed + * @see MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL * @mongodb.server.release 4.0 */ Publisher commitTransaction(); diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClients.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClients.java index 57ee076039e..8c23eece19f 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClients.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClients.java @@ -27,6 +27,7 @@ import com.mongodb.internal.connection.InternalConnectionPoolSettings; import com.mongodb.internal.connection.StreamFactory; import com.mongodb.internal.connection.StreamFactoryFactory; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.lang.Nullable; import com.mongodb.reactivestreams.client.internal.MongoClientImpl; import com.mongodb.spi.dns.InetAddressResolver; @@ -118,8 +119,9 @@ public static MongoClient create(final MongoClientSettings settings, @Nullable f StreamFactory streamFactory = getStreamFactory(streamFactoryFactory, settings, false); StreamFactory heartbeatStreamFactory = getStreamFactory(streamFactoryFactory, settings, true); MongoDriverInformation wrappedMongoDriverInformation = wrapMongoDriverInformation(mongoDriverInformation); - Cluster cluster = createCluster(settings, wrappedMongoDriverInformation, streamFactory, heartbeatStreamFactory); - return new MongoClientImpl(settings, wrappedMongoDriverInformation, cluster, streamFactoryFactory); + AsyncClientExecutor clientExecutor = AsyncClientExecutor.backedBy(streamFactoryFactory.getExecutor()); + Cluster cluster = createCluster(settings, wrappedMongoDriverInformation, streamFactory, heartbeatStreamFactory, clientExecutor); + return new MongoClientImpl(cluster, wrappedMongoDriverInformation, settings, streamFactoryFactory, clientExecutor); } /** @@ -135,12 +137,13 @@ public static CodecRegistry getDefaultCodecRegistry() { private static Cluster createCluster(final MongoClientSettings settings, @Nullable final MongoDriverInformation mongoDriverInformation, - final StreamFactory streamFactory, final StreamFactory heartbeatStreamFactory) { + final StreamFactory streamFactory, final StreamFactory heartbeatStreamFactory, + final AsyncClientExecutor clientExecutor) { notNull("settings", settings); return new DefaultClusterFactory().createCluster(settings.getClusterSettings(), settings.getServerSettings(), settings.getConnectionPoolSettings(), InternalConnectionPoolSettings.builder().prestartAsyncWorkManager(true).build(), TimeoutSettings.create(settings), streamFactory, TimeoutSettings.createHeartbeatSettings(settings), heartbeatStreamFactory, - settings.getCredential(), settings.getLoggerSettings(), getCommandListener(settings.getCommandListeners()), + clientExecutor, settings.getCredential(), settings.getLoggerSettings(), getCommandListener(settings.getCommandListeners()), settings.getApplicationName(), mongoDriverInformation, settings.getCompressorList(), settings.getServerApi(), settings.getDnsClient()); } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ClientSessionPublisherImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ClientSessionPublisherImpl.java index 511f9f62c6b..9a25283f740 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ClientSessionPublisherImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ClientSessionPublisherImpl.java @@ -41,6 +41,7 @@ import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL; import static com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL; +import static com.mongodb.assertions.Assertions.assertFalse; import static com.mongodb.assertions.Assertions.assertNotNull; import static com.mongodb.assertions.Assertions.assertTrue; import static com.mongodb.assertions.Assertions.isTrue; @@ -48,9 +49,10 @@ final class ClientSessionPublisherImpl extends BaseClientSessionImpl implements ClientSession { - private final MongoClientImpl mongoClient; private final OperationExecutor executor; private final TracingManager tracingManager; + @Nullable + private final Integer maxAdaptiveRetriesSetting; private TransactionState transactionState = TransactionState.NONE; private boolean messageSentInCurrentTransaction; private boolean commitInProgress; @@ -63,8 +65,8 @@ final class ClientSessionPublisherImpl extends BaseClientSessionImpl implements final ClientSessionOptions options, final OperationExecutor executor, final TracingManager tracingManager) { super(serverSessionPool, mongoClient, options); this.executor = executor; - this.mongoClient = mongoClient; this.tracingManager = tracingManager; + maxAdaptiveRetriesSetting = mongoClient.getSettings().getMaxAdaptiveRetries(); } @Override @@ -77,7 +79,10 @@ public boolean notifyMessageSent() { if (hasActiveTransaction()) { boolean firstMessageInCurrentTransaction = !messageSentInCurrentTransaction; messageSentInCurrentTransaction = true; - return firstMessageInCurrentTransaction; + OverloadRetryPolicyState.CommandExecutionScoped overloadRetryPolicyState = getOverloadRetryPolicyState().getCommandExecutionScoped(); + return overloadRetryPolicyState == null + ? firstMessageInCurrentTransaction + : overloadRetryPolicyState.notifyMessageSent(firstMessageInCurrentTransaction); } else { if (transactionState == TransactionState.COMMITTED || transactionState == TransactionState.ABORTED) { cleanupTransaction(TransactionState.NONE); @@ -161,7 +166,8 @@ public Publisher commitTransaction() { return Mono.error(new IllegalStateException("There is no transaction started")); } if (!messageSentInCurrentTransaction) { - cleanupTransaction(TransactionState.COMMITTED); + transactionState = TransactionState.COMMITTED; + commitInProgress = false; if (transactionSpan != null) { transactionSpan.finalizeTransactionSpan(TransactionState.COMMITTED.name()); } @@ -172,17 +178,20 @@ public Publisher commitTransaction() { return Mono.error(new MongoInternalException("Invariant violated. Transaction options read concern can not be null")); } boolean alreadyCommitted = commitInProgress || transactionState == TransactionState.COMMITTED; + if (!alreadyCommitted) { + getOverloadRetryPolicyState().openCommitScope(); + } commitInProgress = true; resetTimeout(); TimeoutContext timeoutContext = getTimeoutContext(); WriteConcern writeConcern = assertNotNull(getWriteConcern(timeoutContext)); return executor .execute( - new CommitTransactionOperation(writeConcern, alreadyCommitted) + new CommitTransactionOperation(writeConcern, maxAdaptiveRetriesSetting, alreadyCommitted) .recoveryToken(getRecoveryToken()), readConcern, this) .doOnTerminate(() -> { - commitInProgress = false; transactionState = TransactionState.COMMITTED; + commitInProgress = false; }) .doOnError(MongoException.class, e -> { clearTransactionContextOnError(e); @@ -228,7 +237,7 @@ public Publisher abortTransaction() { TimeoutContext timeoutContext = getTimeoutContext(); WriteConcern writeConcern = assertNotNull(getWriteConcern(timeoutContext)); return executor - .execute(new AbortTransactionOperation(writeConcern) + .execute(new AbortTransactionOperation(writeConcern, maxAdaptiveRetriesSetting) .recoveryToken(getRecoveryToken()), readConcern, this) .onErrorResume(Throwable.class, (e) -> Mono.empty()) .doOnTerminate(() -> { @@ -266,7 +275,9 @@ public void close() { private void cleanupTransaction(final TransactionState nextState) { messageSentInCurrentTransaction = false; transactionOptions = null; + assertFalse(nextState == TransactionState.COMMITTED); transactionState = nextState; + getOverloadRetryPolicyState().closeCommitScope(); setTimeoutContext(null); } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java index 3796e8f3a44..6b6eb7a6a03 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java @@ -29,12 +29,15 @@ import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.connection.ClusterDescription; import com.mongodb.internal.TimeoutSettings; +import com.mongodb.internal.VisibleForTesting; import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; +import com.mongodb.internal.connection.StreamFactoryFactory; import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; import com.mongodb.internal.observability.micrometer.TracingManager; import com.mongodb.internal.session.ServerSessionPool; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.lang.Nullable; import com.mongodb.reactivestreams.client.ChangeStreamPublisher; import com.mongodb.reactivestreams.client.ClientSession; @@ -56,6 +59,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import static com.mongodb.assertions.Assertions.notNull; +import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; import static java.lang.String.format; import static org.bson.codecs.configuration.CodecRegistries.withUuidRepresentation; @@ -69,29 +73,35 @@ public final class MongoClientImpl implements MongoClient { private static final Logger LOGGER = Loggers.getLogger("client"); private final MongoClientSettings settings; - private final AutoCloseable externalResourceCloser; + private final StreamFactoryFactory streamFactoryFactory; + private final AsyncClientExecutor clientExecutor; private final MongoClusterImpl delegate; private final AtomicBoolean closed; - public MongoClientImpl(final MongoClientSettings settings, final MongoDriverInformation mongoDriverInformation, final Cluster cluster, - @Nullable final AutoCloseable externalResourceCloser) { - this(settings, mongoDriverInformation, cluster, null, externalResourceCloser); - } - - public MongoClientImpl(final MongoClientSettings settings, final MongoDriverInformation mongoDriverInformation, final Cluster cluster, + public MongoClientImpl( + final Cluster cluster, + final MongoDriverInformation mongoDriverInformation, + final MongoClientSettings settings, + final StreamFactoryFactory streamFactoryFactory, + final AsyncClientExecutor clientExecutor) { + this(cluster, mongoDriverInformation, settings, streamFactoryFactory, clientExecutor, null); + } + + @VisibleForTesting(otherwise = PRIVATE) + MongoClientImpl( + final Cluster cluster, + final MongoDriverInformation mongoDriverInformation, + final MongoClientSettings settings, + final StreamFactoryFactory streamFactoryFactory, + final AsyncClientExecutor clientExecutor, @Nullable final OperationExecutor executor) { - this(settings, mongoDriverInformation, cluster, executor, null); - } - - private MongoClientImpl(final MongoClientSettings settings, final MongoDriverInformation mongoDriverInformation, final Cluster cluster, - @Nullable final OperationExecutor executor, @Nullable final AutoCloseable externalResourceCloser) { notNull("settings", settings); notNull("cluster", cluster); TracingManager tracingManager = new TracingManager(settings.getObservabilitySettings()); TimeoutSettings timeoutSettings = TimeoutSettings.create(settings); - ServerSessionPool serverSessionPool = new ServerSessionPool(cluster, timeoutSettings, settings.getServerApi()); + ServerSessionPool serverSessionPool = new ServerSessionPool(cluster, clientExecutor, timeoutSettings, settings.getServerApi()); ClientSessionHelper clientSessionHelper = new ClientSessionHelper(this, serverSessionPool, tracingManager); AutoEncryptionSettings autoEncryptSettings = settings.getAutoEncryptionSettings(); @@ -109,7 +119,7 @@ private MongoClientImpl(final MongoClientSettings settings, final MongoDriverInf settings.getUuidRepresentation()), settings.getReadPreference(), settings.getReadConcern(), settings.getWriteConcern(), - settings.getRetryWrites(), settings.getRetryReads(), + settings.getRetryWrites(), settings.getRetryReads(), settings.getMaxAdaptiveRetries(), settings.getUuidRepresentation(), settings.getAutoEncryptionSettings(), timeoutSettings, @@ -117,7 +127,8 @@ private MongoClientImpl(final MongoClientSettings settings, final MongoDriverInf this.delegate = new MongoClusterImpl(cluster, crypt, operationExecutor, serverSessionPool, clientSessionHelper, mongoOperationPublisher); - this.externalResourceCloser = externalResourceCloser; + this.streamFactoryFactory = streamFactoryFactory; + this.clientExecutor = clientExecutor; this.settings = settings; this.closed = new AtomicBoolean(); @@ -155,12 +166,13 @@ public void close() { } getServerSessionPool().close(); getCluster().close(); - if (externalResourceCloser != null) { - try { - externalResourceCloser.close(); - } catch (Exception e) { - LOGGER.warn("Exception closing resource", e); - } + //noinspection EmptyTryBlock + try (AutoCloseable autoClosedStreamFactoryFactory = streamFactoryFactory; + AutoCloseable autoClosedClientExecutor = clientExecutor) { + // `clientExecutor`, `streamFactoryFactory` must be the last resources closed, + // with `streamFactoryFactory` being the very last. + } catch (Exception e) { + LOGGER.warn("Exception closing resource", e); } } } @@ -337,4 +349,8 @@ public void appendMetadata(final MongoDriverInformation mongoDriverInformation) clientMetadata.append(mongoDriverInformation); LOGGER.info(format("MongoClient metadata has been updated to %s", clientMetadata.getBsonDocument())); } + + public AsyncClientExecutor getClientExecutor() { + return clientExecutor; + } } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java index 84c810f1b5e..582453be5ea 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java @@ -95,6 +95,8 @@ public final class MongoOperationPublisher { private final Operations operations; + @Nullable + private final Integer maxAdaptiveRetriesSetting; private final UuidRepresentation uuidRepresentation; @Nullable private final AutoEncryptionSettings autoEncryptionSettings; @@ -102,24 +104,27 @@ public final class MongoOperationPublisher { MongoOperationPublisher( final Class documentClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, - final ReadConcern readConcern, final WriteConcern writeConcern, final boolean retryWrites, final boolean retryReads, + final ReadConcern readConcern, final WriteConcern writeConcern, + final boolean retryWrites, final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, final UuidRepresentation uuidRepresentation, @Nullable final AutoEncryptionSettings autoEncryptionSettings, final TimeoutSettings timeoutSettings, final OperationExecutor executor) { this(new MongoNamespace("_ignored", "_ignored"), documentClass, - codecRegistry, readPreference, readConcern, writeConcern, retryWrites, retryReads, + codecRegistry, readPreference, readConcern, writeConcern, retryWrites, retryReads, maxAdaptiveRetriesSetting, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } MongoOperationPublisher( final MongoNamespace namespace, final Class documentClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, final ReadConcern readConcern, final WriteConcern writeConcern, - final boolean retryWrites, final boolean retryReads, final UuidRepresentation uuidRepresentation, + final boolean retryWrites, final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, + final UuidRepresentation uuidRepresentation, @Nullable final AutoEncryptionSettings autoEncryptionSettings, final TimeoutSettings timeoutSettings, final OperationExecutor executor) { this.operations = new Operations<>(namespace, notNull("documentClass", documentClass), notNull("readPreference", readPreference), notNull("codecRegistry", codecRegistry), notNull("readConcern", readConcern), notNull("writeConcern", writeConcern), - retryWrites, retryReads, timeoutSettings); + retryWrites, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); + this.maxAdaptiveRetriesSetting = maxAdaptiveRetriesSetting; this.uuidRepresentation = notNull("uuidRepresentation", uuidRepresentation); this.autoEncryptionSettings = autoEncryptionSettings; this.executor = notNull("executor", executor); @@ -194,14 +199,14 @@ MongoOperationPublisher withNamespaceAndDocumentClass(final MongoNamespac } return new MongoOperationPublisher<>(notNull("namespace", namespace), notNull("documentClass", documentClass), getCodecRegistry(), getReadPreference(), getReadConcern(), getWriteConcern(), getRetryWrites(), getRetryReads(), - uuidRepresentation, autoEncryptionSettings, getTimeoutSettings(), executor); + maxAdaptiveRetriesSetting, uuidRepresentation, autoEncryptionSettings, getTimeoutSettings(), executor); } MongoOperationPublisher withCodecRegistry(final CodecRegistry codecRegistry) { return new MongoOperationPublisher<>(getNamespace(), getDocumentClass(), withUuidRepresentation(notNull("codecRegistry", codecRegistry), uuidRepresentation), getReadPreference(), getReadConcern(), getWriteConcern(), getRetryWrites(), getRetryReads(), - uuidRepresentation, autoEncryptionSettings, getTimeoutSettings(), executor); + maxAdaptiveRetriesSetting, uuidRepresentation, autoEncryptionSettings, getTimeoutSettings(), executor); } MongoOperationPublisher withReadPreference(final ReadPreference readPreference) { @@ -210,7 +215,7 @@ MongoOperationPublisher withReadPreference(final ReadPreference readPreferenc } return new MongoOperationPublisher<>(getNamespace(), getDocumentClass(), getCodecRegistry(), notNull("readPreference", readPreference), getReadConcern(), getWriteConcern(), getRetryWrites(), getRetryReads(), - uuidRepresentation, autoEncryptionSettings, getTimeoutSettings(), executor); + maxAdaptiveRetriesSetting, uuidRepresentation, autoEncryptionSettings, getTimeoutSettings(), executor); } MongoOperationPublisher withWriteConcern(final WriteConcern writeConcern) { @@ -218,8 +223,8 @@ MongoOperationPublisher withWriteConcern(final WriteConcern writeConcern) { return this; } return new MongoOperationPublisher<>(getNamespace(), getDocumentClass(), getCodecRegistry(), getReadPreference(), getReadConcern(), - notNull("writeConcern", writeConcern), getRetryWrites(), getRetryReads(), uuidRepresentation, autoEncryptionSettings, - getTimeoutSettings(), executor); + notNull("writeConcern", writeConcern), getRetryWrites(), getRetryReads(), + maxAdaptiveRetriesSetting, uuidRepresentation, autoEncryptionSettings, getTimeoutSettings(), executor); } MongoOperationPublisher withReadConcern(final ReadConcern readConcern) { @@ -228,7 +233,7 @@ MongoOperationPublisher withReadConcern(final ReadConcern readConcern) { } return new MongoOperationPublisher<>(getNamespace(), getDocumentClass(), getCodecRegistry(), getReadPreference(), notNull("readConcern", readConcern), - getWriteConcern(), getRetryWrites(), getRetryReads(), uuidRepresentation, + getWriteConcern(), getRetryWrites(), getRetryReads(), maxAdaptiveRetriesSetting, uuidRepresentation, autoEncryptionSettings, getTimeoutSettings(), executor); } @@ -239,7 +244,7 @@ MongoOperationPublisher withTimeout(final long timeout, final TimeUnit timeUn } return new MongoOperationPublisher<>(getNamespace(), getDocumentClass(), getCodecRegistry(), getReadPreference(), getReadConcern(), - getWriteConcern(), getRetryWrites(), getRetryReads(), uuidRepresentation, + getWriteConcern(), getRetryWrites(), getRetryReads(), maxAdaptiveRetriesSetting, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } 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 62a4431cc9a..fa1956c69a1 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 @@ -243,9 +243,11 @@ private OperationContext getOperationContext(final RequestContext requestContext requestContext, new ReadConcernAwareNoOpSessionContext(readConcern), createTimeoutContext(session, timeoutSettings), + mongoClient.getClientExecutor(), tracingManager, 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/main/com/mongodb/reactivestreams/client/internal/TimeoutHelper.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/TimeoutHelper.java index cefdf7184d8..cd538202a2b 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/TimeoutHelper.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/TimeoutHelper.java @@ -24,14 +24,13 @@ import com.mongodb.reactivestreams.client.MongoDatabase; import reactor.core.publisher.Mono; +import static com.mongodb.internal.TimeoutContext.DEFAULT_TIMEOUT_MESSAGE; import static java.util.concurrent.TimeUnit.MILLISECONDS; /** *

      This class is not part of the public API and may be removed or changed at any time

      */ public final class TimeoutHelper { - private static final String DEFAULT_TIMEOUT_MESSAGE = "Operation exceeded the timeout limit."; - private TimeoutHelper() { //NOP } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java index 67ebf421c9c..6dda49da685 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java @@ -36,6 +36,7 @@ import com.mongodb.internal.crypt.capi.MongoKeyDecryptor; import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; import org.bson.ByteBuf; @@ -182,7 +183,7 @@ private OperationContext createOperationContext(@Nullable final Timeout operatio throw new MongoOperationTimeoutException(TIMEOUT_ERROR_MESSAGE); }); } - return OperationContext.simpleOperationContext(new TimeoutContext(timeoutSettings)); + return OperationContext.simpleOperationContext(timeoutSettings, null, AsyncClientExecutor.NO_OP); } @NonNull diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/BackpressureProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/BackpressureProseTest.java new file mode 100644 index 00000000000..242c9ea5b2c --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/BackpressureProseTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.reactivestreams.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoServerException; +import com.mongodb.client.FailPoint; +import com.mongodb.event.CommandEvent; +import com.mongodb.event.CommandFailedEvent; +import com.mongodb.event.CommandStartedEvent; +import com.mongodb.event.CommandSucceededEvent; +import com.mongodb.internal.connection.TestCommandListener; +import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; +import org.bson.BsonDocument; +import org.bson.Document; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.concurrent.TimeoutException; + +import static com.mongodb.ClusterFixture.TIMEOUT_DURATION; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.MongoException.RETRYABLE_ERROR_LABEL; +import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL; +import static com.mongodb.client.Fixture.getDefaultDatabaseName; +import static com.mongodb.client.Fixture.getMongoClientSettings; +import static com.mongodb.client.Fixture.getPrimary; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * + * Prose Tests. + */ +final class BackpressureProseTest extends com.mongodb.client.BackpressureProseTest { + @Override + protected com.mongodb.client.MongoClient createClient(final MongoClientSettings mongoClientSettings) { + return new SyncMongoClient(mongoClientSettings); + } + + /** + * Reactive counterpart of {@code getMore-retried-backpressure.yml} scenario + * "getMores are retried maxAttempts=2 times". Skipped by the unified runner + * because {@code BatchCursorFlux} signals {@code sink.error(e)} without awaiting + * the {@code killCursors} reply, so the runner may snapshot events before + * {@code killCursors} succeeds. Here we wait for that command to complete, then + * assert the full command sequence. + */ + @Test + void getMoreExhaustsOverloadRetriesAndCursorIsKilled() throws TimeoutException, InterruptedException { + assumeTrue(serverVersionAtLeast(8, 0)); + + //given + BsonDocument overloadOnGetMoreAlways = BsonDocument.parse( + "{" + + " configureFailPoint: 'failCommand'," + + " mode: 'alwaysOn'," + + " data: {" + + " failCommands: ['getMore']," + + " errorCode: 2," + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "']" + + " }" + + "}"); + TestCommandListener commandListener = new TestCommandListener(); + try (MongoClient client = MongoClients.create(MongoClientSettings.builder(getMongoClientSettings()) + .retryReads(true) + .addCommandListener(commandListener) + .build())) { + + MongoCollection coll = client.getDatabase(getDefaultDatabaseName()).getCollection("test"); + Mono.from(coll.insertMany(asList(new Document(), new Document(), new Document()))).block(TIMEOUT_DURATION); + commandListener.reset(); + + //when + try (FailPoint ignored = FailPoint.enable(overloadOnGetMoreAlways, getPrimary())) { + assertThrows(MongoServerException.class, + () -> Flux.from(coll.find().batchSize(2)).blockLast(TIMEOUT_DURATION)); + } + commandListener.waitForEvents(CommandSucceededEvent.class, + e -> "killCursors".equals(e.getCommandName()), 1); + + //then + List events = commandListener.getEvents(); + assertEquals(10, events.size()); + assertStarted(events.get(0), "find"); + assertSucceeded(events.get(1), "find"); + assertStarted(events.get(2), "getMore"); + assertFailed(events.get(3), "getMore"); + assertStarted(events.get(4), "getMore"); + assertFailed(events.get(5), "getMore"); + assertStarted(events.get(6), "getMore"); + assertFailed(events.get(7), "getMore"); + assertStarted(events.get(8), "killCursors"); + assertSucceeded(events.get(9), "killCursors"); + } + } + + private static void assertStarted(final CommandEvent event, final String commandName) { + assertInstanceOf(CommandStartedEvent.class, event); + assertEquals(commandName, event.getCommandName()); + } + + private static void assertSucceeded(final CommandEvent event, final String commandName) { + assertInstanceOf(CommandSucceededEvent.class, event); + assertEquals(commandName, event.getCommandName()); + } + + + private static void assertFailed(final CommandEvent event, final String commandName) { + assertInstanceOf(CommandFailedEvent.class, event); + assertEquals(commandName, event.getCommandName()); + } +} diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ContextProviderTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ContextProviderTest.java index 90529171219..80913aab843 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ContextProviderTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ContextProviderTest.java @@ -19,6 +19,7 @@ import com.mongodb.ContextProvider; import com.mongodb.RequestContext; import com.mongodb.WriteConcern; +import com.mongodb.annotations.NotThreadSafe; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandListener; import com.mongodb.event.CommandStartedEvent; @@ -186,6 +187,7 @@ public void contextShouldBeAvailableInCommandEvents() { } } + @NotThreadSafe private static final class TestCommandListener implements CommandListener { private int numCommandStartedEventsWithExpectedContext; private int numCommandSucceededEventsWithExpectedContext; diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/MongoWriteConcernWithResponseExceptionTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/MongoWriteConcernWithResponseExceptionTest.java index 09aafc02d46..847a8233115 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/MongoWriteConcernWithResponseExceptionTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/MongoWriteConcernWithResponseExceptionTest.java @@ -16,15 +16,13 @@ package com.mongodb.reactivestreams.client; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; -import org.junit.Test; -/** - * See {@link com.mongodb.client.MongoWriteConcernWithResponseExceptionTest}. - */ -public class MongoWriteConcernWithResponseExceptionTest { - @Test - public void doesNotLeak() throws InterruptedException { - com.mongodb.client.MongoWriteConcernWithResponseExceptionTest.doesNotLeak(SyncMongoClient::new); +final class MongoWriteConcernWithResponseExceptionTest extends com.mongodb.client.MongoWriteConcernWithResponseExceptionTest { + @Override + protected MongoClient createClient(final MongoClientSettings clientSettings) { + return new SyncMongoClient(clientSettings); } } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java index 22b7f7645e1..bb748f00601 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableReadsProseTest.java @@ -16,63 +16,14 @@ package com.mongodb.reactivestreams.client; -import com.mongodb.client.MongoCursor; -import com.mongodb.client.RetryableWritesProseTest; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.AbstractRetryableReadsProseTest; +import com.mongodb.client.MongoClient; import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; -import org.bson.Document; -import org.junit.jupiter.api.Test; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -import static com.mongodb.client.model.Filters.eq; - -/** - * See - * Retryable Reads Tests. - */ -final class RetryableReadsProseTest { - /** - * See - * - * PoolClearedError Retryability Test. - */ - @Test - void poolClearedExceptionMustBeRetryable() throws InterruptedException, ExecutionException, TimeoutException { - RetryableWritesProseTest.poolClearedExceptionMustBeRetryable( - SyncMongoClient::new, - mongoCollection -> mongoCollection.find(eq(0)).iterator().hasNext(), "find", false); - } - - /** - * See - * - * Retryable Reads Are Retried on a Different mongos When One is Available. - */ - @Test - void retriesOnDifferentMongosWhenAvailable() { - RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable( - SyncMongoClient::new, - mongoCollection -> { - try (MongoCursor cursor = mongoCollection.find().iterator()) { - return cursor.hasNext(); - } - }, "find", false); - } - - /** - * See - * - * Retryable Reads Are Retried on the Same mongos When No Others are Available. - */ - @Test - void retriesOnSameMongosWhenAnotherNotAvailable() { - RetryableWritesProseTest.retriesOnSameMongosWhenAnotherNotAvailable( - SyncMongoClient::new, - mongoCollection -> { - try (MongoCursor cursor = mongoCollection.find().iterator()) { - return cursor.hasNext(); - } - }, "find", false); +final class RetryableReadsProseTest extends AbstractRetryableReadsProseTest { + @Override + protected MongoClient createClient(final MongoClientSettings settings) { + return new SyncMongoClient(settings); } } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableWritesProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/RetryableWritesProseTest.java index 51a37ad1e35..3b1e0e9f287 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 @@ -16,68 +16,87 @@ package com.mongodb.reactivestreams.client; -import com.mongodb.client.test.CollectionHelper; import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; import org.bson.Document; -import org.bson.codecs.DocumentCodec; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; /** - * See - * Retryable Write Prose Tests. + * + * Prose Tests. */ -public class RetryableWritesProseTest extends DatabaseTestCase { - private CollectionHelper collectionHelper; - - @BeforeEach - @Override - public void setUp() { - super.setUp(); - - collectionHelper = new CollectionHelper<>(new DocumentCodec(), collection.getNamespace()); - collectionHelper.create(); - } - +final class RetryableWritesProseTest { /** - * Prose test #2. + * + * 2. Test that drivers properly retry after encountering PoolClearedErrors. */ @Test - public void poolClearedExceptionMustBeRetryable() throws InterruptedException, ExecutionException, TimeoutException { + void poolClearedExceptionMustBeRetryable() throws Exception { com.mongodb.client.RetryableWritesProseTest.poolClearedExceptionMustBeRetryable( SyncMongoClient::new, mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true); } /** - * Prose test #3. + * + * 3. Test that drivers return the original error after encountering a WriteConcernError with a RetryableWriteError label. */ @Test - public void originalErrorMustBePropagatedIfNoWritesPerformed() throws InterruptedException { + void originalErrorMustBePropagatedIfNoWritesPerformed() throws Exception { com.mongodb.client.RetryableWritesProseTest.originalErrorMustBePropagatedIfNoWritesPerformed( SyncMongoClient::new); } /** - * Prose test #4. + * + * 4. Test that in a sharded cluster writes are retried on a different mongos when one is available. */ @Test - public void retriesOnDifferentMongosWhenAvailable() { + void retriesOnDifferentMongosWhenAvailable() throws InterruptedException, TimeoutException { com.mongodb.client.RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable( SyncMongoClient::new, mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true); } /** - * Prose test #5. + * + * 5. Test that in a sharded cluster writes are retried on the same mongos when no others are available. */ @Test - public void retriesOnSameMongosWhenAnotherNotAvailable() { + void retriesOnSameMongosWhenAnotherNotAvailable() { com.mongodb.client.RetryableWritesProseTest.retriesOnSameMongosWhenAnotherNotAvailable( SyncMongoClient::new, mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true); } + + /** + * + * 6. Test error propagation after encountering multiple errors. + * Case 1: Test that drivers return the correct error when receiving only errors without NoWritesPerformed. + */ + @Test + void errorPropagationAfterEncounteringMultipleErrorsCase1() throws Exception { + com.mongodb.client.RetryableWritesProseTest.errorPropagationAfterEncounteringMultipleErrorsCase1(SyncMongoClient::new); + } + + /** + * + * 6. Test error propagation after encountering multiple errors. + * Case 2: Test that drivers return the correct error when receiving only errors with NoWritesPerformed. + */ + @Test + void errorPropagationAfterEncounteringMultipleErrorsCase2() throws Exception { + com.mongodb.client.RetryableWritesProseTest.errorPropagationAfterEncounteringMultipleErrorsCase2(SyncMongoClient::new); + } + + /** + * + * 6. Test error propagation after encountering multiple errors. + * Case 3: Test that drivers return the correct error when receiving some errors with NoWritesPerformed and some without NoWritesPerformed. + */ + @Test + void errorPropagationAfterEncounteringMultipleErrorsCase3() throws Exception { + com.mongodb.client.RetryableWritesProseTest.errorPropagationAfterEncounteringMultipleErrorsCase3(SyncMongoClient::new); + } } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncClientSession.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncClientSession.java index 473d57a3878..339c931c291 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncClientSession.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncClientSession.java @@ -189,6 +189,11 @@ public TimeoutContext getTimeoutContext() { return wrapped.getTimeoutContext(); } + @Override + public Object getOverloadRetryPolicyState() { + return wrapped.getOverloadRetryPolicyState(); + } + @Override @Nullable public TransactionSpan getTransactionSpan() { diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionClock.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedClientBackpressureTest.java similarity index 50% rename from driver-sync/src/main/com/mongodb/client/internal/ClientSessionClock.java rename to driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedClientBackpressureTest.java index a5ba63e3cd6..c8b398c0858 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionClock.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedClientBackpressureTest.java @@ -14,28 +14,14 @@ * limitations under the License. */ -package com.mongodb.client.internal; +package com.mongodb.reactivestreams.client.unified; -/** - *

      This class is not part of the public API and may be removed or changed at any time

      - */ -public final class ClientSessionClock { - public static final ClientSessionClock INSTANCE = new ClientSessionClock(0L); - - private long currentTime; +import org.junit.jupiter.params.provider.Arguments; - private ClientSessionClock(final long millis) { - currentTime = millis; - } - - public long now() { - if (currentTime == 0L) { - return System.currentTimeMillis(); - } - return currentTime; - } +import java.util.Collection; - public void setTime(final long millis) { - currentTime = millis; +final class UnifiedClientBackpressureTest extends UnifiedReactiveStreamsTest { + private static Collection data() { + return getTestData("client-backpressure"); } } diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/AggregatePublisherImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/AggregatePublisherImplTest.java index cfbf5a0a5b8..f065d83fe77 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/AggregatePublisherImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/AggregatePublisherImplTest.java @@ -59,7 +59,8 @@ void shouldBuildTheExpectedOperation() { new AggregatePublisherImpl<>(null, createMongoOperationPublisher(executor), pipeline, AggregationLevel.COLLECTION); AggregateOperation expectedOperation = new AggregateOperation<>(NAMESPACE, pipeline, - getDefaultCodecRegistry().get(Document.class)) + getDefaultCodecRegistry().get(Document.class), + null) .batchSize(Integer.MAX_VALUE) .retryReads(true); @@ -80,8 +81,10 @@ void shouldBuildTheExpectedOperation() { .maxAwaitTime(1001, MILLISECONDS) .maxTime(101, MILLISECONDS); - expectedOperation = new AggregateOperation<>(NAMESPACE, pipeline, - getDefaultCodecRegistry().get(Document.class)) + expectedOperation = new AggregateOperation<>( + NAMESPACE, pipeline, + getDefaultCodecRegistry().get(Document.class), + null) .retryReads(true) .allowDiskUse(true) .batchSize(100) @@ -103,8 +106,10 @@ void shouldBuildTheExpectedOperationForHintString() { AggregatePublisher publisher = new AggregatePublisherImpl<>(null, createMongoOperationPublisher(executor), pipeline, AggregationLevel.COLLECTION); - AggregateOperation expectedOperation = new AggregateOperation<>(NAMESPACE, pipeline, - getDefaultCodecRegistry().get(Document.class)) + AggregateOperation expectedOperation = new AggregateOperation<>( + NAMESPACE, pipeline, + getDefaultCodecRegistry().get(Document.class), + null) .batchSize(Integer.MAX_VALUE) .retryReads(true); @@ -127,8 +132,10 @@ void shouldBuildTheExpectedOperationForHintPlusHintString() { AggregatePublisher publisher = new AggregatePublisherImpl<>(null, createMongoOperationPublisher(executor), pipeline, AggregationLevel.COLLECTION); - AggregateOperation expectedOperation = new AggregateOperation<>(NAMESPACE, pipeline, - getDefaultCodecRegistry().get(Document.class)) + AggregateOperation expectedOperation = new AggregateOperation<>( + NAMESPACE, pipeline, + getDefaultCodecRegistry().get(Document.class), + null) .batchSize(Integer.MAX_VALUE) .retryReads(true); @@ -190,7 +197,7 @@ void shouldBuildTheExpectedOperationsForDollarOut() { assertOperationIsTheSameAs(expectedOperation, operation.getReadOperation()); FindOperation expectedFindOperation = - new FindOperation<>(collectionNamespace, getDefaultCodecRegistry().get(Document.class)) + new FindOperation<>(collectionNamespace, getDefaultCodecRegistry().get(Document.class), null) .batchSize(100) .collation(COLLATION) .filter(new BsonDocument()) @@ -368,7 +375,7 @@ void shouldBuildTheExpectedOperationsForDollarMergeDocument() { assertOperationIsTheSameAs(expectedOperation, operation.getReadOperation()); FindOperation expectedFindOperation = - new FindOperation<>(collectionNamespace, getDefaultCodecRegistry().get(Document.class)) + new FindOperation<>(collectionNamespace, getDefaultCodecRegistry().get(Document.class), null) .batchSize(100) .collation(COLLATION) .filter(new BsonDocument()) @@ -422,7 +429,7 @@ void shouldBuildTheExpectedOperationsForDollarMergeString() { assertOperationIsTheSameAs(expectedOperation, operation.getReadOperation()); FindOperation expectedFindOperation = - new FindOperation<>(collectionNamespace, getDefaultCodecRegistry().get(Document.class)) + new FindOperation<>(collectionNamespace, getDefaultCodecRegistry().get(Document.class), null) .filter(new BsonDocument()) .batchSize(Integer.MAX_VALUE) .retryReads(true); diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ClientSessionBindingSpecification.groovy b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ClientSessionBindingSpecification.groovy index cfe66a8031f..0fcbb5ac31a 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ClientSessionBindingSpecification.groovy +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ClientSessionBindingSpecification.groovy @@ -16,6 +16,7 @@ package com.mongodb.reactivestreams.client.internal +import com.mongodb.ClusterFixture import com.mongodb.ReadPreference import com.mongodb.ServerAddress import com.mongodb.async.FutureResultCallback @@ -31,32 +32,34 @@ import com.mongodb.internal.connection.ServerTuple import com.mongodb.reactivestreams.client.ClientSession import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT +import static com.mongodb.ClusterFixture.createOperationContext class ClientSessionBindingSpecification extends Specification { def 'should return the session context from the connection source'() { given: def session = Stub(ClientSession) + def operationContext = ClusterFixture.createOperationContext() def wrappedBinding = Mock(AsyncClusterAwareReadWriteBinding); wrappedBinding.retain() >> wrappedBinding def binding = new ClientSessionBinding(session, false, wrappedBinding) when: def futureResultCallback = new FutureResultCallback() - binding.getReadConnectionSource(OPERATION_CONTEXT, futureResultCallback) + + binding.getReadConnectionSource(operationContext, futureResultCallback) then: - 1 * wrappedBinding.getReadConnectionSource(OPERATION_CONTEXT, _) >> { + 1 * wrappedBinding.getReadConnectionSource(operationContext, _) >> { it[1].onResult(Stub(AsyncConnectionSource), null) } when: futureResultCallback = new FutureResultCallback() - binding.getWriteConnectionSource(OPERATION_CONTEXT, futureResultCallback) + binding.getWriteConnectionSource(operationContext, futureResultCallback) then: - 1 * wrappedBinding.getWriteConnectionSource(OPERATION_CONTEXT, _) >> { + 1 * wrappedBinding.getWriteConnectionSource(operationContext, _) >> { it[1].onResult(Stub(AsyncConnectionSource), null) } } @@ -87,10 +90,10 @@ class ClientSessionBindingSpecification extends Specification { def wrappedBinding = createStubBinding() def binding = new ClientSessionBinding(session, true, wrappedBinding) def futureResultCallback = new FutureResultCallback() - binding.getReadConnectionSource(OPERATION_CONTEXT, futureResultCallback) + binding.getReadConnectionSource(createOperationContext(), futureResultCallback) def readConnectionSource = futureResultCallback.get() futureResultCallback = new FutureResultCallback() - binding.getWriteConnectionSource(OPERATION_CONTEXT, futureResultCallback) + binding.getWriteConnectionSource(createOperationContext(), futureResultCallback) def writeConnectionSource = futureResultCallback.get() when: diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/DistinctPublisherImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/DistinctPublisherImplTest.java index f9de792574c..e69b6d05d61 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/DistinctPublisherImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/DistinctPublisherImplTest.java @@ -44,7 +44,7 @@ void shouldBuildTheExpectedOperation() { new DistinctPublisherImpl<>(null, createMongoOperationPublisher(executor), fieldName, new Document()); DistinctOperation expectedOperation = new DistinctOperation<>(NAMESPACE, fieldName, - getDefaultCodecRegistry().get(Document.class)) + getDefaultCodecRegistry().get(Document.class), null) .retryReads(true).filter(new BsonDocument()); // default input should be as expected diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/FindPublisherImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/FindPublisherImplTest.java index eab28373f2a..f4805e7608f 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/FindPublisherImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/FindPublisherImplTest.java @@ -50,7 +50,8 @@ void shouldBuildTheExpectedOperation() { FindPublisher publisher = new FindPublisherImpl<>(null, createMongoOperationPublisher(executor), new Document()); FindOperation expectedOperation = new FindOperation<>(NAMESPACE, - getDefaultCodecRegistry().get(Document.class)) + getDefaultCodecRegistry().get(Document.class), + null) .batchSize(Integer.MAX_VALUE) .retryReads(true) .filter(new BsonDocument()); @@ -84,7 +85,8 @@ void shouldBuildTheExpectedOperation() { .allowDiskUse(false); expectedOperation = new FindOperation<>(NAMESPACE, - getDefaultCodecRegistry().get(Document.class)) + getDefaultCodecRegistry().get(Document.class), + null) .retryReads(true) .filter(new BsonDocument()) .allowDiskUse(false) diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionNamesPublisherImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionNamesPublisherImplTest.java index 6613723b49d..a241fd2640b 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionNamesPublisherImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionNamesPublisherImplTest.java @@ -35,7 +35,6 @@ final class ListCollectionNamesPublisherImplTest extends TestHelper { private static final String DATABASE_NAME = NAMESPACE.getDatabaseName(); - @SuppressWarnings("deprecation") @DisplayName("Should build the expected ListCollectionsOperation") @Test void shouldBuildTheExpectedOperation() { @@ -46,7 +45,8 @@ void shouldBuildTheExpectedOperation() { .authorizedCollections(true); ListCollectionsOperation expectedOperation = new ListCollectionsOperation<>(DATABASE_NAME, - getDefaultCodecRegistry().get(Document.class)) + getDefaultCodecRegistry().get(Document.class), + null) .batchSize(Integer.MAX_VALUE) .nameOnly(true) .authorizedCollections(true) @@ -65,7 +65,8 @@ void shouldBuildTheExpectedOperation() { .batchSize(100); expectedOperation = new ListCollectionsOperation<>(DATABASE_NAME, - getDefaultCodecRegistry().get(Document.class)) + getDefaultCodecRegistry().get(Document.class), + null) .nameOnly(true) .authorizedCollections(true) .retryReads(true) diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImplTest.java index a632edbae82..381a688e058 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImplTest.java @@ -43,7 +43,8 @@ void shouldBuildTheExpectedOperation() { .withDocumentClass(String.class), true); ListCollectionsOperation expectedOperation = new ListCollectionsOperation<>(DATABASE_NAME, - getDefaultCodecRegistry().get(String.class)) + getDefaultCodecRegistry().get(String.class), + null) .batchSize(Integer.MAX_VALUE) .nameOnly(true).retryReads(true); @@ -60,7 +61,8 @@ void shouldBuildTheExpectedOperation() { .batchSize(100); expectedOperation = new ListCollectionsOperation<>(DATABASE_NAME, - getDefaultCodecRegistry().get(String.class)) + getDefaultCodecRegistry().get(String.class), + null) .nameOnly(true) .retryReads(true) .filter(new BsonDocument("filter", new BsonInt32(1))) diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListDatabasesPublisherImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListDatabasesPublisherImplTest.java index c19a56f14cc..d7ea7ef2fab 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListDatabasesPublisherImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListDatabasesPublisherImplTest.java @@ -42,7 +42,8 @@ void shouldBuildTheExpectedOperation() { ListDatabasesPublisher publisher = new ListDatabasesPublisherImpl<>(null, createMongoOperationPublisher(executor)); ListDatabasesOperation expectedOperation = new ListDatabasesOperation<>( - getDefaultCodecRegistry().get(Document.class)) + getDefaultCodecRegistry().get(Document.class), + null) .retryReads(true); // default input should be as expected @@ -59,7 +60,8 @@ void shouldBuildTheExpectedOperation() { .batchSize(100); expectedOperation = new ListDatabasesOperation<>( - getDefaultCodecRegistry().get(Document.class)) + getDefaultCodecRegistry().get(Document.class), + null) .retryReads(true) .authorizedDatabasesOnly(true) .filter(new BsonDocument("filter", new BsonInt32(1))); diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListIndexesPublisherImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListIndexesPublisherImplTest.java index 5ae221b8a02..8870a4551af 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListIndexesPublisherImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListIndexesPublisherImplTest.java @@ -43,7 +43,7 @@ void shouldBuildTheExpectedOperation() { ListIndexesPublisher publisher = new ListIndexesPublisherImpl<>(null, createMongoOperationPublisher(executor)); ListIndexesOperation expectedOperation = - new ListIndexesOperation<>(NAMESPACE, getDefaultCodecRegistry().get(Document.class)) + new ListIndexesOperation<>(NAMESPACE, getDefaultCodecRegistry().get(Document.class), null) .batchSize(Integer.MAX_VALUE) .retryReads(true); @@ -58,7 +58,7 @@ void shouldBuildTheExpectedOperation() { .maxTime(100, MILLISECONDS); expectedOperation = - new ListIndexesOperation<>(NAMESPACE, getDefaultCodecRegistry().get(Document.class)) + new ListIndexesOperation<>(NAMESPACE, getDefaultCodecRegistry().get(Document.class), null) .batchSize(100) .retryReads(true); diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java index 0fda131f4ff..fc9f24506aa 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java @@ -24,9 +24,11 @@ import com.mongodb.internal.client.model.changestream.ChangeStreamLevel; import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; +import com.mongodb.internal.connection.StreamFactoryFactory; import com.mongodb.internal.mockito.MongoMockito; import com.mongodb.internal.observability.micrometer.TracingManager; import com.mongodb.internal.session.ServerSessionPool; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.reactivestreams.client.ChangeStreamPublisher; import com.mongodb.reactivestreams.client.ClientSession; import com.mongodb.reactivestreams.client.ListDatabasesPublisher; @@ -202,13 +204,25 @@ void testStartSession() { }); } + @Test + void close() { + com.mongodb.client.MongoClientTest.assertClose((cluster, mongoDriverInformation, streamFactoryFactory, clientExecutor) -> + new MongoClientImpl( + cluster, + mongoDriverInformation, + MongoClientSettings.builder().build(), + streamFactoryFactory, + clientExecutor)); + } + private MongoClientImpl createMongoClient() { MongoDriverInformation mongoDriverInformation = MongoDriverInformation.builder().driverName("reactive-streams").build(); - Cluster mock = MongoMockito.mock(Cluster.class, cluster -> { - when(cluster.getClientMetadata()) + Cluster cluster = MongoMockito.mock(Cluster.class, mock -> { + when(mock.getClientMetadata()) .thenReturn(new ClientMetadata("test", mongoDriverInformation)); }); - return new MongoClientImpl(MongoClientSettings.builder().build(), - mongoDriverInformation, mock, OPERATION_EXECUTOR); + StreamFactoryFactory streamFactoryFactory = MongoMockito.mock(StreamFactoryFactory.class); + return new MongoClientImpl(cluster, mongoDriverInformation, MongoClientSettings.builder().build(), streamFactoryFactory, + AsyncClientExecutor.NO_OP, OPERATION_EXECUTOR); } } diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoOperationPublisherTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoOperationPublisherTest.java index 664cf1428d8..b0d1bdbc4ea 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoOperationPublisherTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoOperationPublisherTest.java @@ -54,7 +54,7 @@ public class MongoOperationPublisherTest { private static final MongoOperationPublisher DEFAULT_MOP = new MongoOperationPublisher<>( MONGO_NAMESPACE, Document.class, MongoClientSettings.getDefaultCodecRegistry(), ReadPreference.primary(), - ReadConcern.DEFAULT, WriteConcern.ACKNOWLEDGED, true, true, UuidRepresentation.STANDARD, + ReadConcern.DEFAULT, WriteConcern.ACKNOWLEDGED, true, true, null, UuidRepresentation.STANDARD, null, TIMEOUT_SETTINGS_WITH_TIMEOUT, OPERATION_EXECUTOR); @Test diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/TestHelper.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/TestHelper.java index 450536df2b8..08df6497039 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/TestHelper.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/TestHelper.java @@ -106,7 +106,7 @@ public class TestHelper { static MongoOperationPublisher createMongoOperationPublisher(final OperationExecutor executor) { return new MongoOperationPublisher<>(NAMESPACE, Document.class, getDefaultCodecRegistry(), ReadPreference.primary(), ReadConcern.DEFAULT, - WriteConcern.ACKNOWLEDGED, true, true, + WriteConcern.ACKNOWLEDGED, true, true, null, UuidRepresentation.STANDARD, null, TIMEOUT_SETTINGS, executor); } diff --git a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncClientSession.scala b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncClientSession.scala index 365df0ab324..78cb1958c36 100644 --- a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncClientSession.scala +++ b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncClientSession.scala @@ -98,5 +98,7 @@ case class SyncClientSession(wrapped: ClientSession, originator: Object) extends override def getTimeoutContext: TimeoutContext = wrapped.getTimeoutContext + override def getOverloadRetryPolicyState: Object = wrapped.getOverloadRetryPolicyState + override def getTransactionSpan: TransactionSpan = null } diff --git a/driver-scala/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala b/driver-scala/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala index 9718b01c1a8..b8824dc31f5 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala @@ -33,7 +33,7 @@ trait ClientSessionImplicits { /** * Commit a transaction in the context of this session. * - * A transaction can only be commmited if one has first been started. + * A transaction can only be committed if one has first been started. */ def commitTransaction(): SingleObservable[Unit] = clientSession.commitTransaction() diff --git a/driver-scala/src/main/scala/org/mongodb/scala/package.scala b/driver-scala/src/main/scala/org/mongodb/scala/package.scala index 1cdc2d0a564..f7797733dca 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/package.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/package.scala @@ -215,18 +215,48 @@ package object scala extends ClientSessionImplicits with ObservableImplicits wit /** * An error label indicating that the exception can be treated as a transient transaction error. + * See the documentation linked below for more information. * + * @see [[https://www.mongodb.com/docs/manual/core/transactions-in-applications/#std-label-transient-transaction-error TransientTransactionError]] * @since 2.4 */ val TRANSIENT_TRANSACTION_ERROR_LABEL: String = com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL /** * An error label indicating that the exception can be treated as an unknown transaction commit result. + * See the documentation linked below for more information. * + * @see [[https://www.mongodb.com/docs/manual/core/transactions-in-applications/#std-label-unknown-transaction-commit-result UnknownTransactionCommitResult]] * @since 2.4 */ val UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL: String = com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL + + /** + * Server is overloaded and shedding load. + * If an application retries explicitly, it should use exponential backoff because the server has indicated overload. + * This label on its own does not mean that the operation can be [[MongoException.RETRYABLE_ERROR_LABEL safely retried]]. + * + * @see [[https://www.mongodb.com/docs/atlas/overload-errors/ Overload errors]] + * @since 5.7 + * @note Requires MongoDB 8.3 or greater + */ + val SYSTEM_OVERLOADED_ERROR_LABEL: String = com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL + + /** + * The operation is safe to retry, that is, + * retry without rereading the relevant data or considering the semantics of the operation. + * + * For more information on how transactions affect retries, + * see the documentation of the + * [[MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL "TransientTransactionError"]], + * [[MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL "UnknownTransactionCommitResult"]] error labels. + * + * @see [[https://www.mongodb.com/docs/atlas/overload-errors/ Overload errors]] + * @since 5.7 + * @note Requires MongoDB 8.3 or greater + */ + val RETRYABLE_ERROR_LABEL: String = com.mongodb.MongoException.RETRYABLE_ERROR_LABEL } /** @@ -359,6 +389,15 @@ package object scala extends ClientSessionImplicits with ObservableImplicits wit */ type MongoSocketWriteException = com.mongodb.MongoSocketWriteException + /** + * This exception is thrown when an error occurs while connecting via a SOCKS5 proxy. This + * covers both failures connecting to the proxy itself and failures where the proxy is + * reachable but cannot connect to the target server on our behalf. + * + * @since 5.9 + */ + type MongoSocksProxyException = com.mongodb.MongoSocksProxyException + /** * An exception indicating that the driver has timed out waiting for either a server or a connection to become available. */ @@ -377,6 +416,14 @@ package object scala extends ClientSessionImplicits with ObservableImplicits wit */ type MongoOperationTimeoutException = com.mongodb.MongoOperationTimeoutException + /** + * An exception indicating that the convenient transactions API (`withTransaction`) exceeded its overall timeout + * while retrying the user-supplied callback or the commit loop. + * + * @since 5.7 + */ + type WithTransactionTimeoutException = com.mongodb.WithTransactionTimeoutException + /** * An exception indicating a failure to apply the write concern to the requested write operation * diff --git a/driver-sync/src/examples/documentation/TransactionExample.java b/driver-sync/src/examples/documentation/TransactionExample.java index 4f73122ee35..dea86b9ad4b 100644 --- a/driver-sync/src/examples/documentation/TransactionExample.java +++ b/driver-sync/src/examples/documentation/TransactionExample.java @@ -77,7 +77,8 @@ private void runTransactionWithRetry(final Runnable transactional) { System.out.println("Transaction aborted. Caught exception during transaction."); if (e.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)) { - System.out.println("TransientTransactionError, aborting transaction and retrying ..."); + System.out.printf("%s, aborting transaction and retrying ...%n", + MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL); } else { throw e; } @@ -94,7 +95,8 @@ private void commitWithRetry(final ClientSession clientSession) { } catch (MongoException e) { // can retry commit if (e.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) { - System.out.println("UnknownTransactionCommitResult, retrying commit operation ..."); + System.out.printf("%s, retrying commit operation ...%n", + MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL); } else { System.out.println("Exception during commit ..."); throw e; diff --git a/driver-sync/src/main/com/mongodb/client/ClientSession.java b/driver-sync/src/main/com/mongodb/client/ClientSession.java index 00ba5eba23c..bc0a1ce0e8a 100644 --- a/driver-sync/src/main/com/mongodb/client/ClientSession.java +++ b/driver-sync/src/main/com/mongodb/client/ClientSession.java @@ -16,8 +16,10 @@ package com.mongodb.client; +import com.mongodb.MongoException; import com.mongodb.ServerAddress; import com.mongodb.TransactionOptions; +import com.mongodb.annotations.Internal; import com.mongodb.internal.observability.micrometer.TransactionSpan; import com.mongodb.lang.Nullable; @@ -46,13 +48,14 @@ public interface ClientSession extends com.mongodb.session.ClientSession { boolean hasActiveTransaction(); /** - * Notify the client session that a message has been sent. + * Notify that a message is about to be sent. *

      * For internal use only *

      * - * @return true if this is the first message sent, false otherwise + * @return true Iff the message must bear {@code startTransaction: true}. */ + @Internal boolean notifyMessageSent(); /** @@ -63,6 +66,7 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * * @param operation the operation */ + @Internal void notifyOperationInitiated(Object operation); /** @@ -76,6 +80,7 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * Start a transaction in the context of this session with default transaction options. A transaction can not be started if there is * already an active transaction on this session. * + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL * @mongodb.server.release 4.0 */ void startTransaction(); @@ -86,13 +91,15 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * * @param transactionOptions the options to apply to the transaction * + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL * @mongodb.server.release 4.0 */ void startTransaction(TransactionOptions transactionOptions); /** - * Commit a transaction in the context of this session. A transaction can only be commmited if one has first been started. + * Commit a transaction in the context of this session. A transaction can only be committed if one has first been started. * + * @see MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL * @mongodb.server.release 4.0 */ void commitTransaction(); @@ -110,6 +117,8 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * @param the return type of the transaction body * @param transactionBody the body of the transaction * @return the return value of the transaction body + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL + * @see MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL * @mongodb.server.release 4.0 * @since 3.11 */ @@ -122,6 +131,8 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * @param transactionBody the body of the transaction * @param options the transaction options * @return the return value of the transaction body + * @see MongoException#TRANSIENT_TRANSACTION_ERROR_LABEL + * @see MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL * @mongodb.server.release 4.0 * @since 3.11 */ diff --git a/driver-sync/src/main/com/mongodb/client/MongoClients.java b/driver-sync/src/main/com/mongodb/client/MongoClients.java index e0e59ba5f78..ccdb30be46f 100644 --- a/driver-sync/src/main/com/mongodb/client/MongoClients.java +++ b/driver-sync/src/main/com/mongodb/client/MongoClients.java @@ -23,6 +23,7 @@ import com.mongodb.client.internal.MongoClientImpl; import com.mongodb.internal.connection.Cluster; import com.mongodb.internal.connection.StreamFactoryFactory; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.lang.Nullable; import static com.mongodb.assertions.Assertions.notNull; @@ -120,13 +121,14 @@ public static MongoClient create(final MongoClientSettings settings, @Nullable f StreamFactoryFactory syncStreamFactoryFactory = getSyncStreamFactoryFactory( settings.getTransportSettings(), getInetAddressResolver(settings)); - + AsyncClientExecutor clientExecutor = AsyncClientExecutor.NO_OP; Cluster cluster = Clusters.createCluster( settings, driverInfo, - syncStreamFactoryFactory); + syncStreamFactoryFactory, + clientExecutor); - return new MongoClientImpl(cluster, settings, driverInfo, syncStreamFactoryFactory); + return new MongoClientImpl(cluster, driverInfo, settings, syncStreamFactoryFactory, clientExecutor); } private MongoClients() { diff --git a/driver-sync/src/main/com/mongodb/client/internal/AggregateIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/AggregateIterableImpl.java index 49ed63cba32..841d2532312 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/AggregateIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/AggregateIterableImpl.java @@ -67,21 +67,24 @@ class AggregateIterableImpl extends MongoIterableImpl documentClass, final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, final ReadConcern readConcern, final WriteConcern writeConcern, final OperationExecutor executor, - final List pipeline, final AggregationLevel aggregationLevel, final boolean retryReads, + final List pipeline, final AggregationLevel aggregationLevel, + final boolean retryWrites, final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, final TimeoutSettings timeoutSettings) { this(clientSession, new MongoNamespace(databaseName, "_ignored"), documentClass, resultClass, codecRegistry, readPreference, - readConcern, writeConcern, executor, pipeline, aggregationLevel, retryReads, timeoutSettings); + readConcern, writeConcern, executor, pipeline, aggregationLevel, retryWrites, retryReads, maxAdaptiveRetriesSetting, + timeoutSettings); } @SuppressWarnings("checkstyle:ParameterNumber") AggregateIterableImpl(@Nullable final ClientSession clientSession, final MongoNamespace namespace, final Class documentClass, final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, final ReadConcern readConcern, final WriteConcern writeConcern, final OperationExecutor executor, - final List pipeline, final AggregationLevel aggregationLevel, final boolean retryReads, + final List pipeline, final AggregationLevel aggregationLevel, + final boolean retryWrites, final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, final TimeoutSettings timeoutSettings) { super(clientSession, executor, readConcern, readPreference, retryReads, timeoutSettings); this.operations = new Operations<>(namespace, documentClass, readPreference, codecRegistry, readConcern, writeConcern, - true, retryReads, timeoutSettings); + retryWrites, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); this.namespace = notNull("namespace", namespace); this.documentClass = notNull("documentClass", documentClass); this.resultClass = notNull("resultClass", resultClass); diff --git a/driver-sync/src/main/com/mongodb/client/internal/ChangeStreamIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ChangeStreamIterableImpl.java index b5b41b375f5..c0f9aaec8af 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ChangeStreamIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ChangeStreamIterableImpl.java @@ -72,21 +72,24 @@ public class ChangeStreamIterableImpl extends MongoIterableImpl pipeline, final Class resultClass, - final ChangeStreamLevel changeStreamLevel, final boolean retryReads, final TimeoutSettings timeoutSettings) { + final ChangeStreamLevel changeStreamLevel, final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, + final TimeoutSettings timeoutSettings) { this(clientSession, new MongoNamespace(databaseName, "_ignored"), codecRegistry, readPreference, readConcern, executor, pipeline, - resultClass, changeStreamLevel, retryReads, timeoutSettings); + resultClass, changeStreamLevel, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } public ChangeStreamIterableImpl(@Nullable final ClientSession clientSession, final MongoNamespace namespace, final CodecRegistry codecRegistry, final ReadPreference readPreference, final ReadConcern readConcern, final OperationExecutor executor, final List pipeline, final Class resultClass, - final ChangeStreamLevel changeStreamLevel, final boolean retryReads, final TimeoutSettings timeoutSettings) { + final ChangeStreamLevel changeStreamLevel, + final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, + final TimeoutSettings timeoutSettings) { super(clientSession, executor, readConcern, readPreference, retryReads, timeoutSettings); this.codecRegistry = notNull("codecRegistry", codecRegistry); this.pipeline = notNull("pipeline", pipeline); this.codec = ChangeStreamDocument.createCodec(notNull("resultClass", resultClass), codecRegistry); this.changeStreamLevel = notNull("changeStreamLevel", changeStreamLevel); - this.operations = new Operations<>(namespace, resultClass, readPreference, codecRegistry, retryReads, timeoutSettings); + this.operations = new Operations<>(namespace, resultClass, readPreference, codecRegistry, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @Override diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index c717a539a8f..d2d6720edd9 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -21,13 +21,16 @@ import com.mongodb.MongoException; import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoInternalException; -import com.mongodb.MongoOperationTimeoutException; +import com.mongodb.MongoTimeoutException; import com.mongodb.ReadConcern; import com.mongodb.TransactionOptions; +import com.mongodb.WithTransactionTimeoutException; import com.mongodb.WriteConcern; import com.mongodb.client.ClientSession; import com.mongodb.client.TransactionBody; import com.mongodb.internal.TimeoutContext; +import com.mongodb.internal.observability.micrometer.TracingManager; +import com.mongodb.internal.observability.micrometer.TransactionSpan; import com.mongodb.internal.operation.AbortTransactionOperation; import com.mongodb.internal.operation.CommitTransactionOperation; import com.mongodb.internal.operation.OperationHelper; @@ -36,20 +39,27 @@ import com.mongodb.internal.operation.WriteOperation; import com.mongodb.internal.session.BaseClientSessionImpl; import com.mongodb.internal.session.ServerSessionPool; -import com.mongodb.internal.observability.micrometer.TracingManager; -import com.mongodb.internal.observability.micrometer.TransactionSpan; +import com.mongodb.internal.time.ExponentialBackoff; +import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; + import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL; import static com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL; +import static com.mongodb.assertions.Assertions.assertFalse; import static com.mongodb.assertions.Assertions.assertNotNull; import static com.mongodb.assertions.Assertions.assertTrue; import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; +import static com.mongodb.internal.TimeoutContext.DEFAULT_TIMEOUT_MESSAGE; +import static com.mongodb.internal.TimeoutContext.createMongoTimeoutException; +import static com.mongodb.internal.thread.InterruptionUtil.interruptAndCreateMongoInterruptedException; final class ClientSessionImpl extends BaseClientSessionImpl implements ClientSession { - private static final int MAX_RETRY_TIME_LIMIT_MS = 120000; + private static final long MAX_RETRY_TIME_LIMIT_MS = 120000; private final OperationExecutor operationExecutor; private TransactionState transactionState = TransactionState.NONE; @@ -57,13 +67,17 @@ final class ClientSessionImpl extends BaseClientSessionImpl implements ClientSes private boolean commitInProgress; private TransactionOptions transactionOptions; private final TracingManager tracingManager; + @Nullable + private final Integer maxAdaptiveRetriesSetting; private TransactionSpan transactionSpan = null; + ClientSessionImpl(final ServerSessionPool serverSessionPool, final Object originator, final ClientSessionOptions options, - final OperationExecutor operationExecutor, final TracingManager tracingManager) { + final OperationExecutor operationExecutor, final TracingManager tracingManager, @Nullable final Integer maxAdaptiveRetriesSetting) { super(serverSessionPool, originator, options); this.operationExecutor = operationExecutor; this.tracingManager = tracingManager; + this.maxAdaptiveRetriesSetting = maxAdaptiveRetriesSetting; } @Override @@ -76,7 +90,10 @@ public boolean notifyMessageSent() { if (hasActiveTransaction()) { boolean firstMessageInCurrentTransaction = !messageSentInCurrentTransaction; messageSentInCurrentTransaction = true; - return firstMessageInCurrentTransaction; + OverloadRetryPolicyState.CommandExecutionScoped overloadRetryPolicyState = getOverloadRetryPolicyState().getCommandExecutionScoped(); + return overloadRetryPolicyState == null + ? firstMessageInCurrentTransaction + : overloadRetryPolicyState.notifyMessageSent(firstMessageInCurrentTransaction); } else { if (transactionState == TransactionState.COMMITTED || transactionState == TransactionState.ABORTED) { cleanupTransaction(TransactionState.NONE); @@ -138,7 +155,7 @@ public void abortTransaction() { TimeoutContext timeoutContext = getTimeoutContext(); WriteConcern writeConcern = assertNotNull(getWriteConcern(timeoutContext)); operationExecutor - .execute(new AbortTransactionOperation(writeConcern) + .execute(new AbortTransactionOperation(writeConcern, maxAdaptiveRetriesSetting) .recoveryToken(getRecoveryToken()), readConcern, this); } } catch (RuntimeException e) { @@ -152,6 +169,12 @@ public void abortTransaction() { } } + private void abortIfInTransaction() { + if (transactionState == TransactionState.IN) { + abortTransaction(); + } + } + private void startTransaction(final TransactionOptions transactionOptions, final TimeoutContext timeoutContext) { Boolean snapshot = getOptions().isSnapshot(); if (snapshot != null && snapshot) { @@ -207,6 +230,10 @@ private void commitTransaction(final boolean resetTimeout) { if (readConcern == null) { throw new MongoInternalException("Invariant violated. Transaction options read concern can not be null"); } + boolean alreadyCommitted = commitInProgress || transactionState == TransactionState.COMMITTED; + if (!alreadyCommitted) { + getOverloadRetryPolicyState().openCommitScope(); + } commitInProgress = true; if (resetTimeout) { resetTimeout(); @@ -214,8 +241,7 @@ private void commitTransaction(final boolean resetTimeout) { TimeoutContext timeoutContext = getTimeoutContext(); WriteConcern writeConcern = assertNotNull(getWriteConcern(timeoutContext)); operationExecutor - .execute(new CommitTransactionOperation(writeConcern, - transactionState == TransactionState.COMMITTED) + .execute(new CommitTransactionOperation(writeConcern, maxAdaptiveRetriesSetting, alreadyCommitted) .recoveryToken(getRecoveryToken()), readConcern, this); } } catch (MongoException e) { @@ -250,30 +276,45 @@ public T withTransaction(final TransactionBody transactionBody) { @Override public T withTransaction(final TransactionBody transactionBody, final TransactionOptions options) { notNull("transactionBody", transactionBody); - long startTime = ClientSessionClock.INSTANCE.now(); TimeoutContext withTransactionTimeoutContext = createTimeoutContext(options); + boolean timeoutMsConfigured = withTransactionTimeoutContext.hasTimeoutMS(); + Timeout withTransactionTimeout = assertNotNull(timeoutMsConfigured + ? withTransactionTimeoutContext.getTimeout() + : TimeoutContext.startTimeout(MAX_RETRY_TIME_LIMIT_MS)); + BooleanSupplier withTransactionTimeoutExpired = () -> withTransactionTimeout.call(TimeUnit.MILLISECONDS, + () -> false, ms -> false, () -> true); + int transactionAttempt = 0; + MongoException lastError = null; try { - outer: + transactionAttempts: while (true) { - T retVal; + if (transactionAttempt > 0) { + backoff(transactionAttempt, withTransactionTimeout, assertNotNull(lastError), timeoutMsConfigured); + } try { - startTransaction(options, withTransactionTimeoutContext.copyTimeoutContext()); + startTransaction(options, withTransactionTimeoutContext); + transactionAttempt++; if (transactionSpan != null) { transactionSpan.setIsConvenientTransaction(); } + } catch (Throwable e) { + abortIfInTransaction(); + throw e; + } + T retVal; + try { retVal = transactionBody.execute(); } catch (Throwable e) { - if (transactionState == TransactionState.IN) { - abortTransaction(); - } - if (e instanceof MongoException && !(e instanceof MongoOperationTimeoutException)) { - MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); - if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) - && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { + abortIfInTransaction(); + if (e instanceof MongoException) { + MongoException mongoException = (MongoException) e; + MongoException labelCarryingException = OperationHelper.unwrap(mongoException); + if (labelCarryingException.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)) { if (transactionSpan != null) { transactionSpan.spanFinalizing(false); } + lastError = mongoException; continue; } } @@ -284,23 +325,21 @@ public T withTransaction(final TransactionBody transactionBody, final Tra try { commitTransaction(false); break; - } catch (MongoException e) { - clearTransactionContextOnError(e); - if (!(e instanceof MongoOperationTimeoutException) - && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { - applyMajorityWriteConcernToTransactionOptions(); - - if (!(e instanceof MongoExecutionTimeoutException) - && e.hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) { - continue; - } else if (e.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)) { - if (transactionSpan != null) { - transactionSpan.spanFinalizing(true); - } - continue outer; + } catch (MongoException mongoException) { + if (mongoException.hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL) + && !(mongoException instanceof MongoExecutionTimeoutException)) { + if (withTransactionTimeoutExpired.getAsBoolean()) { + throw wrapInMongoTimeoutException(mongoException, timeoutMsConfigured); + } + continue; + } else if (mongoException.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)) { + if (transactionSpan != null) { + transactionSpan.spanFinalizing(true); } + lastError = mongoException; + continue transactionAttempts; } - throw e; + throw mongoException; } } } @@ -322,36 +361,19 @@ public TransactionSpan getTransactionSpan() { @Override public void close() { try { - if (transactionState == TransactionState.IN) { - abortTransaction(); - } + abortIfInTransaction(); } finally { clearTransactionContext(); super.close(); } } - // Apply majority write concern if the commit is to be retried. - private void applyMajorityWriteConcernToTransactionOptions() { - if (transactionOptions != null) { - TimeoutContext timeoutContext = getTimeoutContext(); - WriteConcern writeConcern = getWriteConcern(timeoutContext); - if (writeConcern != null) { - transactionOptions = TransactionOptions.merge(TransactionOptions.builder() - .writeConcern(writeConcern.withW("majority")).build(), transactionOptions); - } else { - transactionOptions = TransactionOptions.merge(TransactionOptions.builder() - .writeConcern(WriteConcern.MAJORITY).build(), transactionOptions); - } - } else { - transactionOptions = TransactionOptions.builder().writeConcern(WriteConcern.MAJORITY).build(); - } - } - private void cleanupTransaction(final TransactionState nextState) { messageSentInCurrentTransaction = false; transactionOptions = null; + assertFalse(nextState == TransactionState.COMMITTED); transactionState = nextState; + getOverloadRetryPolicyState().closeCommitScope(); setTimeoutContext(null); } @@ -360,4 +382,36 @@ private TimeoutContext createTimeoutContext(final TransactionOptions transaction TransactionOptions.merge(transactionOptions, getOptions().getDefaultTransactionOptions()), operationExecutor.getTimeoutSettings())); } + + private static void backoff(final int transactionAttempt, + final Timeout withTransactionTimeout, final MongoException lastError, final boolean timeoutMsConfigured) { + long backoffMs = ExponentialBackoff.calculateTransactionBackoffMs(transactionAttempt); + withTransactionTimeout.shortenBy(backoffMs, TimeUnit.MILLISECONDS).onExpired(() -> { + throw wrapInMongoTimeoutException(lastError, timeoutMsConfigured); + }); + try { + if (backoffMs > 0) { + Thread.sleep(backoffMs); + } + } catch (InterruptedException e) { + throw interruptAndCreateMongoInterruptedException("Transaction retry interrupted", e); + } + } + + private static MongoClientException wrapInMongoTimeoutException(final MongoException cause, final boolean timeoutMsConfigured) { + MongoClientException timeoutException = timeoutMsConfigured + ? createMongoTimeoutException(cause) + : wrapInNonTimeoutMsMongoTimeoutException(cause); + //TODO-JAVA-6154 constructor should be used. + if (timeoutException != cause) { + cause.getErrorLabels().forEach(timeoutException::addLabel); + } + return timeoutException; + } + + private static MongoClientException wrapInNonTimeoutMsMongoTimeoutException(final MongoException cause) { + return cause instanceof MongoTimeoutException + ? (MongoTimeoutException) cause + : new WithTransactionTimeoutException(DEFAULT_TIMEOUT_MESSAGE, cause); + } } diff --git a/driver-sync/src/main/com/mongodb/client/internal/Clusters.java b/driver-sync/src/main/com/mongodb/client/internal/Clusters.java index 6c57505e090..88cbebaa561 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/Clusters.java +++ b/driver-sync/src/main/com/mongodb/client/internal/Clusters.java @@ -24,6 +24,7 @@ import com.mongodb.internal.connection.InternalConnectionPoolSettings; import com.mongodb.internal.connection.StreamFactory; import com.mongodb.internal.connection.StreamFactoryFactory; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.lang.Nullable; import static com.mongodb.assertions.Assertions.assertNotNull; @@ -37,7 +38,8 @@ private Clusters() { public static Cluster createCluster(final MongoClientSettings settings, @Nullable final MongoDriverInformation mongoDriverInformation, - final StreamFactoryFactory streamFactoryFactory) { + final StreamFactoryFactory streamFactoryFactory, + final AsyncClientExecutor clientExecutor) { assertNotNull(streamFactoryFactory); assertNotNull(settings); @@ -47,7 +49,7 @@ public static Cluster createCluster(final MongoClientSettings settings, return new DefaultClusterFactory().createCluster(settings.getClusterSettings(), settings.getServerSettings(), settings.getConnectionPoolSettings(), InternalConnectionPoolSettings.builder().build(), TimeoutSettings.create(settings), streamFactory, - TimeoutSettings.createHeartbeatSettings(settings), heartbeatStreamFactory, + TimeoutSettings.createHeartbeatSettings(settings), heartbeatStreamFactory, clientExecutor, settings.getCredential(), settings.getLoggerSettings(), getCommandListener(settings.getCommandListeners()), settings.getApplicationName(), mongoDriverInformation, settings.getCompressorList(), settings.getServerApi(), settings.getDnsClient()); diff --git a/driver-sync/src/main/com/mongodb/client/internal/DistinctIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/DistinctIterableImpl.java index d3403738088..5108d1849e4 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/DistinctIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/DistinctIterableImpl.java @@ -52,9 +52,9 @@ class DistinctIterableImpl extends MongoIterableImpl documentClass, final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, final ReadConcern readConcern, final OperationExecutor executor, final String fieldName, final Bson filter, - final boolean retryReads, final TimeoutSettings timeoutSettings) { + final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, final TimeoutSettings timeoutSettings) { super(clientSession, executor, readConcern, readPreference, retryReads, timeoutSettings); - this.operations = new Operations<>(namespace, documentClass, readPreference, codecRegistry, retryReads, timeoutSettings); + this.operations = new Operations<>(namespace, documentClass, readPreference, codecRegistry, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); this.resultClass = notNull("resultClass", resultClass); this.fieldName = notNull("mapFunction", fieldName); this.filter = filter; diff --git a/driver-sync/src/main/com/mongodb/client/internal/FindIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/FindIterableImpl.java index 9b0321abbbc..5524ca8c145 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/FindIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/FindIterableImpl.java @@ -40,7 +40,10 @@ import static com.mongodb.assertions.Assertions.notNull; -class FindIterableImpl extends MongoIterableImpl implements FindIterable { +/** + * This class is not part of the public API and may be removed or changed at any time. + */ +public class FindIterableImpl extends MongoIterableImpl implements FindIterable { private final Operations operations; @@ -50,12 +53,13 @@ class FindIterableImpl extends MongoIterableImpl im private Bson filter; - FindIterableImpl(@Nullable final ClientSession clientSession, final MongoNamespace namespace, final Class documentClass, + public FindIterableImpl(@Nullable final ClientSession clientSession, final MongoNamespace namespace, + final Class documentClass, final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, - final ReadConcern readConcern, final OperationExecutor executor, final Bson filter, final boolean retryReads, - final TimeoutSettings timeoutSettings) { + final ReadConcern readConcern, final OperationExecutor executor, final Bson filter, + final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, final TimeoutSettings timeoutSettings) { super(clientSession, executor, readConcern, readPreference, retryReads, timeoutSettings); - this.operations = new Operations<>(namespace, documentClass, readPreference, codecRegistry, retryReads, timeoutSettings); + this.operations = new Operations<>(namespace, documentClass, readPreference, codecRegistry, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); this.resultClass = notNull("resultClass", resultClass); this.filter = notNull("filter", filter); this.findOptions = new FindOptions(); diff --git a/driver-sync/src/main/com/mongodb/client/internal/ListCollectionsIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ListCollectionsIterableImpl.java index 60755456811..8e9a7b9e04d 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ListCollectionsIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ListCollectionsIterableImpl.java @@ -49,10 +49,12 @@ class ListCollectionsIterableImpl extends MongoIterableImpl im ListCollectionsIterableImpl(@Nullable final ClientSession clientSession, final String databaseName, final boolean collectionNamesOnly, final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, - final OperationExecutor executor, final boolean retryReads, final TimeoutSettings timeoutSettings) { + final OperationExecutor executor, final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, + final TimeoutSettings timeoutSettings) { super(clientSession, executor, ReadConcern.DEFAULT, readPreference, retryReads, timeoutSettings); // TODO: read concern? this.collectionNamesOnly = collectionNamesOnly; - this.operations = new Operations<>(BsonDocument.class, readPreference, codecRegistry, retryReads, timeoutSettings); + this.operations = new Operations<>( + BsonDocument.class, readPreference, codecRegistry, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); this.databaseName = notNull("databaseName", databaseName); this.resultClass = notNull("resultClass", resultClass); } diff --git a/driver-sync/src/main/com/mongodb/client/internal/ListDatabasesIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ListDatabasesIterableImpl.java index f6e7d9a5021..8b57e02ed0a 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ListDatabasesIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ListDatabasesIterableImpl.java @@ -51,9 +51,10 @@ public class ListDatabasesIterableImpl extends MongoIterableImpl resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, final OperationExecutor executor, - final boolean retryReads, final TimeoutSettings timeoutSettings) { + final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, final TimeoutSettings timeoutSettings) { super(clientSession, executor, ReadConcern.DEFAULT, readPreference, retryReads, timeoutSettings); // TODO: read concern? - this.operations = new Operations<>(BsonDocument.class, readPreference, codecRegistry, retryReads, timeoutSettings); + this.operations = new Operations<>( + BsonDocument.class, readPreference, codecRegistry, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); this.resultClass = notNull("clazz", resultClass); } diff --git a/driver-sync/src/main/com/mongodb/client/internal/ListIndexesIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ListIndexesIterableImpl.java index e555984422b..ae69b1c9fe6 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ListIndexesIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ListIndexesIterableImpl.java @@ -44,9 +44,9 @@ class ListIndexesIterableImpl extends MongoIterableImpl implem ListIndexesIterableImpl(@Nullable final ClientSession clientSession, final MongoNamespace namespace, final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, final OperationExecutor executor, - final boolean retryReads, final TimeoutSettings timeoutSettings) { + final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, final TimeoutSettings timeoutSettings) { super(clientSession, executor, ReadConcern.DEFAULT, readPreference, retryReads, timeoutSettings); - this.operations = new Operations<>(namespace, BsonDocument.class, readPreference, codecRegistry, retryReads, timeoutSettings); + this.operations = new Operations<>(namespace, BsonDocument.class, readPreference, codecRegistry, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); this.resultClass = notNull("resultClass", resultClass); } diff --git a/driver-sync/src/main/com/mongodb/client/internal/ListSearchIndexesIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ListSearchIndexesIterableImpl.java index 7fd6ecd4a93..13fd7e71ba8 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ListSearchIndexesIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ListSearchIndexesIterableImpl.java @@ -43,7 +43,6 @@ final class ListSearchIndexesIterableImpl extends MongoIterableImpl resultClass; @Nullable private Boolean allowDiskUse; - @Nullable private long maxTimeMS; @Nullable private Collation collation; @@ -54,11 +53,12 @@ final class ListSearchIndexesIterableImpl extends MongoIterableImpl resultClass, final CodecRegistry codecRegistry, - final ReadPreference readPreference, final boolean retryReads, final TimeoutSettings timeoutSettings) { + final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, + final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, + final TimeoutSettings timeoutSettings) { super(null, executor, ReadConcern.DEFAULT, readPreference, retryReads, timeoutSettings); this.resultClass = resultClass; - this.operations = new Operations<>(namespace, BsonDocument.class, readPreference, codecRegistry, retryReads, timeoutSettings); + this.operations = new Operations<>(namespace, BsonDocument.class, readPreference, codecRegistry, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); this.codecRegistry = codecRegistry; } diff --git a/driver-sync/src/main/com/mongodb/client/internal/MapReduceIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MapReduceIterableImpl.java index fd79242766e..66a712b0054 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MapReduceIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MapReduceIterableImpl.java @@ -72,10 +72,11 @@ class MapReduceIterableImpl extends MongoIterableImpl documentClass, final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference, final ReadConcern readConcern, final WriteConcern writeConcern, final OperationExecutor executor, - final String mapFunction, final String reduceFunction, final TimeoutSettings timeoutSettings) { + final String mapFunction, final String reduceFunction, + @Nullable final Integer maxAdaptiveRetriesSetting, final TimeoutSettings timeoutSettings) { super(clientSession, executor, readConcern, readPreference, false, timeoutSettings); this.operations = new Operations<>(namespace, documentClass, readPreference, codecRegistry, readConcern, writeConcern, - false, false, timeoutSettings); + false, false, maxAdaptiveRetriesSetting, timeoutSettings); this.namespace = notNull("namespace", namespace); this.resultClass = notNull("resultClass", resultClass); this.mapFunction = notNull("mapFunction", mapFunction); 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 cefee9af461..21a70391a15 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java @@ -36,19 +36,16 @@ import com.mongodb.client.model.bulk.ClientBulkWriteResult; import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.connection.ClusterDescription; -import com.mongodb.connection.SocketSettings; import com.mongodb.internal.TimeoutSettings; import com.mongodb.internal.VisibleForTesting; import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; -import com.mongodb.internal.connection.DefaultClusterFactory; -import com.mongodb.internal.connection.InternalConnectionPoolSettings; -import com.mongodb.internal.connection.StreamFactory; import com.mongodb.internal.connection.StreamFactoryFactory; import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; import com.mongodb.internal.session.ServerSessionPool; import com.mongodb.internal.observability.micrometer.TracingManager; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.Document; @@ -61,7 +58,7 @@ import static com.mongodb.assertions.Assertions.notNull; import static com.mongodb.client.internal.Crypts.createCrypt; -import static com.mongodb.internal.event.EventListenerHelper.getCommandListener; +import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; import static java.lang.String.format; import static org.bson.codecs.configuration.CodecRegistries.withUuidRepresentation; @@ -75,23 +72,29 @@ public final class MongoClientImpl implements MongoClient { private final MongoDriverInformation mongoDriverInformation; private final MongoClusterImpl delegate; private final AtomicBoolean closed; - private final AutoCloseable externalResourceCloser; + private final StreamFactoryFactory streamFactoryFactory; + private final AsyncClientExecutor clientExecutor; - public MongoClientImpl(final Cluster cluster, - final MongoClientSettings settings, - final MongoDriverInformation mongoDriverInformation, - @Nullable final AutoCloseable externalResourceCloser) { - this(cluster, mongoDriverInformation, settings, externalResourceCloser, null); + public MongoClientImpl( + final Cluster cluster, + final MongoDriverInformation mongoDriverInformation, + final MongoClientSettings settings, + final StreamFactoryFactory streamFactoryFactory, + final AsyncClientExecutor clientExecutor) { + this(cluster, mongoDriverInformation, settings, streamFactoryFactory, clientExecutor, null); } - @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) - public MongoClientImpl(final Cluster cluster, - final MongoDriverInformation mongoDriverInformation, - final MongoClientSettings settings, - @Nullable final AutoCloseable externalResourceCloser, - @Nullable final OperationExecutor operationExecutor) { + @VisibleForTesting(otherwise = PRIVATE) + public MongoClientImpl( + final Cluster cluster, + final MongoDriverInformation mongoDriverInformation, + final MongoClientSettings settings, + final StreamFactoryFactory streamFactoryFactory, + final AsyncClientExecutor clientExecutor, + @Nullable final OperationExecutor operationExecutor) { - this.externalResourceCloser = externalResourceCloser; + this.streamFactoryFactory = streamFactoryFactory; + this.clientExecutor = clientExecutor; this.settings = notNull("settings", settings); this.mongoDriverInformation = mongoDriverInformation; AutoEncryptionSettings autoEncryptionSettings = settings.getAutoEncryptionSettings(); @@ -104,11 +107,13 @@ public MongoClientImpl(final Cluster cluster, withUuidRepresentation(settings.getCodecRegistry(), settings.getUuidRepresentation()), (SynchronousContextProvider) settings.getContextProvider(), autoEncryptionSettings == null ? null : createCrypt(settings, autoEncryptionSettings), this, - operationExecutor, settings.getReadConcern(), settings.getReadPreference(), settings.getRetryReads(), - settings.getRetryWrites(), settings.getServerApi(), - new ServerSessionPool(cluster, TimeoutSettings.create(settings), settings.getServerApi()), + operationExecutor, settings.getReadConcern(), settings.getReadPreference(), + settings.getRetryReads(), settings.getRetryWrites(), settings.getMaxAdaptiveRetries(), + settings.getEnableOverloadRetargeting(), settings.getServerApi(), + new ServerSessionPool(cluster, clientExecutor, TimeoutSettings.create(settings), settings.getServerApi()), TimeoutSettings.create(settings), settings.getUuidRepresentation(), - settings.getWriteConcern(), new TracingManager(settings.getObservabilitySettings())); + settings.getWriteConcern(), clientExecutor, + new TracingManager(settings.getObservabilitySettings())); this.closed = new AtomicBoolean(); BsonDocument clientMetadataDocument = delegate.getCluster().getClientMetadata().getBsonDocument(); @@ -124,12 +129,13 @@ public void close() { } delegate.getServerSessionPool().close(); delegate.getCluster().close(); - if (externalResourceCloser != null) { - try { - externalResourceCloser.close(); - } catch (Exception e) { - LOGGER.warn("Exception closing resource", e); - } + //noinspection EmptyTryBlock + try (AutoCloseable autoClosedStreamFactoryFactory = streamFactoryFactory; + AutoCloseable autoClosedClientExecutor = clientExecutor) { + // `clientExecutor`, `streamFactoryFactory` must be the last resources closed, + // with `streamFactoryFactory` being the very last. + } catch (Exception e) { + LOGGER.warn("Exception closing resource", e); } } } @@ -311,27 +317,6 @@ public ClientBulkWriteResult bulkWrite( return delegate.bulkWrite(clientSession, clientWriteModels, options); } - private static Cluster createCluster(final MongoClientSettings settings, - @Nullable final MongoDriverInformation mongoDriverInformation, - final StreamFactory streamFactory, final StreamFactory heartbeatStreamFactory) { - notNull("settings", settings); - return new DefaultClusterFactory().createCluster(settings.getClusterSettings(), settings.getServerSettings(), - settings.getConnectionPoolSettings(), InternalConnectionPoolSettings.builder().build(), - TimeoutSettings.create(settings), streamFactory, - TimeoutSettings.createHeartbeatSettings(settings), heartbeatStreamFactory, - settings.getCredential(), settings.getLoggerSettings(), getCommandListener(settings.getCommandListeners()), - settings.getApplicationName(), mongoDriverInformation, settings.getCompressorList(), settings.getServerApi(), - settings.getDnsClient()); - } - - private static StreamFactory getStreamFactory( - final StreamFactoryFactory streamFactoryFactory, - final MongoClientSettings settings, - final boolean isHeartbeat) { - SocketSettings socketSettings = isHeartbeat ? settings.getHeartbeatSocketSettings() : settings.getSocketSettings(); - return streamFactoryFactory.create(socketSettings, settings.getSslSettings()); - } - public Cluster getCluster() { return delegate.getCluster(); } @@ -355,4 +340,8 @@ public MongoClientSettings getSettings() { public MongoDriverInformation getMongoDriverInformation() { return mongoDriverInformation; } + + public AsyncClientExecutor getClientExecutor() { + return clientExecutor; + } } 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 f11c776754b..11579f86e40 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java @@ -59,6 +59,7 @@ import com.mongodb.internal.operation.ReadOperation; import com.mongodb.internal.operation.WriteOperation; import com.mongodb.internal.session.ServerSessionPool; +import com.mongodb.internal.thread.AsyncClientExecutor; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.Document; @@ -96,22 +97,27 @@ final class MongoClusterImpl implements MongoCluster { private final boolean retryReads; private final boolean retryWrites; @Nullable + private final Integer maxAdaptiveRetriesSetting; + private final boolean enableOverloadRetargeting; + @Nullable private final ServerApi serverApi; private final ServerSessionPool serverSessionPool; private final TimeoutSettings timeoutSettings; private final UuidRepresentation uuidRepresentation; private final WriteConcern writeConcern; private final Operations operations; + private final AsyncClientExecutor clientExecutor; private final TracingManager tracingManager; MongoClusterImpl( @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, + @Nullable final Integer maxAdaptiveRetriesSetting, final boolean enableOverloadRetargeting, + @Nullable final ServerApi serverApi, final ServerSessionPool serverSessionPool, final TimeoutSettings timeoutSettings, + final UuidRepresentation uuidRepresentation, final WriteConcern writeConcern, + final AsyncClientExecutor clientExecutor, final TracingManager tracingManager) { this.autoEncryptionSettings = autoEncryptionSettings; this.cluster = cluster; this.codecRegistry = codecRegistry; @@ -123,11 +129,14 @@ final class MongoClusterImpl implements MongoCluster { this.readPreference = readPreference; this.retryReads = retryReads; this.retryWrites = retryWrites; + this.maxAdaptiveRetriesSetting = maxAdaptiveRetriesSetting; + this.enableOverloadRetargeting = enableOverloadRetargeting; this.serverApi = serverApi; this.serverSessionPool = serverSessionPool; this.timeoutSettings = timeoutSettings; this.uuidRepresentation = uuidRepresentation; this.writeConcern = writeConcern; + this.clientExecutor = clientExecutor; this.tracingManager = tracingManager; operations = new Operations<>( null, @@ -138,6 +147,7 @@ final class MongoClusterImpl implements MongoCluster { writeConcern, retryWrites, retryReads, + maxAdaptiveRetriesSetting, timeoutSettings); } @@ -172,42 +182,43 @@ 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, - uuidRepresentation, writeConcern, tracingManager); + operationExecutor, readConcern, readPreference, retryReads, retryWrites, maxAdaptiveRetriesSetting, enableOverloadRetargeting, + serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, clientExecutor, 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, - uuidRepresentation, writeConcern, tracingManager); + operationExecutor, readConcern, readPreference, retryReads, retryWrites, maxAdaptiveRetriesSetting, enableOverloadRetargeting, + serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, clientExecutor, 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, - uuidRepresentation, writeConcern, tracingManager); + operationExecutor, readConcern, readPreference, retryReads, retryWrites, maxAdaptiveRetriesSetting, enableOverloadRetargeting, + serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, clientExecutor, 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, - uuidRepresentation, writeConcern, tracingManager); + operationExecutor, readConcern, readPreference, retryReads, retryWrites, maxAdaptiveRetriesSetting, enableOverloadRetargeting, + serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, clientExecutor, 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, - timeoutSettings.withTimeout(timeout, timeUnit), uuidRepresentation, writeConcern, tracingManager); + operationExecutor, readConcern, readPreference, retryReads, retryWrites, maxAdaptiveRetriesSetting, enableOverloadRetargeting, + serverApi, serverSessionPool, timeoutSettings.withTimeout(timeout, timeUnit), uuidRepresentation, writeConcern, clientExecutor, + tracingManager); } @Override public MongoDatabase getDatabase(final String databaseName) { - return new MongoDatabaseImpl(databaseName, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, readConcern, - uuidRepresentation, autoEncryptionSettings, timeoutSettings, operationExecutor); + return new MongoDatabaseImpl(databaseName, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetriesSetting, + readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, operationExecutor); } public Cluster getCluster() { @@ -256,7 +267,7 @@ public ClientSession startSession(final ClientSessionOptions options) { .readPreference(readPreference) .build())) .build(); - return new ClientSessionImpl(serverSessionPool, originator, mergedOptions, operationExecutor, tracingManager); + return new ClientSessionImpl(serverSessionPool, originator, mergedOptions, operationExecutor, tracingManager, maxAdaptiveRetriesSetting); } @Override @@ -374,7 +385,8 @@ public ClientBulkWriteResult bulkWrite( } private ListDatabasesIterable createListDatabasesIterable(@Nullable final ClientSession clientSession, final Class clazz) { - return new ListDatabasesIterableImpl<>(clientSession, clazz, codecRegistry, ReadPreference.primary(), operationExecutor, retryReads, timeoutSettings); + return new ListDatabasesIterableImpl<>(clientSession, clazz, codecRegistry, ReadPreference.primary(), operationExecutor, + retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } private MongoIterable createListDatabaseNamesIterable(@Nullable final ClientSession clientSession) { @@ -387,7 +399,7 @@ private ChangeStreamIterable createChangeStreamIterable(@Null final List pipeline, final Class resultClass) { return new ChangeStreamIterableImpl<>(clientSession, "admin", codecRegistry, readPreference, readConcern, operationExecutor, pipeline, resultClass, ChangeStreamLevel.CLIENT, - retryReads, timeoutSettings); + retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } private ClientBulkWriteResult executeBulkWrite( @@ -398,7 +410,7 @@ private ClientBulkWriteResult executeBulkWrite( return operationExecutor.execute(operations.clientBulkWriteOperation(clientWriteModels, options), readConcern, clientSession); } - final class OperationExecutorImpl implements OperationExecutor { + private final class OperationExecutorImpl implements OperationExecutor { private final TimeoutSettings executorTimeoutSettings; OperationExecutorImpl(final TimeoutSettings executorTimeoutSettings) { @@ -527,9 +539,11 @@ private OperationContext getOperationContext(final ClientSession session, final getRequestContext(), new ReadConcernAwareNoOpSessionContext(readConcern), createTimeoutContext(session, executorTimeoutSettings), + clientExecutor, tracingManager, serverApi, - commandName); + commandName, + new OperationContext.ServerDeprioritization(enableOverloadRetargeting)); } private RequestContext getRequestContext() { diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoCollectionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoCollectionImpl.java index 736e1541212..2641616a2d6 100755 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoCollectionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoCollectionImpl.java @@ -97,6 +97,8 @@ class MongoCollectionImpl implements MongoCollection { private final WriteConcern writeConcern; private final boolean retryWrites; private final boolean retryReads; + @Nullable + private final Integer maxAdaptiveRetriesSetting; private final ReadConcern readConcern; private final Operations operations; private final UuidRepresentation uuidRepresentation; @@ -107,10 +109,10 @@ class MongoCollectionImpl implements MongoCollection { private final OperationExecutor executor; MongoCollectionImpl(final MongoNamespace namespace, final Class documentClass, final CodecRegistry codecRegistry, - final ReadPreference readPreference, final WriteConcern writeConcern, final boolean retryWrites, - final boolean retryReads, final ReadConcern readConcern, final UuidRepresentation uuidRepresentation, - @Nullable final AutoEncryptionSettings autoEncryptionSettings, final TimeoutSettings timeoutSettings, - final OperationExecutor executor) { + final ReadPreference readPreference, final WriteConcern writeConcern, + final boolean retryWrites, final boolean retryReads, @Nullable final Integer maxAdaptiveRetriesSetting, + final ReadConcern readConcern, final UuidRepresentation uuidRepresentation, + @Nullable final AutoEncryptionSettings autoEncryptionSettings, final TimeoutSettings timeoutSettings, final OperationExecutor executor) { this.namespace = notNull("namespace", namespace); this.documentClass = notNull("documentClass", documentClass); this.codecRegistry = notNull("codecRegistry", codecRegistry); @@ -118,13 +120,14 @@ class MongoCollectionImpl implements MongoCollection { this.writeConcern = notNull("writeConcern", writeConcern); this.retryWrites = retryWrites; this.retryReads = retryReads; + this.maxAdaptiveRetriesSetting = maxAdaptiveRetriesSetting; this.readConcern = notNull("readConcern", readConcern); this.executor = notNull("executor", executor); this.uuidRepresentation = notNull("uuidRepresentation", uuidRepresentation); this.autoEncryptionSettings = autoEncryptionSettings; this.timeoutSettings = timeoutSettings; this.operations = new Operations<>(namespace, documentClass, readPreference, codecRegistry, readConcern, writeConcern, - retryWrites, retryReads, timeoutSettings); + retryWrites, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @Override @@ -166,37 +169,44 @@ public Long getTimeout(final TimeUnit timeUnit) { @Override public MongoCollection withDocumentClass(final Class clazz) { - return new MongoCollectionImpl<>(namespace, clazz, codecRegistry, readPreference, writeConcern, retryWrites, - retryReads, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + return new MongoCollectionImpl<>(namespace, clazz, codecRegistry, readPreference, writeConcern, + retryWrites, retryReads, maxAdaptiveRetriesSetting, + readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { return new MongoCollectionImpl<>(namespace, documentClass, withUuidRepresentation(codecRegistry, uuidRepresentation), - readPreference, writeConcern, retryWrites, retryReads, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + readPreference, writeConcern, + retryWrites, retryReads, maxAdaptiveRetriesSetting, + readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoCollection withReadPreference(final ReadPreference readPreference) { - return new MongoCollectionImpl<>(namespace, documentClass, codecRegistry, readPreference, writeConcern, retryWrites, - retryReads, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + return new MongoCollectionImpl<>(namespace, documentClass, codecRegistry, readPreference, writeConcern, + retryWrites, retryReads, maxAdaptiveRetriesSetting, + readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoCollection withWriteConcern(final WriteConcern writeConcern) { - return new MongoCollectionImpl<>(namespace, documentClass, codecRegistry, readPreference, writeConcern, retryWrites, - retryReads, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + return new MongoCollectionImpl<>(namespace, documentClass, codecRegistry, readPreference, writeConcern, + retryWrites, retryReads, maxAdaptiveRetriesSetting, + readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoCollection withReadConcern(final ReadConcern readConcern) { - return new MongoCollectionImpl<>(namespace, documentClass, codecRegistry, readPreference, writeConcern, retryWrites, - retryReads, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + return new MongoCollectionImpl<>(namespace, documentClass, codecRegistry, readPreference, writeConcern, + retryWrites, retryReads, maxAdaptiveRetriesSetting, + readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoCollection withTimeout(final long timeout, final TimeUnit timeUnit) { - return new MongoCollectionImpl<>(namespace, documentClass, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, + return new MongoCollectionImpl<>(namespace, documentClass, codecRegistry, readPreference, writeConcern, + retryWrites, retryReads, maxAdaptiveRetriesSetting, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings.withTimeout(timeout, timeUnit), executor); } @@ -273,7 +283,7 @@ public DistinctIterable distinct(final ClientSession clientSe private DistinctIterable createDistinctIterable(@Nullable final ClientSession clientSession, final String fieldName, final Bson filter, final Class resultClass) { return new DistinctIterableImpl<>(clientSession, namespace, documentClass, resultClass, codecRegistry, - readPreference, readConcern, executor, fieldName, filter, retryReads, timeoutSettings); + readPreference, readConcern, executor, fieldName, filter, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @Override @@ -324,7 +334,7 @@ public FindIterable find(final ClientSession clientSession, f private FindIterable createFindIterable(@Nullable final ClientSession clientSession, final Bson filter, final Class resultClass) { return new FindIterableImpl<>(clientSession, namespace, this.documentClass, resultClass, codecRegistry, - readPreference, readConcern, executor, filter, retryReads, timeoutSettings); + readPreference, readConcern, executor, filter, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @Override @@ -353,7 +363,8 @@ private AggregateIterable createAggregateIterable(@Nullable f final List pipeline, final Class resultClass) { return new AggregateIterableImpl<>(clientSession, namespace, documentClass, resultClass, codecRegistry, - readPreference, readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, retryReads, timeoutSettings); + readPreference, readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, + retryWrites, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @Override @@ -402,7 +413,7 @@ private ChangeStreamIterable createChangeStreamIterable(@Null final List pipeline, final Class resultClass) { return new ChangeStreamIterableImpl<>(clientSession, namespace, codecRegistry, readPreference, readConcern, executor, - pipeline, resultClass, ChangeStreamLevel.COLLECTION, retryReads, timeoutSettings); + pipeline, resultClass, ChangeStreamLevel.COLLECTION, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @SuppressWarnings("deprecation") @@ -438,7 +449,8 @@ private com.mongodb.client.MapReduceIterable createMapReduceI final String mapFunction, final String reduceFunction, final Class resultClass) { return new MapReduceIterableImpl<>(clientSession, namespace, documentClass, resultClass, codecRegistry, - readPreference, readConcern, writeConcern, executor, mapFunction, reduceFunction, timeoutSettings); + readPreference, readConcern, writeConcern, executor, mapFunction, reduceFunction, + maxAdaptiveRetriesSetting, timeoutSettings); } @Override @@ -972,12 +984,12 @@ public ListIndexesIterable listIndexes(final ClientSession cl private ListIndexesIterable createListIndexesIterable(@Nullable final ClientSession clientSession, final Class resultClass) { return new ListIndexesIterableImpl<>(clientSession, getNamespace(), resultClass, codecRegistry, ReadPreference.primary(), - executor, retryReads, timeoutSettings); + executor, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } private ListSearchIndexesIterable createListSearchIndexesIterable(final Class resultClass) { return new ListSearchIndexesIterableImpl<>(getNamespace(), executor, resultClass, codecRegistry, readPreference, - retryReads, timeoutSettings); + retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @Override diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoDatabaseImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoDatabaseImpl.java index 1541fbe1c63..1f8a2cdedb1 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoDatabaseImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoDatabaseImpl.java @@ -61,6 +61,8 @@ public class MongoDatabaseImpl implements MongoDatabase { private final WriteConcern writeConcern; private final boolean retryWrites; private final boolean retryReads; + @Nullable + private final Integer maxAdaptiveRetriesSetting; private final ReadConcern readConcern; private final UuidRepresentation uuidRepresentation; @Nullable @@ -72,6 +74,7 @@ public class MongoDatabaseImpl implements MongoDatabase { public MongoDatabaseImpl(final String name, final CodecRegistry codecRegistry, final ReadPreference readPreference, final WriteConcern writeConcern, final boolean retryWrites, final boolean retryReads, + @Nullable final Integer maxAdaptiveRetriesSetting, final ReadConcern readConcern, final UuidRepresentation uuidRepresentation, @Nullable final AutoEncryptionSettings autoEncryptionSettings, final TimeoutSettings timeoutSettings, final OperationExecutor executor) { @@ -82,13 +85,14 @@ public MongoDatabaseImpl(final String name, final CodecRegistry codecRegistry, f this.writeConcern = notNull("writeConcern", writeConcern); this.retryWrites = retryWrites; this.retryReads = retryReads; + this.maxAdaptiveRetriesSetting = maxAdaptiveRetriesSetting; this.readConcern = notNull("readConcern", readConcern); this.uuidRepresentation = notNull("uuidRepresentation", uuidRepresentation); this.autoEncryptionSettings = autoEncryptionSettings; this.timeoutSettings = timeoutSettings; this.executor = notNull("executor", executor); this.operations = new Operations<>(new MongoNamespace(name, "_ignored"), BsonDocument.class, readPreference, - codecRegistry, readConcern, writeConcern, retryWrites, retryReads, timeoutSettings); + codecRegistry, readConcern, writeConcern, retryWrites, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @Override @@ -126,31 +130,31 @@ public Long getTimeout(final TimeUnit timeUnit) { @Override public MongoDatabase withCodecRegistry(final CodecRegistry codecRegistry) { return new MongoDatabaseImpl(name, withUuidRepresentation(codecRegistry, uuidRepresentation), readPreference, writeConcern, retryWrites, - retryReads, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + retryReads, maxAdaptiveRetriesSetting, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoDatabase withReadPreference(final ReadPreference readPreference) { - return new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, readConcern, - uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + return new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, + maxAdaptiveRetriesSetting, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoDatabase withWriteConcern(final WriteConcern writeConcern) { - return new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, readConcern, - uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + return new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, + maxAdaptiveRetriesSetting, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoDatabase withReadConcern(final ReadConcern readConcern) { - return new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, readConcern, - uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + return new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, + maxAdaptiveRetriesSetting, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override public MongoDatabase withTimeout(final long timeout, final TimeUnit timeUnit) { - return new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, readConcern, - uuidRepresentation, autoEncryptionSettings, timeoutSettings.withTimeout(timeout, timeUnit), executor); + return new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, retryWrites, retryReads, + maxAdaptiveRetriesSetting, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings.withTimeout(timeout, timeUnit), executor); } @Override @@ -161,7 +165,8 @@ public MongoCollection getCollection(final String collectionName) { @Override public MongoCollection getCollection(final String collectionName, final Class documentClass) { return new MongoCollectionImpl<>(new MongoNamespace(name, collectionName), documentClass, codecRegistry, readPreference, - writeConcern, retryWrites, retryReads, readConcern, uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); + writeConcern, retryWrites, retryReads, maxAdaptiveRetriesSetting, readConcern, + uuidRepresentation, autoEncryptionSettings, timeoutSettings, executor); } @Override @@ -270,7 +275,7 @@ private ListCollectionsIterableImpl createListCollectionsIter final Class resultClass, final boolean collectionNamesOnly) { return new ListCollectionsIterableImpl<>(clientSession, name, collectionNamesOnly, resultClass, codecRegistry, - ReadPreference.primary(), executor, retryReads, timeoutSettings); + ReadPreference.primary(), executor, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } @Override @@ -393,14 +398,15 @@ private AggregateIterable createAggregateIterable(@Nullable f final List pipeline, final Class resultClass) { return new AggregateIterableImpl<>(clientSession, name, Document.class, resultClass, codecRegistry, - readPreference, readConcern, writeConcern, executor, pipeline, AggregationLevel.DATABASE, retryReads, timeoutSettings); + readPreference, readConcern, writeConcern, executor, pipeline, AggregationLevel.DATABASE, + retryWrites, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } private ChangeStreamIterable createChangeStreamIterable(@Nullable final ClientSession clientSession, final List pipeline, final Class resultClass) { return new ChangeStreamIterableImpl<>(clientSession, name, codecRegistry, readPreference, readConcern, executor, - pipeline, resultClass, ChangeStreamLevel.DATABASE, retryReads, timeoutSettings); + pipeline, resultClass, ChangeStreamLevel.DATABASE, retryReads, maxAdaptiveRetriesSetting, timeoutSettings); } private void executeCreateView(@Nullable final ClientSession clientSession, final String viewName, final String viewOn, diff --git a/driver-sync/src/main/com/mongodb/client/internal/TimeoutHelper.java b/driver-sync/src/main/com/mongodb/client/internal/TimeoutHelper.java index 6a5ef68e615..2556388aed7 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/TimeoutHelper.java +++ b/driver-sync/src/main/com/mongodb/client/internal/TimeoutHelper.java @@ -22,14 +22,13 @@ import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; +import static com.mongodb.internal.TimeoutContext.DEFAULT_TIMEOUT_MESSAGE; import static java.util.concurrent.TimeUnit.MILLISECONDS; /** *

      This class is not part of the public API and may be removed or changed at any time

      */ public final class TimeoutHelper { - private static final String DEFAULT_TIMEOUT_MESSAGE = "Operation exceeded the timeout limit."; - private TimeoutHelper() { //NOP } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java index b958afcf145..320f79e54b2 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java @@ -24,6 +24,7 @@ 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.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -45,6 +46,7 @@ import static com.mongodb.assertions.Assertions.assertTrue; import static java.util.Optional.ofNullable; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assumptions.assumeFalse; /** @@ -333,6 +335,25 @@ void testEmptyStringsAreConsideredUnsetWhenAppendingMetadataIdenticalToInitialMe } } + @DisplayName("Test 9: Handshake documents include backpressure: true") + @Test + void testHandshakeDocumentsIncludeBackpressureTrue() { + try (MongoClient mongoClient = createMongoClient(null, getMongoClientSettings())) { + commandListener.reset(); + mongoClient.getDatabase("admin").runCommand(BsonDocument.parse("{ping: 1}")); + + List handshakeEvents = commandListener.getCommandStartedEvents("isMaster"); + 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"); + } + } + } + public static Stream provideDriverInformation() { return Stream.of( Arguments.of(new DriverInformation("framework", "2.0", "Framework Platform")), diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 63bacfc7596..ada60e09048 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -47,10 +47,6 @@ import com.mongodb.event.ConnectionClosedEvent; import com.mongodb.event.ConnectionCreatedEvent; import com.mongodb.event.ConnectionReadyEvent; - -import static com.mongodb.internal.connection.CommandHelper.HELLO; -import static com.mongodb.internal.connection.CommandHelper.LEGACY_HELLO; - import com.mongodb.internal.connection.InternalStreamConnection; import com.mongodb.internal.connection.ServerHelper; import com.mongodb.internal.connection.TestCommandListener; @@ -89,8 +85,11 @@ import static com.mongodb.ClusterFixture.scaleForWindows; import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.ClusterFixture.sleep; +import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL; import static com.mongodb.client.Fixture.getDefaultDatabaseName; import static com.mongodb.client.Fixture.getPrimary; +import static com.mongodb.internal.connection.CommandHelper.HELLO; +import static com.mongodb.internal.connection.CommandHelper.LEGACY_HELLO; import static java.lang.Long.MAX_VALUE; import static java.lang.String.join; import static java.util.Arrays.asList; @@ -751,7 +750,7 @@ public void test10CustomTestWithTransactionUsesASingleTimeoutWithLock() { + " blockConnection: true," + " blockTimeMS: " + blockTimeMS + " errorCode: " + 24 - + " errorLabels: [\"TransientTransactionError\"]" + + " errorLabels: [\"" + TRANSIENT_TRANSACTION_ERROR_LABEL + "\"]" + " }" + "}"); diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java new file mode 100644 index 00000000000..4c6c536fac1 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java @@ -0,0 +1,248 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.ReadPreference; +import com.mongodb.ServerAddress; +import com.mongodb.client.test.CollectionHelper; +import com.mongodb.event.CommandFailedEvent; +import com.mongodb.event.CommandSucceededEvent; +import com.mongodb.internal.connection.TestClusterListener; +import com.mongodb.internal.connection.TestCommandListener; +import org.bson.BsonDocument; +import org.bson.Document; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.MongoException.RETRYABLE_ERROR_LABEL; +import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL; +import static com.mongodb.client.Fixture.getDefaultDatabaseName; +import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; +import static com.mongodb.client.Fixture.getPrimary; +import static com.mongodb.client.model.Filters.eq; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * + * Prose Tests. + */ +public abstract class AbstractRetryableReadsProseTest { + + private static final String COLLECTION_NAME = "test"; + + private final TestCommandListener commandListener = + new TestCommandListener(asList("commandFailedEvent", "commandSucceededEvent"), emptyList()); + private final TestClusterListener clusterListener = new TestClusterListener(); + + protected abstract MongoClient createClient(MongoClientSettings settings); + + @AfterEach + void afterEach() { + CollectionHelper.dropDatabase(getDefaultDatabaseName()); + commandListener.reset(); + clusterListener.clearClusterDescriptionChangedEvents(); + } + + /** + * + * 1. PoolClearedError Retryability Test. + */ + @Test + void poolClearedExceptionMustBeRetryable() throws Exception { + RetryableWritesProseTest.poolClearedExceptionMustBeRetryable(this::createClient, + mongoCollection -> mongoCollection.find(eq(0)).iterator().hasNext(), "find", false); + } + + /** + * + * 2.1 Retryable Reads Are Retried on a Different mongos When One is Available. + */ + @Test + void retriesOnDifferentMongosWhenAvailable() throws InterruptedException, TimeoutException { + RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable(this::createClient, + mongoCollection -> { + try (MongoCursor cursor = mongoCollection.find().iterator()) { + return cursor.hasNext(); + } + }, "find", false); + } + + /** + * + * 2.2 Retryable Reads Are Retried on the Same mongos When No Others are Available. + */ + @Test + void retriesOnSameMongosWhenAnotherNotAvailable() { + RetryableWritesProseTest.retriesOnSameMongosWhenAnotherNotAvailable(this::createClient, + mongoCollection -> { + try (MongoCursor cursor = mongoCollection.find().iterator()) { + return cursor.hasNext(); + } + }, "find", false); + } + + /** + * + * 3.1 Retryable Reads Caused by Overload Errors Are Retried on a Different Replicaset Server When One is Available and enableOverloadRetargeting is enabled. + */ + @Test + void overloadErrorRetriedOnDifferentReplicaSetServer() throws InterruptedException, TimeoutException { + //given + assumeTrue(serverVersionAtLeast(4, 4)); + assumeTrue(isDiscoverableReplicaSet()); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: [\"find\"],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "'],\n" + + " errorCode: 6\n" + + " }\n" + + "}\n"); + + try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary()); + MongoClient client = createClient(getMongoClientSettingsBuilder() + .retryReads(true) + .readPreference(ReadPreference.primaryPreferred()) + .enableOverloadRetargeting(true) + .addCommandListener(commandListener) + .applyToClusterSettings(builder -> builder.addClusterListener(clusterListener)) + .build())) { + + waitForClusterDiscovery(); + + MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) + .getCollection(COLLECTION_NAME); + commandListener.reset(); + + //when + collection.find().first(); + + //then + List commandFailedEvents = commandListener.getCommandFailedEvents(); + assertEquals(1, commandFailedEvents.size()); + List commandSucceededEvents = commandListener.getCommandSucceededEvents(); + assertEquals(1, commandSucceededEvents.size()); + + ServerAddress failedServer = commandFailedEvents.get(0).getConnectionDescription().getServerAddress(); + ServerAddress succeededServer = commandSucceededEvents.get(0).getConnectionDescription().getServerAddress(); + + assertNotEquals(failedServer, succeededServer, + format("Expected retry on different server but both were %s", failedServer)); + } + } + + /** + * + * 3.2 Retryable Reads Caused by Non-Overload Errors Are Retried on the Same Replicaset Server. + */ + @Test + void nonOverloadErrorRetriedOnSameReplicaSetServer() throws InterruptedException, TimeoutException { + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: [\"find\"],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "'],\n" + + " errorCode: 6\n" + + " }\n" + + "}\n"); + 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() + .retryReads(true) + .readPreference(ReadPreference.primaryPreferred()) + .addCommandListener(commandListener) + .applyToClusterSettings(builder -> builder.addClusterListener(clusterListener)) + .build())) { + + waitForClusterDiscovery(); + + MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) + .getCollection(COLLECTION_NAME); + commandListener.reset(); + + //when + collection.find().first(); + + //then + List commandFailedEvents = commandListener.getCommandFailedEvents(); + assertEquals(1, commandFailedEvents.size()); + List commandSucceededEvents = commandListener.getCommandSucceededEvents(); + assertEquals(1, commandSucceededEvents.size()); + + ServerAddress failedServer = commandFailedEvents.get(0).getConnectionDescription().getServerAddress(); + ServerAddress succeededServer = commandSucceededEvents.get(0).getConnectionDescription().getServerAddress(); + + assertEquals(failedServer, succeededServer, + format("Expected retry on same server but got %s and %s", failedServer, succeededServer)); + } + } + + private void waitForClusterDiscovery() throws InterruptedException, TimeoutException { + // 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/BackpressureProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/BackpressureProseTest.java new file mode 100644 index 00000000000..bb566ed2fe4 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/BackpressureProseTest.java @@ -0,0 +1,886 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoCommandException; +import com.mongodb.MongoNamespace; +import com.mongodb.MongoServerException; +import com.mongodb.client.model.CreateCollectionOptions; +import com.mongodb.client.model.DropCollectionOptions; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.SearchIndexModel; +import com.mongodb.client.model.Updates; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; +import com.mongodb.event.CommandFailedEvent; +import com.mongodb.event.CommandStartedEvent; +import com.mongodb.internal.connection.TestCommandListener; +import com.mongodb.internal.event.ConfigureFailPointCommandListener; +import com.mongodb.internal.time.ExponentialBackoff; +import com.mongodb.internal.time.StartTime; +import com.mongodb.lang.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.Document; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static com.mongodb.client.model.Aggregates.match; +import static com.mongodb.client.model.bulk.ClientBulkWriteOptions.clientBulkWriteOptions; +import static com.mongodb.client.model.bulk.ClientUpdateOneOptions.clientUpdateOneOptions; +import static java.lang.String.join; +import static java.util.Arrays.asList; +import static java.util.Collections.nCopies; +import static java.util.Collections.singletonList; + +import static com.mongodb.ClusterFixture.isStandalone; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.MongoException.RETRYABLE_ERROR_LABEL; +import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL; +import static com.mongodb.client.Fixture.getDefaultDatabaseName; +import static com.mongodb.client.Fixture.getMongoClientSettings; +import static com.mongodb.client.Fixture.getPrimary; +import static com.mongodb.internal.operation.CommandOperationHelper.DEFAULT_MAX_ADAPTIVE_RETRIES; +import static com.mongodb.internal.operation.CommandOperationHelper.NO_WRITES_PERFORMED_ERROR_LABEL; +import static com.mongodb.internal.operation.CommandOperationHelper.RETRYABLE_WRITE_ERROR_LABEL; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * + * Prose Tests. + */ +public class BackpressureProseTest { + private static final String ENCRYPTED_STATE_COLLECTION_PREFIX = "enxcol_."; + private static final int SYSTEM_OVERLOAD_ERROR_CODE = 462; + private static final int RETRYABLE_ERROR_CODE = 11602; + private static final MongoNamespace NAMESPACE = new MongoNamespace(getDefaultDatabaseName(), BackpressureProseTest.class.getSimpleName()); + protected MongoClient createClient(final MongoClientSettings mongoClientSettings) { + return MongoClients.create(mongoClientSettings); + } + + @AfterEach + void tearDown() { + Fixture.getDefaultDatabase().drop(); + } + + /** + * + * Test 1: Operation Retry Uses Exponential Backoff. + */ + @Test + void operationRetryUsesExponentialBackoff() throws InterruptedException { + assumeTrue(serverVersionAtLeast(4, 4)); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorCode: 2,\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(getMongoClientSettings()); + FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + MongoCollection collection = dropAndGetCollection("operationRetryUsesExponentialBackoff", client); + long noBackoffTimeMillis = measureFailedInsertDuration(collection, false).toMillis(); + long withBackoffTimeMillis = measureFailedInsertDuration(collection, true).toMillis(); + long expectedMaxVarianceMillis = 300; + long maxTotalBackoffMillis = 300; + long actualAbsDiffMillis = Math.abs(withBackoffTimeMillis - (noBackoffTimeMillis + maxTotalBackoffMillis)); + assertTrue(actualAbsDiffMillis < expectedMaxVarianceMillis, + format("Expected actualAbsDiffMillis < %d ms, but was %d ms (|%d ms - (%d ms + %d ms)|)", + expectedMaxVarianceMillis, actualAbsDiffMillis, withBackoffTimeMillis, noBackoffTimeMillis, maxTotalBackoffMillis)); + } + } + + private static Duration measureFailedInsertDuration(final MongoCollection collection, final boolean retryBackoff) { + ExponentialBackoff.setTestJitterSupplier(() -> retryBackoff ? 1 : 0); + try { + StartTime startTime = StartTime.now(); + assertThrows(MongoServerException.class, () -> collection.insertOne(Document.parse("{a: 1}"))); + return startTime.elapsed(); + } finally { + ExponentialBackoff.clearTestJitterSupplier(); + } + } + + /** + * + * Test 3: Overload Errors are Retried a Maximum of {@code MAX_RETRIES} times. + */ + @Test + void overloadErrorsAreRetriedAtMostMaxRetriesTimes() throws InterruptedException { + overloadErrorsAreRetriedLimitedNumberOfTimes(null); + } + + /** + * + * Test 4: Overload Errors are Retried a Maximum of {@code maxAdaptiveRetries} times when configured. + */ + @Test + void overloadErrorsAreRetriedAtMostMaxAdaptiveRetriesTimesWhenConfigured() throws InterruptedException { + overloadErrorsAreRetriedLimitedNumberOfTimes(1); + } + + private void overloadErrorsAreRetriedLimitedNumberOfTimes(@Nullable final Integer maxAdaptiveRetries) + throws InterruptedException { + assumeTrue(serverVersionAtLeast(4, 4)); + TestCommandListener commandListener = new TestCommandListener(); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['find'],\n" + + " errorCode: 462,\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .maxAdaptiveRetries(maxAdaptiveRetries) + .addCommandListener(commandListener) + .build()); + FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + MongoCollection collection = dropAndGetCollection("overloadErrorsAreRetriedLimitedNumberOfTimes", client); + commandListener.reset(); + MongoServerException exception = assertThrows(MongoServerException.class, () -> collection.find().first()); + assertTrue(exception.hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL)); + assertTrue(exception.hasErrorLabel(RETRYABLE_ERROR_LABEL)); + int expectedAttempts = (maxAdaptiveRetries == null ? DEFAULT_MAX_ADAPTIVE_RETRIES : maxAdaptiveRetries) + 1; + assertEquals(expectedAttempts, commandListener.getCommandStartedEvents().size()); + } + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void runCommandPropagatesOverloadWhenSubsequentAttemptHasNoWritesPerformed() throws InterruptedException, ExecutionException { + assumeTrue(serverVersionAtLeast(4, 4)); + BsonDocument overloadFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['ping'],\n" + + " errorCode: 462,\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + BsonDocument noWritesPerformedFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['ping'],\n" + + " errorCode: 11602,\n" + + " errorLabels: ['" + NO_WRITES_PERFORMED_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + TestCommandListener commandListener = new TestCommandListener(); + try (ConfigureFailPointCommandListener swapListener = new ConfigureFailPointCommandListener( + noWritesPerformedFailPoint, + getPrimary(), + event -> event instanceof CommandFailedEvent && "ping".equals(event.getCommandName())); + MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .addCommandListener(swapListener) + .addCommandListener(commandListener) + .build()); + FailPoint ignored = FailPoint.enable(overloadFailPoint, getPrimary())) { + + MongoServerException exception = assertThrows(MongoServerException.class, + () -> client.getDatabase("admin").runCommand(BsonDocument.parse("{ping: 1}"))); + assertTrue(exception.hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL), + "Expected propagated original overload error, got: " + exception); + assertEquals(2, commandListener.getCommandStartedEvents("ping").size(), + "Expected exactly two ping attempts (overload retry + NoWritesPerformed terminal)"); + } + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void runCommandPropagatesRetryableWriteErrorAfterOverloadRetry() throws InterruptedException, ExecutionException { + assumeTrue(serverVersionAtLeast(4, 4)); + BsonDocument overloadFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['ping'],\n" + + " errorCode: 462,\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + BsonDocument retryableWriteErrorFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['ping'],\n" + + " errorCode: 11602,\n" + + " errorLabels: ['" + RETRYABLE_WRITE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + TestCommandListener commandListener = new TestCommandListener(); + try (ConfigureFailPointCommandListener swapListener = new ConfigureFailPointCommandListener( + retryableWriteErrorFailPoint, + getPrimary(), + event -> event instanceof CommandFailedEvent && "ping".equals(event.getCommandName())); + MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .addCommandListener(swapListener) + .addCommandListener(commandListener) + .build()); + FailPoint ignored = FailPoint.enable(overloadFailPoint, getPrimary())) { + MongoServerException exception = assertThrows(MongoServerException.class, + () -> client.getDatabase("admin").runCommand(BsonDocument.parse("{ping: 1}"))); + assertTrue(exception.hasErrorLabel(RETRYABLE_WRITE_ERROR_LABEL), + "Expected propagated terminal RetryableWriteError, got: " + exception); + assertEquals(2, commandListener.getCommandStartedEvents("ping").size(), + "Expected exactly two ping attempts (overload retry + RetryableWriteError terminal)"); + } + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void runCommandDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assertCommandNotRetriedOnRetryableWriteError("ping", + client -> client.getDatabase("admin").runCommand(BsonDocument.parse("{ping: 1}"))); + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void runCommandPropagatesRetryableReadErrorAfterOverloadRetry() throws InterruptedException, ExecutionException { + assumeTrue(serverVersionAtLeast(4, 4)); + BsonDocument overloadFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['ping'],\n" + + " errorCode: 462,\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + BsonDocument retryableReadErrorFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['ping'],\n" + + " errorCode: 11602\n" + + " }\n" + + "}\n"); + TestCommandListener commandListener = new TestCommandListener(); + try (ConfigureFailPointCommandListener swapListener = new ConfigureFailPointCommandListener( + retryableReadErrorFailPoint, + getPrimary(), + event -> event instanceof CommandFailedEvent && "ping".equals(event.getCommandName())); + MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .addCommandListener(swapListener) + .addCommandListener(commandListener) + .build()); + FailPoint ignored = FailPoint.enable(overloadFailPoint, getPrimary())) { + MongoServerException exception = assertThrows(MongoServerException.class, + () -> client.getDatabase("admin").runCommand(BsonDocument.parse("{ping: 1}"))); + assertEquals(11602, ((MongoCommandException) exception).getErrorCode(), + "Expected propagated terminal retryable-read-style error code, got: " + exception); + assertEquals(2, commandListener.getCommandStartedEvents("ping").size(), + "Expected exactly two ping attempts (overload retry + retryable-read-style terminal)"); + } + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void runCommandDoesNotRetryOnRetryableReadError() throws InterruptedException { + assertCommandNotRetriedOnRetryableReadError("ping", + client -> client.getDatabase("admin").runCommand(BsonDocument.parse("{ping: 1}"))); + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void clientBulkWriteGetMoreRetriesOverloadWhenRetryReadsEnabled() throws InterruptedException { + assumeTrue(serverVersionAtLeast(8, 0)); + BsonDocument overloadOnGetMoreOnce = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['getMore'],\n" + + " errorCode: 462,\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + TestCommandListener commandListener = new TestCommandListener(); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .retryWrites(false) + .retryReads(true) + .addCommandListener(commandListener) + .build())) { + try (FailPoint ignored = FailPoint.enable(overloadOnGetMoreOnce, getPrimary())) { + ClientBulkWriteResult result = executeClientBulkWrite(client); + assertEquals(2, result.getUpsertedCount()); + } + assertEquals(2, commandListener.getCommandStartedEvents("getMore").size(), + "Expected exactly two getMore attempts (overload retry + terminal success)"); + } + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void clientBulkWriteGetMoreExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assumeTrue(serverVersionAtLeast(8, 0)); + BsonDocument overloadOnGetMoreAlways = BsonDocument.parse( + "{" + + " configureFailPoint: 'failCommand'," + + " mode: {times: " + (DEFAULT_MAX_ADAPTIVE_RETRIES + 1) + "}," + + " data: {" + + " failCommands: ['getMore']," + + " errorCode: 462," + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']" + + " }" + + "}"); + TestCommandListener commandListener = new TestCommandListener(); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .retryWrites(false) + .retryReads(true) + .addCommandListener(commandListener) + .build())) { + try (FailPoint ignored = FailPoint.enable(overloadOnGetMoreAlways, getPrimary())) { + MongoServerException exception = assertThrows(MongoServerException.class, () -> executeClientBulkWrite(client)); + assertTrue(exception.hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL)); + } + assertEquals(DEFAULT_MAX_ADAPTIVE_RETRIES + 1, commandListener.getCommandStartedEvents("getMore").size(), + "Expected all overload retry attempts to be exhausted (initial + maxAdaptiveRetries)"); + } + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void clientBulkWriteGetMoreDoesNotRetryNonOverloadError() throws InterruptedException { + assumeTrue(serverVersionAtLeast(8, 0)); + BsonDocument retryableReadCodeOnGetMoreOnce = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['getMore'],\n" + + " errorCode: 11602\n" + + " }\n" + + "}\n"); + TestCommandListener commandListener = new TestCommandListener(); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .retryWrites(false) + .retryReads(true) + .addCommandListener(commandListener) + .build())) { + try (FailPoint ignored = FailPoint.enable(retryableReadCodeOnGetMoreOnce, getPrimary())) { + MongoServerException exception = assertThrows(MongoServerException.class, + () -> executeClientBulkWrite(client)); + assertEquals(11602, ((MongoCommandException) exception).getErrorCode(), + "Expected propagated non-overload error, got: " + exception); + } + assertEquals(1, commandListener.getCommandStartedEvents("getMore").size(), + "Expected exactly one getMore attempt (non-overload error is not retried)"); + } + } + + /** + * Coverage test (not part of the spec prose suite). + */ + @Test + void clientBulkWriteGetMoreDoesNotRetryOverloadWhenRetryReadsDisabled() throws InterruptedException { + assumeTrue(serverVersionAtLeast(8, 0)); + assertCommandNotRetriedWhenRetryReadsDisabled("getMore", BackpressureProseTest::executeClientBulkWrite); + } + + private static ClientBulkWriteResult executeClientBulkWrite(final MongoClient client) { + // Two upserts whose result docs each approach maxBsonObjectSize force the response cursor to span two + // batches, guaranteeing a getMore. + int maxBsonObjectSize = client.getDatabase("admin") + .runCommand(new Document("hello", 1)).getInteger("maxBsonObjectSize"); + MongoNamespace namespace = new MongoNamespace(getDefaultDatabaseName(), BackpressureProseTest.class.getName()); + List models = asList( + ClientNamespacedWriteModel.updateOne( + namespace, + Filters.eq(join("", nCopies(maxBsonObjectSize / 2, "a"))), + Updates.set("x", 1), + clientUpdateOneOptions().upsert(true)), + ClientNamespacedWriteModel.updateOne( + namespace, + Filters.eq(join("", nCopies(maxBsonObjectSize / 2, "b"))), + Updates.set("x", 1), + clientUpdateOneOptions().upsert(true))); + return client.bulkWrite(models, clientBulkWriteOptions().verboseResults(true)); + } + + @Test + void createViewExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assertCommandExhaustsOverloadRetriesAndThrows("create", + client -> client.getDatabase(NAMESPACE.getDatabaseName()) + .createView(NAMESPACE.getCollectionName() + "View", NAMESPACE.getCollectionName(), + singletonList(match(Filters.empty())))); + } + + @Test + void dropCollectionExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assertCommandExhaustsOverloadRetriesAndThrows("drop", client -> getCollection(client).drop()); + } + + @Test + void dropDatabaseExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assertCommandExhaustsOverloadRetriesAndThrows("dropDatabase", + client -> client.getDatabase(NAMESPACE.getDatabaseName()).drop()); + } + + @Test + void renameCollectionExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assertCommandExhaustsOverloadRetriesAndThrows("renameCollection", + client -> getCollection(client).renameCollection( + new MongoNamespace(NAMESPACE.getDatabaseName(), NAMESPACE.getCollectionName() + "Renamed"))); + } + + @Test + void createSearchIndexesExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + assertCommandExhaustsOverloadRetriesAndThrows("createSearchIndexes", + client -> getCollection(client).createSearchIndexes( + singletonList(new SearchIndexModel(new Document("mappings", new Document("dynamic", true)))))); + } + + @Test + void updateSearchIndexExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + assertCommandExhaustsOverloadRetriesAndThrows("updateSearchIndex", + client -> getCollection(client).updateSearchIndex("default", new Document("mappings", new Document("dynamic", true)))); + } + + @Test + void dropSearchIndexExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + assertCommandExhaustsOverloadRetriesAndThrows("dropSearchIndex", + client -> getCollection(client).dropSearchIndex("default")); + } + + @Test + void createCollectionExhaustsOverloadRetriesAndThrows() throws InterruptedException { + assertCommandExhaustsOverloadRetriesAndThrows("create", + client -> client.getDatabase(NAMESPACE.getDatabaseName()).createCollection(NAMESPACE.getCollectionName())); + } + + + @Test + void createViewDoesNotRetryOverloadWhenRetryWritesDisabled() throws InterruptedException { + assertCommandNotRetriedWhenRetryWritesDisabled("create", + client -> client.getDatabase(NAMESPACE.getDatabaseName()) + .createView(NAMESPACE.getCollectionName() + "View", NAMESPACE.getCollectionName(), + singletonList(match(Filters.empty())))); + } + + @Test + void dropCollectionDoesNotRetryOverloadWhenRetryWritesDisabled() throws InterruptedException { + assertCommandNotRetriedWhenRetryWritesDisabled("drop", client -> getCollection(client).drop()); + } + + @Test + void dropDatabaseDoesNotRetryOverloadWhenRetryWritesDisabled() throws InterruptedException { + assertCommandNotRetriedWhenRetryWritesDisabled("dropDatabase", + client -> client.getDatabase(NAMESPACE.getDatabaseName()).drop()); + } + + @Test + void renameCollectionDoesNotRetryOverloadWhenRetryWritesDisabled() throws InterruptedException { + assertCommandNotRetriedWhenRetryWritesDisabled("renameCollection", + client -> getCollection(client).renameCollection( + new MongoNamespace(NAMESPACE.getDatabaseName(), NAMESPACE.getCollectionName() + "Renamed"))); + } + + @Test + void createCollectionDoesNotRetryOverloadWhenRetryWritesDisabled() throws InterruptedException { + assertCommandNotRetriedWhenRetryWritesDisabled("create", + client -> client.getDatabase(NAMESPACE.getDatabaseName()).createCollection(NAMESPACE.getCollectionName())); + } + + @Test + void createSearchIndexesDoesNotRetryOverloadWhenRetryWritesDisabled() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + + assertCommandNotRetriedWhenRetryWritesDisabled("createSearchIndexes", + client -> getCollection(client).createSearchIndexes( + singletonList(new SearchIndexModel(new Document("mappings", new Document("dynamic", true)))))); + } + + @Test + void updateSearchIndexDoesNotRetryOverloadWhenRetryWritesDisabled() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + + assertCommandNotRetriedWhenRetryWritesDisabled("updateSearchIndex", + client -> getCollection(client).updateSearchIndex("default", new Document("mappings", new Document("dynamic", true)))); + } + + @Test + void dropSearchIndexDoesNotRetryOverloadWhenRetryWritesDisabled() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + + assertCommandNotRetriedWhenRetryWritesDisabled("dropSearchIndex", + client -> getCollection(client).dropSearchIndex("default")); + } + + @Test + void createViewDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assertCommandNotRetriedOnRetryableWriteError("create", + client -> client.getDatabase(NAMESPACE.getDatabaseName()) + .createView(NAMESPACE.getCollectionName() + "View", NAMESPACE.getCollectionName(), + singletonList(match(Filters.empty())))); + } + + @Test + void dropCollectionDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assertCommandNotRetriedOnRetryableWriteError("drop", client -> getCollection(client).drop()); + } + + @Test + void dropDatabaseDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assertCommandNotRetriedOnRetryableWriteError("dropDatabase", + client -> client.getDatabase(NAMESPACE.getDatabaseName()).drop()); + } + + @Test + void renameCollectionDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assertCommandNotRetriedOnRetryableWriteError("renameCollection", + client -> getCollection(client).renameCollection( + new MongoNamespace(NAMESPACE.getDatabaseName(), NAMESPACE.getCollectionName() + "Renamed"))); + } + + @Test + void createCollectionDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assertCommandNotRetriedOnRetryableWriteError("create", + client -> client.getDatabase(NAMESPACE.getDatabaseName()).createCollection(NAMESPACE.getCollectionName())); + } + + @Test + void createSearchIndexesDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + + assertCommandNotRetriedOnRetryableWriteError("createSearchIndexes", + client -> getCollection(client).createSearchIndexes( + singletonList(new SearchIndexModel(new Document("mappings", new Document("dynamic", true)))))); + } + + @Test + void updateSearchIndexDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + + assertCommandNotRetriedOnRetryableWriteError("updateSearchIndex", + client -> getCollection(client).updateSearchIndex("default", new Document("mappings", new Document("dynamic", true)))); + } + + @Test + void dropSearchIndexDoesNotRetryOnRetryableWriteError() throws InterruptedException { + assumeTrue(serverVersionAtLeast(6, 0)); + // assumeTrue(hasAtlasSearchIndexHelperEnabled(), "Atlas Search Index tests are disabled"); + assertCommandNotRetriedOnRetryableWriteError("dropSearchIndex", + client -> getCollection(client).dropSearchIndex("default")); + } + + private static Stream createEncryptedCollectionRetriesEachCommandIndependently() { + String collectionName = NAMESPACE.getCollectionName(); + List commandSequence = asList( + new BsonDocument("create", new BsonString(ENCRYPTED_STATE_COLLECTION_PREFIX + collectionName + ".esc")), + new BsonDocument("create", new BsonString(ENCRYPTED_STATE_COLLECTION_PREFIX + collectionName + ".ecoc")), + new BsonDocument("create", new BsonString(collectionName)), + new BsonDocument("createIndexes", new BsonString(collectionName))); + // QE createCollection issues the command sequence above; we generate one variant per command in round-robin, + // where that command is the one expected to fail and exhaust its overload retries. + return IntStream.range(0, commandSequence.size()).mapToObj(failingCommandIndex -> { + BsonDocument failingCommand = commandSequence.get(failingCommandIndex); + List expectedCommands = new ArrayList<>(commandSequence.subList(0, failingCommandIndex)); + expectedCommands.addAll(nCopies(DEFAULT_MAX_ADAPTIVE_RETRIES + 1, failingCommand)); + return Arguments.of(failingCommand, failingCommandIndex, expectedCommands); + }); + } + + @ParameterizedTest(name = "createEncryptedCollectionRetriesEachCommandIndependently. failingCommand={0}, failPointSkip=={1}") + @MethodSource + void createEncryptedCollectionRetriesEachCommandIndependently( + final BsonDocument failingCommand, + final int failPointSkip, + final List expectedCommands) throws InterruptedException { + assumeTrue(serverVersionAtLeast(7, 0)); + assumeFalse(isStandalone(), "Encrypted collections are not supported on standalone"); + TestCommandListener commandListener = new TestCommandListener(); + // The failPoint fails every command of the sequence, so `skip` is the number of commands preceding the + // failing one. It lets them pass through and then fails every subsequent one, so that all the retries of a + // single command in the sequence are exhausted. + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {skip: " + failPointSkip + "},\n" + + " data: {\n" + + " failCommands: ['create', 'createIndexes'],\n" + + " errorCode: " + SYSTEM_OVERLOAD_ERROR_CODE + ",\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .addCommandListener(commandListener) + .build())) { + MongoDatabase database = client.getDatabase(NAMESPACE.getDatabaseName()); + try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + commandListener.reset(); + MongoServerException e = assertThrows(MongoServerException.class, () -> database.createCollection( + NAMESPACE.getCollectionName(), encryptedCollectionOptions())); + assertEquals(SYSTEM_OVERLOAD_ERROR_CODE, e.getCode()); + assertCommandsStarted(expectedCommands, commandListener); + } + } + } + + private static Stream dropEncryptedCollectionRetriesEachCommandIndependently() { + String collectionName = NAMESPACE.getCollectionName(); + List commandSequence = asList( + new BsonDocument("drop", new BsonString(ENCRYPTED_STATE_COLLECTION_PREFIX + collectionName + ".esc")), + new BsonDocument("drop", new BsonString(ENCRYPTED_STATE_COLLECTION_PREFIX + collectionName + ".ecoc")), + new BsonDocument("drop", new BsonString(collectionName))); + return IntStream.range(0, commandSequence.size()).mapToObj(failingCommandIndex -> { + BsonDocument failingCommand = commandSequence.get(failingCommandIndex); + List expectedCommands = new ArrayList<>(commandSequence.subList(0, failingCommandIndex)); + expectedCommands.addAll(nCopies(DEFAULT_MAX_ADAPTIVE_RETRIES + 1, failingCommand)); + return Arguments.of(failingCommand, failingCommandIndex, expectedCommands); + }); + } + + @ParameterizedTest(name = "dropEncryptedCollectionRetriesEachCommandIndependently. failingCommand={0}, failPointSkip=={1}") + @MethodSource + void dropEncryptedCollectionRetriesEachCommandIndependently( + final BsonDocument failingCommand, + final int failPointSkip, + final List expectedCommands) throws InterruptedException { + assumeTrue(serverVersionAtLeast(7, 0)); + assumeFalse(isStandalone(), "Encrypted collections are not supported on standalone"); + TestCommandListener commandListener = new TestCommandListener(); + // The failPoint fails every command of the sequence, so `skip` is the number of the commands preceding the + // failing one. It lets them pass through and then fails every subsequent one, so that all the retries of a + // single command in the sequence are exhausted. + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {skip: " + failPointSkip + "},\n" + + " data: {\n" + + " failCommands: ['drop'],\n" + + " errorCode: " + SYSTEM_OVERLOAD_ERROR_CODE + ",\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .addCommandListener(commandListener) + .build())) { + try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + commandListener.reset(); + MongoServerException e = assertThrows(MongoServerException.class, () -> getCollection(client).drop( + new DropCollectionOptions().encryptedFields(encryptedCollectionOptions().getEncryptedFields()))); + assertEquals(SYSTEM_OVERLOAD_ERROR_CODE, e.getCode()); + assertCommandsStarted(expectedCommands, commandListener); + } + } + } + + private void assertCommandExhaustsOverloadRetriesAndThrows(final String failingCommandName, final Consumer operation) + throws InterruptedException { + assumeTrue(serverVersionAtLeast(4, 4)); + TestCommandListener commandListener = new TestCommandListener(); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['" + failingCommandName + "'],\n" + + " errorCode: " + SYSTEM_OVERLOAD_ERROR_CODE + ",\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .addCommandListener(commandListener) + .build())) { + try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + commandListener.reset(); + MongoServerException exception = assertThrows(MongoServerException.class, () -> operation.accept(client)); + assertEquals(SYSTEM_OVERLOAD_ERROR_CODE, exception.getCode()); + assertTrue(exception.hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL)); + assertTrue(exception.hasErrorLabel(RETRYABLE_ERROR_LABEL)); + assertEquals(DEFAULT_MAX_ADAPTIVE_RETRIES + 1, + commandListener.getCommandStartedEvents(failingCommandName).size(), + "Expected initial attempt plus " + DEFAULT_MAX_ADAPTIVE_RETRIES + " overload retries"); + } + } + } + + private void assertCommandNotRetriedOnRetryableWriteError(final String failingCommandName, final Consumer operation) + throws InterruptedException { + assertCommandNotRetriedOnNonOverloadError(failingCommandName, operation, RETRYABLE_WRITE_ERROR_LABEL); + } + + private void assertCommandNotRetriedOnRetryableReadError(final String failingCommandName, final Consumer operation) + throws InterruptedException { + assertCommandNotRetriedOnNonOverloadError(failingCommandName, operation, null); + } + + private void assertCommandNotRetriedOnNonOverloadError(final String failingCommandName, final Consumer operation, + @Nullable final String errorLabel) + throws InterruptedException { + assumeTrue(serverVersionAtLeast(4, 4)); + TestCommandListener commandListener = new TestCommandListener(); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {times: 1},\n" + + " data: {\n" + + " failCommands: ['" + failingCommandName + "'],\n" + + " errorCode: " + RETRYABLE_ERROR_CODE + ",\n" + + " errorLabels: [" + (errorLabel == null ? "" : "'" + errorLabel + "'") + "]\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .addCommandListener(commandListener) + .build())) { + try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + commandListener.reset(); + MongoServerException exception = assertThrows(MongoServerException.class, () -> operation.accept(client)); + assertEquals(RETRYABLE_ERROR_CODE, ((MongoCommandException) exception).getErrorCode(), + format("Expected the propagated non-overload error, got: %s", exception)); + if (errorLabel != null) { + assertTrue(exception.hasErrorLabel(errorLabel), + format("Expected the propagated error to have the %s label, got: %s", errorLabel, exception)); + } + assertEquals(1, commandListener.getCommandStartedEvents(failingCommandName).size(), + format("Expected exactly one attempt of %s, as the overload-only policy does not retry" + + " non-overload errors", failingCommandName)); + } + } + } + + private void assertCommandNotRetriedWhenRetryWritesDisabled(final String failingCommandName, final Consumer operation) + throws InterruptedException { + assertCommandNotOverloadRetried(failingCommandName, operation, true, false); + } + + private void assertCommandNotRetriedWhenRetryReadsDisabled(final String failingCommandName, final Consumer operation) + throws InterruptedException { + assertCommandNotOverloadRetried(failingCommandName, operation, false, true); + } + + private void assertCommandNotOverloadRetried(final String failingCommandName, final Consumer operation, + final boolean retryReads, final boolean retryWrites) + throws InterruptedException { + assumeTrue(serverVersionAtLeast(4, 4)); + TestCommandListener commandListener = new TestCommandListener(); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['" + failingCommandName + "'],\n" + + " errorCode: " + SYSTEM_OVERLOAD_ERROR_CODE + ",\n" + + " errorLabels: ['" + SYSTEM_OVERLOADED_ERROR_LABEL + "', '" + RETRYABLE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (MongoClient client = createClient(MongoClientSettings.builder(getMongoClientSettings()) + .retryReads(retryReads) + .retryWrites(retryWrites) + .addCommandListener(commandListener) + .build())) { + try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + commandListener.reset(); + MongoServerException exception = assertThrows(MongoServerException.class, () -> operation.accept(client)); + assertEquals(SYSTEM_OVERLOAD_ERROR_CODE, exception.getCode()); + assertTrue(exception.hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL), + "Expected propagated overload error, got: " + exception); + assertTrue(exception.hasErrorLabel(RETRYABLE_ERROR_LABEL)); + assertEquals(1, commandListener.getCommandStartedEvents(failingCommandName).size(), + format("Expected exactly one attempt of %s, as retryReads=%b, retryWrites=%b disable the" + + " overload retry", failingCommandName, retryReads, retryWrites)); + } + } + } + + private static MongoCollection dropAndGetCollection(final String name, final MongoClient client) { + MongoCollection result = client.getDatabase(getDefaultDatabaseName()).getCollection(name); + result.drop(); + return result; + } + + /** + * Asserts that the commands started by the {@code commandListener} are exactly the {@code expectedCommands}, in + * order. Each expected command is required to be a subset of the actual one, so that it has to specify only the + * entries identifying the command. + */ + private static void assertCommandsStarted(final List expectedCommands, + final TestCommandListener commandListener) { + List actualCommands = commandListener.getCommandStartedEvents().stream() + .map(CommandStartedEvent::getCommand) + .collect(Collectors.toList()); + assertEquals(expectedCommands.size(), actualCommands.size(), + format("Expected %s but observed %s", expectedCommands, actualCommands)); + for (int i = 0; i < expectedCommands.size(); i++) { + BsonDocument expected = expectedCommands.get(i); + BsonDocument actual = actualCommands.get(i); + assertTrue(actual.entrySet().containsAll(expected.entrySet()), + format("Expected the command at index %d to contain %s but it was %s", i, expected, actual)); + } + } + + private static CreateCollectionOptions encryptedCollectionOptions() { + return new CreateCollectionOptions().encryptedFields(BsonDocument.parse( + "{fields: [{path: 'ssn', bsonType: 'string'," + + " keyId: {$binary: {base64: 'AAAAAAAAAAAAAAAAAAAAAA==', subType: '04'}}}]}")); + } + private static MongoCollection getCollection(final MongoClient client) { + return client.getDatabase(NAMESPACE.getDatabaseName()).getCollection(NAMESPACE.getCollectionName()); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java index 4dcbc4d1a0f..bbabcd8f61a 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java @@ -19,6 +19,9 @@ import com.mongodb.MongoClientSettings; import com.mongodb.client.gridfs.GridFSBucket; import com.mongodb.client.gridfs.GridFSBuckets; +import com.mongodb.internal.time.ExponentialBackoff; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; /** @@ -36,6 +39,23 @@ protected GridFSBucket createGridFsBucket(final MongoDatabase mongoDatabase, fin return GridFSBuckets.create(mongoDatabase, bucketName); } + @BeforeEach + @Override + public void setUp() { + super.setUp(); + ExponentialBackoff.setTestJitterSupplier(() -> 0); + } + + @AfterEach + @Override + public void tearDown() throws InterruptedException { + try { + super.tearDown(); + } finally { + ExponentialBackoff.clearTestJitterSupplier(); + } + } + @Override protected boolean isAsync() { return false; diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java index cb62545f4e4..9fc2f0e6acc 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java @@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assumptions.assumeFalse; - // See https://github.com/mongodb/specifications/tree/master/source/client-side-operation-timeout/tests public class ClientSideOperationTimeoutTest extends UnifiedSyncTest { diff --git a/driver-sync/src/test/functional/com/mongodb/client/ContextProviderTest.java b/driver-sync/src/test/functional/com/mongodb/client/ContextProviderTest.java index caf676a8ab7..c0247c9c7a2 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ContextProviderTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ContextProviderTest.java @@ -19,6 +19,7 @@ import com.mongodb.ContextProvider; import com.mongodb.RequestContext; import com.mongodb.WriteConcern; +import com.mongodb.annotations.NotThreadSafe; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandListener; import com.mongodb.event.CommandStartedEvent; @@ -206,6 +207,7 @@ public void contextShouldBeAvailableInCommandEvents() { } } + @NotThreadSafe private static final class TestCommandListener implements CommandListener { private int numCommandStartedEventsWithExpectedContext; private int numCommandSucceededEventsWithExpectedContext; diff --git a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java index d269a3cad57..0e6e751bd61 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java @@ -21,6 +21,7 @@ import com.mongodb.Function; import com.mongodb.MongoBulkWriteException; import com.mongodb.MongoClientSettings; +import com.mongodb.MongoException; import com.mongodb.MongoNamespace; import com.mongodb.MongoWriteConcernException; import com.mongodb.MongoWriteException; @@ -36,7 +37,9 @@ import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.client.test.CollectionHelper; import com.mongodb.event.CommandStartedEvent; +import com.mongodb.event.CommandSucceededEvent; import com.mongodb.internal.connection.TestCommandListener; +import com.mongodb.internal.event.ConfigureFailPointCommandListener; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonDocumentWrapper; @@ -74,6 +77,7 @@ import static com.mongodb.client.model.bulk.ClientBulkWriteOptions.clientBulkWriteOptions; import static com.mongodb.client.model.bulk.ClientNamespacedWriteModel.insertOne; import static com.mongodb.client.model.bulk.ClientUpdateOneOptions.clientUpdateOneOptions; +import static com.mongodb.internal.operation.CommandOperationHelper.RETRYABLE_WRITE_ERROR_LABEL; import static java.lang.String.join; import static java.util.Arrays.asList; import static java.util.Collections.nCopies; @@ -117,12 +121,14 @@ void testWriteConcernErrInfoIsPropagated() throws InterruptedException { FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { MongoWriteConcernException actual = assertThrows(MongoWriteConcernException.class, () -> droppedCollection(client, Document.class).insertOne(Document.parse("{ x: 1 }"))); - assertEquals(actual.getWriteConcernError().getCode(), 100); + assertEquals(100, actual.getWriteConcernError().getCode()); assertEquals("UnsatisfiableWriteConcern", actual.getWriteConcernError().getCodeName()); - assertEquals(actual.getWriteConcernError().getDetails(), new BsonDocument("writeConcern", - new BsonDocument("w", new BsonInt32(2)) - .append("wtimeout", new BsonInt32(0)) - .append("provenance", new BsonString("clientSupplied")))); + assertEquals( + new BsonDocument("writeConcern", + new BsonDocument("w", new BsonInt32(2)) + .append("wtimeout", new BsonInt32(0)) + .append("provenance", new BsonString("clientSupplied"))), + actual.getWriteConcernError().getDetails()); } } @@ -211,7 +217,7 @@ void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytes() { @DisplayName("5. MongoClient.bulkWrite collects WriteConcernErrors across batches") @Test @SuppressWarnings("try") - protected void testBulkWriteCollectsWriteConcernErrorsAcrossBatches() throws InterruptedException { + void testBulkWriteCollectsWriteConcernErrorsAcrossBatches() throws InterruptedException { assumeTrue(serverVersionAtLeast(8, 0)); TestCommandListener commandListener = new TestCommandListener(); BsonDocument failPointDocument = new BsonDocument("configureFailPoint", new BsonString("failCommand")) @@ -240,7 +246,7 @@ protected void testBulkWriteCollectsWriteConcernErrorsAcrossBatches() throws Int @DisplayName("6. MongoClient.bulkWrite handles individual WriteErrors across batches") @ParameterizedTest(name = "6. MongoClient.bulkWrite handles individual WriteErrors across batches--ordered:{0}") @ValueSource(booleans = {false, true}) - protected void testBulkWriteHandlesWriteErrorsAcrossBatches(final boolean ordered) { + void testBulkWriteHandlesWriteErrorsAcrossBatches(final boolean ordered) { assumeTrue(serverVersionAtLeast(8, 0)); TestCommandListener commandListener = new TestCommandListener(); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() @@ -270,48 +276,84 @@ void testBulkWriteHandlesCursorRequiringGetMore() { @DisplayName("8. MongoClient.bulkWrite handles a cursor requiring getMore within a transaction") @Test - protected void testBulkWriteHandlesCursorRequiringGetMoreWithinTransaction() { + void testBulkWriteHandlesCursorRequiringGetMoreWithinTransaction() { assumeTrue(serverVersionAtLeast(8, 0)); assumeFalse(isStandalone()); assertBulkWriteHandlesCursorRequiringGetMore(true); } + /** + * This test is not from the specification. + */ + @DisplayName("MongoClient.bulkWrite must not retry the bulkWrite command when the corresponding getMore command fails with an error" + + " eligible for retry under the write retry policy") + @Test + void testBulkWriteCommandNotRetriedWhenGetMoreFails() throws Exception { + assumeTrue(serverVersionAtLeast(8, 0)); + TestCommandListener commandListener = new TestCommandListener(); + BsonDocument configureFailPointFromListener = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: ['getMore'],\n" + + " errorCode: 6,\n" + + " errorLabels: ['" + RETRYABLE_WRITE_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + try (ConfigureFailPointCommandListener failGetMoreAfterBulkWrite = + new ConfigureFailPointCommandListener(configureFailPointFromListener, getPrimary(), commandEvent -> + (commandEvent instanceof CommandSucceededEvent) && commandEvent.getCommandName().equals("bulkWrite")); + MongoClient client = createMongoClient(getMongoClientSettingsBuilder() + .retryWrites(true) + .addCommandListener(commandListener) + .addCommandListener(failGetMoreAfterBulkWrite))) { + assertThrows(MongoException.class, () -> clientBulkWriteWithGetMore(client, false)); + } finally { + assertEquals(1, commandListener.getCommandStartedEvents("bulkWrite").size()); + assertEquals(1, commandListener.getCommandStartedEvents("getMore").size()); + assertEquals(1, commandListener.getCommandFailedEvents("getMore").size()); + } + } + private void assertBulkWriteHandlesCursorRequiringGetMore(final boolean transaction) { TestCommandListener commandListener = new TestCommandListener(); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() .retryWrites(false) .addCommandListener(commandListener))) { - int maxBsonObjectSize = droppedDatabase(client).runCommand(new Document("hello", 1)).getInteger("maxBsonObjectSize"); - try (ClientSession session = transaction ? client.startSession() : null) { - BiFunction, ClientBulkWriteOptions, ClientBulkWriteResult> bulkWrite = - (models, options) -> session == null - ? client.bulkWrite(models, options) - : client.bulkWrite(session, models, options); - Supplier action = () -> bulkWrite.apply(asList( - ClientNamespacedWriteModel.updateOne( - NAMESPACE, - Filters.eq(join("", nCopies(maxBsonObjectSize / 2, "a"))), - Updates.set("x", 1), - clientUpdateOneOptions().upsert(true)), - ClientNamespacedWriteModel.updateOne( - NAMESPACE, - Filters.eq(join("", nCopies(maxBsonObjectSize / 2, "b"))), - Updates.set("x", 1), - clientUpdateOneOptions().upsert(true))), - clientBulkWriteOptions().verboseResults(true) - ); - - ClientBulkWriteResult result = transaction ? runInTransaction(session, action) : action.get(); - assertEquals(2, result.getUpsertedCount()); - assertEquals(2, result.getVerboseResults().orElseThrow(Assertions::fail).getUpdateResults().size()); - assertEquals(1, commandListener.getCommandStartedEvents("bulkWrite").size()); - } + ClientBulkWriteResult result = clientBulkWriteWithGetMore(client, transaction); + assertEquals(2, result.getUpsertedCount()); + assertEquals(2, result.getVerboseResults().orElseThrow(Assertions::fail).getUpdateResults().size()); + assertEquals(1, commandListener.getCommandStartedEvents("getMore").size()); + } + } + + private static ClientBulkWriteResult clientBulkWriteWithGetMore(final MongoClient client, final boolean transaction) { + int maxBsonObjectSize = droppedDatabase(client).runCommand(new Document("hello", 1)).getInteger("maxBsonObjectSize"); + try (ClientSession session = transaction ? client.startSession() : null) { + BiFunction, ClientBulkWriteOptions, ClientBulkWriteResult> bulkWrite = + (models, options) -> session == null + ? client.bulkWrite(models, options) + : runInTransaction(session, () -> client.bulkWrite(session, models, options)); + return bulkWrite.apply(asList( + ClientNamespacedWriteModel.updateOne( + NAMESPACE, + Filters.eq(join("", nCopies(maxBsonObjectSize / 2, "a"))), + Updates.set("x", 1), + clientUpdateOneOptions().upsert(true)), + ClientNamespacedWriteModel.updateOne( + NAMESPACE, + Filters.eq(join("", nCopies(maxBsonObjectSize / 2, "b"))), + Updates.set("x", 1), + clientUpdateOneOptions().upsert(true))), + clientBulkWriteOptions().verboseResults(true) + ); } } @DisplayName("11. MongoClient.bulkWrite batch splits when the addition of a new namespace exceeds the maximum message size") @Test - protected void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo() { + void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo() { assumeTrue(serverVersionAtLeast(8, 0)); assertAll( () -> { @@ -382,7 +424,7 @@ private void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo( @DisplayName("12. MongoClient.bulkWrite returns an error if no operations can be added to ops") @ParameterizedTest(name = "12. MongoClient.bulkWrite returns an error if no operations can be added to ops--tooLarge:{0}") @ValueSource(strings = {"document", "namespace"}) - protected void testBulkWriteSplitsErrorsForTooLargeOpsOrNsInfo(final String tooLarge) { + void testBulkWriteSplitsErrorsForTooLargeOpsOrNsInfo(final String tooLarge) { assumeTrue(serverVersionAtLeast(8, 0)); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder())) { int maxMessageSizeBytes = droppedDatabase(client).runCommand(new Document("hello", 1)).getInteger("maxMessageSizeBytes"); @@ -410,7 +452,7 @@ protected void testBulkWriteSplitsErrorsForTooLargeOpsOrNsInfo(final String tooL @DisplayName("13. MongoClient.bulkWrite returns an error if auto-encryption is configured") @Test - protected void testBulkWriteErrorsForAutoEncryption() { + void testBulkWriteErrorsForAutoEncryption() { assumeTrue(serverVersionAtLeast(8, 0)); HashMap awsKmsProviderProperties = new HashMap<>(); awsKmsProviderProperties.put("accessKeyId", "foo"); @@ -431,7 +473,7 @@ protected void testBulkWriteErrorsForAutoEncryption() { @DisplayName("15. MongoClient.bulkWrite with unacknowledged write concern uses w:0 for all batches") @Test - protected void testWriteConcernOfAllBatchesWhenUnacknowledgedRequested() { + void testWriteConcernOfAllBatchesWhenUnacknowledgedRequested() { assumeTrue(serverVersionAtLeast(8, 0)); TestCommandListener commandListener = new TestCommandListener(); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder().addCommandListener(commandListener) @@ -468,7 +510,7 @@ protected void testWriteConcernOfAllBatchesWhenUnacknowledgedRequested() { @DisplayName("insertMustGenerateIdAtMostOnce") @ParameterizedTest(name = "insertMustGenerateIdAtMostOnce--documentClass:{0}, expectIdGenerated:{1}") @MethodSource("insertMustGenerateIdAtMostOnceArgs") - protected void insertMustGenerateIdAtMostOnce( + void insertMustGenerateIdAtMostOnce( final Class documentClass, final boolean expectIdGenerated, final Supplier documentSupplier) { @@ -564,11 +606,11 @@ protected MongoClient createMongoClient(final MongoClientSettings.Builder mongoC return MongoClients.create(mongoClientSettingsBuilder.build()); } - private MongoCollection droppedCollection(final MongoClient client, final Class documentClass) { + private static MongoCollection droppedCollection(final MongoClient client, final Class documentClass) { return droppedDatabase(client).getCollection(NAMESPACE.getCollectionName(), documentClass); } - private MongoDatabase droppedDatabase(final MongoClient client) { + private static MongoDatabase droppedDatabase(final MongoClient client) { MongoDatabase database = client.getDatabase(NAMESPACE.getDatabaseName()); database.drop(); return database; diff --git a/driver-sync/src/test/functional/com/mongodb/client/FailPoint.java b/driver-sync/src/test/functional/com/mongodb/client/FailPoint.java index 736ad8976d9..6e98f69bcea 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/FailPoint.java +++ b/driver-sync/src/test/functional/com/mongodb/client/FailPoint.java @@ -81,8 +81,7 @@ private static final class Guard implements AutoCloseable { /** * May be invoked at most once. - * - * @see #close() + * Must not be invoked if {@link #close()} was invoked. */ FailPoint intoFailPoint() { assertFalse(consumed); @@ -93,10 +92,13 @@ FailPoint intoFailPoint() { /** * Invokes {@link #disableAndClose(BsonDocument, MongoClient)} unless {@link #intoFailPoint()} was invoked. + *

      + * Idempotent. */ @Override public void close() { if (!consumed) { + consumed = true; disableAndClose(failPointDocument, client); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/MongoClientTest.java b/driver-sync/src/test/functional/com/mongodb/client/MongoClientTest.java index 6d3413f032a..c7912a25741 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/MongoClientTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/MongoClientTest.java @@ -25,8 +25,11 @@ import com.mongodb.event.ClusterOpeningEvent; import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; +import com.mongodb.internal.connection.StreamFactoryFactory; import com.mongodb.internal.mockito.MongoMockito; +import com.mongodb.internal.thread.AsyncClientExecutor; import org.junit.jupiter.api.Test; +import org.mockito.InOrder; import org.mockito.Mockito; import java.util.concurrent.CompletableFuture; @@ -35,11 +38,12 @@ import java.util.concurrent.TimeoutException; import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; -class MongoClientTest { +public final class MongoClientTest { @SuppressWarnings("try") @Test @@ -63,38 +67,45 @@ public void clusterOpening(final ClusterOpeningEvent event) { } @Test - void shouldCloseExternalResources() throws Exception { + void close() { + assertClose((cluster, mongoDriverInformation, streamFactoryFactory, clientExecutor) -> + new MongoClientImpl( + cluster, + mongoDriverInformation, + MongoClientSettings.builder().build(), + streamFactoryFactory, + clientExecutor)); + } - //given + public static void assertClose(final MongoClientCreator clientCreator) { MongoDriverInformation mongoDriverInformation = MongoDriverInformation.builder().build(); - Cluster cluster = MongoMockito.mock( - Cluster.class, - mockedCluster -> { - doNothing().when(mockedCluster).close(); - when(mockedCluster.getClientMetadata()) - .thenReturn(new ClientMetadata("test", mongoDriverInformation)); - }); - AutoCloseable externalResource = MongoMockito.mock( - AutoCloseable.class, - mockedExternalResource -> { - try { - doNothing().when(mockedExternalResource).close(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - - MongoClientImpl mongoClient = new MongoClientImpl( + Cluster cluster = MongoMockito.mock(Cluster.class, mock -> { + doNothing().when(mock).close(); + when(mock.getClientMetadata()).thenReturn(new ClientMetadata("test", mongoDriverInformation)); + }); + StreamFactoryFactory streamFactoryFactory = MongoMockito.mock(StreamFactoryFactory.class, mock -> { + doNothing().when(mock).close(); + }); + AsyncClientExecutor clientExecutor = MongoMockito.mock(AsyncClientExecutor.class, mock -> { + doNothing().when(mock).close(); + }); + AutoCloseable mongoClient = clientCreator.create( cluster, - MongoClientSettings.builder().build(), mongoDriverInformation, - externalResource); - - //when - mongoClient.close(); + streamFactoryFactory, + clientExecutor); + assertDoesNotThrow(() -> mongoClient.close()); + InOrder inOrder = Mockito.inOrder(cluster, clientExecutor, streamFactoryFactory); + inOrder.verify(cluster).close(); + inOrder.verify(clientExecutor).close(); + inOrder.verify(streamFactoryFactory).close(); + } - //then - Mockito.verify(externalResource).close(); - Mockito.verify(cluster).close(); + public interface MongoClientCreator { + AutoCloseable create( + Cluster cluster, + MongoDriverInformation mongoDriverInformation, + StreamFactoryFactory streamFactoryFactory, + AsyncClientExecutor clientExecutor); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/MongoWriteConcernWithResponseExceptionTest.java b/driver-sync/src/test/functional/com/mongodb/client/MongoWriteConcernWithResponseExceptionTest.java index 6f90b3f5f01..0998bea9e8d 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/MongoWriteConcernWithResponseExceptionTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/MongoWriteConcernWithResponseExceptionTest.java @@ -16,26 +16,24 @@ package com.mongodb.client; -import com.mongodb.Function; import com.mongodb.MongoClientSettings; import com.mongodb.MongoWriteConcernException; import com.mongodb.ServerAddress; -import com.mongodb.assertions.Assertions; import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandFailedEvent; -import com.mongodb.event.CommandListener; import com.mongodb.event.CommandSucceededEvent; import com.mongodb.internal.connection.MongoWriteConcernWithResponseException; +import com.mongodb.internal.event.ConfigureFailPointCommandListener; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonInt32; import org.bson.BsonString; import org.bson.Document; -import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -43,25 +41,28 @@ import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.Fixture.getDefaultDatabaseName; import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; +import static com.mongodb.internal.operation.CommandOperationHelper.NO_WRITES_PERFORMED_ERROR_LABEL; +import static com.mongodb.internal.operation.CommandOperationHelper.RETRYABLE_WRITE_ERROR_LABEL; import static java.util.Collections.singletonList; -import static org.junit.Assert.assertThrows; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * Tests in this class check that the internal {@link MongoWriteConcernWithResponseException} does not leak from our API. */ -public final class MongoWriteConcernWithResponseExceptionTest { +public class MongoWriteConcernWithResponseExceptionTest { + protected MongoClient createClient(final MongoClientSettings clientSettings) { + return MongoClients.create(clientSettings); + } + /** * This test is similar to {@link RetryableWritesProseTest#originalErrorMustBePropagatedIfNoWritesPerformed()}. * The difference is in the assertions, it also verifies situations when `writeConcernError` happens on the first attempt * and on the last attempt. */ - @Test - public void doesNotLeak() throws InterruptedException { - doesNotLeak(MongoClients::create); - } - - public static void doesNotLeak(final Function clientCreator) throws InterruptedException { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + protected void doesNotLeak(final boolean writeConcernErrorOnFirstAttempt) throws Exception { BsonDocument writeConcernErrorFpDoc = new BsonDocument() .append("configureFailPoint", new BsonString("failCommand")) .append("mode", new BsonDocument() @@ -69,7 +70,7 @@ public static void doesNotLeak(final Function .append("data", new BsonDocument() .append("writeConcernError", new BsonDocument() .append("code", new BsonInt32(91)) - .append("errorLabels", new BsonArray(Stream.of("RetryableWriteError") + .append("errorLabels", new BsonArray(Stream.of(RETRYABLE_WRITE_ERROR_LABEL) .map(BsonString::new).collect(Collectors.toList()))) .append("errmsg", new BsonString("")) ) @@ -81,49 +82,37 @@ public static void doesNotLeak(final Function .append("data", new BsonDocument() .append("failCommands", new BsonArray(singletonList(new BsonString("insert")))) .append("errorCode", new BsonInt32(10107)) - .append("errorLabels", new BsonArray(Stream.of("RetryableWriteError", "NoWritesPerformed") + .append("errorLabels", new BsonArray(Stream.of(RETRYABLE_WRITE_ERROR_LABEL, NO_WRITES_PERFORMED_ERROR_LABEL) .map(BsonString::new).collect(Collectors.toList())))); - doesNotLeak(clientCreator, writeConcernErrorFpDoc, true, noWritesPerformedFpDoc); - doesNotLeak(clientCreator, noWritesPerformedFpDoc, false, writeConcernErrorFpDoc); + if (writeConcernErrorOnFirstAttempt) { + doesNotLeak(writeConcernErrorFpDoc, true, noWritesPerformedFpDoc); + } else { + doesNotLeak(noWritesPerformedFpDoc, false, writeConcernErrorFpDoc); + } } @SuppressWarnings("try") - private static void doesNotLeak( - final Function clientCreator, + private void doesNotLeak( final BsonDocument firstAttemptFpDoc, - final boolean firstAttemptCommandSucceededEvent, - final BsonDocument lastAttemptFpDoc) throws InterruptedException { + final boolean firstAttemptSucceeds, + final BsonDocument lastAttemptFpDoc) throws Exception { assumeTrue(serverVersionAtLeast(6, 0) && isDiscoverableReplicaSet()); ServerAddress primaryServerAddress = Fixture.getPrimary(); - CompletableFuture futureFailPointFromListener = new CompletableFuture<>(); - CommandListener commandListener = new CommandListener() { - private final AtomicBoolean configureFailPoint = new AtomicBoolean(true); - - @Override - public void commandSucceeded(final CommandSucceededEvent event) { - if (firstAttemptCommandSucceededEvent) { - enableLastAttemptFp(event); - } - } - - @Override - public void commandFailed(final CommandFailedEvent event) { - if (!firstAttemptCommandSucceededEvent) { - enableLastAttemptFp(event); - } - } - - private void enableLastAttemptFp(final CommandEvent event) { - if (event.getCommandName().equals("insert") && configureFailPoint.compareAndSet(true, false)) { - Assertions.assertTrue(futureFailPointFromListener.complete(FailPoint.enable(lastAttemptFpDoc, primaryServerAddress))); - } + Predicate configureFailPointEventMatcher = event -> { + if (event.getCommandName().equals("insert")) { + return firstAttemptSucceeds + ? event instanceof CommandSucceededEvent + : event instanceof CommandFailedEvent; } + return false; }; - try (MongoClient client = clientCreator.apply(getMongoClientSettingsBuilder() - .retryWrites(true) - .addCommandListener(commandListener) - .applyToServerSettings(builder -> builder.heartbeatFrequency(50, TimeUnit.MILLISECONDS)) - .build()); + try (ConfigureFailPointCommandListener commandListener = new ConfigureFailPointCommandListener( + lastAttemptFpDoc, primaryServerAddress, configureFailPointEventMatcher); + MongoClient client = createClient(getMongoClientSettingsBuilder() + .retryWrites(true) + .addCommandListener(commandListener) + .applyToServerSettings(builder -> builder.heartbeatFrequency(50, TimeUnit.MILLISECONDS)) + .build()); FailPoint ignored = FailPoint.enable(firstAttemptFpDoc, primaryServerAddress)) { MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) .getCollection("originalErrorMustBePropagatedIfNoWritesPerformed"); @@ -137,8 +126,6 @@ private void enableLastAttemptFp(final CommandEvent event) { throw new AssertionError("The internal exception leaked.", e); } }); - } finally { - futureFailPointFromListener.thenAccept(FailPoint::close); } } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java index ccf18aad5b9..5ca0f75d56b 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/RetryableReadsProseTest.java @@ -16,57 +16,11 @@ package com.mongodb.client; -import org.bson.Document; -import org.junit.jupiter.api.Test; +import com.mongodb.MongoClientSettings; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -import static com.mongodb.client.model.Filters.eq; - -/** - * See - * Retryable Reads Tests. - */ -final class RetryableReadsProseTest { - /** - * See - * - * PoolClearedError Retryability Test. - */ - @Test - void poolClearedExceptionMustBeRetryable() throws InterruptedException, ExecutionException, TimeoutException { - RetryableWritesProseTest.poolClearedExceptionMustBeRetryable(MongoClients::create, - mongoCollection -> mongoCollection.find(eq(0)).iterator().hasNext(), "find", false); - } - - /** - * See - * - * Retryable Reads Are Retried on a Different mongos When One is Available. - */ - @Test - void retriesOnDifferentMongosWhenAvailable() { - RetryableWritesProseTest.retriesOnDifferentMongosWhenAvailable(MongoClients::create, - mongoCollection -> { - try (MongoCursor cursor = mongoCollection.find().iterator()) { - return cursor.hasNext(); - } - }, "find", false); - } - - /** - * See - * - * Retryable Reads Are Retried on the Same mongos When No Others are Available. - */ - @Test - void retriesOnSameMongosWhenAnotherNotAvailable() { - RetryableWritesProseTest.retriesOnSameMongosWhenAnotherNotAvailable(MongoClients::create, - mongoCollection -> { - try (MongoCursor cursor = mongoCollection.find().iterator()) { - return cursor.hasNext(); - } - }, "find", false); +final class RetryableReadsProseTest extends AbstractRetryableReadsProseTest { + @Override + protected MongoClient createClient(final MongoClientSettings settings) { + return MongoClients.create(settings); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java index a6c8802c033..e93beb33e4f 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/RetryableWritesProseTest.java @@ -19,44 +19,39 @@ import com.mongodb.ConnectionString; import com.mongodb.Function; import com.mongodb.MongoClientSettings; +import com.mongodb.MongoException; import com.mongodb.MongoServerException; import com.mongodb.MongoWriteConcernException; import com.mongodb.ServerAddress; -import com.mongodb.assertions.Assertions; import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.connection.ConnectionDescription; import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandFailedEvent; -import com.mongodb.event.CommandListener; import com.mongodb.event.CommandSucceededEvent; import com.mongodb.event.ConnectionCheckOutFailedEvent; 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 org.bson.BsonArray; -import org.bson.BsonBoolean; +import com.mongodb.internal.event.ConfigureFailPointCommandListener; +import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.BsonInt32; -import org.bson.BsonString; import org.bson.Document; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import static com.mongodb.ClusterFixture.getConnectionString; import static com.mongodb.ClusterFixture.getMultiMongosConnectionString; @@ -64,36 +59,37 @@ import static com.mongodb.ClusterFixture.isSharded; import static com.mongodb.ClusterFixture.isStandalone; import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.MongoException.RETRYABLE_ERROR_LABEL; +import static com.mongodb.MongoException.SYSTEM_OVERLOADED_ERROR_LABEL; import static com.mongodb.client.Fixture.getDefaultDatabaseName; import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; import static com.mongodb.client.Fixture.getMultiMongosMongoClientSettingsBuilder; +import static com.mongodb.client.Fixture.getPrimary; +import static com.mongodb.internal.operation.CommandOperationHelper.NO_WRITES_PERFORMED_ERROR_LABEL; +import static com.mongodb.internal.operation.CommandOperationHelper.RETRYABLE_WRITE_ERROR_LABEL; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; /** - * See - * Retryable Write Prose Tests. + * + * Prose Tests. */ -public class RetryableWritesProseTest extends DatabaseTestCase { - - @BeforeEach - @Override - public void setUp() { - super.setUp(); - } - +public class RetryableWritesProseTest { /** - * Prose test #2. + * + * 2. Test that drivers properly retry after encountering PoolClearedErrors. */ @Test - public void poolClearedExceptionMustBeRetryable() throws InterruptedException, ExecutionException, TimeoutException { + void poolClearedExceptionMustBeRetryable() throws Exception { poolClearedExceptionMustBeRetryable(MongoClients::create, mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true); } @@ -101,8 +97,7 @@ public void poolClearedExceptionMustBeRetryable() throws InterruptedException, E @SuppressWarnings("try") public static void poolClearedExceptionMustBeRetryable( final Function clientCreator, - final Function, R> operation, final String operationName, final boolean write) - throws InterruptedException, ExecutionException, TimeoutException { + final Function, R> operation, final String commandName, final boolean write) throws Exception { assumeTrue(serverVersionAtLeast(4, 3) && !(write && isStandalone())); TestConnectionPoolListener connectionPoolListener = new TestConnectionPoolListener(asList( "connectionCheckedOutEvent", @@ -118,33 +113,32 @@ public static void poolClearedExceptionMustBeRetryable( /* We fake server's state by configuring a fail point. This breaks the mechanism of the * streaming server monitoring protocol * (https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-monitoring.md#streaming-protocol) - * that allows the server to determine whether or not it needs to send a new state to the client. + * that allows the server to determine whether it needs to send a new state to the client. * As a result, the client has to wait for at least its heartbeat delay until it hears back from a server * (while it waits for a response, calling `ServerMonitor.connect` has no effect). * Thus, we want to use small heartbeat delay to reduce delays in the test. */ - .heartbeatFrequency(50, TimeUnit.MILLISECONDS)) + .heartbeatFrequency(50, MILLISECONDS)) .retryReads(true) .retryWrites(true) .addCommandListener(commandListener) .build(); - BsonDocument configureFailPoint = new BsonDocument() - .append("configureFailPoint", new BsonString("failCommand")) - .append("mode", new BsonDocument() - .append("times", new BsonInt32(1))) - .append("data", new BsonDocument() - .append("failCommands", new BsonArray(singletonList(new BsonString(operationName)))) - .append("errorCode", new BsonInt32(91)) - .append("errorLabels", write - ? new BsonArray(singletonList(new BsonString("RetryableWriteError"))) - : new BsonArray()) - .append("blockConnection", BsonBoolean.valueOf(true)) - .append("blockTimeMS", new BsonInt32(1000))); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {'times': 1},\n" + + " data: {\n" + + " failCommands: ['" + commandName + "'],\n" + + " errorCode: 91,\n" + + " blockConnection: true,\n" + + " blockTimeMS: 1000,\n" + + (write + ? " errorLabels: ['" + RETRYABLE_WRITE_ERROR_LABEL + "']\n" : "") + + " }\n" + + "}\n"); int timeoutSeconds = 10; try (MongoClient client = clientCreator.apply(clientSettings); - FailPoint ignored = FailPoint.enable(configureFailPoint, Fixture.getPrimary())) { - MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) - .getCollection("poolClearedExceptionMustBeRetryable"); - collection.drop(); + FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary())) { + MongoCollection collection = dropAndGetCollection("poolClearedExceptionMustBeRetryable", client); ExecutorService ex = Executors.newFixedThreadPool(2); try { Future result1 = ex.submit(() -> operation.apply(collection)); @@ -158,83 +152,81 @@ public static void poolClearedExceptionMustBeRetryable( ex.shutdownNow(); } assertEquals(3, commandListener.getCommandStartedEvents().size()); - commandListener.getCommandStartedEvents().forEach(event -> assertEquals(operationName, event.getCommandName())); + commandListener.getCommandStartedEvents().forEach(event -> assertEquals(commandName, event.getCommandName())); } } /** - * Prose test #3. + * + * 3. Test that drivers return the original error after encountering a WriteConcernError with a RetryableWriteError label. */ @Test - public void originalErrorMustBePropagatedIfNoWritesPerformed() throws InterruptedException { + void originalErrorMustBePropagatedIfNoWritesPerformed() throws Exception { originalErrorMustBePropagatedIfNoWritesPerformed(MongoClients::create); } @SuppressWarnings("try") public static void originalErrorMustBePropagatedIfNoWritesPerformed( - final Function clientCreator) throws InterruptedException { + final Function clientCreator) throws Exception { assumeTrue(serverVersionAtLeast(6, 0) && isDiscoverableReplicaSet()); - ServerAddress primaryServerAddress = Fixture.getPrimary(); - CompletableFuture futureFailPointFromListener = new CompletableFuture<>(); - CommandListener commandListener = new CommandListener() { - private final AtomicBoolean configureFailPoint = new AtomicBoolean(true); - - @Override - public void commandSucceeded(final CommandSucceededEvent event) { - if (event.getCommandName().equals("insert") - && event.getResponse().getDocument("writeConcernError", new BsonDocument()) - .getInt32("code", new BsonInt32(-1)).intValue() == 91 - && configureFailPoint.compareAndSet(true, false)) { - Assertions.assertTrue(futureFailPointFromListener.complete(FailPoint.enable( - new BsonDocument() - .append("configureFailPoint", new BsonString("failCommand")) - .append("mode", new BsonDocument() - .append("times", new BsonInt32(1))) - .append("data", new BsonDocument() - .append("failCommands", new BsonArray(singletonList(new BsonString("insert")))) - .append("errorCode", new BsonInt32(10107)) - .append("errorLabels", new BsonArray(Stream.of("RetryableWriteError", "NoWritesPerformed") - .map(BsonString::new).collect(Collectors.toList())))), - primaryServerAddress - ))); + ServerAddress primaryServerAddress = getPrimary(); + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " writeConcernError: {" + + " errorLabels: ['" + RETRYABLE_WRITE_ERROR_LABEL + "'],\n" + + " code: 91,\n" + + " errmsg: ''\n" + + " }\n" + + " }\n" + + "}\n"); + BsonDocument configureFailPointFromListener = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorCode: 10107,\n" + + " errorLabels: ['" + RETRYABLE_WRITE_ERROR_LABEL + "', '" + NO_WRITES_PERFORMED_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + Predicate configureFailPointEventMatcher = event -> { + if (event instanceof CommandSucceededEvent) { + CommandSucceededEvent commandSucceededEvent = (CommandSucceededEvent) event; + if (commandSucceededEvent.getCommandName().equals("insert")) { + assertEquals(91, commandSucceededEvent.getResponse().getDocument("writeConcernError", new BsonDocument()) + .getInt32("code", new BsonInt32(-1)).intValue()); + return true; } + return false; } + return false; }; - BsonDocument failPointDocument = new BsonDocument() - .append("configureFailPoint", new BsonString("failCommand")) - .append("mode", new BsonDocument() - .append("times", new BsonInt32(1))) - .append("data", new BsonDocument() - .append("writeConcernError", new BsonDocument() - .append("code", new BsonInt32(91)) - .append("errorLabels", new BsonArray(Stream.of("RetryableWriteError") - .map(BsonString::new).collect(Collectors.toList()))) - .append("errmsg", new BsonString("")) - ) - .append("failCommands", new BsonArray(singletonList(new BsonString("insert"))))); - try (MongoClient client = clientCreator.apply(getMongoClientSettingsBuilder() - .retryWrites(true) - .addCommandListener(commandListener) - .applyToServerSettings(builder -> - // see `poolClearedExceptionMustBeRetryable` for the explanation - builder.heartbeatFrequency(50, TimeUnit.MILLISECONDS)) - .build()); - FailPoint ignored = FailPoint.enable(failPointDocument, primaryServerAddress)) { - MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) - .getCollection("originalErrorMustBePropagatedIfNoWritesPerformed"); - collection.drop(); + try (ConfigureFailPointCommandListener commandListener = new ConfigureFailPointCommandListener( + configureFailPointFromListener, primaryServerAddress, configureFailPointEventMatcher); + MongoClient client = clientCreator.apply(getMongoClientSettingsBuilder() + .retryWrites(true) + .addCommandListener(commandListener) + .applyToServerSettings(builder -> + // see `poolClearedExceptionMustBeRetryable` for the explanation + builder.heartbeatFrequency(50, MILLISECONDS)) + .build()); + FailPoint ignored = FailPoint.enable(configureFailPoint, primaryServerAddress)) { + MongoCollection collection = dropAndGetCollection("originalErrorMustBePropagatedIfNoWritesPerformed", client); MongoWriteConcernException e = assertThrows(MongoWriteConcernException.class, () -> collection.insertOne(new Document())); assertEquals(91, e.getCode()); - } finally { - futureFailPointFromListener.thenAccept(FailPoint::close); } } /** - * Prose test #4. + * + * 4. Test that in a sharded cluster writes are retried on a different mongos when one is available. */ @Test - public void retriesOnDifferentMongosWhenAvailable() { + void retriesOnDifferentMongosWhenAvailable() throws InterruptedException, TimeoutException { retriesOnDifferentMongosWhenAvailable(MongoClients::create, mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true); } @@ -242,7 +234,8 @@ public void retriesOnDifferentMongosWhenAvailable() { @SuppressWarnings("try") public static void retriesOnDifferentMongosWhenAvailable( final Function clientCreator, - final Function, R> operation, final String operationName, final boolean write) { + final Function, R> operation, final String expectedCommandName, final boolean write) + throws InterruptedException, TimeoutException { if (write) { assumeTrue(serverVersionAtLeast(4, 4)); } @@ -251,37 +244,44 @@ public static void retriesOnDifferentMongosWhenAvailable( assumeTrue(connectionString != null); ServerAddress s0Address = ServerAddressHelper.createServerAddress(connectionString.getHosts().get(0)); ServerAddress s1Address = ServerAddressHelper.createServerAddress(connectionString.getHosts().get(1)); - BsonDocument failPointDocument = BsonDocument.parse( + BsonDocument configureFailPoint = BsonDocument.parse( "{\n" + " configureFailPoint: \"failCommand\",\n" + " mode: { times: 1 },\n" + " data: {\n" - + " failCommands: [\"" + operationName + "\"],\n" + + " failCommands: [\"" + expectedCommandName + "\"],\n" + + " errorCode: 6,\n" + (write - ? " errorLabels: [\"RetryableWriteError\"]," : "") - + " errorCode: 6\n" + ? " errorLabels: ['" + RETRYABLE_WRITE_ERROR_LABEL + "']" : "") + " }\n" + "}\n"); TestCommandListener commandListener = new TestCommandListener(singletonList("commandFailedEvent"), emptyList()); - try (FailPoint s0FailPoint = FailPoint.enable(failPointDocument, s0Address); - FailPoint s1FailPoint = FailPoint.enable(failPointDocument, s1Address); + TestClusterListener clusterListener = new TestClusterListener(); + try (FailPoint s0FailPoint = FailPoint.enable(configureFailPoint, s0Address); + FailPoint s1FailPoint = FailPoint.enable(configureFailPoint, s1Address); MongoClient client = clientCreator.apply(getMultiMongosMongoClientSettingsBuilder() .retryReads(true) .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())) { - MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) - .getCollection("retriesOnDifferentMongosWhenAvailable"); - collection.drop(); + // 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)); List failedCommandEvents = commandListener.getEvents(); assertEquals(2, failedCommandEvents.size(), failedCommandEvents::toString); List unexpectedCommandNames = failedCommandEvents.stream() .map(CommandEvent::getCommandName) - .filter(commandName -> !commandName.equals(operationName)) + .filter(commandName -> !commandName.equals(expectedCommandName)) .collect(Collectors.toList()); assertTrue(unexpectedCommandNames.isEmpty(), unexpectedCommandNames::toString); Set failedServerAddresses = failedCommandEvents.stream() @@ -293,10 +293,11 @@ public static void retriesOnDifferentMongosWhenAvailable( } /** - * Prose test #5. + * + * 5. Test that in a sharded cluster writes are retried on the same mongos when no others are available. */ @Test - public void retriesOnSameMongosWhenAnotherNotAvailable() { + void retriesOnSameMongosWhenAnotherNotAvailable() { retriesOnSameMongosWhenAnotherNotAvailable(MongoClients::create, mongoCollection -> mongoCollection.insertOne(new Document()), "insert", true); } @@ -304,27 +305,29 @@ public void retriesOnSameMongosWhenAnotherNotAvailable() { @SuppressWarnings("try") public static void retriesOnSameMongosWhenAnotherNotAvailable( final Function clientCreator, - final Function, R> operation, final String operationName, final boolean write) { + final Function, R> operation, final String expectedCommandName, final boolean write) { if (write) { assumeTrue(serverVersionAtLeast(4, 4)); } assumeTrue(isSharded()); ConnectionString connectionString = getConnectionString(); ServerAddress s0Address = ServerAddressHelper.createServerAddress(connectionString.getHosts().get(0)); - BsonDocument failPointDocument = BsonDocument.parse( + BsonDocument configureFailPoint = BsonDocument.parse( "{\n" + " configureFailPoint: \"failCommand\",\n" + " mode: { times: 1 },\n" + " data: {\n" - + " failCommands: [\"" + operationName + "\"],\n" + + " failCommands: [\"" + expectedCommandName + "\"],\n" + + " errorCode: 6,\n" + + (write + ? " errorLabels: ['" + RETRYABLE_WRITE_ERROR_LABEL + "']," : "") + (write - ? " errorLabels: [\"RetryableWriteError\"]," : "") - + " errorCode: 6\n" + ? " closeConnection: true\n" : "") + " }\n" + "}\n"); TestCommandListener commandListener = new TestCommandListener( asList("commandFailedEvent", "commandSucceededEvent"), emptyList()); - try (FailPoint s0FailPoint = FailPoint.enable(failPointDocument, s0Address); + try (FailPoint s0FailPoint = FailPoint.enable(configureFailPoint, s0Address); MongoClient client = clientCreator.apply(getMongoClientSettingsBuilder() .retryReads(true) .retryWrites(true) @@ -334,16 +337,14 @@ public static void retriesOnSameMongosWhenAnotherNotAvailable( .hosts(singletonList(s0Address)) .mode(ClusterConnectionMode.MULTIPLE)) .build())) { - MongoCollection collection = client.getDatabase(getDefaultDatabaseName()) - .getCollection("retriesOnSameMongosWhenAnotherNotAvailable"); - collection.drop(); + MongoCollection collection = dropAndGetCollection("retriesOnSameMongosWhenAnotherNotAvailable", client); commandListener.reset(); operation.apply(collection); List commandEvents = commandListener.getEvents(); assertEquals(2, commandEvents.size(), commandEvents::toString); List unexpectedCommandNames = commandEvents.stream() .map(CommandEvent::getCommandName) - .filter(commandName -> !commandName.equals(operationName)) + .filter(commandName -> !commandName.equals(expectedCommandName)) .collect(Collectors.toList()); assertTrue(unexpectedCommandNames.isEmpty(), unexpectedCommandNames::toString); assertInstanceOf(CommandFailedEvent.class, commandEvents.get(0), commandEvents::toString); @@ -352,4 +353,175 @@ public static void retriesOnSameMongosWhenAnotherNotAvailable( assertEquals(s0Address, commandEvents.get(1).getConnectionDescription().getServerAddress(), commandEvents::toString); } } + + /** + * + * 6. Test error propagation after encountering multiple errors. + * Case 1: Test that drivers return the correct error when receiving only errors without NoWritesPerformed. + */ + @Test + void errorPropagationAfterEncounteringMultipleErrorsCase1() throws Exception { + errorPropagationAfterEncounteringMultipleErrorsCase1(MongoClients::create); + } + + public static void errorPropagationAfterEncounteringMultipleErrorsCase1(final Function clientCreator) + throws Exception { + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {'times': 1},\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "'],\n" + + " errorCode: 91\n" + + " }\n" + + "}\n"); + BsonDocument configureFailPointFromListener = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorCode: 10107,\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + errorPropagationAfterEncounteringMultipleErrors( + clientCreator, + configureFailPoint, + configureFailPointFromListener, + 10107, + null); + } + + /** + * + * 6. Test error propagation after encountering multiple errors. + * Case 2: Test that drivers return the correct error when receiving only errors with NoWritesPerformed. + */ + @Test + void errorPropagationAfterEncounteringMultipleErrorsCase2() throws Exception { + errorPropagationAfterEncounteringMultipleErrorsCase2(MongoClients::create); + } + + public static void errorPropagationAfterEncounteringMultipleErrorsCase2(final Function clientCreator) + throws Exception { + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {'times': 1},\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + + "', '" + NO_WRITES_PERFORMED_ERROR_LABEL + "'],\n" + + " errorCode: 91\n" + + " }\n" + + "}\n"); + BsonDocument configureFailPointFromListener = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorCode: 10107,\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + + "', '" + NO_WRITES_PERFORMED_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + errorPropagationAfterEncounteringMultipleErrors( + clientCreator, + configureFailPoint, + configureFailPointFromListener, + 91, + null); + } + + /** + * + * 6. Test error propagation after encountering multiple errors. + * Case 3: Test that drivers return the correct error when receiving some errors with NoWritesPerformed and some without NoWritesPerformed. + */ + @Test + void errorPropagationAfterEncounteringMultipleErrorsCase3() throws Exception { + errorPropagationAfterEncounteringMultipleErrorsCase3(MongoClients::create); + } + + public static void errorPropagationAfterEncounteringMultipleErrorsCase3(final Function clientCreator) + throws Exception { + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: 'failCommand',\n" + + " mode: {'times': 1},\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "'],\n" + + " errorCode: 91\n" + + " }\n" + + "}\n"); + BsonDocument configureFailPointFromListener = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: 'alwaysOn',\n" + + " data: {\n" + + " failCommands: ['insert'],\n" + + " errorCode: 91,\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + + "', '" + NO_WRITES_PERFORMED_ERROR_LABEL + "']\n" + + " }\n" + + "}\n"); + errorPropagationAfterEncounteringMultipleErrors( + clientCreator, + configureFailPoint, + configureFailPointFromListener, + 91, + NO_WRITES_PERFORMED_ERROR_LABEL); + } + + /** + * @param unexpectedErrorLabel {@code null} means there is no expectation. + */ + private static void errorPropagationAfterEncounteringMultipleErrors( + final Function clientCreator, + final BsonDocument configureFailPoint, + final BsonDocument configureFailPointFromListener, + final int expectedErrorCode, + @Nullable final String unexpectedErrorLabel) throws Exception { + assumeTrue(serverVersionAtLeast(6, 0)); + assumeTrue(isDiscoverableReplicaSet()); + ServerAddress primaryServerAddress = getPrimary(); + Predicate configureFailPointEventMatcher = event -> { + if (event instanceof CommandFailedEvent) { + CommandFailedEvent commandFailedEvent = (CommandFailedEvent) event; + if (commandFailedEvent.getCommandName().equals("drop")) { + // this code may run against MongoDB 6, where dropping a nonexistent collection results in an error + return false; + } + MongoException cause = assertInstanceOf(MongoException.class, commandFailedEvent.getThrowable()); + assertEquals(91, cause.getCode()); + return true; + } + return false; + }; + try (ConfigureFailPointCommandListener commandListener = new ConfigureFailPointCommandListener( + configureFailPointFromListener, primaryServerAddress, configureFailPointEventMatcher); + MongoClient client = clientCreator.apply(getMongoClientSettingsBuilder() + .retryWrites(true) + .addCommandListener(commandListener) + .applyToServerSettings(builder -> builder.heartbeatFrequency(500, MILLISECONDS)) + .build()); + FailPoint ignored = FailPoint.enable(configureFailPoint, primaryServerAddress)) { + MongoCollection collection = dropAndGetCollection("errorPropagationAfterEncounteringMultipleErrors", client); + MongoException e = assertThrows(MongoException.class, () -> collection.insertOne(new Document())); + assertEquals(expectedErrorCode, e.getCode()); + if (unexpectedErrorLabel != null) { + assertFalse(e.hasErrorLabel(unexpectedErrorLabel)); + } + } + } + + private static MongoCollection dropAndGetCollection(final String name, final MongoClient client) { + MongoCollection result = client.getDatabase(getDefaultDatabaseName()).getCollection(name); + result.drop(); + return result; + } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java index 18b3b3f4fc5..8e20fdf2118 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java @@ -18,6 +18,7 @@ import com.mongodb.ClusterFixture; import com.mongodb.MongoClientSettings; +import com.mongodb.event.ConnectionCheckOutFailedEvent; import com.mongodb.event.ConnectionPoolClearedEvent; import com.mongodb.event.ConnectionPoolListener; import com.mongodb.event.ConnectionPoolReadyEvent; @@ -26,6 +27,7 @@ import com.mongodb.event.ServerHeartbeatSucceededEvent; import com.mongodb.event.ServerListener; import com.mongodb.event.ServerMonitorListener; +import com.mongodb.internal.connection.TestConnectionPoolListener; import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; import com.mongodb.internal.time.TimePointTest; @@ -47,6 +49,8 @@ import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import static com.mongodb.ClusterFixture.configureFailPoint; @@ -268,6 +272,72 @@ public void shouldEmitHeartbeatStartedBeforeSocketIsConnected() { // As it requires mocking and package access to `com.mongodb.internal.connection` } + /** + * See + * Connection Pool Backpressure. + */ + @Test + public void testConnectionPoolBackpressure() throws InterruptedException { + assumeTrue(serverVersionAtLeast(7, 0)); + + TestConnectionPoolListener connectionPoolListener = new TestConnectionPoolListener(); + + MongoClientSettings clientSettings = getMongoClientSettingsBuilder() + .applyToConnectionPoolSettings(builder -> builder + .maxConnecting(100) + .addConnectionPoolListener(connectionPoolListener)) + .build(); + + try (MongoClient adminClient = MongoClients.create(getMongoClientSettingsBuilder().build()); + MongoClient client = MongoClients.create(clientSettings)) { + + MongoDatabase adminDatabase = adminClient.getDatabase("admin"); + MongoDatabase database = client.getDatabase(getDefaultDatabaseName()); + MongoCollection collection = database.getCollection("testCollection"); + + try { + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRateLimiterEnabled", true)); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRatePerSec", 20)); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentBurstCapacitySecs", 1)); + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentMaxQueueDepth", 1)); + + collection.insertOne(Document.parse("{}")); + + ExecutorService executor = Executors.newFixedThreadPool(100); + try { + for (int i = 0; i < 100; i++) { + executor.submit(() -> + collection.find(new Document("$where", "function() { sleep(2000); return true; }")).first()); + } + executor.shutdown(); + assertTrue("Executor did not terminate within timeout", + executor.awaitTermination(20, SECONDS)); + } finally { + if (!executor.isTerminated()) { + executor.shutdownNow(); + } + } + + int failedCheckOutCount = connectionPoolListener.countEvents(ConnectionCheckOutFailedEvent.class); + assertTrue("Expected at least 10 ConnectionCheckOutFailedEvents, but got " + failedCheckOutCount, + failedCheckOutCount >= 10); + assertEquals(0, connectionPoolListener.countEvents(ConnectionPoolClearedEvent.class)); + } finally { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + adminDatabase.runCommand(new Document("setParameter", 1) + .append("ingressConnectionEstablishmentRateLimiterEnabled", false)); + } + } + } + private static void assertPoll(final BlockingQueue queue, @Nullable final Class allowed, final Set> required) throws InterruptedException { assertPoll(queue, allowed, required, Timeout.expiresIn(TEST_WAIT_TIMEOUT_MILLIS, MILLISECONDS, ZERO_DURATION_MEANS_EXPIRED)); diff --git a/driver-sync/src/test/functional/com/mongodb/client/Socks5ProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/Socks5ProseTest.java index 20e3a35534d..d09617aef31 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/Socks5ProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/Socks5ProseTest.java @@ -17,7 +17,7 @@ import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; -import com.mongodb.MongoSocketOpenException; +import com.mongodb.MongoSocksProxyException; import com.mongodb.MongoTimeoutException; import com.mongodb.connection.ClusterDescription; import com.mongodb.connection.ServerDescription; @@ -151,7 +151,7 @@ private static void assertSocksAuthenticationIssue(final ClusterListener cluster .filter(Objects::nonNull) .collect(Collectors.toList()); assumeFalse(errors.isEmpty()); - errors.forEach(throwable -> Assertions.assertEquals(MongoSocketOpenException.class, throwable.getClass())); + errors.forEach(throwable -> Assertions.assertInstanceOf(MongoSocksProxyException.class, throwable)); } private static void runHelloCommand(final MongoClient mongoClient) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 1afbf61565e..6d2b928e8ec 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -18,29 +18,44 @@ import com.mongodb.ClientSessionOptions; import com.mongodb.MongoClientException; +import com.mongodb.MongoCommandException; import com.mongodb.MongoException; +import com.mongodb.MongoNodeIsRecoveringException; import com.mongodb.TransactionOptions; -import com.mongodb.client.internal.ClientSessionClock; +import com.mongodb.WithTransactionTimeoutException; import com.mongodb.client.model.Sorts; +import com.mongodb.internal.time.ExponentialBackoff; +import com.mongodb.internal.time.StartTime; +import com.mongodb.internal.time.SystemNanoTime; +import org.bson.BsonDocument; import org.bson.Document; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import java.time.Duration; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import static com.mongodb.ClusterFixture.TIMEOUT; import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet; import static com.mongodb.ClusterFixture.isSharded; +import static com.mongodb.client.Fixture.getPrimary; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; -// See https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/tests/README.md#prose-tests +/** + * Prose Tests. + */ public class WithTransactionProseTest extends DatabaseTestCase { - private static final long START_TIME_MS = 1L; - private static final long ERROR_GENERATING_INTERVAL = 121000L; + private static final Duration TIMEOUT_EXCEEDING_DURATION = Duration.ofSeconds(120); @BeforeEach @Override @@ -52,16 +67,15 @@ public void setUp() { collection.insertOne(Document.parse("{ _id : 0 }")); } - // - // Test that the callback raises a custom exception or error that does not include either UnknownTransactionCommitResult or - // TransientTransactionError error labels. The callback will execute using withTransaction and assert that the callback's error - // bypasses any retry logic within withTransaction and is propagated to the caller of withTransaction. - // + /** + * + * Callback Raises a Custom Error. + */ @Test public void testCallbackRaisesCustomError() { final String exceptionMessage = "NotTransientOrUnknownError"; try (ClientSession session = client.startSession()) { - session.withTransaction((TransactionBody) () -> { + session.withTransaction(() -> { throw new MongoException(exceptionMessage); }); // should not get here @@ -71,10 +85,10 @@ public void testCallbackRaisesCustomError() { } } - // - // Test that the callback that returns a custom value (e.g. boolean, string, object). Execute this callback using withTransaction - // and assert that the callback's return value is propagated to the caller of withTransaction. - // + /** + * + * Callback Returns a Value. + */ @Test public void testCallbackReturnsValue() { try (ClientSession session = client.startSession()) { @@ -87,33 +101,36 @@ public void testCallbackReturnsValue() { } } - // - // If the callback raises an error with the TransientTransactionError label and the retry timeout has been exceeded, withTransaction - // should propagate the error to its caller. - // + /** + * + * Retry Timeout is Enforced, first scenario on the list. + */ @Test public void testRetryTimeoutEnforcedTransientTransactionError() { final String errorMessage = "transient transaction error"; try (ClientSession session = client.startSession()) { - ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction((TransactionBody) () -> { - ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); - MongoException e = new MongoException(112, errorMessage); - e.addLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL); - throw e; - }); + doWithSystemNanoTimeHandle(systemNanoTimeHandle -> + session.withTransaction(() -> { + systemNanoTimeHandle.setRelativeToStart(TIMEOUT_EXCEEDING_DURATION); + MongoException e = new MongoException(112, errorMessage); + e.addLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL); + throw e; + })); fail("Test should have thrown an exception."); } catch (Exception e) { - assertEquals(errorMessage, e.getMessage()); - assertTrue(((MongoException) e).getErrorLabels().contains(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)); + WithTransactionTimeoutException exception = assertInstanceOf(WithTransactionTimeoutException.class, e); + assertTrue(exception.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)); + MongoException cause = assertInstanceOf(MongoException.class, exception.getCause()); + assertEquals(errorMessage, cause.getMessage()); + assertTrue(cause.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)); } } - // - // If committing raises an error with the UnknownTransactionCommitResult label, the error is not a write concern timeout, and the - // retry timeout has been exceeded, withTransaction should propagate the error to its caller. - // + /** + * + * Retry Timeout is Enforced, second scenario on the list. + */ @Test public void testRetryTimeoutEnforcedUnknownTransactionCommit() { MongoDatabase failPointAdminDb = client.getDatabase("admin"); @@ -122,26 +139,28 @@ public void testRetryTimeoutEnforcedUnknownTransactionCommit() { + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 91, 'closeConnection': false}}")); try (ClientSession session = client.startSession()) { - ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction((TransactionBody) () -> { - ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); - collection.insertOne(session, new Document("_id", 2)); - return null; - }); + doWithSystemNanoTimeHandle(systemNanoTimeHandle -> + session.withTransaction(() -> { + systemNanoTimeHandle.setRelativeToStart(TIMEOUT_EXCEEDING_DURATION); + collection.insertOne(session, new Document("_id", 2)); + return null; + })); fail("Test should have thrown an exception."); } catch (Exception e) { - assertEquals(91, ((MongoException) e).getCode()); - assertTrue(((MongoException) e).getErrorLabels().contains(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)); + WithTransactionTimeoutException exception = assertInstanceOf(WithTransactionTimeoutException.class, e); + assertTrue(exception.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)); + MongoNodeIsRecoveringException cause = assertInstanceOf(MongoNodeIsRecoveringException.class, exception.getCause()); + assertEquals(91, cause.getCode()); + assertTrue(cause.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)); } finally { failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); } } - // - // If committing raises an error with the TransientTransactionError label and the retry timeout has been exceeded, withTransaction - // should propagate the error to its caller. This case may occur if the commit was internally retried against a new primary after - // a failover and the second primary returned a NoSuchTransaction error response. - // + /** + * + * Retry Timeout is Enforced, third scenario on the list. + */ @Test public void testRetryTimeoutEnforcedTransientTransactionErrorOnCommit() { MongoDatabase failPointAdminDb = client.getDatabase("admin"); @@ -151,24 +170,27 @@ public void testRetryTimeoutEnforcedTransientTransactionErrorOnCommit() { + "'errmsg': 'Transaction 0 has been aborted', 'closeConnection': false}}")); try (ClientSession session = client.startSession()) { - ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction((TransactionBody) () -> { - ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); - collection.insertOne(session, Document.parse("{ _id : 1 }")); - return null; - }); + doWithSystemNanoTimeHandle(systemNanoTimeHandle -> + session.withTransaction(() -> { + systemNanoTimeHandle.setRelativeToStart(TIMEOUT_EXCEEDING_DURATION); + collection.insertOne(session, Document.parse("{ _id : 1 }")); + return null; + })); fail("Test should have thrown an exception."); } catch (Exception e) { - assertEquals(251, ((MongoException) e).getCode()); - assertTrue(((MongoException) e).getErrorLabels().contains(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)); + WithTransactionTimeoutException exception = assertInstanceOf(WithTransactionTimeoutException.class, e); + assertTrue(exception.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)); + MongoCommandException cause = assertInstanceOf(MongoCommandException.class, exception.getCause()); + assertEquals(251, cause.getCode()); + assertTrue(cause.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)); } finally { failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); } } - // - // Ensure cannot override timeout in transaction - // + /** + * This test is not from the specification. Ensures cannot override timeout in transaction. + */ @Test public void testTimeoutMS() { try (ClientSession session = client.startSession(ClientSessionOptions.builder() @@ -182,9 +204,9 @@ public void testTimeoutMS() { } } - // - // Ensure legacy settings don't cause issues in sessions - // + /** + * This test is not from the specification. Ensures legacy settings don't cause issues in sessions. + */ @Test public void testTimeoutMSAndLegacySettings() { try (ClientSession session = client.startSession(ClientSessionOptions.builder() @@ -203,7 +225,73 @@ public void testTimeoutMSAndLegacySettings() { } } - private boolean canRunTests() { + /** + * See + * Retry Backoff is Enforced. + */ + @DisplayName("Retry Backoff is Enforced") + @Test + public void testRetryBackoffIsEnforced() throws InterruptedException { + long noBackoffTimeMs = measureTransactionLatencyMs(0.0); + long withBackoffTimeMs = measureTransactionLatencyMs(1.0); + + long sumOfBackoffsMs = 1800; + long toleranceMs = 500; + long actualDifferenceMs = Math.abs(withBackoffTimeMs - (noBackoffTimeMs + sumOfBackoffsMs)); + + assertTrue(actualDifferenceMs < toleranceMs, + String.format("Observed backoff time deviates from expected by %d ms (tolerance: %d ms)", actualDifferenceMs, toleranceMs)); + } + + /** + * This test is not from the specification. + */ + @Test + public void testExponentialBackoffOnTransientError() throws InterruptedException { + BsonDocument failPointDocument = BsonDocument.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 3}, " + + "'data': {'failCommands': ['insert'], 'errorCode': 112, " + + "'errorLabels': ['TransientTransactionError']}}"); + + try (ClientSession session = client.startSession(); + FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { + AtomicInteger attemptsCount = new AtomicInteger(0); + + session.withTransaction(() -> { + attemptsCount.incrementAndGet(); // Count the attempt before the operation that might fail + return collection.insertOne(session, Document.parse("{}")); + }); + + assertEquals(4, attemptsCount.get(), "Expected 1 initial attempt + 3 retries"); + } + } + + private long measureTransactionLatencyMs(final double jitter) throws InterruptedException { + BsonDocument failPointDocument = BsonDocument.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}"); + ExponentialBackoff.setTestJitterSupplier(() -> jitter); + try (ClientSession session = client.startSession(); + FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { + StartTime startTime = StartTime.now(); + session.withTransaction(() -> collection.insertOne(session, Document.parse("{}"))); + return startTime.elapsed().toMillis(); + } finally { + ExponentialBackoff.clearTestJitterSupplier(); + } + } + + private static boolean canRunTests() { return isSharded() || isDiscoverableReplicaSet(); } + + private static void doWithSystemNanoTimeHandle(final Consumer action) { + long startNanos = SystemNanoTime.get(); + try (MockedStatic mockedStaticSystemNanoTime = Mockito.mockStatic(SystemNanoTime.class)) { + mockedStaticSystemNanoTime.when(SystemNanoTime::get).thenReturn(startNanos); + action.accept(change -> mockedStaticSystemNanoTime.when(SystemNanoTime::get).thenReturn(startNanos + change.toNanos())); + } + } + + private interface SystemNanoTimeHandle { + void setRelativeToStart(Duration change); + } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/ContextElement.java b/driver-sync/src/test/functional/com/mongodb/client/unified/ContextElement.java index 7e6e86fb01c..6f9b8f13f9c 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/ContextElement.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/ContextElement.java @@ -26,6 +26,7 @@ import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandSucceededEvent; +import com.mongodb.event.ServerDescriptionChangedEvent; import com.mongodb.internal.logging.LogMessage; import com.mongodb.lang.Nullable; import org.bson.BsonArray; @@ -509,6 +510,10 @@ private static BsonDocument connectionPoolEventToDocument(final Object event) { } private static BsonDocument serverMonitorEventToDocument(final Object event) { + // ServerDescriptionChangedEvent is not a heartbeat event and has no 'awaited' field. + if (event instanceof ServerDescriptionChangedEvent) { + return new BsonDocument(EventMatcher.getEventType(event.getClass()), new BsonDocument()); + } return new BsonDocument(EventMatcher.getEventType(event.getClass()), new BsonDocument("awaited", BsonBoolean.valueOf(EventMatcher.getAwaitedFromServerMonitorEvent(event)))); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java b/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java index b2718b4b2d7..b28e4db74a5 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/EventMatcher.java @@ -26,6 +26,7 @@ import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandSucceededEvent; import com.mongodb.event.ConnectionCheckOutFailedEvent; +import com.mongodb.event.ConnectionCheckedInEvent; import com.mongodb.event.ConnectionClosedEvent; import com.mongodb.event.ConnectionCreatedEvent; import com.mongodb.event.ConnectionPoolClearedEvent; @@ -208,6 +209,12 @@ public void waitForConnectionPoolEvents(final String client, final BsonDocument case "connectionReadyEvent": eventClass = ConnectionReadyEvent.class; break; + case "connectionClosedEvent": + eventClass = ConnectionClosedEvent.class; + break; + case "connectionCheckedInEvent": + eventClass = ConnectionCheckedInEvent.class; + break; default: throw new UnsupportedOperationException("Unsupported event: " + event.getFirstKey()); } @@ -436,11 +443,18 @@ private static boolean serverDescriptionChangedEventMatches(final BsonDocument e switch (newType) { case "Unknown": return event.getNewDescription().getType() == ServerType.UNKNOWN; - case "LoadBalancer": { + case "LoadBalancer": return event.getNewDescription().getType() == ServerType.LOAD_BALANCER; - } + case "Mongos": + return event.getNewDescription().getType() == ServerType.SHARD_ROUTER; + case "Standalone": + return event.getNewDescription().getType() == ServerType.STANDALONE; + case "RSPrimary": + return event.getNewDescription().getType() == ServerType.REPLICA_SET_PRIMARY; + case "RSSecondary": + return event.getNewDescription().getType() == ServerType.REPLICA_SET_SECONDARY; default: - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Unsupported server type " + newType); } } @@ -499,6 +513,13 @@ private static boolean serverMonitorEventMatches( if (expectedEventContents.size() > 1) { throw new UnsupportedOperationException("Matching for the following event is not implemented " + expectedEventContents.toJson()); } + if (event instanceof ServerDescriptionChangedEvent) { + boolean matches = serverDescriptionChangedEventMatches(expectedEventContents, (ServerDescriptionChangedEvent) event); + if (context != null) { + assertTrue(context.getMessage("Expected serverDescriptionChangedEvent contents to match"), matches); + } + return matches; + } if (expectedEventContents.containsKey("awaited")) { boolean expectedAwaited = expectedEventContents.getBoolean("awaited").getValue(); boolean actualAwaited = getAwaitedFromServerMonitorEvent(event); @@ -531,7 +552,7 @@ static String getEventType(final Class eventClass) { return eventClassName.replace("ConnectionPool", "pool"); } else if (eventClassName.startsWith("Connection")) { return eventClassName.replace("Connection", "connection"); - } else if (eventClassName.startsWith("ServerHeartbeat")) { + } else if (eventClassName.startsWith("Server")) { StringBuilder eventTypeBuilder = new StringBuilder(eventClassName); eventTypeBuilder.setCharAt(0, Character.toLowerCase(eventTypeBuilder.charAt(0))); return eventTypeBuilder.toString(); diff --git a/driver-core/src/main/com/mongodb/internal/operation/retry/package-info.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedClientBackpressureTest.java similarity index 61% rename from driver-core/src/main/com/mongodb/internal/operation/retry/package-info.java rename to driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedClientBackpressureTest.java index 29c27a47914..479c2051355 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/retry/package-info.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedClientBackpressureTest.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,12 +14,14 @@ * limitations under the License. */ -/** - * This package contains internal functionality that may change at any time. - */ -@Internal -@NonNullApi -package com.mongodb.internal.operation.retry; +package com.mongodb.client.unified; + +import org.junit.jupiter.params.provider.Arguments; + +import java.util.Collection; -import com.mongodb.annotations.Internal; -import com.mongodb.lang.NonNullApi; +final class UnifiedClientBackpressureTest extends UnifiedSyncTest { + private static Collection data() { + return getTestData("client-backpressure"); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java index 44394985611..9b4286b20aa 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java @@ -40,6 +40,7 @@ import com.mongodb.internal.connection.TestClusterListener; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.internal.connection.TestConnectionPoolListener; +import com.mongodb.internal.connection.TestServerListener; import com.mongodb.internal.logging.LogMessage; import com.mongodb.lang.NonNull; import com.mongodb.lang.Nullable; @@ -71,7 +72,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -119,6 +119,7 @@ public abstract class UnifiedTest { private static final String TOPOLOGY_CLOSED_EVENT = "topologyClosedEvent"; private static final List TOPOLOGY_EVENT_NAMES = asList("topologyOpeningEvent", "topologyDescriptionChangedEvent", TOPOLOGY_CLOSED_EVENT); + private static final String SERVER_DESCRIPTION_CHANGED_EVENT = "serverDescriptionChangedEvent"; public static final int RETRY_ATTEMPTS = 3; public static final int FORCE_FLAKY_ATTEMPTS = 10; @@ -431,33 +432,46 @@ private void compareEvents(final UnifiedTestContext context, final BsonDocument context.getEventMatcher().assertConnectionPoolEventsEquality(client, ignoreExtraEvents, expectedEvents, listener.getEvents()); } else if (eventType.equals("sdam")) { + List expectedTopologyEvents = new ArrayList<>(); + List expectedServerDescriptionChangedEvents = new ArrayList<>(); + List expectedHeartbeatEvents = new ArrayList<>(); + + for (BsonValue event : expectedEvents) { + BsonDocument doc = event.asDocument(); + if (TOPOLOGY_EVENT_NAMES.stream().anyMatch(doc::containsKey)) { + expectedTopologyEvents.add(doc); + } else if (doc.containsKey(SERVER_DESCRIPTION_CHANGED_EVENT)) { + expectedServerDescriptionChangedEvents.add(doc); + } else { + expectedHeartbeatEvents.add(doc); + } + } - // SDAM tests also include topology events, so we need to separate them to be able to assert them separately. - // Partition the expected events into two lists with the key being if it's a topology based event or not. - Map> partitionedEventsMap = expectedEvents.stream() - .map(BsonValue::asDocument) - .collect(Collectors.partitioningBy(doc -> TOPOLOGY_EVENT_NAMES.stream().anyMatch(doc::containsKey))); - - BsonArray expectedTopologyEvents = new BsonArray(partitionedEventsMap.get(true)); if (!expectedTopologyEvents.isEmpty()) { TestClusterListener clusterListener = entities.getClusterListener(client); - // Unfortunately, some tests expect the cluster to be closed, but do not define it as a waitForEvent in the spec - - // causing a race condition in the test. - if (expectedTopologyEvents.stream().anyMatch(doc -> doc.asDocument().containsKey(TOPOLOGY_CLOSED_EVENT))) { + // Race guard: some tests expect topologyClosedEvent without a prior waitForEvent. + if (expectedTopologyEvents.stream().anyMatch(doc -> doc.containsKey(TOPOLOGY_CLOSED_EVENT))) { context.getEventMatcher().waitForClusterClosedEvent(client, clusterListener); } - List topologyEvents = new ArrayList<>(); topologyEvents.add(clusterListener.getClusterOpeningEvent()); topologyEvents.addAll(clusterListener.getClusterDescriptionChangedEvents()); topologyEvents.add(clusterListener.getClusterClosingEvent()); - context.getEventMatcher().assertTopologyEventsEquality(client, ignoreExtraEvents, expectedTopologyEvents, topologyEvents); + context.getEventMatcher().assertTopologyEventsEquality(client, ignoreExtraEvents, + new BsonArray(expectedTopologyEvents), topologyEvents); + } + + if (!expectedServerDescriptionChangedEvents.isEmpty()) { + TestServerListener serverListener = entities.getServerListener(client); + context.getEventMatcher().assertServerMonitorEventsEquality(client, ignoreExtraEvents, + new BsonArray(expectedServerDescriptionChangedEvents), + serverListener.getServerDescriptionChangedEvents()); } - BsonArray expectedSdamEvents = new BsonArray(partitionedEventsMap.get(false)); - if (!expectedSdamEvents.isEmpty()) { + if (!expectedHeartbeatEvents.isEmpty()) { TestServerMonitorListener serverMonitorListener = entities.getServerMonitorListener(client); - context.getEventMatcher().assertServerMonitorEventsEquality(client, ignoreExtraEvents, expectedSdamEvents, serverMonitorListener.getEvents()); + context.getEventMatcher().assertServerMonitorEventsEquality(client, ignoreExtraEvents, + new BsonArray(expectedHeartbeatEvents), serverMonitorListener.getEvents()); } } else { throw new UnsupportedOperationException("Unexpected event type: " + eventType); @@ -810,6 +824,8 @@ private OperationResult executeWaitForEvent(final UnifiedTestContext context, fi case "poolReadyEvent": case "connectionCreatedEvent": case "connectionReadyEvent": + case "connectionClosedEvent": + case "connectionCheckedInEvent": context.getEventMatcher().waitForConnectionPoolEvents(clientId, event, count, entities.getConnectionPoolListener(clientId)); break; case "serverHeartbeatStartedEvent": diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index 67cb82f3656..6a267ed3c2b 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -539,16 +539,6 @@ public static void applyCustomizations(final TestDef def) { .file("server-discovery-and-monitoring", "pool-clear-on-error-checkout"); def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") .file("server-discovery-and-monitoring", "pool-cleared-on-min-pool-size-population-error"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-error-fail-single"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-timeout-error-single"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-error-fail-replicaset"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-timeout-error-replicaset"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-server-description-unchanged-on-min-pool-size-population-error"); // session tests def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") @@ -575,14 +565,6 @@ public static void applyCustomizations(final TestDef def) { def.skipNoncompliant("`MongoCluster.getWriteConcern`/`MongoCollection.getWriteConcern` are silently ignored in a transaction") .test("transactions", "client bulkWrite transactions", "client bulkWrite with writeConcern in a transaction causes a transaction error"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5956 TODO-JAVA-5956") - .file("transactions", "backpressure-retryable-writes"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5956 TODO-JAVA-5956") - .file("transactions", "backpressure-retryable-reads"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5956 TODO-JAVA-5956") - .file("transactions", "backpressure-retryable-commit"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-5956 TODO-JAVA-5956") - .file("transactions", "backpressure-retryable-abort"); def.skipJira("https://jira.mongodb.org/browse/JAVA-6179") .test("transactions", "retryable-writes", "increment txnNumber") .test("transactions", "commit", "reset session state commit") @@ -592,6 +574,34 @@ public static void applyCustomizations(final TestDef def) { .test("transactions-convenient-api", "callback-aborts", "withTransaction still succeeds if callback aborts and runs extra op"); + // backpressure + + def.modify(WAIT_FOR_BATCH_CURSOR_CREATION, IGNORE_EXTRA_EVENTS) + .test("client-backpressure", "tests that operations retry at most maxAttempts=2 times", + "client.createChangeStream retries at most maxAttempts=2 times") + .test("client-backpressure", "tests that operations retry at most maxAttempts=2 times", + "database.createChangeStream retries at most maxAttempts=2 times") + .test("client-backpressure", "tests that operations retry at most maxAttempts=2 times", + "collection.createChangeStream retries at most maxAttempts=2 times") + .test("client-backpressure", "tests that operations respect overload backoff retry loop", + "client.createChangeStream retries using operation loop") + .test("client-backpressure", "tests that operations respect overload backoff retry loop", + "client.createChangeStream (read) does not retry if retryReads=false") + .test("client-backpressure", "tests that operations respect overload backoff retry loop", + "database.createChangeStream retries using operation loop") + .test("client-backpressure", "tests that operations respect overload backoff retry loop", + "database.createChangeStream (read) does not retry if retryReads=false") + .test("client-backpressure", "tests that operations respect overload backoff retry loop", + "collection.createChangeStream retries using operation loop") + .test("client-backpressure", "tests that operations respect overload backoff retry loop", + "collection.createChangeStream (read) does not retry if retryReads=false"); + + // BatchCursorFlux fires closeCursor() then sink.error(e) without awaiting the killCursors reply, + // so under reactive the test framework snapshots command events before killCursors succeeded lands. + // Equivalent coverage is provided by the reactive BackpressureProseTest. + def.skipNoncompliantReactive("Reactive cursor auto-close on error does not await killCursors reply") + .test("client-backpressure", "getMore-retried-backpressure", "getMores are retried maxAttempts=2 times"); + // valid-pass def.skipDeprecated("MongoDB releases prior to 4.4 incorrectly add " diff --git a/driver-sync/src/test/functional/com/mongodb/internal/event/ConfigureFailPointCommandListener.java b/driver-sync/src/test/functional/com/mongodb/internal/event/ConfigureFailPointCommandListener.java new file mode 100644 index 00000000000..a31182a51c0 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/internal/event/ConfigureFailPointCommandListener.java @@ -0,0 +1,105 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.event; + +import com.mongodb.ServerAddress; +import com.mongodb.annotations.ThreadSafe; +import com.mongodb.client.FailPoint; +import com.mongodb.event.CommandEvent; +import com.mongodb.event.CommandFailedEvent; +import com.mongodb.event.CommandListener; +import com.mongodb.event.CommandStartedEvent; +import com.mongodb.event.CommandSucceededEvent; +import org.bson.BsonDocument; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.Predicate; + +import static com.mongodb.assertions.Assertions.assertNotNull; +import static com.mongodb.assertions.Assertions.assertTrue; +import static com.mongodb.assertions.Assertions.fail; + +@ThreadSafe +public final class ConfigureFailPointCommandListener implements CommandListener, AutoCloseable { + private final BsonDocument configureFailPoint; + private final ServerAddress serverAddress; + private final Predicate eventMatcher; + private final Object lock; + private final CompletableFuture failPointFuture; + + /** + * @param configureFailPoint See {@link FailPoint#enable(BsonDocument, ServerAddress)}. + * @param serverAddress See {@link FailPoint#enable(BsonDocument, ServerAddress)}. + * @param eventMatcher When an event is matched, an attempt to configure the fail point + * specified via {@code configureFailPoint} is made. + * The {@code eventMatcher} is guaranteed to be {@linkplain Predicate#test(Object) used} sequentially. + * The attempt is made at most once, + * and the {@code eventMatcher} {@linkplain Predicate#test(Object) test} that caused the attempt is the last one. + */ + public ConfigureFailPointCommandListener( + final BsonDocument configureFailPoint, + final ServerAddress serverAddress, + final Predicate eventMatcher) { + this.configureFailPoint = configureFailPoint; + this.serverAddress = serverAddress; + this.eventMatcher = eventMatcher; + lock = new Object(); + failPointFuture = new CompletableFuture<>(); + } + + @Override + public void commandStarted(final CommandStartedEvent event) { + onEvent(event); + } + + @Override + public void commandSucceeded(final CommandSucceededEvent event) { + onEvent(event); + } + + @Override + public void commandFailed(final CommandFailedEvent event) { + onEvent(event); + } + + private void onEvent(final CommandEvent event) { + synchronized (lock) { + if (!failPointFuture.isDone()) { + try { + if (eventMatcher.test(event)) { + assertTrue(failPointFuture.complete(FailPoint.enable(configureFailPoint, serverAddress))); + } + } catch (Throwable e) { + assertTrue(failPointFuture.completeExceptionally(e)); + } + } + } + } + + @Override + public void close() throws InterruptedException, ExecutionException { + synchronized (lock) { + if (failPointFuture.cancel(true)) { + fail("The listener was closed before (in the happens-before order) it attempted to configure the fail point"); + } else { + assertTrue(failPointFuture.isDone()); + assertNotNull(failPointFuture.get()).close(); + } + } + } +} diff --git a/driver-sync/src/test/unit/com/mongodb/client/MongoClientSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/MongoClientSpecification.groovy index 916d8179af5..6b9a1c1a7d8 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/MongoClientSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/MongoClientSpecification.groovy @@ -37,6 +37,8 @@ import com.mongodb.internal.TimeoutSettings import com.mongodb.internal.client.model.changestream.ChangeStreamLevel import com.mongodb.internal.connection.ClientMetadata import com.mongodb.internal.connection.Cluster +import com.mongodb.internal.connection.StreamFactoryFactory +import com.mongodb.internal.thread.AsyncClientExecutor import org.bson.BsonDocument import org.bson.Document import org.bson.codecs.UuidCodec @@ -48,6 +50,7 @@ import static com.mongodb.CustomMatchers.isTheSameAs import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry import static com.mongodb.ReadPreference.primary import static com.mongodb.ReadPreference.secondary +import static com.mongodb.assertions.Assertions.fail import static com.mongodb.client.internal.TestHelper.execute import static java.util.concurrent.TimeUnit.SECONDS import static org.bson.UuidRepresentation.C_SHARP_LEGACY @@ -70,7 +73,8 @@ class MongoClientSpecification extends Specification { .retryWrites(true) .codecRegistry(CODEC_REGISTRY) .build() - def client = new MongoClientImpl(Stub(Cluster), null, settings, null, new TestOperationExecutor([])) + def client = new MongoClientImpl(Stub(Cluster), null, settings, mockStreamFactoryFactory(), + AsyncClientExecutor.NO_OP, new TestOperationExecutor([])) when: def database = client.getDatabase('name') @@ -80,14 +84,15 @@ class MongoClientSpecification extends Specification { where: expectedDatabase << new MongoDatabaseImpl('name', withUuidRepresentation(CODEC_REGISTRY, UNSPECIFIED), secondary(), - WriteConcern.MAJORITY, true, true, ReadConcern.MAJORITY, UNSPECIFIED, null, + WriteConcern.MAJORITY, true, true, null, ReadConcern.MAJORITY, UNSPECIFIED, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) } def 'should use ListDatabasesIterableImpl correctly'() { given: def executor = new TestOperationExecutor([null, null]) - def client = new MongoClientImpl(Stub(Cluster), null, MongoClientSettings.builder().build(), null, executor) + def client = new MongoClientImpl(Stub(Cluster), null, MongoClientSettings.builder().build(), mockStreamFactoryFactory(), + AsyncClientExecutor.NO_OP, executor) def listDatabasesMethod = client.&listDatabases def listDatabasesNamesMethod = client.&listDatabaseNames @@ -96,14 +101,14 @@ class MongoClientSpecification extends Specification { then: expect listDatabasesIterable, isTheSameAs(new ListDatabasesIterableImpl<>(session, Document, - withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), primary(), executor, true, TIMEOUT_SETTINGS)) + withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), primary(), executor, true, null, TIMEOUT_SETTINGS)) when: listDatabasesIterable = execute(listDatabasesMethod, session, BsonDocument) then: expect listDatabasesIterable, isTheSameAs(new ListDatabasesIterableImpl<>(session, BsonDocument, - withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), primary(), executor, true, TIMEOUT_SETTINGS)) + withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), primary(), executor, true, null, TIMEOUT_SETTINGS)) when: def listDatabaseNamesIterable = execute(listDatabasesNamesMethod, session) as MongoIterable @@ -111,7 +116,7 @@ class MongoClientSpecification extends Specification { then: // listDatabaseNamesIterable is an instance of a MappingIterable, so have to get the mapped iterable inside it expect listDatabaseNamesIterable.getMapped(), isTheSameAs(new ListDatabasesIterableImpl<>(session, BsonDocument, - withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), primary(), executor, true, TIMEOUT_SETTINGS) + withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), primary(), executor, true, null, TIMEOUT_SETTINGS) .nameOnly(true)) cleanup: @@ -132,7 +137,8 @@ class MongoClientSpecification extends Specification { .build() def readPreference = settings.getReadPreference() def readConcern = settings.getReadConcern() - def client = new MongoClientImpl(Stub(Cluster), null, settings, null, executor) + def client = new MongoClientImpl(Stub(Cluster), null, settings, mockStreamFactoryFactory(), + AsyncClientExecutor.NO_OP, executor) def watchMethod = client.&watch when: @@ -141,7 +147,7 @@ class MongoClientSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), - readPreference, readConcern, executor, [], Document, ChangeStreamLevel.CLIENT, true, TIMEOUT_SETTINGS), + readPreference, readConcern, executor, [], Document, ChangeStreamLevel.CLIENT, true, null, TIMEOUT_SETTINGS), ['codec']) when: @@ -151,7 +157,7 @@ class MongoClientSpecification extends Specification { expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), readPreference, readConcern, executor, [new Document('$match', 1)], Document, ChangeStreamLevel.CLIENT, - true, TIMEOUT_SETTINGS), ['codec']) + true, null, TIMEOUT_SETTINGS), ['codec']) when: changeStreamIterable = execute(watchMethod, session, [new Document('$match', 1)], BsonDocument) @@ -160,7 +166,7 @@ class MongoClientSpecification extends Specification { expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, withUuidRepresentation(getDefaultCodecRegistry(), UNSPECIFIED), readPreference, readConcern, executor, [new Document('$match', 1)], BsonDocument, - ChangeStreamLevel.CLIENT, true, TIMEOUT_SETTINGS), ['codec']) + ChangeStreamLevel.CLIENT, true, null, TIMEOUT_SETTINGS), ['codec']) where: session << [null, Stub(ClientSession)] @@ -169,8 +175,8 @@ class MongoClientSpecification extends Specification { def 'should validate the ChangeStreamIterable pipeline data correctly'() { given: def executor = new TestOperationExecutor([]) - def client = new MongoClientImpl(Stub(Cluster), null, MongoClientSettings.builder().build(), null, - executor) + def client = new MongoClientImpl(Stub(Cluster), null, MongoClientSettings.builder().build(), mockStreamFactoryFactory(), + AsyncClientExecutor.NO_OP, executor) when: client.watch((Class) null) @@ -201,7 +207,8 @@ class MongoClientSpecification extends Specification { 1 * getClientMetadata() >> new ClientMetadata("test", driverInformation) } def settings = MongoClientSettings.builder().build() - def client = new MongoClientImpl(cluster, driverInformation, settings, null, new TestOperationExecutor([])) + def client = new MongoClientImpl(cluster, driverInformation, settings, mockStreamFactoryFactory(), + AsyncClientExecutor.NO_OP, new TestOperationExecutor([])) expect: client.getClusterDescription() == clusterDescription @@ -216,7 +223,8 @@ class MongoClientSpecification extends Specification { .build() when: - def client = new MongoClientImpl(Stub(Cluster), null, settings, null, new TestOperationExecutor([])) + def client = new MongoClientImpl(Stub(Cluster), null, settings, mockStreamFactoryFactory(), + AsyncClientExecutor.NO_OP, new TestOperationExecutor([])) then: (client.getCodecRegistry().get(UUID) as UuidCodec).getUuidRepresentation() == C_SHARP_LEGACY @@ -224,4 +232,12 @@ class MongoClientSpecification extends Specification { cleanup: client?.close() } + + def mockStreamFactoryFactory() { + Mock(StreamFactoryFactory) { + getExecutor() >> { + fail() + } + } + } } diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy index cb34236c627..143da45fc98 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy @@ -64,7 +64,7 @@ class GridFSBucketSpecification extends Specification { def registry = MongoClientSettings.getDefaultCodecRegistry() def database = databaseWithExecutor(Stub(OperationExecutor)) def databaseWithExecutor(OperationExecutor executor) { - new MongoDatabaseImpl('test', registry, primary(), WriteConcern.ACKNOWLEDGED, false, false, readConcern, + new MongoDatabaseImpl('test', registry, primary(), WriteConcern.ACKNOWLEDGED, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) } @@ -160,7 +160,7 @@ class GridFSBucketSpecification extends Specification { given: def defaultChunkSizeBytes = 255 * 1024 def database = new MongoDatabaseImpl('test', fromProviders(new DocumentCodecProvider()), secondary(), WriteConcern.ACKNOWLEDGED, - false, false, readConcern, JAVA_LEGACY, null, + false, false, null, readConcern, JAVA_LEGACY, null, new TimeoutSettings(0, 0, 0, null, 0), new TestOperationExecutor([])) @@ -604,7 +604,8 @@ class GridFSBucketSpecification extends Specification { then: executor.getReadPreference() == primary() - expect executor.getReadOperation(), isTheSameAs(new FindOperation(new MongoNamespace('test.fs.files'), decoder) + expect executor.getReadOperation(), isTheSameAs(new FindOperation(new MongoNamespace('test.fs.files'), decoder, + null) .filter(new BsonDocument())) when: @@ -615,7 +616,7 @@ class GridFSBucketSpecification extends Specification { then: executor.getReadPreference() == secondary() expect executor.getReadOperation(), isTheSameAs( - new FindOperation(new MongoNamespace('test.fs.files'), decoder).filter(filter)) + new FindOperation(new MongoNamespace('test.fs.files'), decoder, null).filter(filter)) } def 'should throw an exception if file not found when opening by name'() { diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketsSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketsSpecification.groovy index 0064cc9aad8..ef980fd7271 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketsSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketsSpecification.groovy @@ -35,8 +35,8 @@ class GridFSBucketsSpecification extends Specification { def 'should create a GridFSBucket with default bucket name'() { given: - def database = new MongoDatabaseImpl('db', Stub(CodecRegistry), Stub(ReadPreference), Stub(WriteConcern), false, true, readConcern, - JAVA_LEGACY, null, ClusterFixture.TIMEOUT_SETTINGS, Stub(OperationExecutor)) + def database = new MongoDatabaseImpl('db', Stub(CodecRegistry), Stub(ReadPreference), Stub(WriteConcern), false, true, null, + readConcern, JAVA_LEGACY, null, ClusterFixture.TIMEOUT_SETTINGS, Stub(OperationExecutor)) when: def gridFSBucket = GridFSBuckets.create(database) @@ -48,8 +48,8 @@ class GridFSBucketsSpecification extends Specification { def 'should create a GridFSBucket with custom bucket name'() { given: - def database = new MongoDatabaseImpl('db', Stub(CodecRegistry), Stub(ReadPreference), Stub(WriteConcern), false, true, readConcern, - JAVA_LEGACY, null, ClusterFixture.TIMEOUT_SETTINGS, Stub(OperationExecutor)) + def database = new MongoDatabaseImpl('db', Stub(CodecRegistry), Stub(ReadPreference), Stub(WriteConcern), false, true, null, + readConcern, JAVA_LEGACY, null, ClusterFixture.TIMEOUT_SETTINGS, Stub(OperationExecutor)) def customName = 'custom' when: diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSFindIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSFindIterableSpecification.groovy index 40cd03bc7e9..7aabe09dd21 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSFindIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSFindIterableSpecification.groovy @@ -57,7 +57,7 @@ class GridFSFindIterableSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null]) def underlying = new FindIterableImpl(null, namespace, GridFSFile, GridFSFile, codecRegistry, readPreference, readConcern, executor, - new Document(), true, TIMEOUT_SETTINGS) + new Document(), true, null, TIMEOUT_SETTINGS) def findIterable = new GridFSFindIterableImpl(underlying) when: 'default input should be as expected' @@ -67,7 +67,8 @@ class GridFSFindIterableSpecification extends Specification { def readPreference = executor.getReadPreference() then: - expect operation, isTheSameAs(new FindOperation(namespace, gridFSFileCodec) + expect operation, isTheSameAs(new FindOperation(namespace, gridFSFileCodec, + null) .filter(new BsonDocument()).retryReads(true)) readPreference == secondary() @@ -85,7 +86,8 @@ class GridFSFindIterableSpecification extends Specification { operation = executor.getReadOperation() as FindOperation then: 'should use the overrides' - expect operation, isTheSameAs(new FindOperation(namespace, gridFSFileCodec) + expect operation, isTheSameAs(new FindOperation(namespace, gridFSFileCodec, + null) .filter(new BsonDocument('filter', new BsonInt32(2))) .sort(new BsonDocument('sort', new BsonInt32(2))) .batchSize(99) @@ -101,7 +103,7 @@ class GridFSFindIterableSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null]) def findIterable = new FindIterableImpl(null, namespace, GridFSFile, GridFSFile, codecRegistry, readPreference, readConcern, - executor, new Document('filter', 1), true, TIMEOUT_SETTINGS) + executor, new Document('filter', 1), true, null, TIMEOUT_SETTINGS) when: findIterable.filter(new Document('filter', 1)) @@ -111,7 +113,8 @@ class GridFSFindIterableSpecification extends Specification { def operation = executor.getReadOperation() as FindOperation then: - expect operation, isTheSameAs(new FindOperation(namespace, gridFSFileCodec) + expect operation, isTheSameAs(new FindOperation(namespace, gridFSFileCodec, + null) .filter(new BsonDocument('filter', new BsonInt32(1))) .sort(new BsonDocument('sort', new BsonInt32(1))) .cursorType(CursorType.NonTailable) @@ -130,7 +133,7 @@ class GridFSFindIterableSpecification extends Specification { , null), ] def cursor = { - def batchToReturn = cannedResults.collect(); + def batchToReturn = cannedResults.collect() Stub(BatchCursor) { def count = 0 def results @@ -149,7 +152,7 @@ class GridFSFindIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]) def underlying = new FindIterableImpl(null, namespace, GridFSFile, GridFSFile, codecRegistry, readPreference, readConcern, executor, - new Document(), true, TIMEOUT_SETTINGS) + new Document(), true, null, TIMEOUT_SETTINGS) def mongoIterable = new GridFSFindIterableImpl(underlying) when: diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/AggregateIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/AggregateIterableSpecification.groovy index 467e9614424..42ac081943f 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/AggregateIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/AggregateIterableSpecification.groovy @@ -63,7 +63,7 @@ class AggregateIterableSpecification extends Specification { def pipeline = [new Document('$match', 1)] def aggregationIterable = new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, - true, TIMEOUT_SETTINGS) + false, true, null, TIMEOUT_SETTINGS) when: 'default input should be as expected' aggregationIterable.iterator() @@ -73,7 +73,7 @@ class AggregateIterableSpecification extends Specification { then: expect operation, isTheSameAs(new AggregateOperation(namespace, - [new BsonDocument('$match', new BsonInt32(1))], new DocumentCodec()) + [new BsonDocument('$match', new BsonInt32(1))], new DocumentCodec(), null) .retryReads(true)) readPreference == secondary() @@ -90,7 +90,7 @@ class AggregateIterableSpecification extends Specification { then: 'should use the overrides' expect operation, isTheSameAs(new AggregateOperation(namespace, - [new BsonDocument('$match', new BsonInt32(1))], new DocumentCodec()) + [new BsonDocument('$match', new BsonInt32(1))], new DocumentCodec(), null) .retryReads(true) .collation(collation) .hint(new BsonDocument('a', new BsonInt32(1))) @@ -98,7 +98,7 @@ class AggregateIterableSpecification extends Specification { when: 'both hint and hint string are set' aggregationIterable = new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) aggregationIterable .hint(new Document('a', 1)) @@ -109,7 +109,7 @@ class AggregateIterableSpecification extends Specification { then: 'should use hint not hint string' expect operation, isTheSameAs(new AggregateOperation(namespace, - [new BsonDocument('$match', new BsonInt32(1))], new DocumentCodec()) + [new BsonDocument('$match', new BsonInt32(1))], new DocumentCodec(), null) .hint(new BsonDocument('a', new BsonInt32(1)))) } @@ -122,7 +122,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $out' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) .batchSize(99) .allowDiskUse(true) .collation(collation) @@ -152,7 +152,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $out and is at the database level' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.DATABASE, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.DATABASE, false, false, null, TIMEOUT_SETTINGS) .batchSize(99) .maxTime(100, MILLISECONDS) .allowDiskUse(true) @@ -185,7 +185,7 @@ class AggregateIterableSpecification extends Specification { when: 'toCollection should work as expected' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) .allowDiskUse(true) .collation(collation) .hint(new Document('a', 1)) @@ -212,7 +212,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $out and hint string' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) .hintString('x_1').iterator() def operation = executor.getReadOperation() as AggregateToCollectionOperation @@ -226,7 +226,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $out and hint and hint string' executor = new TestOperationExecutor([null, null, null, null, null]) new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) .hint(new BsonDocument('x', new BsonInt32(1))) .hintString('x_1').iterator() @@ -250,7 +250,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $merge' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) .batchSize(99) .allowDiskUse(true) .collation(collation) @@ -281,7 +281,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $merge into a different database' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipelineWithIntoDocument, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + pipelineWithIntoDocument, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) .batchSize(99) .maxTime(100, MILLISECONDS) .allowDiskUse(true) @@ -314,7 +314,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $merge and is at the database level' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.DATABASE, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.DATABASE, false, false, null, TIMEOUT_SETTINGS) .batchSize(99) .maxTime(100, MILLISECONDS) .allowDiskUse(true) @@ -346,7 +346,7 @@ class AggregateIterableSpecification extends Specification { when: 'toCollection should work as expected' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) .allowDiskUse(true) .collation(collation) .hint(new Document('a', 1)) @@ -375,7 +375,7 @@ class AggregateIterableSpecification extends Specification { when: new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) .iterator() def operation = executor.getReadOperation() as AggregateToCollectionOperation @@ -419,7 +419,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $out' def aggregateIterable = new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) aggregateIterable.toCollection() def operation = executor.getReadOperation() as AggregateToCollectionOperation @@ -438,7 +438,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $out and is at the database level' aggregateIterable = new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, pipeline, AggregationLevel.DATABASE, false, TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, pipeline, AggregationLevel.DATABASE, false, false, null, TIMEOUT_SETTINGS) aggregateIterable.toCollection() operation = executor.getReadOperation() as AggregateToCollectionOperation @@ -457,7 +457,7 @@ class AggregateIterableSpecification extends Specification { when: 'toCollection should work as expected' aggregateIterable = new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) aggregateIterable.toCollection() operation = executor.getReadOperation() as AggregateToCollectionOperation @@ -475,7 +475,7 @@ class AggregateIterableSpecification extends Specification { when: 'aggregation includes $out with namespace' aggregateIterable = new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, outWithDBpipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, outWithDBpipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) aggregateIterable.toCollection() operation = executor.getReadOperation() as AggregateToCollectionOperation @@ -502,7 +502,7 @@ class AggregateIterableSpecification extends Specification { def executor = new TestOperationExecutor([batchCursor, batchCursor]) def pipeline = [new Document('$match', 1)] def aggregationIterable = new AggregateIterableImpl(clientSession, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) when: aggregationIterable.first() @@ -528,7 +528,7 @@ class AggregateIterableSpecification extends Specification { def executor = new TestOperationExecutor([null, batchCursor, null, batchCursor, null]) def pipeline = [new Document('$match', 1), new Document('$out', 'collName')] def aggregationIterable = new AggregateIterableImpl(clientSession, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) when: aggregationIterable.first() @@ -559,7 +559,7 @@ class AggregateIterableSpecification extends Specification { def executor = new TestOperationExecutor([new MongoException('failure')]) def pipeline = [new BsonDocument('$match', new BsonInt32(1))] def aggregationIterable = new AggregateIterableImpl(null, namespace, BsonDocument, BsonDocument, codecRegistry, readPreference, - readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) when: 'The operation fails with an exception' aggregationIterable.iterator() @@ -575,14 +575,14 @@ class AggregateIterableSpecification extends Specification { when: 'a codec is missing' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - pipeline, AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS).iterator() + pipeline, AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS).iterator() then: thrown(CodecConfigurationException) when: 'pipeline contains null' new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - [null], AggregationLevel.COLLECTION, false, TIMEOUT_SETTINGS).iterator() + [null], AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS).iterator() then: thrown(IllegalArgumentException) @@ -611,7 +611,7 @@ class AggregateIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]) def mongoIterable = new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, [new Document('$match', 1)], AggregationLevel.COLLECTION, false, + readConcern, writeConcern, executor, [new Document('$match', 1)], AggregationLevel.COLLECTION, false, false, null, TIMEOUT_SETTINGS) when: @@ -657,7 +657,7 @@ class AggregateIterableSpecification extends Specification { def batchSize = 5 def mongoIterable = new AggregateIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, Stub(OperationExecutor), [new Document('$match', 1)], AggregationLevel.COLLECTION, - false, TIMEOUT_SETTINGS) + false, false, null, TIMEOUT_SETTINGS) then: mongoIterable.getBatchSize() == null diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/ChangeStreamIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/ChangeStreamIterableSpecification.groovy index fdf31a76b56..9915f8109eb 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/ChangeStreamIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/ChangeStreamIterableSpecification.groovy @@ -61,7 +61,7 @@ class ChangeStreamIterableSpecification extends Specification { def executor = new TestOperationExecutor([null, null, null, null, null]) def pipeline = [new Document('$match', 1)] def changeStreamIterable = new ChangeStreamIterableImpl(null, namespace, codecRegistry, readPreference, readConcern, - executor, pipeline, Document, ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS) + executor, pipeline, Document, ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS) when: 'default input should be as expected' changeStreamIterable.iterator() @@ -73,7 +73,7 @@ class ChangeStreamIterableSpecification extends Specification { then: expect operation, isTheSameAs(new ChangeStreamOperation(namespace, FullDocument.DEFAULT, FullDocumentBeforeChange.DEFAULT, [BsonDocument.parse('{$match: 1}')], codec, - ChangeStreamLevel.COLLECTION) + ChangeStreamLevel.COLLECTION, null) .retryReads(true)) readPreference == secondary() @@ -92,7 +92,7 @@ class ChangeStreamIterableSpecification extends Specification { then: 'should use the overrides' expect operation, isTheSameAs(new ChangeStreamOperation(namespace, FullDocument.UPDATE_LOOKUP, FullDocumentBeforeChange.WHEN_AVAILABLE, [BsonDocument.parse('{$match: 1}')], codec, - ChangeStreamLevel.COLLECTION) + ChangeStreamLevel.COLLECTION, null) .retryReads(true) .collation(collation) .resumeAfter(resumeToken) @@ -107,7 +107,7 @@ class ChangeStreamIterableSpecification extends Specification { } def executor = new TestOperationExecutor([batchCursor, batchCursor]) def changeStreamIterable = new ChangeStreamIterableImpl(clientSession, namespace, codecRegistry, readPreference, readConcern, - executor, [], Document, ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS) + executor, [], Document, ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS) when: changeStreamIterable.first() @@ -131,7 +131,7 @@ class ChangeStreamIterableSpecification extends Specification { def executor = new TestOperationExecutor([new MongoException('failure')]) def pipeline = [new BsonDocument('$match', new BsonInt32(1))] def changeStreamIterable = new ChangeStreamIterableImpl(null, namespace, codecRegistry, readPreference, readConcern, - executor, pipeline, BsonDocument, ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS) + executor, pipeline, BsonDocument, ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS) when: 'The operation fails with an exception' changeStreamIterable.iterator() @@ -141,14 +141,14 @@ class ChangeStreamIterableSpecification extends Specification { when: 'a codec is missing' new ChangeStreamIterableImpl(null, namespace, altRegistry, readPreference, readConcern, executor, pipeline, Document, - ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS).iterator() + ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS).iterator() then: thrown(CodecConfigurationException) when: 'pipeline contains null' new ChangeStreamIterableImpl(null, namespace, codecRegistry, readPreference, readConcern, executor, [null], Document, - ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS).iterator() + ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS).iterator() then: thrown(IllegalArgumentException) @@ -163,7 +163,7 @@ class ChangeStreamIterableSpecification extends Specification { def executor = new TestOperationExecutor([cursor(cannedResults.collect()), cursor(cannedResults.collect()), cursor(cannedResults.collect()), cursor(cannedResults.collect())]) def mongoIterable = new ChangeStreamIterableImpl(null, namespace, codecRegistry, readPreference, readConcern, executor, [], - Document, ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS) + Document, ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS) when: def results = mongoIterable.first() @@ -212,7 +212,7 @@ class ChangeStreamIterableSpecification extends Specification { cursor(cannedResults.collect()), cursor(cannedResults.collect()), cursor(cannedResults.collect())]) def mongoIterable = new ChangeStreamIterableImpl(null, namespace, codecRegistry, readPreference, readConcern, executor, [], - Document, ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS).withDocumentClass(RawBsonDocument) + Document, ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS).withDocumentClass(RawBsonDocument) when: def results = mongoIterable.first() @@ -256,7 +256,7 @@ class ChangeStreamIterableSpecification extends Specification { when: def batchSize = 5 def mongoIterable = new ChangeStreamIterableImpl(null, namespace, codecRegistry, readPreference, readConcern, - Stub(OperationExecutor), [BsonDocument.parse('{$match: 1}')], BsonDocument, ChangeStreamLevel.COLLECTION, true, + Stub(OperationExecutor), [BsonDocument.parse('{$match: 1}')], BsonDocument, ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS) then: diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/ClientSessionBindingSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/ClientSessionBindingSpecification.groovy index e2e664f324d..f2ecac0c170 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/ClientSessionBindingSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/ClientSessionBindingSpecification.groovy @@ -16,7 +16,7 @@ package com.mongodb.client.internal - +import com.mongodb.ClusterFixture import com.mongodb.ReadPreference import com.mongodb.client.ClientSession import com.mongodb.internal.binding.ClusterBinding @@ -25,29 +25,28 @@ import com.mongodb.internal.binding.ReadWriteBinding import com.mongodb.internal.connection.Cluster import spock.lang.Specification -import static com.mongodb.ClusterFixture.OPERATION_CONTEXT - class ClientSessionBindingSpecification extends Specification { def 'should call underlying wrapped binding'() { given: def session = Stub(ClientSession) + def operationContext = ClusterFixture.createOperationContext() def wrappedBinding = Mock(ClusterBinding); def binding = new ClientSessionBinding(session, false, wrappedBinding) when: - binding.getReadConnectionSource(OPERATION_CONTEXT) + binding.getReadConnectionSource(operationContext) then: - 1 * wrappedBinding.getReadConnectionSource(OPERATION_CONTEXT) >> { + 1 * wrappedBinding.getReadConnectionSource(operationContext) >> { Stub(ConnectionSource) } when: - binding.getWriteConnectionSource(OPERATION_CONTEXT) + binding.getWriteConnectionSource(operationContext) then: - 1 * wrappedBinding.getWriteConnectionSource(OPERATION_CONTEXT) >> { + 1 * wrappedBinding.getWriteConnectionSource(operationContext) >> { Stub(ConnectionSource) } } @@ -77,8 +76,9 @@ class ClientSessionBindingSpecification extends Specification { def session = Mock(ClientSession) def wrappedBinding = createStubBinding() def binding = new ClientSessionBinding(session, true, wrappedBinding) - def readConnectionSource = binding.getReadConnectionSource(OPERATION_CONTEXT) - def writeConnectionSource = binding.getWriteConnectionSource(OPERATION_CONTEXT) + def operationContext = ClusterFixture.createOperationContext() + def readConnectionSource = binding.getReadConnectionSource(operationContext) + def writeConnectionSource = binding.getWriteConnectionSource(operationContext) when: binding.release() diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/CryptConnectionSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/CryptConnectionSpecification.groovy index 8a38f966754..3ec9a889e29 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/CryptConnectionSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/CryptConnectionSpecification.groovy @@ -61,7 +61,7 @@ class CryptConnectionSpecification extends Specification { def cryptConnection = new CryptConnection(wrappedConnection, crypt) def codec = new DocumentCodec() def timeoutContext = Mock(TimeoutContext) - def operationContext = ClusterFixture.OPERATION_CONTEXT.withTimeoutContext(timeoutContext) + def operationContext = ClusterFixture.createOperationContext().withTimeoutContext(timeoutContext) def operationTimeout = Mock(Timeout) timeoutContext.getTimeout() >> operationTimeout @@ -127,7 +127,7 @@ class CryptConnectionSpecification extends Specification { def encryptedResponse = toRaw(new BsonDocument('ok', new BsonInt32(1))) def decryptedResponse = encryptedResponse def timeoutContext = Mock(TimeoutContext) - def operationContext = ClusterFixture.OPERATION_CONTEXT.withTimeoutContext(timeoutContext) + def operationContext = ClusterFixture.createOperationContext().withTimeoutContext(timeoutContext) def operationTimeout = Mock(Timeout) timeoutContext.getTimeout() >> operationTimeout @@ -183,7 +183,7 @@ class CryptConnectionSpecification extends Specification { def encryptedResponse = toRaw(new BsonDocument('ok', new BsonInt32(1))) def decryptedResponse = encryptedResponse def timeoutContext = Mock(TimeoutContext) - def operationContext = ClusterFixture.OPERATION_CONTEXT.withTimeoutContext(timeoutContext) + def operationContext = ClusterFixture.createOperationContext().withTimeoutContext(timeoutContext) def operationTimeout = Mock(Timeout) timeoutContext.getTimeout() >> operationTimeout diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/DistinctIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/DistinctIterableSpecification.groovy index 82c4bf9a037..bb7aac6f3c9 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/DistinctIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/DistinctIterableSpecification.groovy @@ -56,7 +56,7 @@ class DistinctIterableSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null]) def distinctIterable = new DistinctIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, - executor, 'field', new BsonDocument(), true, TIMEOUT_SETTINGS) + executor, 'field', new BsonDocument(), true, null, TIMEOUT_SETTINGS) when: 'default input should be as expected' distinctIterable.iterator() @@ -65,7 +65,7 @@ class DistinctIterableSpecification extends Specification { def readPreference = executor.getReadPreference() then: - expect operation, isTheSameAs(new DistinctOperation(namespace, 'field', new DocumentCodec()) + expect operation, isTheSameAs(new DistinctOperation(namespace, 'field', new DocumentCodec(), null) .filter(new BsonDocument()).retryReads(true)) readPreference == secondary() @@ -76,7 +76,7 @@ class DistinctIterableSpecification extends Specification { then: 'should use the overrides' expect operation, isTheSameAs( - new DistinctOperation(namespace, 'field', new DocumentCodec()) + new DistinctOperation(namespace, 'field', new DocumentCodec(), null) .filter(new BsonDocument('field', new BsonInt32(1))).collation(collation).retryReads(true)) } @@ -87,7 +87,7 @@ class DistinctIterableSpecification extends Specification { } def executor = new TestOperationExecutor([batchCursor, batchCursor]) def distinctIterable = new DistinctIterableImpl(clientSession, namespace, Document, Document, codecRegistry, readPreference, - readConcern, executor, 'field', new BsonDocument(), true, TIMEOUT_SETTINGS) + readConcern, executor, 'field', new BsonDocument(), true, null, TIMEOUT_SETTINGS) when: distinctIterable.first() @@ -110,7 +110,7 @@ class DistinctIterableSpecification extends Specification { def codecRegistry = fromProviders([new ValueCodecProvider(), new BsonValueCodecProvider()]) def executor = new TestOperationExecutor([new MongoException('failure')]) def distinctIterable = new DistinctIterableImpl(null, namespace, Document, BsonDocument, codecRegistry, readPreference, - readConcern, executor, 'field', new BsonDocument(), true, TIMEOUT_SETTINGS) + readConcern, executor, 'field', new BsonDocument(), true, null, TIMEOUT_SETTINGS) when: 'The operation fails with an exception' distinctIterable.iterator() @@ -147,7 +147,7 @@ class DistinctIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]) def mongoIterable = new DistinctIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, ReadConcern.LOCAL, - executor, 'field', new BsonDocument(), true, TIMEOUT_SETTINGS) + executor, 'field', new BsonDocument(), true, null, TIMEOUT_SETTINGS) when: def results = mongoIterable.first() @@ -191,7 +191,7 @@ class DistinctIterableSpecification extends Specification { when: def batchSize = 5 def mongoIterable = new DistinctIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, - Stub(OperationExecutor), 'field', new BsonDocument(), true, TIMEOUT_SETTINGS) + Stub(OperationExecutor), 'field', new BsonDocument(), true, null, TIMEOUT_SETTINGS) then: mongoIterable.getBatchSize() == null diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/FindIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/FindIterableSpecification.groovy index 78ab9a3601b..24b3bea0512 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/FindIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/FindIterableSpecification.groovy @@ -58,7 +58,7 @@ class FindIterableSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null, null]) def findIterable = new FindIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, - executor, new Document('filter', 1), true, TIMEOUT_SETTINGS) + executor, new Document('filter', 1), true, null, TIMEOUT_SETTINGS) .sort(new Document('sort', 1)) .projection(new Document('projection', 1)) .batchSize(100) @@ -83,7 +83,8 @@ class FindIterableSpecification extends Specification { def readPreference = executor.getReadPreference() then: - expect operation, isTheSameAs(new FindOperation(namespace, new DocumentCodec()) + expect operation, isTheSameAs(new FindOperation(namespace, new DocumentCodec(), + null) .filter(new BsonDocument('filter', new BsonInt32(1))) .sort(new BsonDocument('sort', new BsonInt32(1))) .projection(new BsonDocument('projection', new BsonInt32(1))) @@ -128,7 +129,8 @@ class FindIterableSpecification extends Specification { then: 'should use the overrides' expect operation, isTheSameAs( - new FindOperation(namespace, new DocumentCodec()) + new FindOperation(namespace, new DocumentCodec(), + null) .filter(new BsonDocument('filter', new BsonInt32(2))) .sort(new BsonDocument('sort', new BsonInt32(2))) .projection(new BsonDocument('projection', new BsonInt32(2))) @@ -151,7 +153,7 @@ class FindIterableSpecification extends Specification { when: 'passing nulls to nullable methods' new FindIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, - executor, new Document('filter', 1), true, TIMEOUT_SETTINGS) + executor, new Document('filter', 1), true, null, TIMEOUT_SETTINGS) .filter(null as Bson) .collation(null) .projection(null) @@ -165,7 +167,8 @@ class FindIterableSpecification extends Specification { operation = executor.getReadOperation() as FindOperation then: 'should set an empty doc for the filter' - expect operation, isTheSameAs(new FindOperation(namespace, new DocumentCodec()) + expect operation, isTheSameAs(new FindOperation(namespace, new DocumentCodec(), + null) .filter(new BsonDocument()).retryReads(true)) } @@ -176,7 +179,7 @@ class FindIterableSpecification extends Specification { } def executor = new TestOperationExecutor([batchCursor, batchCursor]) def findIterable = new FindIterableImpl(clientSession, namespace, Document, Document, codecRegistry, readPreference, readConcern, - executor, new Document('filter', 1), true, TIMEOUT_SETTINGS) + executor, new Document('filter', 1), true, null, TIMEOUT_SETTINGS) when: findIterable.first() @@ -198,7 +201,7 @@ class FindIterableSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null]) def findIterable = new FindIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, - executor, new Document('filter', 1), true, TIMEOUT_SETTINGS) + executor, new Document('filter', 1), true, null, TIMEOUT_SETTINGS) when: findIterable.filter(new Document('filter', 1)) @@ -208,7 +211,8 @@ class FindIterableSpecification extends Specification { def operation = executor.getReadOperation() as FindOperation then: - expect operation, isTheSameAs(new FindOperation(namespace, new DocumentCodec()) + expect operation, isTheSameAs(new FindOperation(namespace, new DocumentCodec(), + null) .filter(new BsonDocument('filter', new BsonInt32(1))) .sort(new BsonDocument('sort', new BsonInt32(1))) .cursorType(CursorType.NonTailable) @@ -239,7 +243,7 @@ class FindIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]) def mongoIterable = new FindIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, - executor, new Document(), true, TIMEOUT_SETTINGS) + executor, new Document(), true, null, TIMEOUT_SETTINGS) when: def results = mongoIterable.first() @@ -283,7 +287,7 @@ class FindIterableSpecification extends Specification { when: def batchSize = 5 def mongoIterable = new FindIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, Stub(OperationExecutor), new Document(), true, TIMEOUT_SETTINGS) + readConcern, Stub(OperationExecutor), new Document(), true, null, TIMEOUT_SETTINGS) then: mongoIterable.getBatchSize() == null @@ -305,7 +309,7 @@ class FindIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor]) def mongoIterable = new FindIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, - executor, new Document(), true, TIMEOUT_SETTINGS) + executor, new Document(), true, null, TIMEOUT_SETTINGS) when: mongoIterable.forEach(new Consumer() { diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/ListCollectionsIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/ListCollectionsIterableSpecification.groovy index 12556430167..b6950e4d950 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/ListCollectionsIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/ListCollectionsIterableSpecification.groovy @@ -49,11 +49,11 @@ class ListCollectionsIterableSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null, null, null]) def listCollectionIterable = new ListCollectionsIterableImpl(null, 'db', false, Document, codecRegistry, - readPreference, executor, true, TIMEOUT_SETTINGS) + readPreference, executor, true, null, TIMEOUT_SETTINGS) .filter(new Document('filter', 1)) .batchSize(100) def listCollectionNamesIterable = new ListCollectionsIterableImpl(null, 'db', true, Document, codecRegistry, - readPreference, executor, true, TIMEOUT_SETTINGS) + readPreference, executor, true, null, TIMEOUT_SETTINGS) when: 'default input should be as expected' listCollectionIterable.iterator() @@ -62,7 +62,7 @@ class ListCollectionsIterableSpecification extends Specification { def readPreference = executor.getReadPreference() then: - expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec()) + expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec(), null) .filter(new BsonDocument('filter', new BsonInt32(1))).batchSize(100) .retryReads(true) .authorizedCollections(false)) @@ -74,7 +74,7 @@ class ListCollectionsIterableSpecification extends Specification { operation = executor.getReadOperation() as ListCollectionsOperation then: 'should use the overrides' - expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec()) + expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec(), null) .filter(new BsonDocument('filter', new BsonInt32(2))).batchSize(99) .retryReads(true)) @@ -84,7 +84,7 @@ class ListCollectionsIterableSpecification extends Specification { operation = executor.getReadOperation() as ListCollectionsOperation then: 'should create operation with nameOnly' - expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec()).nameOnly(true) + expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec(), null).nameOnly(true) .retryReads(true)) when: 'requesting `authorizedCollections`' @@ -92,7 +92,7 @@ class ListCollectionsIterableSpecification extends Specification { operation = executor.getReadOperation() as ListCollectionsOperation then: 'should create operation with `authorizedCollections`' - expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec()) + expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec(), null) .authorizedCollections(true) .nameOnly(true) .retryReads(true)) @@ -105,7 +105,7 @@ class ListCollectionsIterableSpecification extends Specification { } def executor = new TestOperationExecutor([batchCursor, batchCursor]) def listCollectionIterable = new ListCollectionsIterableImpl(clientSession, 'db', false, Document, codecRegistry, - readPreference, executor, true, TIMEOUT_SETTINGS) + readPreference, executor, true, null, TIMEOUT_SETTINGS) when: listCollectionIterable.first() @@ -146,7 +146,7 @@ class ListCollectionsIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]) def mongoIterable = new ListCollectionsIterableImpl(null, 'db', false, Document, codecRegistry, readPreference, - executor, true, TIMEOUT_SETTINGS) + executor, true, null, TIMEOUT_SETTINGS) when: def results = mongoIterable.first() @@ -190,7 +190,7 @@ class ListCollectionsIterableSpecification extends Specification { when: def batchSize = 5 def mongoIterable = new ListCollectionsIterableImpl(null, 'db', false, Document, codecRegistry, readPreference, - Stub(OperationExecutor), true, TIMEOUT_SETTINGS) + Stub(OperationExecutor), true, null, TIMEOUT_SETTINGS) then: mongoIterable.getBatchSize() == null diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/ListDatabasesIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/ListDatabasesIterableSpecification.groovy index 627cc13ef3c..eed19a5a063 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/ListDatabasesIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/ListDatabasesIterableSpecification.groovy @@ -46,8 +46,8 @@ class ListDatabasesIterableSpecification extends Specification { def 'should build the expected listCollectionOperation'() { given: def executor = new TestOperationExecutor([null, null, null]) - def listDatabaseIterable = new ListDatabasesIterableImpl(null, Document, codecRegistry, readPreference, executor, true, - TIMEOUT_SETTINGS) + def listDatabaseIterable = new ListDatabasesIterableImpl(null, Document, codecRegistry, readPreference, executor, + true, null, TIMEOUT_SETTINGS) when: 'default input should be as expected' listDatabaseIterable.iterator() @@ -56,7 +56,7 @@ class ListDatabasesIterableSpecification extends Specification { def readPreference = executor.getReadPreference() then: - expect operation, isTheSameAs(new ListDatabasesOperation(new DocumentCodec()) + expect operation, isTheSameAs(new ListDatabasesOperation(new DocumentCodec(), null) .retryReads(true)) readPreference == secondary() @@ -66,7 +66,7 @@ class ListDatabasesIterableSpecification extends Specification { operation = executor.getReadOperation() as ListDatabasesOperation then: 'should use the overrides' - expect operation, isTheSameAs(new ListDatabasesOperation(new DocumentCodec()) + expect operation, isTheSameAs(new ListDatabasesOperation(new DocumentCodec(), null) .filter(BsonDocument.parse('{a: 1}')).nameOnly(true).retryReads(true)) when: 'overriding initial options' @@ -75,7 +75,7 @@ class ListDatabasesIterableSpecification extends Specification { operation = executor.getReadOperation() as ListDatabasesOperation then: 'should use the overrides' - expect operation, isTheSameAs(new ListDatabasesOperation(new DocumentCodec()) + expect operation, isTheSameAs(new ListDatabasesOperation(new DocumentCodec(), null) .filter(BsonDocument.parse('{a: 1}')).nameOnly(true).authorizedDatabasesOnly(true).retryReads(true)) } @@ -102,7 +102,7 @@ class ListDatabasesIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]) def mongoIterable = new ListDatabasesIterableImpl(null, Document, codecRegistry, readPreference, executor, - true, TIMEOUT_SETTINGS) + true, null, TIMEOUT_SETTINGS) when: def results = mongoIterable.first() @@ -145,8 +145,8 @@ class ListDatabasesIterableSpecification extends Specification { def 'should get and set batchSize as expected'() { when: def batchSize = 5 - def mongoIterable = new ListDatabasesIterableImpl(null, Document, codecRegistry, readPreference, - Stub(OperationExecutor), true, TIMEOUT_SETTINGS) + def mongoIterable = new ListDatabasesIterableImpl(null, Document, codecRegistry, readPreference, Stub(OperationExecutor), + true, null, TIMEOUT_SETTINGS) then: mongoIterable.getBatchSize() == null diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/ListIndexesIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/ListIndexesIterableSpecification.groovy index f7bad5189dd..10b63480893 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/ListIndexesIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/ListIndexesIterableSpecification.groovy @@ -49,7 +49,7 @@ class ListIndexesIterableSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null]) def listIndexesIterable = new ListIndexesIterableImpl(null, namespace, Document, codecRegistry, readPreference, - executor, true, TIMEOUT_SETTINGS).batchSize(100) + executor, true, null, TIMEOUT_SETTINGS).batchSize(100) when: 'default input should be as expected' listIndexesIterable.iterator() @@ -58,7 +58,7 @@ class ListIndexesIterableSpecification extends Specification { def readPreference = executor.getReadPreference() then: - expect operation, isTheSameAs(new ListIndexesOperation(namespace, new DocumentCodec()) + expect operation, isTheSameAs(new ListIndexesOperation(namespace, new DocumentCodec(), null) .batchSize(100).retryReads(true)) readPreference == secondary() @@ -70,7 +70,7 @@ class ListIndexesIterableSpecification extends Specification { operation = executor.getReadOperation() as ListIndexesOperation then: 'should use the overrides' - expect operation, isTheSameAs(new ListIndexesOperation(namespace, new DocumentCodec()) + expect operation, isTheSameAs(new ListIndexesOperation(namespace, new DocumentCodec(), null) .batchSize(99).retryReads(true)) } @@ -81,7 +81,7 @@ class ListIndexesIterableSpecification extends Specification { } def executor = new TestOperationExecutor([batchCursor, batchCursor]) def listIndexesIterable = new ListIndexesIterableImpl(clientSession, namespace, Document, codecRegistry, readPreference, - executor, true, TIMEOUT_SETTINGS) + executor, true, null, TIMEOUT_SETTINGS) when: listIndexesIterable.first() @@ -123,7 +123,7 @@ class ListIndexesIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]) def mongoIterable = new ListIndexesIterableImpl(null, namespace, Document, codecRegistry, readPreference, - executor, true, TIMEOUT_SETTINGS) + executor, true, null, TIMEOUT_SETTINGS) when: def results = mongoIterable.first() @@ -167,7 +167,7 @@ class ListIndexesIterableSpecification extends Specification { when: def batchSize = 5 def mongoIterable = new ListIndexesIterableImpl(null, namespace, Document, codecRegistry, readPreference, - Stub(OperationExecutor), true, TIMEOUT_SETTINGS) + Stub(OperationExecutor), true, null, TIMEOUT_SETTINGS) then: mongoIterable.getBatchSize() == null diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/MapReduceIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/MapReduceIterableSpecification.groovy index b2b7faa6b2a..e51cb062e76 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/MapReduceIterableSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/MapReduceIterableSpecification.groovy @@ -63,7 +63,7 @@ class MapReduceIterableSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null]) def mapReduceIterable = new MapReduceIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, 'map', 'reduce', TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, 'map', 'reduce', null, TIMEOUT_SETTINGS) when: 'default input should be as expected' mapReduceIterable.iterator() @@ -110,7 +110,7 @@ class MapReduceIterableSpecification extends Specification { when: 'mapReduce to a collection' def collectionNamespace = new MongoNamespace('dbName', 'collName') def mapReduceIterable = new MapReduceIterableImpl(null, namespace, Document, Document, codecRegistry, - readPreference, readConcern, writeConcern, executor, 'map', 'reduce', TIMEOUT_SETTINGS) + readPreference, readConcern, writeConcern, executor, 'map', 'reduce', null, TIMEOUT_SETTINGS) .collectionName(collectionNamespace.getCollectionName()) .databaseName(collectionNamespace.getDatabaseName()) .filter(new Document('filter', 1)) @@ -169,7 +169,7 @@ class MapReduceIterableSpecification extends Specification { } def executor = new TestOperationExecutor([batchCursor, batchCursor]) def mapReduceIterable = new MapReduceIterableImpl(clientSession, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, 'map', 'reduce', TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, 'map', 'reduce', null, TIMEOUT_SETTINGS) when: mapReduceIterable.first() @@ -194,7 +194,7 @@ class MapReduceIterableSpecification extends Specification { } def executor = new TestOperationExecutor([null, batchCursor, null, batchCursor, null]) def mapReduceIterable = new MapReduceIterableImpl(clientSession, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, executor, 'map', 'reduce', TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, 'map', 'reduce', null, TIMEOUT_SETTINGS) .collectionName('collName') when: @@ -227,7 +227,7 @@ class MapReduceIterableSpecification extends Specification { def codecRegistry = fromProviders([new ValueCodecProvider(), new BsonValueCodecProvider()]) def executor = new TestOperationExecutor([new MongoException('failure')]) def mapReduceIterable = new MapReduceIterableImpl(null, namespace, BsonDocument, BsonDocument, codecRegistry, - readPreference, readConcern, writeConcern, executor, 'map', 'reduce', TIMEOUT_SETTINGS) + readPreference, readConcern, writeConcern, executor, 'map', 'reduce', null, TIMEOUT_SETTINGS) when: 'The operation fails with an exception' @@ -244,7 +244,7 @@ class MapReduceIterableSpecification extends Specification { when: 'a codec is missing' new MapReduceIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, - 'map', 'reduce', TIMEOUT_SETTINGS).iterator() + 'map', 'reduce', null, TIMEOUT_SETTINGS).iterator() then: thrown(CodecConfigurationException) @@ -274,7 +274,7 @@ class MapReduceIterableSpecification extends Specification { } def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]) def mongoIterable = new MapReduceIterableImpl(null, namespace, BsonDocument, BsonDocument, codecRegistry, readPreference, - readConcern, writeConcern, executor, 'map', 'reduce', TIMEOUT_SETTINGS) + readConcern, writeConcern, executor, 'map', 'reduce', null, TIMEOUT_SETTINGS) when: def results = mongoIterable.first() @@ -318,7 +318,7 @@ class MapReduceIterableSpecification extends Specification { when: def batchSize = 5 def mongoIterable = new MapReduceIterableImpl(null, namespace, Document, Document, codecRegistry, readPreference, - readConcern, writeConcern, Stub(OperationExecutor), 'map', 'reduce', TIMEOUT_SETTINGS) + readConcern, writeConcern, Stub(OperationExecutor), 'map', 'reduce', null, TIMEOUT_SETTINGS) then: mongoIterable.getBatchSize() == null diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy index c75a4255595..77464fa0183 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 @@ -29,6 +29,7 @@ import com.mongodb.internal.client.model.changestream.ChangeStreamLevel import com.mongodb.internal.connection.Cluster import com.mongodb.internal.session.ServerSessionPool import com.mongodb.internal.observability.micrometer.TracingManager +import com.mongodb.internal.thread.AsyncClientExecutor import org.bson.BsonDocument import org.bson.Document import org.bson.codecs.UuidCodec @@ -77,7 +78,7 @@ class MongoClusterSpecification extends Specification { where: expectedDatabase << new MongoDatabaseImpl('name', CODEC_REGISTRY, secondary(), - WriteConcern.MAJORITY, true, true, ReadConcern.MAJORITY, UNSPECIFIED, null, + WriteConcern.MAJORITY, true, true, null, ReadConcern.MAJORITY, UNSPECIFIED, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) } @@ -162,14 +163,14 @@ class MongoClusterSpecification extends Specification { then: expect listDatabasesIterable, isTheSameAs(new ListDatabasesIterableImpl<>(session, Document, - CLIENT_SETTINGS.codecRegistry, primary(), executor, true, TIMEOUT_SETTINGS)) + CLIENT_SETTINGS.codecRegistry, primary(), executor, true, null, TIMEOUT_SETTINGS)) when: listDatabasesIterable = execute(listDatabasesMethod, session, BsonDocument) then: expect listDatabasesIterable, isTheSameAs(new ListDatabasesIterableImpl<>(session, BsonDocument, - CLIENT_SETTINGS.codecRegistry, primary(), executor, true, TIMEOUT_SETTINGS)) + CLIENT_SETTINGS.codecRegistry, primary(), executor, true, null, TIMEOUT_SETTINGS)) when: def listDatabaseNamesIterable = execute(listDatabasesNamesMethod, session) as MongoIterable @@ -177,7 +178,7 @@ class MongoClusterSpecification extends Specification { then: // listDatabaseNamesIterable is an instance of a MappingIterable, so have to get the mapped iterable inside it expect listDatabaseNamesIterable.getMapped(), isTheSameAs(new ListDatabasesIterableImpl<>(session, BsonDocument, - CLIENT_SETTINGS.codecRegistry, primary(), executor, true, TIMEOUT_SETTINGS) + CLIENT_SETTINGS.codecRegistry, primary(), executor, true, null, TIMEOUT_SETTINGS) .nameOnly(true)) where: @@ -203,7 +204,7 @@ class MongoClusterSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, settings.codecRegistry, - readPreference, readConcern, executor, [], Document, ChangeStreamLevel.CLIENT, true, TIMEOUT_SETTINGS), + readPreference, readConcern, executor, [], Document, ChangeStreamLevel.CLIENT, true, null, TIMEOUT_SETTINGS), ['codec']) when: @@ -212,7 +213,7 @@ class MongoClusterSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, settings.codecRegistry, readPreference, readConcern, executor, [new Document('$match', 1)], Document, ChangeStreamLevel.CLIENT, - true, TIMEOUT_SETTINGS), ['codec']) + true, null, TIMEOUT_SETTINGS), ['codec']) when: changeStreamIterable = execute(watchMethod, session, [new Document('$match', 1)], BsonDocument) @@ -220,7 +221,7 @@ class MongoClusterSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, settings.codecRegistry, readPreference, readConcern, executor, [new Document('$match', 1)], BsonDocument, - ChangeStreamLevel.CLIENT, true, TIMEOUT_SETTINGS), ['codec']) + ChangeStreamLevel.CLIENT, true, null, TIMEOUT_SETTINGS), ['codec']) where: session << [null, Stub(ClientSession)] @@ -258,8 +259,9 @@ 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.writeConcern, TracingManager.NO_OP) + originator, operationExecutor, settings.readConcern, settings.readPreference, + settings.retryReads, settings.retryWrites, null, + settings.enableOverloadRetargeting, null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation, + settings.writeConcern, AsyncClientExecutor.NO_OP, TracingManager.NO_OP) } } diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoCollectionSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoCollectionSpecification.groovy index cbe43c10517..69aedcd4790 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoCollectionSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoCollectionSpecification.groovy @@ -123,7 +123,7 @@ class MongoCollectionSpecification extends Specification { def 'should return the correct name from getName'() { given: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, true, - true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([null])) + true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([null])) expect: collection.getNamespace() == namespace @@ -136,12 +136,12 @@ class MongoCollectionSpecification extends Specification { when: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor).withDocumentClass(newClass) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor).withDocumentClass(newClass) then: collection.getDocumentClass() == newClass expect collection, isTheSameAs(new MongoCollectionImpl(namespace, newClass, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) } def 'should behave correctly when using withCodecRegistry'() { @@ -151,12 +151,12 @@ class MongoCollectionSpecification extends Specification { when: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, C_SHARP_LEGACY, null, TIMEOUT_SETTINGS, executor).withCodecRegistry(newCodecRegistry) + true, true, null, readConcern, C_SHARP_LEGACY, null, TIMEOUT_SETTINGS, executor).withCodecRegistry(newCodecRegistry) then: (collection.getCodecRegistry().get(UUID) as UuidCodec).getUuidRepresentation() == C_SHARP_LEGACY expect collection, isTheSameAs(new MongoCollectionImpl(namespace, Document, collection.getCodecRegistry(), readPreference, - ACKNOWLEDGED, true, true, readConcern, C_SHARP_LEGACY, null, TIMEOUT_SETTINGS, executor)) + ACKNOWLEDGED, true, true, null, readConcern, C_SHARP_LEGACY, null, TIMEOUT_SETTINGS, executor)) } def 'should behave correctly when using withReadPreference'() { @@ -166,12 +166,12 @@ class MongoCollectionSpecification extends Specification { when: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor).withReadPreference(newReadPreference) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor).withReadPreference(newReadPreference) then: collection.getReadPreference() == newReadPreference expect collection, isTheSameAs(new MongoCollectionImpl(namespace, Document, codecRegistry, newReadPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) } def 'should behave correctly when using withWriteConcern'() { @@ -181,12 +181,12 @@ class MongoCollectionSpecification extends Specification { when: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor).withWriteConcern(newWriteConcern) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor).withWriteConcern(newWriteConcern) then: collection.getWriteConcern() == newWriteConcern expect collection, isTheSameAs(new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, newWriteConcern, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) } def 'should behave correctly when using withReadConcern'() { @@ -196,19 +196,19 @@ class MongoCollectionSpecification extends Specification { when: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor).withReadConcern(newReadConcern) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor).withReadConcern(newReadConcern) then: collection.getReadConcern() == newReadConcern expect collection, isTheSameAs(new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, newReadConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) + true, true, null, newReadConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) } def 'should behave correctly when using withTimeout'() { given: def executor = new TestOperationExecutor([]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: def newCollection = collection.withTimeout(10_000, MILLISECONDS) @@ -216,7 +216,7 @@ class MongoCollectionSpecification extends Specification { then: newCollection.getTimeout(MILLISECONDS) == 10_000 expect newCollection, isTheSameAs(new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS.withTimeout(10_000, MILLISECONDS), executor)) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS.withTimeout(10_000, MILLISECONDS), executor)) when: collection.withTimeout(500, TimeUnit.NANOSECONDS) @@ -230,8 +230,8 @@ class MongoCollectionSpecification extends Specification { def executor = new TestOperationExecutor([1L, 2L, 3L, 4L]) def filter = new BsonDocument() def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, true, - true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) - def expectedOperation = new CountDocumentsOperation(namespace) + true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + def expectedOperation = new CountDocumentsOperation(namespace, null) .filter(filter).retryReads(true) def countMethod = collection.&countDocuments @@ -271,8 +271,8 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([1L, 2L]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, true, - true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) - def expectedOperation = new EstimatedDocumentCountOperation(namespace) + true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + def expectedOperation = new EstimatedDocumentCountOperation(namespace, null) .retryReads(true) def countMethod = collection.&estimatedDocumentCount @@ -286,7 +286,7 @@ class MongoCollectionSpecification extends Specification { expect operation, isTheSameAs(expectedOperation) when: - expectedOperation = new EstimatedDocumentCountOperation(namespace).retryReads(true) + expectedOperation = new EstimatedDocumentCountOperation(namespace, null).retryReads(true) execute(countMethod, session, new EstimatedDocumentCountOptions().maxTime(100, MILLISECONDS)) operation = executor.getReadOperation() as EstimatedDocumentCountOperation @@ -302,7 +302,7 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def filter = new Document('a', 1) def distinctMethod = collection.&distinct @@ -311,14 +311,14 @@ class MongoCollectionSpecification extends Specification { then: expect distinctIterable, isTheSameAs(new DistinctIterableImpl<>(session, namespace, Document, String, - codecRegistry, readPreference, readConcern, executor, 'field', new BsonDocument(), true, TIMEOUT_SETTINGS)) + codecRegistry, readPreference, readConcern, executor, 'field', new BsonDocument(), true, null, TIMEOUT_SETTINGS)) when: distinctIterable = execute(distinctMethod, session, 'field', String).filter(filter) then: expect distinctIterable, isTheSameAs(new DistinctIterableImpl<>(session, namespace, Document, String, - codecRegistry, readPreference, readConcern, executor, 'field', filter, true, TIMEOUT_SETTINGS)) + codecRegistry, readPreference, readConcern, executor, 'field', filter, true, null, TIMEOUT_SETTINGS)) where: session << [null, Stub(ClientSession)] @@ -328,7 +328,7 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def findMethod = collection.&find when: @@ -336,28 +336,28 @@ class MongoCollectionSpecification extends Specification { then: expect findIterable, isTheSameAs(new FindIterableImpl<>(session, namespace, Document, Document, codecRegistry, - readPreference, readConcern, executor, new BsonDocument(), true, TIMEOUT_SETTINGS)) + readPreference, readConcern, executor, new BsonDocument(), true, null, TIMEOUT_SETTINGS)) when: findIterable = execute(findMethod, session, BsonDocument) then: expect findIterable, isTheSameAs(new FindIterableImpl<>(session, namespace, Document, BsonDocument, - codecRegistry, readPreference, readConcern, executor, new BsonDocument(), true, TIMEOUT_SETTINGS)) + codecRegistry, readPreference, readConcern, executor, new BsonDocument(), true, null, TIMEOUT_SETTINGS)) when: findIterable = execute(findMethod, session, new Document()) then: expect findIterable, isTheSameAs(new FindIterableImpl<>(session, namespace, Document, Document, - codecRegistry, readPreference, readConcern, executor, new Document(), true, TIMEOUT_SETTINGS)) + codecRegistry, readPreference, readConcern, executor, new Document(), true, null, TIMEOUT_SETTINGS)) when: findIterable = execute(findMethod, session, new Document(), BsonDocument) then: expect findIterable, isTheSameAs(new FindIterableImpl<>(session, namespace, Document, BsonDocument, - codecRegistry, readPreference, readConcern, executor, new Document(), true, TIMEOUT_SETTINGS)) + codecRegistry, readPreference, readConcern, executor, new Document(), true, null, TIMEOUT_SETTINGS)) where: session << [null, Stub(ClientSession)] @@ -367,7 +367,7 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def aggregateMethod = collection.&aggregate when: @@ -376,7 +376,7 @@ class MongoCollectionSpecification extends Specification { then: expect aggregateIterable, isTheSameAs(new AggregateIterableImpl<>(session, namespace, Document, Document, codecRegistry, readPreference, readConcern, ACKNOWLEDGED, executor, [new Document('$match', 1)], - AggregationLevel.COLLECTION, true, TIMEOUT_SETTINGS)) + AggregationLevel.COLLECTION, true, true, null, TIMEOUT_SETTINGS)) when: aggregateIterable = execute(aggregateMethod, session, [new Document('$match', 1)], BsonDocument) @@ -384,7 +384,7 @@ class MongoCollectionSpecification extends Specification { then: expect aggregateIterable, isTheSameAs(new AggregateIterableImpl<>(session, namespace, Document, BsonDocument, codecRegistry, readPreference, readConcern, ACKNOWLEDGED, executor, [new Document('$match', 1)], - AggregationLevel.COLLECTION, true, TIMEOUT_SETTINGS)) + AggregationLevel.COLLECTION, true, true, null, TIMEOUT_SETTINGS)) where: session << [null, Stub(ClientSession)] @@ -394,7 +394,7 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: collection.aggregate(null) @@ -413,7 +413,7 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def watchMethod = collection.&watch when: @@ -421,7 +421,7 @@ class MongoCollectionSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, codecRegistry, - readPreference, readConcern, executor, [], Document, ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS), + readPreference, readConcern, executor, [], Document, ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS), ['codec']) when: @@ -430,7 +430,7 @@ class MongoCollectionSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, codecRegistry, readPreference, readConcern, executor, [new Document('$match', 1)], Document, - ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS), ['codec']) + ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS), ['codec']) when: changeStreamIterable = execute(watchMethod, session, [new Document('$match', 1)], BsonDocument) @@ -438,7 +438,7 @@ class MongoCollectionSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, codecRegistry, readPreference, readConcern, executor, [new Document('$match', 1)], BsonDocument, - ChangeStreamLevel.COLLECTION, true, TIMEOUT_SETTINGS), ['codec']) + ChangeStreamLevel.COLLECTION, true, null, TIMEOUT_SETTINGS), ['codec']) where: session << [null, Stub(ClientSession)] @@ -448,7 +448,7 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: collection.watch((Class) null) @@ -467,7 +467,7 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def mapReduceMethod = collection.&mapReduce when: @@ -475,14 +475,14 @@ class MongoCollectionSpecification extends Specification { then: expect mapReduceIterable, isTheSameAs(new MapReduceIterableImpl<>(session, namespace, Document, Document, - codecRegistry, readPreference, readConcern, ACKNOWLEDGED, executor, 'map', 'reduce', TIMEOUT_SETTINGS)) + codecRegistry, readPreference, readConcern, ACKNOWLEDGED, executor, 'map', 'reduce', null, TIMEOUT_SETTINGS)) when: mapReduceIterable = execute(mapReduceMethod, session, 'map', 'reduce', BsonDocument) then: expect mapReduceIterable, isTheSameAs(new MapReduceIterableImpl<>(session, namespace, Document, BsonDocument, - codecRegistry, readPreference, readConcern, ACKNOWLEDGED, executor, 'map', 'reduce', TIMEOUT_SETTINGS)) + codecRegistry, readPreference, readConcern, ACKNOWLEDGED, executor, 'map', 'reduce', null, TIMEOUT_SETTINGS)) where: session << [null, Stub(ClientSession)] @@ -494,7 +494,7 @@ class MongoCollectionSpecification extends Specification { writeConcern.isAcknowledged() ? acknowledged(INSERT, 0, 0, [], []) : unacknowledged() }) def collection = new MongoCollectionImpl(namespace, BsonDocument, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def expectedOperation = { boolean ordered, WriteConcern wc, Boolean bypassValidation, List filters -> new MixedBulkWriteOperation(namespace, [ new InsertRequest(BsonDocument.parse('{_id: 1}')), @@ -507,7 +507,7 @@ class MongoCollectionSpecification extends Specification { .upsert(true).collation(collation).arrayFilters(filters).hint(hint).hintString(hintString), new DeleteRequest(BsonDocument.parse('{a: 5}')).multi(false), new DeleteRequest(BsonDocument.parse('{a: 6}')).multi(true).collation(collation) - ], ordered, wc, retryWrites).bypassDocumentValidation(bypassValidation) + ], ordered, wc, retryWrites, null).bypassDocumentValidation(bypassValidation) } def updateOptions = new UpdateOptions().upsert(true).collation(collation).arrayFilters(arrayFilters) .hint(hint).hintString(hintString) @@ -561,7 +561,7 @@ class MongoCollectionSpecification extends Specification { def codecRegistry = fromProviders([new ValueCodecProvider(), new BsonValueCodecProvider()]) def executor = new TestOperationExecutor([new MongoException('failure')]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: collection.bulkWrite(null) @@ -588,10 +588,10 @@ class MongoCollectionSpecification extends Specification { writeConcern.isAcknowledged() ? acknowledged(INSERT, 0, 0, [], []) : unacknowledged() }) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def expectedOperation = { WriteConcern wc, Boolean bypassDocumentValidation -> new MixedBulkWriteOperation(namespace, [new InsertRequest(new BsonDocument('_id', new BsonInt32(1)))], - true, wc, retryWrites).bypassDocumentValidation(bypassDocumentValidation) + true, wc, retryWrites, null).bypassDocumentValidation(bypassDocumentValidation) } def insertOneMethod = collection.&insertOne @@ -633,12 +633,12 @@ class MongoCollectionSpecification extends Specification { writeConcern.isAcknowledged() ? acknowledged(INSERT, 0, 0, [], []) : unacknowledged() }) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def expectedOperation = { boolean ordered, WriteConcern wc, Boolean bypassDocumentValidation -> new MixedBulkWriteOperation(namespace, [new InsertRequest(new BsonDocument('_id', new BsonInt32(1))), new InsertRequest(new BsonDocument('_id', new BsonInt32(2)))], - ordered, wc, retryWrites).bypassDocumentValidation(bypassDocumentValidation) + ordered, wc, retryWrites, null).bypassDocumentValidation(bypassDocumentValidation) } def insertManyMethod = collection.&insertMany @@ -679,7 +679,7 @@ class MongoCollectionSpecification extends Specification { def 'should validate the insertMany data correctly'() { given: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, Stub(OperationExecutor)) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, Stub(OperationExecutor)) when: collection.insertMany(null) @@ -701,7 +701,7 @@ class MongoCollectionSpecification extends Specification { }) def expectedResult = writeConcern.isAcknowledged() ? DeleteResult.acknowledged(1) : DeleteResult.unacknowledged() def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def deleteOneMethod = collection.&deleteOne when: @@ -712,7 +712,7 @@ class MongoCollectionSpecification extends Specification { result.wasAcknowledged() == writeConcern.isAcknowledged() expect operation, isTheSameAs(new MixedBulkWriteOperation(namespace, [new DeleteRequest(new BsonDocument('_id', new BsonInt32(1))).multi(false)], - true, writeConcern, retryWrites)) + true, writeConcern, retryWrites, null)) result == expectedResult executor.getClientSession() == session @@ -724,7 +724,7 @@ class MongoCollectionSpecification extends Specification { result.wasAcknowledged() == writeConcern.isAcknowledged() expect operation, isTheSameAs(new MixedBulkWriteOperation(namespace, [new DeleteRequest(new BsonDocument('_id', new BsonInt32(1))).multi(false).collation(collation)], - true, writeConcern, retryWrites)) + true, writeConcern, retryWrites, null)) result == expectedResult executor.getClientSession() == session @@ -743,7 +743,7 @@ class MongoCollectionSpecification extends Specification { def executor = new TestOperationExecutor([bulkWriteException]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: collection.deleteOne(new Document('_id', 1)) @@ -764,7 +764,7 @@ class MongoCollectionSpecification extends Specification { }) def expectedResult = writeConcern.isAcknowledged() ? DeleteResult.acknowledged(1) : DeleteResult.unacknowledged() def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def deleteManyMethod = collection.&deleteMany when: @@ -775,7 +775,7 @@ class MongoCollectionSpecification extends Specification { result.wasAcknowledged() == writeConcern.isAcknowledged() expect operation, isTheSameAs(new MixedBulkWriteOperation(namespace, [new DeleteRequest(new BsonDocument('_id', new BsonInt32(1))).multi(true)], - true, writeConcern, retryWrites)) + true, writeConcern, retryWrites, null)) result == expectedResult when: @@ -786,7 +786,7 @@ class MongoCollectionSpecification extends Specification { result.wasAcknowledged() == writeConcern.isAcknowledged() expect operation, isTheSameAs(new MixedBulkWriteOperation(namespace, [new DeleteRequest(new BsonDocument('_id', new BsonInt32(1))).multi(true).collation(collation)], - true, writeConcern, retryWrites)) + true, writeConcern, retryWrites, null)) result == expectedResult where: @@ -808,12 +808,13 @@ class MongoCollectionSpecification extends Specification { def expectedResult = writeConcern.isAcknowledged() ? UpdateResult.acknowledged(1, modifiedCount, upsertedId) : UpdateResult.unacknowledged() def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def expectedOperation = { boolean upsert, WriteConcern wc, Boolean bypassValidation, Collation collation -> new MixedBulkWriteOperation(namespace, [new UpdateRequest(new BsonDocument('a', new BsonInt32(1)), new BsonDocument('a', new BsonInt32(10)), REPLACE) - .collation(collation).upsert(upsert).hint(hint).hintString(hintString).sort(sort)], true, wc, retryWrites) + .collation(collation).upsert(upsert).hint(hint).hintString(hintString).sort(sort)], true, wc, + retryWrites, null) .bypassDocumentValidation(bypassValidation) } def replaceOneMethod = collection.&replaceOne @@ -851,7 +852,7 @@ class MongoCollectionSpecification extends Specification { def executor = new TestOperationExecutor([bulkWriteException]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: collection.replaceOne(new Document('_id', 1), new Document('_id', 1)) @@ -879,13 +880,13 @@ class MongoCollectionSpecification extends Specification { }) def expectedResult = writeConcern.isAcknowledged() ? UpdateResult.acknowledged(1, 0, null) : UpdateResult.unacknowledged() def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def expectedOperation = { boolean upsert, WriteConcern wc, Boolean bypassDocumentValidation, Collation collation, List filters, BsonDocument hintDoc, String hintStr, BsonDocument sortDoc -> new MixedBulkWriteOperation(namespace, [new UpdateRequest(new BsonDocument('a', new BsonInt32(1)), new BsonDocument('a', new BsonInt32(10)), UPDATE) .multi(false).upsert(upsert).collation(collation).arrayFilters(filters) - .hint(hintDoc).hintString(hintStr).sort(sortDoc)], true, wc, retryWrites) + .hint(hintDoc).hintString(hintStr).sort(sortDoc)], true, wc, retryWrites, null) .bypassDocumentValidation(bypassDocumentValidation) } def updateOneMethod = collection.&updateOne @@ -929,13 +930,13 @@ class MongoCollectionSpecification extends Specification { }) def expectedResult = writeConcern.isAcknowledged() ? UpdateResult.acknowledged(5, 3, null) : UpdateResult.unacknowledged() def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def expectedOperation = { boolean upsert, WriteConcern wc, Boolean bypassDocumentValidation, Collation collation, List filters, BsonDocument hintDoc, String hintStr -> new MixedBulkWriteOperation(namespace, [new UpdateRequest(new BsonDocument('a', new BsonInt32(1)), new BsonDocument('a', new BsonInt32(10)), UPDATE) .multi(true).upsert(upsert).collation(collation).arrayFilters(filters) - .hint(hintDoc).hintString(hintStr)], true, wc, retryWrites) + .hint(hintDoc).hintString(hintStr)], true, wc, retryWrites, null) .bypassDocumentValidation(bypassDocumentValidation) } def updateManyMethod = collection.&updateMany @@ -973,7 +974,7 @@ class MongoCollectionSpecification extends Specification { def 'should translate MongoBulkWriteException to MongoWriteException'() { given: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: collection.insertOne(new Document('_id', 1)) @@ -995,7 +996,7 @@ class MongoCollectionSpecification extends Specification { new WriteConcernError(42, 'codeName', 'Message', new BsonDocument()), new ServerAddress(), [] as Set)]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: collection.insertOne(new Document('_id', 1)) @@ -1011,8 +1012,8 @@ class MongoCollectionSpecification extends Specification { writeConcern.isAcknowledged() ? WriteConcernResult.acknowledged(1, true, null) : unacknowledged() }) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) - def expectedOperation = new FindAndDeleteOperation(namespace, ACKNOWLEDGED, retryWrites, + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + def expectedOperation = new FindAndDeleteOperation(namespace, ACKNOWLEDGED, retryWrites, null, new DocumentCodec()) .filter(new BsonDocument('a', new BsonInt32(1))) def findOneAndDeleteMethod = collection.&findOneAndDelete @@ -1026,7 +1027,7 @@ class MongoCollectionSpecification extends Specification { when: expectedOperation = - new FindAndDeleteOperation(namespace, ACKNOWLEDGED, retryWrites, new DocumentCodec()) + new FindAndDeleteOperation(namespace, ACKNOWLEDGED, retryWrites, null, new DocumentCodec()) .filter(new BsonDocument('a', new BsonInt32(1))) .projection(new BsonDocument('projection', new BsonInt32(1))) .collation(collation) @@ -1054,9 +1055,9 @@ class MongoCollectionSpecification extends Specification { writeConcern.isAcknowledged() ? WriteConcernResult.acknowledged(1, true, null) : WriteConcernResult.unacknowledged() }) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def expectedOperation = new FindAndReplaceOperation(namespace, writeConcern, - retryWrites, new DocumentCodec(), new BsonDocument('a', new BsonInt32(10))) + retryWrites, null, new DocumentCodec(), new BsonDocument('a', new BsonInt32(10))) .filter(new BsonDocument('a', new BsonInt32(1))) def findOneAndReplaceMethod = collection.&findOneAndReplace @@ -1069,7 +1070,7 @@ class MongoCollectionSpecification extends Specification { when: expectedOperation = new FindAndReplaceOperation(namespace, writeConcern, - retryWrites, new DocumentCodec(), new BsonDocument('a', new BsonInt32(10))) + retryWrites, null, new DocumentCodec(), new BsonDocument('a', new BsonInt32(10))) .filter(new BsonDocument('a', new BsonInt32(1))) .projection(new BsonDocument('projection', new BsonInt32(1))) .bypassDocumentValidation(false) @@ -1099,8 +1100,8 @@ class MongoCollectionSpecification extends Specification { writeConcern.isAcknowledged() ? WriteConcernResult.acknowledged(1, true, null) : unacknowledged() }) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern, - retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) - def expectedOperation = new FindAndUpdateOperation(namespace, writeConcern, retryWrites, + retryWrites, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + def expectedOperation = new FindAndUpdateOperation(namespace, writeConcern, retryWrites, null, new DocumentCodec(), new BsonDocument('a', new BsonInt32(10))) .filter(new BsonDocument('a', new BsonInt32(1))) def findOneAndUpdateMethod = collection.&findOneAndUpdate @@ -1113,7 +1114,7 @@ class MongoCollectionSpecification extends Specification { expect operation, isTheSameAs(expectedOperation) when: - expectedOperation = new FindAndUpdateOperation(namespace, writeConcern, retryWrites, + expectedOperation = new FindAndUpdateOperation(namespace, writeConcern, retryWrites, null, new DocumentCodec(), new BsonDocument('a', new BsonInt32(10))) .filter(new BsonDocument('a', new BsonInt32(1))) .projection(new BsonDocument('projection', new BsonInt32(1))) @@ -1147,8 +1148,8 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([null]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) - def expectedOperation = new DropCollectionOperation(namespace, ACKNOWLEDGED) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + def expectedOperation = new DropCollectionOperation(namespace, ACKNOWLEDGED, true, null) def dropMethod = collection.&drop when: @@ -1167,13 +1168,13 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null, null, null, null]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def createIndexMethod = collection.&createIndex def createIndexesMethod = collection.&createIndexes when: def expectedOperation = new CreateIndexesOperation(namespace, - [new IndexRequest(new BsonDocument('key', new BsonInt32(1)))], ACKNOWLEDGED) + [new IndexRequest(new BsonDocument('key', new BsonInt32(1)))], ACKNOWLEDGED, true, null) def indexName = execute(createIndexMethod, session, new Document('key', 1)) def operation = executor.getWriteOperation() as CreateIndexesOperation @@ -1184,7 +1185,7 @@ class MongoCollectionSpecification extends Specification { when: expectedOperation = new CreateIndexesOperation(namespace, [new IndexRequest(new BsonDocument('key', new BsonInt32(1))), - new IndexRequest(new BsonDocument('key1', new BsonInt32(1)))], ACKNOWLEDGED) + new IndexRequest(new BsonDocument('key1', new BsonInt32(1)))], ACKNOWLEDGED, true, null) def indexNames = execute(createIndexesMethod, session, [new IndexModel(new Document('key', 1)), new IndexModel(new Document('key1', 1))]) operation = executor.getWriteOperation() as CreateIndexesOperation @@ -1197,7 +1198,7 @@ class MongoCollectionSpecification extends Specification { when: expectedOperation = new CreateIndexesOperation(namespace, [new IndexRequest(new BsonDocument('key', new BsonInt32(1))), - new IndexRequest(new BsonDocument('key1', new BsonInt32(1)))], ACKNOWLEDGED) + new IndexRequest(new BsonDocument('key1', new BsonInt32(1)))], ACKNOWLEDGED, true, null) indexNames = execute(createIndexesMethod, session, [new IndexModel(new Document('key', 1)), new IndexModel(new Document('key1', 1))], new CreateIndexOptions().maxTime(100, MILLISECONDS)) @@ -1211,7 +1212,7 @@ class MongoCollectionSpecification extends Specification { when: expectedOperation = new CreateIndexesOperation(namespace, [new IndexRequest(new BsonDocument('key', new BsonInt32(1))), - new IndexRequest(new BsonDocument('key1', new BsonInt32(1)))], ACKNOWLEDGED) + new IndexRequest(new BsonDocument('key1', new BsonInt32(1)))], ACKNOWLEDGED, true, null) .commitQuorum(CreateIndexCommitQuorum.VOTING_MEMBERS) indexNames = execute(createIndexesMethod, session, [new IndexModel(new Document('key', 1)), new IndexModel(new Document('key1', 1))], @@ -1245,7 +1246,7 @@ class MongoCollectionSpecification extends Specification { .collation(collation) .wildcardProjection(new BsonDocument('a', new BsonInt32(1))) .hidden(true) - ], ACKNOWLEDGED) + ], ACKNOWLEDGED, true, null) indexName = execute(createIndexMethod, session, new Document('key', 1), new IndexOptions() .background(true) .unique(true) @@ -1280,7 +1281,7 @@ class MongoCollectionSpecification extends Specification { def 'should validate the createIndexes data correctly'() { given: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, Stub(OperationExecutor)) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, Stub(OperationExecutor)) when: collection.createIndexes(null) @@ -1300,7 +1301,7 @@ class MongoCollectionSpecification extends Specification { def batchCursor = Stub(BatchCursor) def executor = new TestOperationExecutor([batchCursor, batchCursor, batchCursor]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def listIndexesMethod = collection.&listIndexes when: @@ -1308,7 +1309,7 @@ class MongoCollectionSpecification extends Specification { def operation = executor.getReadOperation() as ListIndexesOperation then: - expect operation, isTheSameAs(new ListIndexesOperation(namespace, new DocumentCodec()).retryReads(true)) + expect operation, isTheSameAs(new ListIndexesOperation(namespace, new DocumentCodec(), null).retryReads(true)) executor.getClientSession() == session when: @@ -1317,7 +1318,7 @@ class MongoCollectionSpecification extends Specification { indexes == [] then: - expect operation, isTheSameAs(new ListIndexesOperation(namespace, new BsonDocumentCodec()).retryReads(true)) + expect operation, isTheSameAs(new ListIndexesOperation(namespace, new BsonDocumentCodec(), null).retryReads(true)) executor.getClientSession() == session when: @@ -1325,7 +1326,7 @@ class MongoCollectionSpecification extends Specification { operation = executor.getReadOperation() as ListIndexesOperation then: - expect operation, isTheSameAs(new ListIndexesOperation(namespace, new DocumentCodec()).batchSize(10) + expect operation, isTheSameAs(new ListIndexesOperation(namespace, new DocumentCodec(), null).batchSize(10) .retryReads(true)) executor.getClientSession() == session @@ -1337,11 +1338,11 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null, null]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def dropIndexMethod = collection.&dropIndex when: - def expectedOperation = new DropIndexOperation(namespace, 'indexName', ACKNOWLEDGED) + def expectedOperation = new DropIndexOperation(namespace, 'indexName', ACKNOWLEDGED, true, null) execute(dropIndexMethod, session, 'indexName') def operation = executor.getWriteOperation() as DropIndexOperation @@ -1351,7 +1352,7 @@ class MongoCollectionSpecification extends Specification { when: def keys = new BsonDocument('x', new BsonInt32(1)) - expectedOperation = new DropIndexOperation(namespace, keys, ACKNOWLEDGED) + expectedOperation = new DropIndexOperation(namespace, keys, ACKNOWLEDGED, true, null) execute(dropIndexMethod, session, keys) operation = executor.getWriteOperation() as DropIndexOperation @@ -1360,7 +1361,7 @@ class MongoCollectionSpecification extends Specification { executor.getClientSession() == session when: - expectedOperation = new DropIndexOperation(namespace, keys, ACKNOWLEDGED) + expectedOperation = new DropIndexOperation(namespace, keys, ACKNOWLEDGED, true, null) execute(dropIndexMethod, session, keys, new DropIndexOptions().maxTime(100, MILLISECONDS)) operation = executor.getWriteOperation() as DropIndexOperation @@ -1376,8 +1377,8 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) - def expectedOperation = new DropIndexOperation(namespace, '*', ACKNOWLEDGED) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + def expectedOperation = new DropIndexOperation(namespace, '*', ACKNOWLEDGED, true, null) def dropIndexesMethod = collection.&dropIndexes when: @@ -1389,7 +1390,7 @@ class MongoCollectionSpecification extends Specification { executor.getClientSession() == session when: - expectedOperation = new DropIndexOperation(namespace, '*', ACKNOWLEDGED) + expectedOperation = new DropIndexOperation(namespace, '*', ACKNOWLEDGED, true, null) execute(dropIndexesMethod, session, new DropIndexOptions().maxTime(100, MILLISECONDS)) operation = executor.getWriteOperation() as DropIndexOperation @@ -1405,10 +1406,10 @@ class MongoCollectionSpecification extends Specification { given: def executor = new TestOperationExecutor([null, null]) def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def newNamespace = new MongoNamespace(namespace.getDatabaseName(), 'newName') def renameCollectionOptions = new RenameCollectionOptions().dropTarget(dropTarget) - def expectedOperation = new RenameCollectionOperation(namespace, newNamespace, ACKNOWLEDGED) + def expectedOperation = new RenameCollectionOperation(namespace, newNamespace, ACKNOWLEDGED, true, null) def renameCollection = collection.&renameCollection when: @@ -1436,7 +1437,7 @@ class MongoCollectionSpecification extends Specification { def executor = new TestOperationExecutor([acknowledged(INSERT, 1, 0, [], [])]) def customCodecRegistry = CodecRegistries.fromRegistries(fromProviders(new ImmutableDocumentCodecProvider()), codecRegistry) def collection = new MongoCollectionImpl(namespace, ImmutableDocument, customCodecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def document = new ImmutableDocument(['a': 1]) when: @@ -1458,7 +1459,7 @@ class MongoCollectionSpecification extends Specification { def executor = new TestOperationExecutor([null]) def customCodecRegistry = CodecRegistries.fromRegistries(fromProviders(new ImmutableDocumentCodecProvider()), codecRegistry) def collection = new MongoCollectionImpl(namespace, ImmutableDocument, customCodecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def document = new ImmutableDocument(['a': 1]) when: @@ -1478,7 +1479,7 @@ class MongoCollectionSpecification extends Specification { def 'should validate the client session correctly'() { given: def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, ACKNOWLEDGED, - true, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, + true, true, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, Stub(OperationExecutor)) when: diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoDatabaseSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoDatabaseSpecification.groovy index 56b55f61332..4e8ac62d8cb 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoDatabaseSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoDatabaseSpecification.groovy @@ -68,8 +68,8 @@ class MongoDatabaseSpecification extends Specification { def 'should throw IllegalArgumentException if name is invalid'() { when: - new MongoDatabaseImpl('a.b', codecRegistry, readPreference, writeConcern, false, false, readConcern, - JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) + new MongoDatabaseImpl('a.b', codecRegistry, readPreference, writeConcern, false, false, null, + readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) then: thrown(IllegalArgumentException) @@ -77,8 +77,8 @@ class MongoDatabaseSpecification extends Specification { def 'should throw IllegalArgumentException from getCollection if collectionName is invalid'() { given: - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, readConcern, - JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, + readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) when: database.getCollection('') @@ -89,8 +89,8 @@ class MongoDatabaseSpecification extends Specification { def 'should return the correct name from getName'() { given: - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, readConcern, - JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, + readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) expect: database.getName() == name @@ -102,14 +102,15 @@ class MongoDatabaseSpecification extends Specification { def executor = new TestOperationExecutor([]) when: - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, true, readConcern, - C_SHARP_LEGACY, null, TIMEOUT_SETTINGS, executor) + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, true, null, + readConcern, C_SHARP_LEGACY, null, TIMEOUT_SETTINGS, executor) .withCodecRegistry(newCodecRegistry) then: (database.getCodecRegistry().get(UUID) as UuidCodec).getUuidRepresentation() == C_SHARP_LEGACY expect database, isTheSameAs(new MongoDatabaseImpl(name, database.getCodecRegistry(), readPreference, writeConcern, - false, true, readConcern, C_SHARP_LEGACY, null, TIMEOUT_SETTINGS, executor)) + false, true, null, + readConcern, C_SHARP_LEGACY, null, TIMEOUT_SETTINGS, executor)) } def 'should behave correctly when using withReadPreference'() { @@ -118,13 +119,13 @@ class MongoDatabaseSpecification extends Specification { def executor = new TestOperationExecutor([]) when: - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) .withReadPreference(newReadPreference) then: database.getReadPreference() == newReadPreference - expect database, isTheSameAs(new MongoDatabaseImpl(name, codecRegistry, newReadPreference, writeConcern, false, false, + expect database, isTheSameAs(new MongoDatabaseImpl(name, codecRegistry, newReadPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) } @@ -134,13 +135,13 @@ class MongoDatabaseSpecification extends Specification { def executor = new TestOperationExecutor([]) when: - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) .withWriteConcern(newWriteConcern) then: database.getWriteConcern() == newWriteConcern - expect database, isTheSameAs(new MongoDatabaseImpl(name, codecRegistry, readPreference, newWriteConcern, false, false, + expect database, isTheSameAs(new MongoDatabaseImpl(name, codecRegistry, readPreference, newWriteConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) } @@ -150,20 +151,20 @@ class MongoDatabaseSpecification extends Specification { def executor = new TestOperationExecutor([]) when: - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) .withReadConcern(newReadConcern) then: database.getReadConcern() == newReadConcern - expect database, isTheSameAs(new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + expect database, isTheSameAs(new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, newReadConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)) } def 'should behave correctly when using withTimeout'() { given: def executor = new TestOperationExecutor([]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: @@ -171,7 +172,7 @@ class MongoDatabaseSpecification extends Specification { then: newDatabase.getTimeout(TimeUnit.MILLISECONDS) == 10_000 - expect newDatabase, isTheSameAs(new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + expect newDatabase, isTheSameAs(new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS.withTimeout(10_000, TimeUnit.MILLISECONDS), executor)) when: @@ -185,7 +186,7 @@ class MongoDatabaseSpecification extends Specification { given: def command = new BsonDocument('command', new BsonInt32(1)) def executor = new TestOperationExecutor([null, null, null, null]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def runCommandMethod = database.&runCommand @@ -228,7 +229,7 @@ class MongoDatabaseSpecification extends Specification { def 'should use DropDatabaseOperation correctly'() { given: def executor = new TestOperationExecutor([null]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def dropMethod = database.&drop @@ -247,7 +248,7 @@ class MongoDatabaseSpecification extends Specification { def 'should use ListCollectionsOperation correctly'() { given: def executor = new TestOperationExecutor([null, null, null]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def listCollectionsMethod = database.&listCollections def listCollectionNamesMethod = database.&listCollectionNames @@ -257,14 +258,14 @@ class MongoDatabaseSpecification extends Specification { then: expect listCollectionIterable, isTheSameAs(new ListCollectionsIterableImpl<>(session, name, false, - Document, codecRegistry, primary(), executor, false, TIMEOUT_SETTINGS)) + Document, codecRegistry, primary(), executor, false, null, TIMEOUT_SETTINGS)) when: listCollectionIterable = execute(listCollectionsMethod, session, BsonDocument) then: expect listCollectionIterable, isTheSameAs(new ListCollectionsIterableImpl<>(session, name, false, - BsonDocument, codecRegistry, primary(), executor, false, TIMEOUT_SETTINGS)) + BsonDocument, codecRegistry, primary(), executor, false, null, TIMEOUT_SETTINGS)) when: def listCollectionNamesIterable = execute(listCollectionNamesMethod, session) @@ -272,7 +273,7 @@ class MongoDatabaseSpecification extends Specification { then: // `listCollectionNamesIterable` is an instance of a `ListCollectionNamesIterableImpl`, so have to get the wrapped iterable from it expect listCollectionNamesIterable.getWrapped(), isTheSameAs(new ListCollectionsIterableImpl<>(session, name, - true, BsonDocument, codecRegistry, primary(), executor, false, TIMEOUT_SETTINGS)) + true, BsonDocument, codecRegistry, primary(), executor, false, null, TIMEOUT_SETTINGS)) where: session << [null, Stub(ClientSession)] @@ -282,7 +283,7 @@ class MongoDatabaseSpecification extends Specification { given: def collectionName = 'collectionName' def executor = new TestOperationExecutor([null, null]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def createCollectionMethod = database.&createCollection @@ -333,7 +334,7 @@ class MongoDatabaseSpecification extends Specification { def pipeline = [new Document('$match', new Document('x', true))] def writeConcern = WriteConcern.JOURNALED def executor = new TestOperationExecutor([null, null]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def createViewMethod = database.&createView @@ -363,7 +364,7 @@ class MongoDatabaseSpecification extends Specification { given: def viewName = 'view1' def viewOn = 'col1' - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, Stub(OperationExecutor)) when: @@ -383,7 +384,7 @@ class MongoDatabaseSpecification extends Specification { given: def executor = new TestOperationExecutor([]) def namespace = new MongoNamespace(name, '_ignored') - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def watchMethod = database.&watch @@ -392,7 +393,7 @@ class MongoDatabaseSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, codecRegistry, - readPreference, readConcern, executor, [], Document, ChangeStreamLevel.DATABASE, false, TIMEOUT_SETTINGS), + readPreference, readConcern, executor, [], Document, ChangeStreamLevel.DATABASE, false, null, TIMEOUT_SETTINGS), ['codec']) when: @@ -401,7 +402,7 @@ class MongoDatabaseSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, codecRegistry, readPreference, readConcern, executor, [new Document('$match', 1)], Document, - ChangeStreamLevel.DATABASE, false, TIMEOUT_SETTINGS), ['codec']) + ChangeStreamLevel.DATABASE, false, null, TIMEOUT_SETTINGS), ['codec']) when: changeStreamIterable = execute(watchMethod, session, [new Document('$match', 1)], BsonDocument) @@ -409,7 +410,7 @@ class MongoDatabaseSpecification extends Specification { then: expect changeStreamIterable, isTheSameAs(new ChangeStreamIterableImpl<>(session, namespace, codecRegistry, readPreference, readConcern, executor, [new Document('$match', 1)], BsonDocument, - ChangeStreamLevel.DATABASE, false, TIMEOUT_SETTINGS), ['codec']) + ChangeStreamLevel.DATABASE, false, null, TIMEOUT_SETTINGS), ['codec']) where: session << [null, Stub(ClientSession)] @@ -418,7 +419,7 @@ class MongoDatabaseSpecification extends Specification { def 'should validate the ChangeStreamIterable pipeline data correctly'() { given: def executor = new TestOperationExecutor([]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: @@ -437,7 +438,7 @@ class MongoDatabaseSpecification extends Specification { def 'should create AggregateIterable correctly'() { given: def executor = new TestOperationExecutor([]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) def aggregateMethod = database.&aggregate @@ -447,7 +448,7 @@ class MongoDatabaseSpecification extends Specification { then: expect aggregateIterable, isTheSameAs(new AggregateIterableImpl<>(session, name, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, [], AggregationLevel.DATABASE, - false, TIMEOUT_SETTINGS), ['codec']) + false, false, null, TIMEOUT_SETTINGS), ['codec']) when: aggregateIterable = execute(aggregateMethod, session, [new Document('$match', 1)]) @@ -455,7 +456,7 @@ class MongoDatabaseSpecification extends Specification { then: expect aggregateIterable, isTheSameAs(new AggregateIterableImpl<>(session, name, Document, Document, codecRegistry, readPreference, readConcern, writeConcern, executor, [new Document('$match', 1)], - AggregationLevel.DATABASE, false, TIMEOUT_SETTINGS), ['codec']) + AggregationLevel.DATABASE, false, false, null, TIMEOUT_SETTINGS), ['codec']) when: aggregateIterable = execute(aggregateMethod, session, [new Document('$match', 1)], BsonDocument) @@ -463,7 +464,7 @@ class MongoDatabaseSpecification extends Specification { then: expect aggregateIterable, isTheSameAs(new AggregateIterableImpl<>(session, name, Document, BsonDocument, codecRegistry, readPreference, readConcern, writeConcern, executor, [new Document('$match', 1)], - AggregationLevel.DATABASE, false, TIMEOUT_SETTINGS), ['codec']) + AggregationLevel.DATABASE, false, false, null, TIMEOUT_SETTINGS), ['codec']) where: session << [null, Stub(ClientSession)] @@ -472,7 +473,7 @@ class MongoDatabaseSpecification extends Specification { def 'should validate the AggregationIterable pipeline data correctly'() { given: def executor = new TestOperationExecutor([]) - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor) when: @@ -497,7 +498,7 @@ class MongoDatabaseSpecification extends Specification { def 'should pass the correct options to getCollection'() { given: def codecRegistry = fromProviders([new ValueCodecProvider(), new DocumentCodecProvider(), new BsonValueCodecProvider()]) - def database = new MongoDatabaseImpl('databaseName', codecRegistry, secondary(), WriteConcern.MAJORITY, true, true, + def database = new MongoDatabaseImpl('databaseName', codecRegistry, secondary(), WriteConcern.MAJORITY, true, true, null, ReadConcern.MAJORITY, JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) when: @@ -509,14 +510,14 @@ class MongoDatabaseSpecification extends Specification { where: expectedCollection = new MongoCollectionImpl(new MongoNamespace('databaseName', 'collectionName'), Document, fromProviders([new ValueCodecProvider(), new DocumentCodecProvider(), new BsonValueCodecProvider()]), secondary(), - WriteConcern.MAJORITY, true, true, ReadConcern.MAJORITY, JAVA_LEGACY, null, TIMEOUT_SETTINGS, + WriteConcern.MAJORITY, true, true, null, ReadConcern.MAJORITY, JAVA_LEGACY, null, TIMEOUT_SETTINGS, new TestOperationExecutor([])) } def 'should validate the client session correctly'() { given: - def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, - false, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, Stub(OperationExecutor)) + def database = new MongoDatabaseImpl(name, codecRegistry, readPreference, writeConcern, false, false, null, + readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, Stub(OperationExecutor)) when: database.createCollection(null, 'newColl')