From a19be26c3771d04bd8a150cff8186d3ab46670ea Mon Sep 17 00:00:00 2001
From: Maxim Katcharov This class is not part of the public API and may be removed or changed at any time
+ * The span is only created if tracing is enabled and the command is not security-sensitive.
+ * It attaches various tags to the span, such as database system, namespace, query summary, opcode,
+ * server address, port, server type, client and server connection IDs, and, if applicable,
+ * transaction number and session ID. For cursor fetching commands, the parent context is retrieved using the cursor ID.
+ * If command payload tracing is enabled, the command document is also attached as a tag.
+ *
+ * @param message the command message to trace
+ * @param operationContext the operation context containing tracing and session information
+ * @param bsonOutput the BSON output used to serialize the command
+ * @return the created {@link Span}, or {@code null} if tracing is not enabled or the command is security-sensitive
+ */
+ @Nullable
+ private Span createTracingSpan(final CommandMessage message, final OperationContext operationContext, final ByteBufferBsonOutput bsonOutput) {
+
+ TracingManager tracingManager = operationContext.getTracingManager();
+ BsonDocument command = message.getCommandDocument(bsonOutput);
+
+// BsonDocument command = message.getCommand();
+ String commandName = command.getFirstKey();
+// Span newSpan = tracingManager.addSpan("_____Command_____[ " + commandName + " ]", myparentContext);
+ if (!tracingManager.isEnabled()
+ || SECURITY_SENSITIVE_COMMANDS.contains(commandName)
+ || SECURITY_SENSITIVE_HELLO_COMMANDS.contains(commandName)) {
+ return null;
+ }
+
+ Span span = tracingManager
+ .addSpan("Command " + commandName, operationContext.getTracingSpanContext())
+ .tag(SYSTEM, "mongodb")
+ .tag(NAMESPACE, message.getNamespace().getDatabaseName())
+ .tag(QUERY_SUMMARY, commandName);
+
+ if (command.containsKey("getMore")) {
+ span.tag(CURSOR_ID, command.getInt64("getMore").longValue());
+ }
+
+ tagServerAndConnectionInfo(span, message);
+ tagSessionAndTransactionInfo(span, operationContext);
+
+ return span;
+ }
+
+ private void tagServerAndConnectionInfo(final Span span, final CommandMessage message) {
+ span.tag(SERVER_ADDRESS, serverId.getAddress().getHost())
+ .tag(SERVER_PORT, String.valueOf(serverId.getAddress().getPort()))
+ .tag(SERVER_TYPE, message.getSettings().getServerType().name())
+ .tag(CLIENT_CONNECTION_ID, this.description.getConnectionId().toString())
+ .tag(SERVER_CONNECTION_ID, String.valueOf(this.description.getConnectionId().getServerValue()));
+ }
+
+ private void tagSessionAndTransactionInfo(final Span span, final OperationContext operationContext) {
+ SessionContext sessionContext = operationContext.getSessionContext();
+ if (sessionContext.hasSession() && !sessionContext.isImplicitSession()) {
+ span.tag(TRANSACTION_NUMBER, String.valueOf(sessionContext.getTransactionNumber()))
+ .tag(SESSION_ID, String.valueOf(sessionContext.getSessionId()
+ .get(sessionContext.getSessionId().getFirstKey())
+ .asBinary().asUuid()));
+ }
+ }
}
diff --git a/driver-core/src/main/com/mongodb/internal/connection/LoggingCommandEventSender.java b/driver-core/src/main/com/mongodb/internal/connection/LoggingCommandEventSender.java
index 3821ca947c6..136ec10a1dd 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/LoggingCommandEventSender.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/LoggingCommandEventSender.java
@@ -78,7 +78,7 @@ class LoggingCommandEventSender implements CommandEventSender {
@Nullable final CommandListener commandListener,
final OperationContext operationContext,
final CommandMessage message,
- final ByteBufferBsonOutput bsonOutput,
+ final BsonDocument commandDocument,
final StructuredLogger logger,
final LoggerSettings loggerSettings) {
this.description = description;
@@ -88,7 +88,7 @@ class LoggingCommandEventSender implements CommandEventSender {
this.loggerSettings = loggerSettings;
this.startTimeNanos = System.nanoTime();
this.message = message;
- this.commandDocument = message.getCommandDocument(bsonOutput);
+ this.commandDocument = commandDocument;
this.commandName = commandDocument.getFirstKey();
this.redactionRequired = securitySensitiveCommands.contains(commandName)
|| (securitySensitiveHelloCommands.contains(commandName) && commandDocument.containsKey("speculativeAuthenticate"));
diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
index c74f4116a3f..9f1c232b274 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
@@ -27,6 +27,7 @@
import com.mongodb.internal.TimeoutSettings;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.session.SessionContext;
+import com.mongodb.internal.tracing.TraceContext;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
@@ -53,6 +54,8 @@ public class OperationContext {
private final ServerApi serverApi;
@Nullable
private final String operationName;
+ @Nullable
+ private TraceContext tracingContext;
public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext,
@Nullable final ServerApi serverApi) {
@@ -66,7 +69,8 @@ public OperationContext(final RequestContext requestContext, final SessionContex
this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(),
tracingManager,
serverApi,
- operationName);
+ operationName,
+ null);
}
public static OperationContext simpleOperationContext(
@@ -93,17 +97,17 @@ public static OperationContext simpleOperationContext(final TimeoutContext timeo
public OperationContext withSessionContext(final SessionContext sessionContext) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName);
+ operationName, tracingContext);
}
public OperationContext withTimeoutContext(final TimeoutContext timeoutContext) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName);
+ operationName, tracingContext);
}
public OperationContext withOperationName(final String operationName) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName);
+ operationName, tracingContext);
}
public long getId() {
@@ -136,6 +140,15 @@ public String getOperationName() {
return operationName;
}
+ @Nullable
+ public TraceContext getTracingSpanContext() {
+ return tracingContext != null ? tracingContext : null;
+ }
+
+ public void setTracingContext(final TraceContext tracingContext) {
+ this.tracingContext = tracingContext;
+ }
+
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
public OperationContext(final long id,
final RequestContext requestContext,
@@ -144,7 +157,9 @@ public OperationContext(final long id,
final ServerDeprioritization serverDeprioritization,
final TracingManager tracingManager,
@Nullable final ServerApi serverApi,
- @Nullable final String operationName) {
+ @Nullable final String operationName,
+ @Nullable final TraceContext tracingContext) {
+
this.id = id;
this.serverDeprioritization = serverDeprioritization;
this.requestContext = requestContext;
@@ -153,6 +168,7 @@ public OperationContext(final long id,
this.tracingManager = tracingManager;
this.serverApi = serverApi;
this.operationName = operationName;
+ this.tracingContext = tracingContext;
}
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
@@ -171,6 +187,7 @@ public OperationContext(final long id,
this.tracingManager = tracingManager;
this.serverApi = serverApi;
this.operationName = operationName;
+ this.tracingContext = null;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
index a061abafbe9..d201976e5ed 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
@@ -75,7 +75,6 @@ class CommandBatchCursor
+ * A span records information about a single operation, such as tags, events, errors, and its context.
+ * Implementations can be used to propagate tracing information and record telemetry.
+ *
+ * Spans can be used to trace different aspects of MongoDB driver activity:
+ *
+ *
+ *
+ * This implementation is used as a default when no actual tracing is required. + * All methods in this implementation perform no operations and return default values. + *
+ */ Span EMPTY = new Span() { @Override - public void tag(final String key, final String value) { + public Span tag(final String key, final String value) { + return this; + } + + @Override + public Span tag(final String key, final Long value) { + return this; } @Override @@ -40,13 +71,47 @@ public TraceContext context() { } }; - void tag(String key, String value); + /** + * Adds a tag to the span with a key-value pair. + * + * @param key The tag key. + * @param value The tag value. + * @return The current instance of the span. + */ + Span tag(String key, String value); + + /** + * Adds a tag to the span with a key and a numeric value. + * + * @param key The tag key. + * @param value The numeric tag value. + * @return The current instance of the span. + */ + Span tag(String key, Long value); + /** + * Records an event in the span. + * + * @param event The event description. + */ void event(String event); + /** + * Records an error for this span. + * + * @param throwable The error to record. + */ void error(Throwable throwable); + /** + * Ends the span, marking it as complete. + */ void end(); + /** + * Retrieves the context associated with the span. + * + * @return The trace context associated with the span. + */ TraceContext context(); } diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tags.java b/driver-core/src/main/com/mongodb/internal/tracing/Tags.java new file mode 100644 index 00000000000..e4407f2a401 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/tracing/Tags.java @@ -0,0 +1,42 @@ +/* + * 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.tracing; + +/** + * Contains constant tag names used for tracing and monitoring MongoDB operations. + * These tags are typically used to annotate spans or events with relevant metadata. + * + * @since 5.6 + */ +public final class Tags { + private Tags() { + } + + public static final String SYSTEM = "db.system"; + public static final String NAMESPACE = "db.namespace"; + public static final String COLLECTION = "db.collection.name"; + public static final String QUERY_SUMMARY = "db.query.summary"; + public static final String QUERY_TEXT = "db.query.text"; + public static final String CURSOR_ID = "db.mongodb.cursor_id"; + public static final String SERVER_ADDRESS = "server.address"; + public static final String SERVER_PORT = "server.port"; + public static final String SERVER_TYPE = "server.type"; + public static final String CLIENT_CONNECTION_ID = "db.mongodb.client_connection_id"; + public static final String SERVER_CONNECTION_ID = "db.mongodb.server_connection_id"; + public static final String TRANSACTION_NUMBER = "db.mongodb.txnNumber"; + public static final String SESSION_ID = "db.mongodb.lsid"; +} diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java new file mode 100644 index 00000000000..c2881e9e2fb --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java @@ -0,0 +1,97 @@ +/* + * 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.tracing; + +import com.mongodb.lang.Nullable; + +/** + * A Tracer interface that provides methods for tracing commands, operations and transactions. + *+ * This interface defines methods to retrieve the current trace context, create new spans, and check if tracing is enabled. + * It also includes a no-operation (NO_OP) implementation for cases where tracing is not required. + *
+ * + * @since 5.6 + */ +public interface Tracer { + Tracer NO_OP = new Tracer() { + + @Override + public TraceContext currentContext() { + return TraceContext.EMPTY; + } + + @Override + public Span nextSpan(final String name) { + return Span.EMPTY; + } + + @Override + public Span nextSpan(final String name, @Nullable final TraceContext parent) { + return Span.EMPTY; + } + + @Override + public boolean enabled() { + return false; + } + + @Override + public boolean includeCommandPayload() { + return false; + } + }; + + /** + * Retrieves the current trace context from the Micrometer tracer. + * + * @return A {@link TraceContext} representing the underlying {@link io.micrometer.tracing.TraceContext}. + * exists. + */ + TraceContext currentContext(); + + /** + * Creates a new span with the specified name. + * + * @param name The name of the span. + * @return A {@link Span} representing the newly created span. + */ + Span nextSpan(String name); // uses current active span + + /** + * Creates a new span with the specified name and optional parent trace context. + * + * @param name The name of the span. + * @param parent The parent {@link TraceContext}, or null if no parent context is provided. + * @return A {@link Span} representing the newly created span. + */ + Span nextSpan(String name, @Nullable TraceContext parent); // manually attach the next span to the provided parent + + /** + * Indicates whether tracing is enabled. + * + * @return {@code true} if tracing is enabled, {@code false} otherwise. + */ + boolean enabled(); + + /** + * Indicates whether command payloads are included in the trace context. + * + * @return {@code true} if command payloads are allowed, {@code false} otherwise. + */ + boolean includeCommandPayload(); // whether the tracer allows command payloads in the trace context +} diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java index f6bc5d14d81..6db92f0d75a 100644 --- a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java +++ b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java @@ -16,65 +16,97 @@ package com.mongodb.internal.tracing; -import com.mongodb.tracing.Tracer; +import com.mongodb.lang.Nullable; -import java.util.HashMap; +import static com.mongodb.internal.tracing.Tags.SYSTEM; +import static java.lang.System.getenv; +/** + * Manages tracing spans for MongoDB driver activities. + *+ * This class provides methods to create and manage spans for commands, operations and transactions. + * It integrates with a {@link Tracer} to propagate tracing information and record telemetry. + *
+ */ public class TracingManager { + /** + * A no-op instance of the TracingManager used when tracing is disabled. + */ public static final TracingManager NO_OP = new TracingManager(Tracer.NO_OP); + private static final String ENV_ALLOW_COMMAND_PAYLOAD = "MONGODB_TRACING_ALLOW_COMMAND_PAYLOAD"; private final Tracer tracer; private final TraceContext parentContext; + private final boolean enableCommandPayload; - // Map a cursor id to its parent context (useful for folding getMore commands under the parent operation) - private final HashMap+ * This method is used to create a span that is linked to a parent context, + * enabling hierarchical tracing of operations. + *
+ * + * @param name The name of the span. + * @param parentContext The parent trace context to associate with the span. + * @return The created span. + */ + public Span addSpan(final String name, @Nullable final TraceContext parentContext) { return tracer.nextSpan(name, parentContext); } - public void cleanContexts(final Long operationId) { - operationContexts.remove(operationId); - } - - public TraceContext getParentContext(final Long operationId) { - assert operationContexts.containsKey(operationId); - return operationContexts.get(operationId); - } - - public void addCursorParentContext(final long cursorId, final long operationId) { - assert operationContexts.containsKey(operationId); - cursors.put(cursorId, operationContexts.get(operationId)); - } - - public TraceContext getCursorParentContext(final long cursorId) { - return cursors.get(cursorId); - } - - public void removeCursorParentContext(final long cursorId) { - cursors.remove(cursorId); + /** + * Creates a new transaction span for the specified server session. + * + * @return The created transaction span. + */ + public Span addTransactionSpan() { + Span span = tracer.nextSpan("transaction", parentContext); + span.tag(SYSTEM, "mongodb"); + return span; } + /** + * Checks whether tracing is enabled. + * + * @return True if tracing is enabled, false otherwise. + */ public boolean isEnabled() { return tracer.enabled(); } + + /** + * Checks whether command payload tracing is enabled. + * + * @return True if command payload tracing is enabled, false otherwise. + */ + public boolean isCommandPayloadEnabled() { + return enableCommandPayload; + } } diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java b/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java new file mode 100644 index 00000000000..d975f6931e0 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java @@ -0,0 +1,110 @@ +/* + * 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.tracing; + +import com.mongodb.lang.Nullable; + +/** + * State class for transaction tracing. + */ +public class TransactionSpan { + private boolean isConvenientTransaction = false; + private final Span span; + @Nullable + private Throwable reportedError; + + public TransactionSpan(final TracingManager tracingManager) { + this.span = tracingManager.addTransactionSpan(); + } + + /** + * Handles a transaction span error. + *+ * If the transaction is convenient, the error is reported as an event. This is done since + * the error is not fatal and the transaction may be retried. + *
+ * If the transaction is not convenient, the error is reported as a span error and the + * transaction context is cleaned up. + * + * @param e The error to report. + */ + public void handleTransactionSpanError(final Throwable e) { + if (isConvenientTransaction) { + // report error as event (since subsequent retries might succeed, also keep track of the last event + span.event(e.toString()); + reportedError = e; + } else { + span.error(e); + } + + if (!isConvenientTransaction) { + span.end(); + } + } + + /** + * Finalizes the transaction span by logging the specified status as an event and ending the span. + * + * @param status The status to log as an event. + */ + public void finalizeTransactionSpan(final String status) { + span.event(status); + // clear previous commit error if any + if (!isConvenientTransaction) { + span.end(); + } + reportedError = null; // clear previous commit error if any + } + + /** + * Finalizes the transaction span by logging any last span event as an error and ending the span. + * Optionally cleans up the transaction context if specified. + * + * @param cleanupTransactionContext A boolean indicating whether to clean up the transaction context. + */ + public void spanFinalizing(final boolean cleanupTransactionContext) { + if (reportedError != null) { + span.error(reportedError); + } + span.end(); + reportedError = null; + // Don't clean up transaction context if we're still retrying (we want the retries to fold under the original transaction span) + if (cleanupTransactionContext) { + isConvenientTransaction = false; + } + } + + /** + * Indicates that the transaction is a convenient transaction. + *
+ * This has an impact on how the transaction span is handled. If the transaction is convenient, any errors that occur + * during the transaction are reported as events. If the transaction is not convenient, errors are reported as span + * errors and the transaction context is cleaned up. + */ + public void setIsConvenientTransaction() { + this.isConvenientTransaction = true; + } + + /** + * Retrieves the trace context associated with the transaction span. + * + * @return The trace context associated with the transaction span. + */ + public TraceContext getContext() { + return span.context(); + } +} diff --git a/driver-core/src/main/com/mongodb/internal/tracing/package-info.java b/driver-core/src/main/com/mongodb/internal/tracing/package-info.java index 6b1f711c20b..e7dd3311143 100644 --- a/driver-core/src/main/com/mongodb/internal/tracing/package-info.java +++ b/driver-core/src/main/com/mongodb/internal/tracing/package-info.java @@ -15,7 +15,7 @@ */ /** - * Contains classes related to sessions + * Contains classes related to tracing */ @NonNullApi package com.mongodb.internal.tracing; diff --git a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java index b34fa2247b4..ed678b93cab 100644 --- a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java +++ b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java @@ -18,12 +18,40 @@ import com.mongodb.internal.tracing.Span; import com.mongodb.internal.tracing.TraceContext; - +import com.mongodb.internal.tracing.Tracer; +import com.mongodb.lang.Nullable; + +/** + * A {@link Tracer} implementation that delegates tracing operations to a Micrometer {@link io.micrometer.tracing.Tracer}. + *
+ * This class enables integration of MongoDB driver tracing with Micrometer-based tracing systems. + * It provides methods to create and manage spans using the Micrometer tracing API. + *
+ * + * @since 5.6 + */ public class MicrometerTracer implements Tracer { private final io.micrometer.tracing.Tracer tracer; + private final boolean allowCommandPayload; + /** + * Constructs a new {@link MicrometerTracer} instance. + * + * @param tracer The Micrometer {@link io.micrometer.tracing.Tracer} to delegate tracing operations to. + */ public MicrometerTracer(final io.micrometer.tracing.Tracer tracer) { + this(tracer, false); + } + + /** + * Constructs a new {@link MicrometerTracer} instance with an option to allow command payloads. + * + * @param tracer The Micrometer {@link io.micrometer.tracing.Tracer} to delegate tracing operations to. + * @param allowCommandPayload Whether to allow command payloads in the trace context. + */ + public MicrometerTracer(final io.micrometer.tracing.Tracer tracer, final boolean allowCommandPayload) { this.tracer = tracer; + this.allowCommandPayload = allowCommandPayload; } @Override @@ -37,16 +65,17 @@ public Span nextSpan(final String name) { } @Override - public Span nextSpan(final String name, final TraceContext parent) { - if (parent != null) { - io.micrometer.tracing.Span span = tracer.spanBuilder() - .name(name) - .setParent(((MicrometerTraceContext) parent).getTraceContext()) - .start(); - return new MicrometerSpan(span); - } else { - return nextSpan(name); + public Span nextSpan(final String name, @Nullable final TraceContext parent) { + if (parent instanceof MicrometerTraceContext) { + io.micrometer.tracing.TraceContext micrometerContext = ((MicrometerTraceContext) parent).getTraceContext(); + if (micrometerContext != null) { + return new MicrometerSpan(tracer.spanBuilder() + .name(name) + .setParent(micrometerContext) + .start()); + } } + return nextSpan(name); } @Override @@ -54,31 +83,64 @@ public boolean enabled() { return true; } + @Override + public boolean includeCommandPayload() { + return allowCommandPayload; + } + + /** + * Represents a Micrometer-based trace context. + */ private static class MicrometerTraceContext implements TraceContext { private final io.micrometer.tracing.TraceContext traceContext; - MicrometerTraceContext(final io.micrometer.tracing.TraceContext traceContext) { + /** + * Constructs a new {@link MicrometerTraceContext} instance. + * + * @param traceContext The Micrometer {@link io.micrometer.tracing.TraceContext}, or null if none exists. + */ + MicrometerTraceContext(@Nullable final io.micrometer.tracing.TraceContext traceContext) { this.traceContext = traceContext; } + /** + * Retrieves the underlying Micrometer trace context. + * + * @return The Micrometer {@link io.micrometer.tracing.TraceContext}, or null if none exists. + */ + @Nullable public io.micrometer.tracing.TraceContext getTraceContext() { return traceContext; } } + /** + * Represents a Micrometer-based span. + */ private static class MicrometerSpan implements Span { private final io.micrometer.tracing.Span span; + /** + * Constructs a new {@link MicrometerSpan} instance. + * + * @param span The Micrometer {@link io.micrometer.tracing.Span} to delegate operations to. + */ MicrometerSpan(final io.micrometer.tracing.Span span) { this.span = span; } @Override - public void tag(final String key, final String value) { + public Span tag(final String key, final String value) { + span.tag(key, value); + return this; + } + + @Override + public Span tag(final String key, final Long value) { span.tag(key, value); + return this; } - // TODO add variant with TimeUnit @Override public void event(final String event) { span.event(event); diff --git a/driver-core/src/main/com/mongodb/tracing/Tracer.java b/driver-core/src/main/com/mongodb/tracing/Tracer.java deleted file mode 100644 index 14d7093d4cb..00000000000 --- a/driver-core/src/main/com/mongodb/tracing/Tracer.java +++ /dev/null @@ -1,53 +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.tracing; - -import com.mongodb.internal.tracing.Span; -import com.mongodb.internal.tracing.TraceContext; - -public interface Tracer { - Tracer NO_OP = new Tracer() { - - @Override - public TraceContext currentContext() { - return TraceContext.EMPTY; - } - - @Override - public Span nextSpan(final String name) { - return Span.EMPTY; - } - - @Override - public Span nextSpan(final String name, final TraceContext parent) { - return Span.EMPTY; - } - - @Override - public boolean enabled() { - return false; - } - }; - - TraceContext currentContext(); - - Span nextSpan(String name); // uses current active span - - Span nextSpan(String name, TraceContext parent); // manually attach the next span to the provided parent - - boolean enabled(); -} diff --git a/driver-core/src/main/com/mongodb/tracing/package-info.java b/driver-core/src/main/com/mongodb/tracing/package-info.java index 2ec7551d300..43e82603a09 100644 --- a/driver-core/src/main/com/mongodb/tracing/package-info.java +++ b/driver-core/src/main/com/mongodb/tracing/package-info.java @@ -15,7 +15,9 @@ */ /** - * Contains classes related to sessions + * This package defines the API for MongoDB driver tracing. + * + * @since 5.6 */ @NonNullApi package com.mongodb.tracing; diff --git a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java index d2916206a7d..30792bf0487 100644 --- a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java +++ b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java @@ -71,7 +71,6 @@ import com.mongodb.internal.operation.DropDatabaseOperation; import com.mongodb.internal.operation.ReadOperation; import com.mongodb.internal.operation.WriteOperation; -import com.mongodb.internal.tracing.TracingManager; import com.mongodb.lang.Nullable; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java index f40c7d17f20..447a1efb5dd 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java @@ -26,7 +26,6 @@ import com.mongodb.internal.IgnorableRequestContext; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.TimeoutSettings; -import com.mongodb.internal.tracing.TracingManager; import org.bson.BsonDocument; import org.bson.codecs.Decoder; import org.junit.jupiter.api.Test; @@ -121,6 +120,6 @@ void testIsCommandOk() { OperationContext createOperationContext() { return new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE, - new TimeoutContext(TimeoutSettings.DEFAULT), ServerApi.builder().version(ServerApiVersion.V1).build(), TracingManager.NO_OP); + new TimeoutContext(TimeoutSettings.DEFAULT), ServerApi.builder().version(ServerApiVersion.V1).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 3ceb7567eef..533e74f0d23 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 @@ -32,7 +32,6 @@ import com.mongodb.internal.operation.ClientBulkWriteOperation; import com.mongodb.internal.operation.ClientBulkWriteOperation.ClientBulkWriteCommand.OpsAndNsInfo; import com.mongodb.internal.session.SessionContext; -import com.mongodb.internal.tracing.TracingManager; import com.mongodb.internal.validator.NoOpFieldNameValidator; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -164,7 +163,7 @@ void getCommandDocumentFromClientBulkWrite() { output, new OperationContext( IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE, - new TimeoutContext(TimeoutSettings.DEFAULT), null, TracingManager.NO_OP)); + new TimeoutContext(TimeoutSettings.DEFAULT), null)); BsonDocument actualCommandDocument = commandMessage.getCommandDocument(output); assertEquals(expectedCommandDocument, actualCommandDocument); } 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 83ba91df16b..3c66babd5d8 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 @@ -21,6 +21,7 @@ import com.mongodb.TransactionOptions import com.mongodb.client.ClientSession as JClientSession import com.mongodb.client.TransactionBody import com.mongodb.internal.TimeoutContext +import com.mongodb.internal.tracing.TransactionSpan import com.mongodb.kotlin.client.coroutine.ClientSession import com.mongodb.session.ServerSession import kotlinx.coroutines.runBlocking @@ -89,4 +90,6 @@ class SyncClientSession(internal val wrapped: ClientSession, private val origina throw UnsupportedOperationException() override fun getTimeoutContext(): TimeoutContext? = wrapped.getTimeoutContext() + + override fun getTransactionSpan(): TransactionSpan? = null } 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 64cd27b776f..001198dbcd0 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 @@ -21,6 +21,7 @@ import com.mongodb.TransactionOptions import com.mongodb.client.ClientSession as JClientSession import com.mongodb.client.TransactionBody import com.mongodb.internal.TimeoutContext +import com.mongodb.internal.tracing.TransactionSpan import com.mongodb.kotlin.client.ClientSession import com.mongodb.session.ServerSession import org.bson.BsonDocument @@ -93,4 +94,6 @@ internal class SyncClientSession(internal val wrapped: ClientSession, private va throw UnsupportedOperationException() override fun getTimeoutContext(): TimeoutContext = throw UnsupportedOperationException() + + override fun getTransactionSpan(): TransactionSpan? = null } diff --git a/driver-legacy/src/main/com/mongodb/MongoClient.java b/driver-legacy/src/main/com/mongodb/MongoClient.java index 9478dc76177..09d58e1b493 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClient.java +++ b/driver-legacy/src/main/com/mongodb/MongoClient.java @@ -43,7 +43,6 @@ import com.mongodb.internal.diagnostics.logging.Loggers; import com.mongodb.internal.session.ServerSessionPool; import com.mongodb.internal.thread.DaemonThreadFactory; -import com.mongodb.internal.tracing.TracingManager; import com.mongodb.internal.validator.NoOpFieldNameValidator; import com.mongodb.lang.Nullable; import org.bson.BsonArray; 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 1689c9b31e9..250949549f2 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 @@ -205,7 +205,7 @@ private OperationContext getOperationContext(final RequestContext requestContext requestContext, new ReadConcernAwareNoOpSessionContext(readConcern), createTimeoutContext(session, timeoutSettings), - mongoClient.getSettings().getTracingManager(), + TracingManager.NO_OP, mongoClient.getSettings().getServerApi(), commandName); } 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 494e5f8c74e..ab234ad6f3e 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 @@ -22,6 +22,7 @@ import com.mongodb.client.ClientSession; import com.mongodb.client.TransactionBody; import com.mongodb.internal.TimeoutContext; +import com.mongodb.internal.tracing.TransactionSpan; import com.mongodb.lang.Nullable; import com.mongodb.session.ServerSession; import org.bson.BsonDocument; @@ -188,6 +189,12 @@ public TimeoutContext getTimeoutContext() { return wrapped.getTimeoutContext(); } + @Override + @Nullable + public TransactionSpan getTransactionSpan() { + return null; + } + private static void sleep(final long millis) { try { Thread.sleep(millis); 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 2866ce7427d..4c0cb19217d 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 @@ -19,6 +19,7 @@ package org.mongodb.scala.syncadapter import com.mongodb.{ ClientSessionOptions, MongoInterruptedException, ServerAddress, TransactionOptions } import com.mongodb.client.{ ClientSession => JClientSession, TransactionBody } import com.mongodb.internal.TimeoutContext +import com.mongodb.internal.tracing.TransactionSpan import com.mongodb.session.ServerSession import org.bson.{ BsonDocument, BsonTimestamp } import org.mongodb.scala._ @@ -96,4 +97,6 @@ case class SyncClientSession(wrapped: ClientSession, originator: Object) extends } override def getTimeoutContext: TimeoutContext = wrapped.getTimeoutContext + + override def getTransactionSpan: TransactionSpan = null } diff --git a/driver-sync/build.gradle.kts b/driver-sync/build.gradle.kts index 95cd0979973..b37d0226295 100644 --- a/driver-sync/build.gradle.kts +++ b/driver-sync/build.gradle.kts @@ -38,6 +38,9 @@ dependencies { // lambda testing testImplementation(libs.aws.lambda.core) + + // Tracing + testImplementation(libs.bundles.micrometer.test) } configureMavenPublication { diff --git a/driver-sync/src/main/com/mongodb/client/ClientSession.java b/driver-sync/src/main/com/mongodb/client/ClientSession.java index 5d994b863e8..6af2b4be664 100644 --- a/driver-sync/src/main/com/mongodb/client/ClientSession.java +++ b/driver-sync/src/main/com/mongodb/client/ClientSession.java @@ -18,6 +18,7 @@ import com.mongodb.ServerAddress; import com.mongodb.TransactionOptions; +import com.mongodb.internal.tracing.TransactionSpan; import com.mongodb.lang.Nullable; /** @@ -125,4 +126,13 @@ public interface ClientSession extends com.mongodb.session.ClientSession { * @since 3.11 */+ * Spans are visible in the Zipkin UI at .... + *
+ * To Start Zipkin server, you can use the following command: + *
{@code + * docker run -d -p 9411:9411 openzipkin/zipkin + * }+ */ +public final class ZipkinTracer { + private static final String ENDPOINT = "http://localhost:9411/api/v2/spans"; + + private ZipkinTracer() { + } + + /** + * Creates a Zipkin tracer with the specified service name. + * + * @param serviceName the name of the service to be used in the tracer + * @return a Tracer instance configured to send spans to Zipkin + */ + public static Tracer getTracer(final String serviceName) { + ZipkinSpanExporter zipkinExporter = ZipkinSpanExporter.builder() + .setEndpoint(ENDPOINT) + .build(); + + Resource resource = Resource.getDefault() + .merge(Resource.create( + Attributes.of( + ResourceAttributes.SERVICE_NAME, serviceName, + ResourceAttributes.SERVICE_VERSION, "1.0.0" + ) + )); + + SpanProcessor spanProcessor = SimpleSpanProcessor.create(zipkinExporter); + + SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(spanProcessor) + .setResource(resource) + .build(); + + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(tracerProvider) + .setPropagators(ContextPropagators.create( + B3Propagator.injectingSingleHeader() + )) + .build(); + + io.opentelemetry.api.trace.Tracer otelTracer = openTelemetry.getTracer("my-java-service", "1.0.0"); + + OtelCurrentTraceContext otelCurrentTraceContext = new OtelCurrentTraceContext(); + + return new OtelTracer( + otelTracer, + otelCurrentTraceContext, + null // EventPublisher can be null for basic usage + ); + } + +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 90aec4c1c38..38805b02353 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,8 @@ snappy = "1.1.10.3" zstd = "1.5.5-3" jetbrains-annotations = "26.0.2" micrometer = "1.4.5" +zipkin-reporter = "2.16.3" +opentelemetry-exporter-zipkin = "1.30.0" kotlin = "1.8.10" kotlinx-coroutines-bom = "1.6.4" @@ -174,6 +176,12 @@ project-reactor-test = { module = "io.projectreactor:reactor-test" } reactive-streams-tck = { module = " org.reactivestreams:reactive-streams-tck", version.ref = "reactive-streams" } reflections = { module = "org.reflections:reflections", version.ref = "reflections" } +micrometer-tracing-test = { module = " io.micrometer:micrometer-tracing-test", version.ref = "micrometer" } +micrometer-tracing-bridge-brave = { module = " io.micrometer:micrometer-tracing-bridge-brave", version.ref = "micrometer" } +micrometer-tracing = { module = " io.micrometer:micrometer-tracing", version.ref = "micrometer" } +micrometer-tracing-bridge-otel = { module = " io.micrometer:micrometer-tracing-bridge-otel", version.ref = "micrometer" } +zipkin-reporter = { module = " io.zipkin.reporter2:zipkin-reporter", version.ref = "zipkin-reporter" } +opentelemetry-exporter-zipkin = { module = " io.opentelemetry:opentelemetry-exporter-zipkin", version.ref = "opentelemetry-exporter-zipkin" } [bundles] aws-java-sdk-v1 = ["aws-java-sdk-v1-core", "aws-java-sdk-v1-sts"] @@ -200,6 +208,9 @@ scala-test-v2-v12 = ["scala-test-flatspec-v2-v12", "scala-test-shouldmatchers-v2 scala-test-v2-v11 = ["scala-test-flatspec-v2-v11", "scala-test-shouldmatchers-v2-v11", "scala-test-mockito-v2-v11", "scala-test-junit-runner-v2-v11", "reflections"] +micrometer-test = ["micrometer-tracing-test", "micrometer-tracing-bridge-brave", "micrometer-tracing-bridge-otel", + "micrometer-tracing", "zipkin-reporter", "opentelemetry-exporter-zipkin"] + [plugins] kotlin-gradle = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } bnd = { id = "biz.aQute.bnd.builder", version.ref = "plugin-bnd" } From 33d46e8ca5f87a7f5ad96318930ab3bfe2aa8e48 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha
- * Spans are visible in the Zipkin UI at .... + * Spans are visible in the Zipkin UI at http://localhost:9411. *
* To Start Zipkin server, you can use the following command: *
{@code diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java b/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java index f1429431690..4dfedf0a8d5 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java @@ -66,6 +66,9 @@ import com.mongodb.lang.NonNull; import com.mongodb.lang.Nullable; import com.mongodb.logging.TestLoggingInterceptor; +import com.mongodb.tracing.MicrometerTracer; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.test.simple.SimpleTracer; import org.bson.BsonArray; import org.bson.BsonBoolean; import org.bson.BsonDocument; @@ -112,7 +115,7 @@ public final class Entities { private static final SetSUPPORTED_CLIENT_ENTITY_OPTIONS = new HashSet<>( asList( "id", "uriOptions", "serverApi", "useMultipleMongoses", "storeEventsAsEntities", - "observeEvents", "observeLogMessages", "observeSensitiveCommands", "ignoreCommandMonitoringEvents")); + "observeEvents", "observeLogMessages", "observeSensitiveCommands", "ignoreCommandMonitoringEvents", "tracing")); private final Set entityNames = new HashSet<>(); private final Map threads = new HashMap<>(); private final Map >> tasks = new HashMap<>(); @@ -126,6 +129,7 @@ public final class Entities { private final Map clientEncryptions = new HashMap<>(); private final Map clientCommandListeners = new HashMap<>(); private final Map clientLoggingInterceptors = new HashMap<>(); + private final Map clientTracing = new HashMap<>(); private final Map clientConnectionPoolListeners = new HashMap<>(); private final Map clientServerListeners = new HashMap<>(); private final Map clientClusterListeners = new HashMap<>(); @@ -294,6 +298,10 @@ public TestLoggingInterceptor getClientLoggingInterceptor(final String id) { return getEntity(id + "-logging-interceptor", clientLoggingInterceptors, "logging interceptor"); } + public Tracer getClientTracer(final String id) { + return getEntity(id + "-tracing", clientTracing, "micrometer tracing"); + } + public TestConnectionPoolListener getConnectionPoolListener(final String id) { return getEntity(id + "-connection-pool-listener", clientConnectionPoolListeners, "connection pool listener"); } @@ -604,6 +612,22 @@ private void initClient(final BsonDocument entity, final String id, } clientSettingsBuilder.serverApi(serverApiBuilder.build()); } + + if (entity.containsKey("tracing")) { + boolean enableCommandPayload = entity.getDocument("tracing").get("enableCommandPayload", BsonBoolean.FALSE).asBoolean().getValue(); + /* To enable Zipkin backend, uncomment the following lines and ensure you have the server started + (docker run -d -p 9411:9411 openzipkin/zipkin). The tests will fail but the captured spans will be + visible in the Zipkin UI at http://localhost:9411 for debugging purpose. + * + * Tracer tracer = ZipkinTracer.getTracer("UTR"); + * putEntity(id + "-tracing", new SimpleTracer(), clientTracing); + */ + Tracer tracer = new SimpleTracer(); + putEntity(id + "-tracing", tracer, clientTracing); + + clientSettingsBuilder.tracer(new MicrometerTracer(tracer, enableCommandPayload)); + } + MongoClientSettings clientSettings = clientSettingsBuilder.build(); if (entity.containsKey("observeLogMessages")) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java new file mode 100644 index 00000000000..84bc0734c5f --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java @@ -0,0 +1,27 @@ +/* + * 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.unified; + +import org.junit.jupiter.params.provider.Arguments; + +import java.util.Collection; + +final class MicrometerTracingTest extends UnifiedSyncTest { + private static Collection data() { + return getTestData("tracing/tests"); + } +} 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 62c16330950..f7b099a604d 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 @@ -28,6 +28,7 @@ import com.mongodb.internal.TimeoutSettings import com.mongodb.internal.client.model.changestream.ChangeStreamLevel import com.mongodb.internal.connection.Cluster import com.mongodb.internal.session.ServerSessionPool +import com.mongodb.internal.tracing.TracingManager import org.bson.BsonDocument import org.bson.Document import org.bson.codecs.UuidCodec @@ -258,6 +259,7 @@ class MongoClusterSpecification extends Specification { MongoClusterImpl createMongoCluster(final MongoClientSettings settings, final OperationExecutor operationExecutor) { new MongoClusterImpl(null, cluster, settings.codecRegistry, null, null, originator, operationExecutor, settings.readConcern, settings.readPreference, settings.retryReads, settings.retryWrites, - null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation, settings.writeConcern) + null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation, + settings.writeConcern, TracingManager.NO_OP) } } From 86f179171381bcb432db45926afcad675133e02f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 5 Aug 2025 00:27:55 +0100 Subject: [PATCH 08/36] Fixing tests --- .../internal/connection/InternalStreamConnection.java | 10 +++++----- .../mongodb/internal/connection/CommandHelperTest.java | 1 + .../LoggingCommandEventSenderSpecification.groovy | 10 ++++++---- .../kotlin/com/mongodb/kotlin/client/ClientSession.kt | 4 ++++ driver-reactive-streams/build.gradle.kts | 3 +++ .../com/mongodb/client/internal/MongoClusterImpl.java | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java index bd3008cf927..56bcc48ce34 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java @@ -390,7 +390,7 @@ public T sendAndReceive(final CommandMessage message, final Decoder decod message, decoder, operationContext); try { return sendAndReceiveInternal.get(); - } catch (Throwable e) { + } catch (MongoCommandException e) { if (reauthenticationIsTriggered(e)) { return reauthenticateAndRetry(sendAndReceiveInternal, operationContext); } @@ -405,8 +405,9 @@ public void sendAndReceiveAsync(final CommandMessage message, final Decoder< AsyncSupplier sendAndReceiveAsyncInternal = c -> sendAndReceiveAsyncInternal( message, decoder, operationContext, c); - - beginAsync().thenSupply(sendAndReceiveAsyncInternal::getAsync).onErrorIf(this::reauthenticationIsTriggered, (t, c) -> { + beginAsync(). thenSupply(c -> { + sendAndReceiveAsyncInternal.getAsync(c); + }).onErrorIf(e -> reauthenticationIsTriggered(e), (t, c) -> { reauthenticateAndRetryAsync(sendAndReceiveAsyncInternal, operationContext, c); }).finish(callback); } @@ -1034,9 +1035,8 @@ private Span createTracingSpan(final CommandMessage message, final OperationCont TracingManager tracingManager = operationContext.getTracingManager(); BsonDocument command = message.getCommandDocument(bsonOutput); -// BsonDocument command = message.getCommand(); String commandName = command.getFirstKey(); -// Span newSpan = tracingManager.addSpan("_____Command_____[ " + commandName + " ]", myparentContext); + if (!tracingManager.isEnabled() || SECURITY_SENSITIVE_COMMANDS.contains(commandName) || SECURITY_SENSITIVE_HELLO_COMMANDS.contains(commandName)) { diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java index 447a1efb5dd..f7873379c3b 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java @@ -118,6 +118,7 @@ void testIsCommandOk() { assertFalse(CommandHelper.isCommandOk(new BsonDocument())); } + OperationContext createOperationContext() { return new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE, new TimeoutContext(TimeoutSettings.DEFAULT), ServerApi.builder().version(ServerApiVersion.V1).build()); 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 6f8eaf33314..35bced971fa 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 @@ -66,7 +66,8 @@ class LoggingCommandEventSenderSpecification extends Specification { } def operationContext = OPERATION_CONTEXT def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, commandListener, - operationContext, message, bsonOutput, new StructuredLogger(logger), LoggerSettings.builder().build()) + operationContext, message, message.getCommandDocument(bsonOutput), + new StructuredLogger(logger), LoggerSettings.builder().build()) when: sender.sendStartedEvent() @@ -111,7 +112,7 @@ class LoggingCommandEventSenderSpecification extends Specification { } def operationContext = OPERATION_CONTEXT def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, commandListener, - operationContext, message, bsonOutput, new StructuredLogger(logger), + operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) when: sender.sendStartedEvent() @@ -169,7 +170,7 @@ class LoggingCommandEventSenderSpecification extends Specification { def operationContext = OPERATION_CONTEXT def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, null, operationContext, - message, bsonOutput, new StructuredLogger(logger), LoggerSettings.builder().build()) + message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build()) when: sender.sendStartedEvent() @@ -202,7 +203,8 @@ class LoggingCommandEventSenderSpecification extends Specification { } def operationContext = OPERATION_CONTEXT def sender = new LoggingCommandEventSender(['createUser'] as Set, [] as Set, connectionDescription, null, - operationContext, message, bsonOutput, new StructuredLogger(logger), LoggerSettings.builder().build()) + operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), + LoggerSettings.builder().build()) when: sender.sendStartedEvent() 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 9103689b251..9786f5592e6 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 @@ -18,6 +18,7 @@ package com.mongodb.kotlin.client import com.mongodb.ClientSessionOptions import com.mongodb.TransactionOptions import com.mongodb.client.ClientSession as JClientSession +import com.mongodb.internal.tracing.TransactionSpan import java.io.Closeable import java.util.concurrent.TimeUnit @@ -86,6 +87,9 @@ public class ClientSession(public val wrapped: JClientSession) : Closeable { transactionBody: () -> T, options: TransactionOptions = TransactionOptions.builder().build() ): T = wrapped.withTransaction(transactionBody, options) + + /** Get the transaction span (if started). */ + public fun getTransactionSpan(): TransactionSpan? = wrapped.getTransactionSpan() } /** diff --git a/driver-reactive-streams/build.gradle.kts b/driver-reactive-streams/build.gradle.kts index f1c758b31da..f9ac5301625 100644 --- a/driver-reactive-streams/build.gradle.kts +++ b/driver-reactive-streams/build.gradle.kts @@ -44,6 +44,9 @@ dependencies { // Reactive Streams TCK testing testImplementation(libs.reactive.streams.tck) + + // Tracing + testImplementation(libs.micrometer) } configureMavenPublication { 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 f1ea046b591..ec2fbb5c759 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java @@ -590,7 +590,7 @@ ClientSession getClientSession(@Nullable final ClientSession clientSessionFromOp * @param commandName the name of the command */ @Nullable - private Span createOperationSpan(ClientSession actualClientSession, BindingContext binding, String commandName) { + private Span createOperationSpan(final ClientSession actualClientSession, final BindingContext binding, final String commandName) { TracingManager tracingManager = binding.getOperationContext().getTracingManager(); if (tracingManager.isEnabled()) { TraceContext parentContext = null; From ad5937c10ea1f0135ce97ba581e10fb74275945d Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 16 Jul 2025 12:26:35 -0700 Subject: [PATCH 09/36] Decouple OperationContext from BindingContext. Make TimeoutContext immutable. --- .../com/mongodb/internal/TimeoutContext.java | 226 +++++++---- .../internal/async/SingleResultCallback.java | 2 +- .../function/AsyncCallbackTriFunction.java | 40 ++ .../AsyncClusterAwareReadWriteBinding.java | 3 +- .../internal/binding/AsyncClusterBinding.java | 56 ++- .../binding/AsyncConnectionSource.java | 8 +- .../internal/binding/AsyncReadBinding.java | 4 +- .../internal/binding/AsyncWriteBinding.java | 3 +- .../internal/binding/BindingContext.java | 5 +- .../binding/ClusterAwareReadWriteBinding.java | 3 +- .../internal/binding/ClusterBinding.java | 58 ++- .../internal/binding/ConnectionSource.java | 5 +- .../mongodb/internal/binding/ReadBinding.java | 5 +- .../internal/binding/SingleServerBinding.java | 35 +- .../internal/binding/WriteBinding.java | 4 +- .../internal/connection/CommandHelper.java | 1 - .../connection/DefaultConnectionPool.java | 2 +- .../connection/InternalStreamConnection.java | 21 +- .../InternalStreamConnectionInitializer.java | 2 - .../internal/connection/OperationContext.java | 10 + .../connection/SaslAuthenticator.java | 46 +-- .../operation/AbortTransactionOperation.java | 5 +- .../AbstractWriteSearchIndexOperation.java | 24 +- .../operation/AggregateOperation.java | 11 +- .../operation/AggregateOperationImpl.java | 27 +- .../AggregateToCollectionOperation.java | 64 ++- .../AsyncChangeStreamBatchCursor.java | 75 ++-- .../operation/AsyncCommandBatchCursor.java | 335 ++-------------- .../operation/AsyncCommandCoreCursor.java | 348 +++++++++++++++++ .../internal/operation/AsyncCoreCursor.java | 68 ++++ .../operation/AsyncOperationHelper.java | 188 +++++---- .../operation/AsyncReadOperation.java | 3 +- .../operation/AsyncWriteOperation.java | 3 +- .../operation/BaseFindAndModifyOperation.java | 9 +- .../operation/ChangeStreamBatchCursor.java | 47 ++- .../operation/ChangeStreamOperation.java | 35 +- .../operation/ClientBulkWriteOperation.java | 55 +-- .../operation/CommandBatchCursor.java | 330 ++-------------- .../internal/operation/CommandCoreCursor.java | 364 ++++++++++++++++++ .../operation/CommandReadOperation.java | 25 +- .../operation/CommitTransactionOperation.java | 15 +- .../internal/operation/CoreCursor.java | 78 ++++ .../operation/CountDocumentsOperation.java | 10 +- .../internal/operation/CountOperation.java | 13 +- .../operation/CreateCollectionOperation.java | 23 +- .../operation/CreateIndexesOperation.java | 12 +- .../operation/CreateViewOperation.java | 17 +- .../operation/CursorResourceManagerNew.java | 262 +++++++++++++ .../internal/operation/DistinctOperation.java | 9 +- .../operation/DropCollectionOperation.java | 38 +- .../operation/DropDatabaseOperation.java | 15 +- .../operation/DropIndexOperation.java | 15 +- .../EstimatedDocumentCountOperation.java | 13 +- .../operation/ExplainCommandOperation.java | 63 +++ .../internal/operation/FindOperation.java | 72 ++-- .../operation/ListCollectionsOperation.java | 45 ++- .../operation/ListDatabasesOperation.java | 11 +- .../operation/ListIndexesOperation.java | 44 ++- .../operation/ListSearchIndexesOperation.java | 9 +- .../MapReduceToCollectionOperation.java | 21 +- .../MapReduceWithInlineResultsOperation.java | 16 +- .../operation/MixedBulkWriteOperation.java | 49 +-- .../internal/operation/OperationHelper.java | 7 +- .../internal/operation/ReadOperation.java | 3 +- .../operation/RenameCollectionOperation.java | 17 +- .../operation/SyncOperationHelper.java | 168 ++++---- .../operation/TransactionOperation.java | 17 +- .../internal/operation/WriteOperation.java | 3 +- .../session/BaseClientSessionImpl.java | 2 +- .../com/mongodb/ClusterFixture.java | 75 ++-- .../OperationFunctionalSpecification.groovy | 58 +-- .../mongodb/client/test/CollectionHelper.java | 58 +-- .../connection/ConnectionSpecification.groovy | 14 +- .../binding/AsyncOperationContextBinding.java | 30 +- .../internal/binding/AsyncSessionBinding.java | 29 +- .../AsyncSessionBindingSpecification.groovy | 16 +- .../binding/AsyncSingleConnectionBinding.java | 22 +- .../binding/OperationContextBinding.java | 22 +- .../internal/binding/SessionBinding.java | 29 +- .../binding/SingleConnectionBinding.java | 24 +- ...amSha256AuthenticationSpecification.groovy | 24 +- .../AggregateOperationSpecification.groovy | 58 +-- ...eToCollectionOperationSpecification.groovy | 6 +- ...AsyncCommandBatchCursorFunctionalTest.java | 101 ++--- .../AsyncCommandBatchCursorTest.java | 243 ++---------- .../operation/AsyncCommandCoreCursorTest.java | 211 ++++++++++ .../ChangeStreamOperationSpecification.groovy | 25 +- .../CommandBatchCursorFunctionalTest.java | 108 +++--- ...ountDocumentsOperationSpecification.groovy | 26 +- ...ateCollectionOperationSpecification.groovy | 23 +- ...CreateIndexesOperationSpecification.groovy | 7 +- .../CreateViewOperationSpecification.groovy | 4 +- .../DistinctOperationSpecification.groovy | 27 +- ...ropCollectionOperationSpecification.groovy | 14 +- .../DropDatabaseOperationSpecification.groovy | 9 +- .../DropIndexOperationSpecification.groovy | 5 +- .../FindOperationSpecification.groovy | 90 +++-- ...stCollectionsOperationSpecification.groovy | 94 +++-- ...ListDatabasesOperationSpecification.groovy | 16 +- .../ListIndexesOperationSpecification.groovy | 47 ++- ...eToCollectionOperationSpecification.groovy | 8 +- ...InlineResultsOperationSpecification.groovy | 22 +- ...ameCollectionOperationSpecification.groovy | 13 +- .../src/test/resources/logback-test.xml | 2 +- .../mongodb/internal/TimeoutContextTest.java | 12 +- .../SingleServerBindingSpecification.groovy | 18 +- .../AbstractConnectionPoolTest.java | 3 +- .../DefaultServerSpecification.groovy | 3 +- .../InsufficientStubbingDetectorDemoTest.java | 10 +- ...hangeStreamBatchCursorSpecification.groovy | 64 +-- ...syncCommandBatchCursorSpecification.groovy | 73 ++-- .../AsyncOperationHelperSpecification.groovy | 23 +- ...hangeStreamBatchCursorSpecification.groovy | 32 +- .../ChangeStreamBatchCursorTest.java | 264 +++++++++---- .../CommandBatchCursorSpecification.groovy | 85 ++-- .../operation/CommandBatchCursorTest.java | 259 +++++-------- .../operation/CommandCoreCursorTest.java | 177 +++++++++ ...ansactionOperationUnitSpecification.groovy | 14 +- .../ListCollectionsOperationTest.java | 8 +- .../OperationUnitSpecification.groovy | 26 +- .../SyncOperationHelperSpecification.groovy | 30 +- .../LegacyMixedBulkWriteOperation.java | 5 +- .../src/main/com/mongodb/MongoClient.java | 14 +- .../test/functional/com/mongodb/DBTest.java | 3 +- ...ixedBulkWriteOperationSpecification.groovy | 3 +- .../client/internal/ClientSessionBinding.java | 61 +-- .../internal/MapReducePublisherImpl.java | 9 +- .../internal/OperationExecutorImpl.java | 41 +- ...dReadOperationThenCursorReadOperation.java | 7 +- ...WriteOperationThenCursorReadOperation.java | 7 +- .../client/internal/crypt/CryptBinding.java | 32 +- .../ClientSideOperationTimeoutProseTest.java | 2 +- .../src/test/resources/logback-test.xml | 2 +- .../ClientSessionBindingSpecification.groovy | 79 ++-- .../client/internal/ClientSessionBinding.java | 73 ++-- .../mongodb/client/internal/CryptBinding.java | 30 +- .../internal/MapReduceIterableImpl.java | 5 +- .../client/internal/MongoClusterImpl.java | 33 +- ...tClientSideOperationsTimeoutProseTest.java | 31 ++ .../src/test/resources/logback-test.xml | 2 +- .../ClientSessionBindingSpecification.groovy | 70 ++-- 141 files changed, 4089 insertions(+), 2703 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/internal/async/function/AsyncCallbackTriFunction.java create mode 100644 driver-core/src/main/com/mongodb/internal/operation/AsyncCommandCoreCursor.java create mode 100644 driver-core/src/main/com/mongodb/internal/operation/AsyncCoreCursor.java create mode 100644 driver-core/src/main/com/mongodb/internal/operation/CommandCoreCursor.java create mode 100644 driver-core/src/main/com/mongodb/internal/operation/CoreCursor.java create mode 100644 driver-core/src/main/com/mongodb/internal/operation/CursorResourceManagerNew.java create mode 100644 driver-core/src/main/com/mongodb/internal/operation/ExplainCommandOperation.java create mode 100644 driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandCoreCursorTest.java create mode 100644 driver-core/src/test/unit/com/mongodb/internal/operation/CommandCoreCursorTest.java diff --git a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java index b4561e6fdc6..b3bdc65a9f7 100644 --- a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java +++ b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java @@ -32,7 +32,6 @@ import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; import static com.mongodb.internal.time.Timeout.ZeroSemantics.ZERO_DURATION_MEANS_INFINITE; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * Timeout Context. @@ -40,18 +39,15 @@ * The context for handling timeouts in relation to the Client Side Operation Timeout specification.
*/ public class TimeoutContext { - - private final boolean isMaintenanceContext; private final TimeoutSettings timeoutSettings; - @Nullable - private Timeout timeout; + private final Timeout timeout; @Nullable - private Timeout computedServerSelectionTimeout; - private long minRoundTripTimeMS = 0; - + private final Timeout computedServerSelectionTimeout; @Nullable - private MaxTimeSupplier maxTimeSupplier = null; + private final MaxTimeSupplier maxTimeSupplier; + private final boolean isMaintenanceContext; + private final long minRoundTripTimeMS; public static MongoOperationTimeoutException createMongoRoundTripTimeoutException() { return createMongoTimeoutException("Remaining timeoutMS is less than or equal to the server's minimum round trip time."); @@ -110,17 +106,6 @@ public static TimeoutContext createTimeoutContext(final ClientSession session, f return new TimeoutContext(timeoutSettings); } - // Creates a copy of the timeout context that can be reset without resetting the original. - public TimeoutContext copyTimeoutContext() { - return new TimeoutContext(getTimeoutSettings(), getTimeout()); - } - - public TimeoutContext withNewlyStartedTimeout() { - TimeoutContext newContext = copyTimeoutContext(); - newContext.timeout = startTimeout(newContext.timeoutSettings.getTimeoutMS()); - return newContext; - } - public TimeoutContext(final TimeoutSettings timeoutSettings) { this(false, timeoutSettings, startTimeout(timeoutSettings.getTimeoutMS())); } @@ -129,9 +114,41 @@ private TimeoutContext(final TimeoutSettings timeoutSettings, @Nullable final Ti this(false, timeoutSettings, timeout); } - private TimeoutContext(final boolean isMaintenanceContext, final TimeoutSettings timeoutSettings, @Nullable final Timeout timeout) { + private TimeoutContext(final boolean isMaintenanceContext, + final TimeoutSettings timeoutSettings, + @Nullable final Timeout timeout) { + this(isMaintenanceContext, + null, + 0, + timeoutSettings, + null, + timeout); + } + + private TimeoutContext(final boolean isMaintenanceContext, + @Nullable final Timeout computedServerSelectionTimeout, + final long minRoundTripTimeMS, + final TimeoutSettings timeoutSettings, + @Nullable final MaxTimeSupplier maxTimeSupplier) { + this(isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + maxTimeSupplier, + startTimeout(timeoutSettings.getTimeoutMS())); + } + + private TimeoutContext(final boolean isMaintenanceContext, + @Nullable final Timeout computedServerSelectionTimeout, + final long minRoundTripTimeMS, + final TimeoutSettings timeoutSettings, + @Nullable final MaxTimeSupplier maxTimeSupplier, + @Nullable final Timeout timeout) { this.isMaintenanceContext = isMaintenanceContext; this.timeoutSettings = timeoutSettings; + this.computedServerSelectionTimeout = computedServerSelectionTimeout; + this.minRoundTripTimeMS = minRoundTripTimeMS; + this.maxTimeSupplier = maxTimeSupplier; this.timeout = timeout; } @@ -152,17 +169,6 @@ public void onExpired(final Runnable onExpired) { Timeout.nullAsInfinite(timeout).onExpired(onExpired); } - /** - * Sets the recent min round trip time - * @param minRoundTripTimeMS the min round trip time - * @return this - */ - public TimeoutContext minRoundTripTimeMS(final long minRoundTripTimeMS) { - isTrue("'minRoundTripTimeMS' must be a positive number", minRoundTripTimeMS >= 0); - this.minRoundTripTimeMS = minRoundTripTimeMS; - return this; - } - @Nullable public Timeout timeoutIncludingRoundTrip() { return timeout == null ? null : timeout.shortenBy(minRoundTripTimeMS, MILLISECONDS); @@ -237,8 +243,19 @@ private static void runWithFixedTimeout(final long ms, final LongConsumer onRema } } - public void resetToDefaultMaxTime() { - this.maxTimeSupplier = null; + /** + * Creates a new {@link TimeoutContext} with the same settings, but with the + * {@link TimeoutSettings#getMaxAwaitTimeMS()} as the maxTimeMS override which will be used + * in {@link #runMaxTimeMS(LongConsumer)}. + */ + public TimeoutContext withMaxTimeAsMaxAwaitTimeOverride() { + return new TimeoutContext( + isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + timeoutSettings::getMaxAwaitTimeMS, + timeout); } /** @@ -253,26 +270,77 @@ public void resetToDefaultMaxTime() { * If remaining CSOT timeout is less than this static timeout, then CSOT timeout will be used. * */ - public void setMaxTimeOverride(final long maxTimeMS) { - this.maxTimeSupplier = () -> maxTimeMS; + public TimeoutContext withMaxTimeOverride(final long maxTimeMS) { + return new TimeoutContext( + isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + () -> maxTimeMS, + timeout); + } + + /** + * Creates {@link TimeoutContext} with the default maxTimeMS behaviour in {@link #runMaxTimeMS(LongConsumer)}: + * - if timeoutMS is set, the remaining timeoutMS will be used as the maxTimeMS. + * - if timeoutMS is not set, the {@link TimeoutSettings#getMaxTimeMS()} will be used. + */ + public TimeoutContext withDefaultMaxTime() { + return new TimeoutContext( + isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + null, + timeout); } /** * Disable the maxTimeMS override. This way the maxTimeMS will not * be appended to the command in the {@link CommandMessage}. */ - public void disableMaxTimeOverride() { - this.maxTimeSupplier = () -> 0; + public TimeoutContext withDisabledMaxTimeOverride() { + return new TimeoutContext( + isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + () -> 0, + timeout); } /** * The override will be provided as the remaining value in * {@link #runMaxTimeMS}, where 0 is ignored. */ - public void setMaxTimeOverrideToMaxCommitTime() { - this.maxTimeSupplier = () -> getMaxCommitTimeMS(); + public TimeoutContext withMaxTimeOverrideAsMaxCommitTime() { + return new TimeoutContext( + isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + () -> getMaxCommitTimeMS(), + timeout); + } + + + /** + * Creates {@link TimeoutContext} with the recent min round trip time. + * + * @param minRoundTripTimeMS the min round trip time + * @return this + */ + public TimeoutContext withMinRoundTripTime(final long minRoundTripTimeMS) { + return new TimeoutContext( + isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + maxTimeSupplier, + timeout); } + @VisibleForTesting(otherwise = PRIVATE) public long getMaxCommitTimeMS() { Long maxCommitTimeMS = timeoutSettings.getMaxCommitTimeMS(); @@ -295,28 +363,45 @@ public int getConnectTimeoutMs() { () -> throwMongoTimeoutException("The operation exceeded the timeout limit."))); } - /** - * @see #hasTimeoutMS() - */ - @Deprecated // TODO-JAVA-5640 REMOVE method - public void resetTimeoutIfPresent() { - if (hasTimeoutMS()) { - timeout = startTimeout(timeoutSettings.getTimeoutMS()); - } - } - /** * Resets the timeout if this timeout context is being used by pool maintenance */ - @Deprecated // TODO-JAVA-5640 REMOVE method - public void resetMaintenanceTimeout() { + public TimeoutContext withNewlyStartedTimeoutMaintenanceTimeout() { if (!isMaintenanceContext) { - return; + return this; } - timeout = Timeout.nullAsInfinite(timeout).call(NANOSECONDS, - () -> timeout, - (ms) -> startTimeout(timeoutSettings.getTimeoutMS()), - () -> startTimeout(timeoutSettings.getTimeoutMS())); + + return new TimeoutContext( + true, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + maxTimeSupplier); + } + + + public TimeoutContext withMinRoundTripTimeMS(final long minRoundTripTimeMS) { + isTrue("'minRoundTripTimeMS' must be a positive number", minRoundTripTimeMS >= 0); + return new TimeoutContext(isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + maxTimeSupplier, + timeout); + } + + // Creates a copy of the timeout context that can be reset without resetting the original. + public TimeoutContext copyTimeoutContext() { + return new TimeoutContext(getTimeoutSettings(), getTimeout()); + } + + public TimeoutContext withNewlyStartedTimeout() { + return new TimeoutContext( + isMaintenanceContext, + computedServerSelectionTimeout, + minRoundTripTimeMS, + timeoutSettings, + maxTimeSupplier); } public TimeoutContext withAdditionalReadTimeout(final int additionalReadTimeout) { @@ -383,20 +468,11 @@ public static Timeout startTimeout(@Nullable final Long timeoutMS) { * @return the timeout context */ public Timeout computeServerSelectionTimeout() { - Timeout serverSelectionTimeout = StartTime.now() - .timeoutAfterOrInfiniteIfNegative(getTimeoutSettings().getServerSelectionTimeoutMS(), MILLISECONDS); - - - if (isMaintenanceContext || !hasTimeoutMS()) { - return serverSelectionTimeout; - } - - if (timeout != null && Timeout.earliest(serverSelectionTimeout, timeout) == timeout) { - return timeout; + if (hasTimeoutMS()) { + return assertNotNull(timeout); } - computedServerSelectionTimeout = serverSelectionTimeout; - return computedServerSelectionTimeout; + return StartTime.now().timeoutAfterOrInfiniteIfNegative(getTimeoutSettings().getServerSelectionTimeoutMS(), MILLISECONDS); } /** @@ -404,10 +480,16 @@ public Timeout computeServerSelectionTimeout() { * * @return a new timeout context with the cached computed server selection timeout if available or this */ - public TimeoutContext withComputedServerSelectionTimeoutContext() { - if (this.hasTimeoutMS() && computedServerSelectionTimeout != null) { - return new TimeoutContext(false, timeoutSettings, computedServerSelectionTimeout); + public TimeoutContext withComputedServerSelectionTimeoutContextNew() { + if (this.hasTimeoutMS()) { + Timeout serverSelectionTimeout = StartTime.now() + .timeoutAfterOrInfiniteIfNegative(getTimeoutSettings().getServerSelectionTimeoutMS(), MILLISECONDS); + if (isMaintenanceContext) { + return new TimeoutContext(false, timeoutSettings, serverSelectionTimeout); + } + return new TimeoutContext(false, timeoutSettings, Timeout.earliest(serverSelectionTimeout, timeout)); } + return this; } diff --git a/driver-core/src/main/com/mongodb/internal/async/SingleResultCallback.java b/driver-core/src/main/com/mongodb/internal/async/SingleResultCallback.java index de0771f4b57..11da1c97f75 100644 --- a/driver-core/src/main/com/mongodb/internal/async/SingleResultCallback.java +++ b/driver-core/src/main/com/mongodb/internal/async/SingleResultCallback.java @@ -29,7 +29,7 @@ *This class is not part of the public API and may be removed or changed at any time
*/ public interface SingleResultCallback{ - public static SingleResultCallback THEN_DO_NOTHING = (r, t) -> {}; + SingleResultCallback THEN_DO_NOTHING = (r, t) -> {}; /** * Called when the function completes. This method must not complete abruptly, see {@link AsyncCallbackFunction} for more details. diff --git a/driver-core/src/main/com/mongodb/internal/async/function/AsyncCallbackTriFunction.java b/driver-core/src/main/com/mongodb/internal/async/function/AsyncCallbackTriFunction.java new file mode 100644 index 00000000000..0df5ff8c358 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/async/function/AsyncCallbackTriFunction.java @@ -0,0 +1,40 @@ +/* + * 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.SingleResultCallback; +import com.mongodb.lang.Nullable; + +/** + * An {@linkplain AsyncCallbackFunction asynchronous callback-based function} of three parameters. + * This class is a callback-based. + * + * This class is not part of the public API and may be removed or changed at any time
+ * + * @paramThe type of the first parameter to the function. + * @param The type of the second parameter to the function. + * @param See {@link AsyncCallbackFunction} + * @see AsyncCallbackFunction + */ +@FunctionalInterface +public interface AsyncCallbackTriFunction { + /** + * @param p1 The first {@code @}{@link Nullable} argument of the asynchronous function. + * @param p2 The second {@code @}{@link Nullable} argument of the asynchronous function. + * @see AsyncCallbackFunction#apply(Object, SingleResultCallback) + */ + void apply(P1 p1, P2 p2, P3 p3, SingleResultCallback callback); +} diff --git a/driver-core/src/main/com/mongodb/internal/binding/AsyncClusterAwareReadWriteBinding.java b/driver-core/src/main/com/mongodb/internal/binding/AsyncClusterAwareReadWriteBinding.java index c66dc321513..5de9df43174 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/AsyncClusterAwareReadWriteBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/AsyncClusterAwareReadWriteBinding.java @@ -18,6 +18,7 @@ import com.mongodb.ServerAddress; import com.mongodb.internal.async.SingleResultCallback; +import com.mongodb.internal.connection.OperationContext; /** * This class is not part of the public API and may be removed or changed at any time
@@ -30,7 +31,7 @@ public interface AsyncClusterAwareReadWriteBinding extends AsyncReadWriteBinding * @param serverAddress the server address * @param callback the to be passed the connection source */ - void getConnectionSource(ServerAddress serverAddress, SingleResultCallbackcallback); + void getConnectionSource(ServerAddress serverAddress, OperationContext operationContext, SingleResultCallback callback); @Override AsyncClusterAwareReadWriteBinding retain(); diff --git a/driver-core/src/main/com/mongodb/internal/binding/AsyncClusterBinding.java b/driver-core/src/main/com/mongodb/internal/binding/AsyncClusterBinding.java index fd46261a6df..322ef374381 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/AsyncClusterBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/AsyncClusterBinding.java @@ -25,6 +25,7 @@ import com.mongodb.internal.connection.AsyncConnection; import com.mongodb.internal.connection.Cluster; import com.mongodb.internal.connection.OperationContext; +import com.mongodb.internal.connection.ReadConcernAwareNoOpSessionContext; import com.mongodb.internal.connection.Server; import com.mongodb.internal.selector.ReadPreferenceServerSelector; import com.mongodb.internal.selector.ReadPreferenceWithFallbackServerSelector; @@ -33,7 +34,6 @@ import com.mongodb.selector.ServerSelector; import static com.mongodb.assertions.Assertions.notNull; -import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * A simple ReadWriteBinding implementation that supplies write connection sources bound to a possibly different primary each time, and a @@ -44,24 +44,17 @@ public class AsyncClusterBinding extends AbstractReferenceCounted implements AsyncClusterAwareReadWriteBinding { private final Cluster cluster; private final ReadPreference readPreference; - private final ReadConcern readConcern; - private final OperationContext operationContext; /** * Creates an instance. * * @param cluster a non-null Cluster which will be used to select a server to bind to * @param readPreference a non-null ReadPreference for read operations - * @param readConcern a non-null read concern - * @param operationContext the operation context * This class is not part of the public API and may be removed or changed at any time
*/ - public AsyncClusterBinding(final Cluster cluster, final ReadPreference readPreference, final ReadConcern readConcern, - final OperationContext operationContext) { + public AsyncClusterBinding(final Cluster cluster, final ReadPreference readPreference) { this.cluster = notNull("cluster", cluster); this.readPreference = notNull("readPreference", readPreference); - this.readConcern = notNull("readConcern", readConcern); - this.operationContext = notNull("operationContext", operationContext); } @Override @@ -76,21 +69,18 @@ public ReadPreference getReadPreference() { } @Override - public OperationContext getOperationContext() { - return operationContext; - } - - @Override - public void getReadConnectionSource(final SingleResultCallbackcallback) { - getAsyncClusterBindingConnectionSource(new ReadPreferenceServerSelector(readPreference), callback); + public void getReadConnectionSource(final OperationContext operationContext, + final SingleResultCallback callback) { + getAsyncClusterBindingConnectionSource(new ReadPreferenceServerSelector(readPreference), operationContext, callback); } @Override public void getReadConnectionSource(final int minWireVersion, final ReadPreference fallbackReadPreference, + final OperationContext operationContext, final SingleResultCallback callback) { // Assume 5.0+ for load-balanced mode if (cluster.getSettings().getMode() == ClusterConnectionMode.LOAD_BALANCED) { - getReadConnectionSource(callback); + getReadConnectionSource(operationContext, callback); } else { ReadPreferenceWithFallbackServerSelector readPreferenceWithFallbackServerSelector = new ReadPreferenceWithFallbackServerSelector(readPreference, minWireVersion, fallbackReadPreference); @@ -106,16 +96,19 @@ public void getReadConnectionSource(final int minWireVersion, final ReadPreferen } @Override - public void getWriteConnectionSource(final SingleResultCallback callback) { - getAsyncClusterBindingConnectionSource(new WritableServerSelector(), callback); + public void getWriteConnectionSource(final OperationContext operationContext, + final SingleResultCallback callback) { + getAsyncClusterBindingConnectionSource(new WritableServerSelector(), operationContext, callback); } @Override - public void getConnectionSource(final ServerAddress serverAddress, final SingleResultCallback callback) { - getAsyncClusterBindingConnectionSource(new ServerAddressSelector(serverAddress), callback); + public void getConnectionSource(final ServerAddress serverAddress, final OperationContext operationContext, + final SingleResultCallback callback) { + getAsyncClusterBindingConnectionSource(new ServerAddressSelector(serverAddress), operationContext, callback); } private void getAsyncClusterBindingConnectionSource(final ServerSelector serverSelector, + final OperationContext operationContext, final SingleResultCallback callback) { cluster.selectServerAsync(serverSelector, operationContext, (result, t) -> { if (t != null) { @@ -132,12 +125,14 @@ private final class AsyncClusterBindingConnectionSource extends AbstractReferenc private final ServerDescription serverDescription; private final ReadPreference appliedReadPreference; - private AsyncClusterBindingConnectionSource(final Server server, final ServerDescription serverDescription, - final ReadPreference appliedReadPreference) { + private AsyncClusterBindingConnectionSource(final Server server, + final ServerDescription serverDescription, + final ReadPreference appliedReadPreference) { this.server = server; this.serverDescription = serverDescription; this.appliedReadPreference = appliedReadPreference; - operationContext.getTimeoutContext().minRoundTripTimeMS(NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos())); + // TODO should be calculated externaly + // operationContext.getTimeoutContext().minRoundTripTimeMS(NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos())); AsyncClusterBinding.this.retain(); } @@ -146,19 +141,18 @@ public ServerDescription getServerDescription() { return serverDescription; } - @Override - public OperationContext getOperationContext() { - return operationContext; - } - @Override public ReadPreference getReadPreference() { return appliedReadPreference; } @Override - public void getConnection(final SingleResultCallback callback) { - server.getConnectionAsync(operationContext, callback); + public void getConnection(final OperationContext operationContext, final SingleResultCallback callback) { + // The first read in a causally consistent session MUST not send afterClusterTime to the server + // (because the operationTime has not yet been determined). Therefore, we use ReadConcernAwareNoOpSessionContext to + // so that we do not advance clusterTime on ClientSession in given operationContext because it might not be yet set. + ReadConcern readConcern = operationContext.getSessionContext().getReadConcern(); + server.getConnectionAsync(operationContext.withSessionContext(new ReadConcernAwareNoOpSessionContext(readConcern)), callback); } public AsyncConnectionSource retain() { diff --git a/driver-core/src/main/com/mongodb/internal/binding/AsyncConnectionSource.java b/driver-core/src/main/com/mongodb/internal/binding/AsyncConnectionSource.java index 5d70faf598e..aae7d6b7419 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/AsyncConnectionSource.java +++ b/driver-core/src/main/com/mongodb/internal/binding/AsyncConnectionSource.java @@ -20,6 +20,7 @@ import com.mongodb.connection.ServerDescription; import com.mongodb.internal.async.SingleResultCallback; import com.mongodb.internal.connection.AsyncConnection; +import com.mongodb.internal.connection.OperationContext; /** * A source of connections to a single MongoDB server. @@ -42,12 +43,7 @@ public interface AsyncConnectionSource extends BindingContext, ReferenceCounted */ ReadPreference getReadPreference(); - /** - * Gets a connection from this source. - * - * @param callback the to be passed the connection - */ - void getConnection(SingleResultCallback callback); + void getConnection(OperationContext operationContext, SingleResultCallback callback); @Override AsyncConnectionSource retain(); diff --git a/driver-core/src/main/com/mongodb/internal/binding/AsyncReadBinding.java b/driver-core/src/main/com/mongodb/internal/binding/AsyncReadBinding.java index 633091b3efb..e6d5cdfbbcf 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/AsyncReadBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/AsyncReadBinding.java @@ -18,6 +18,7 @@ import com.mongodb.ReadPreference; import com.mongodb.internal.async.SingleResultCallback; +import com.mongodb.internal.connection.OperationContext; /** * An asynchronous factory of connection sources to servers that can be read from and that satisfy the specified read preference. @@ -35,7 +36,7 @@ public interface AsyncReadBinding extends BindingContext, ReferenceCounted { * Returns a connection source to a server that satisfies the read preference with which this instance is configured. * @param callback the to be passed the connection source */ - void getReadConnectionSource(SingleResultCallback callback); + void getReadConnectionSource(OperationContext operationContext, SingleResultCallback callback); /** * Return a connection source that satisfies the read preference with which this instance is configured, if all connected servers have @@ -48,6 +49,7 @@ public interface AsyncReadBinding extends BindingContext, ReferenceCounted { * @see com.mongodb.internal.operation.AggregateToCollectionOperation */ void getReadConnectionSource(int minWireVersion, ReadPreference fallbackReadPreference, + OperationContext operationContext, SingleResultCallback callback); @Override diff --git a/driver-core/src/main/com/mongodb/internal/binding/AsyncWriteBinding.java b/driver-core/src/main/com/mongodb/internal/binding/AsyncWriteBinding.java index 39bdf4729c2..1dba1c245e6 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/AsyncWriteBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/AsyncWriteBinding.java @@ -17,6 +17,7 @@ package com.mongodb.internal.binding; import com.mongodb.internal.async.SingleResultCallback; +import com.mongodb.internal.connection.OperationContext; /** * An asynchronous factory of connection sources to servers that can be written to, e.g, a standalone, a mongos, or a replica set primary. @@ -30,7 +31,7 @@ public interface AsyncWriteBinding extends BindingContext, ReferenceCounted { * * @param callback the to be passed the connection source */ - void getWriteConnectionSource(SingleResultCallback callback); + void getWriteConnectionSource(OperationContext operationContext, SingleResultCallback callback); @Override AsyncWriteBinding retain(); diff --git a/driver-core/src/main/com/mongodb/internal/binding/BindingContext.java b/driver-core/src/main/com/mongodb/internal/binding/BindingContext.java index c10f0fb16ac..289d9070c5e 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/BindingContext.java +++ b/driver-core/src/main/com/mongodb/internal/binding/BindingContext.java @@ -16,9 +16,6 @@ package com.mongodb.internal.binding; -import com.mongodb.internal.connection.OperationContext; - - /** * This class is not part of the public API and may be removed or changed at any time
*/ @@ -29,5 +26,5 @@ public interface BindingContext { * * @return the operation context for the binding context. */ - OperationContext getOperationContext(); + // OperationContext getOperationContext(); } diff --git a/driver-core/src/main/com/mongodb/internal/binding/ClusterAwareReadWriteBinding.java b/driver-core/src/main/com/mongodb/internal/binding/ClusterAwareReadWriteBinding.java index 8f7552341a7..b97b22c3a06 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/ClusterAwareReadWriteBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/ClusterAwareReadWriteBinding.java @@ -17,6 +17,7 @@ package com.mongodb.internal.binding; import com.mongodb.ServerAddress; +import com.mongodb.internal.connection.OperationContext; /** * This interface is not part of the public API and may be removed or changed at any time. @@ -27,5 +28,5 @@ public interface ClusterAwareReadWriteBinding extends ReadWriteBinding { * Returns a connection source to the specified server address. * @return the connection source */ - ConnectionSource getConnectionSource(ServerAddress serverAddress); + ConnectionSource getConnectionSource(ServerAddress serverAddress, OperationContext operationContext); } diff --git a/driver-core/src/main/com/mongodb/internal/binding/ClusterBinding.java b/driver-core/src/main/com/mongodb/internal/binding/ClusterBinding.java index cd3f8473bbb..d6f39d308ea 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/ClusterBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/ClusterBinding.java @@ -24,6 +24,7 @@ import com.mongodb.internal.connection.Cluster; import com.mongodb.internal.connection.Connection; import com.mongodb.internal.connection.OperationContext; +import com.mongodb.internal.connection.ReadConcernAwareNoOpSessionContext; import com.mongodb.internal.connection.Server; import com.mongodb.internal.connection.ServerTuple; import com.mongodb.internal.selector.ReadPreferenceServerSelector; @@ -32,7 +33,6 @@ import com.mongodb.internal.selector.WritableServerSelector; import static com.mongodb.assertions.Assertions.notNull; -import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * A simple ReadWriteBinding implementation that supplies write connection sources bound to a possibly different primary each time, and a @@ -43,22 +43,15 @@ public class ClusterBinding extends AbstractReferenceCounted implements ClusterAwareReadWriteBinding { private final Cluster cluster; private final ReadPreference readPreference; - private final ReadConcern readConcern; - private final OperationContext operationContext; /** * Creates an instance. * @param cluster a non-null Cluster which will be used to select a server to bind to * @param readPreference a non-null ReadPreference for read operations - * @param readConcern a non-null read concern - * @param operationContext the operation context */ - public ClusterBinding(final Cluster cluster, final ReadPreference readPreference, final ReadConcern readConcern, - final OperationContext operationContext) { + public ClusterBinding(final Cluster cluster, final ReadPreference readPreference) { this.cluster = notNull("cluster", cluster); this.readPreference = notNull("readPreference", readPreference); - this.readConcern = notNull("readConcern", readConcern); - this.operationContext = notNull("operationContext", operationContext); } @Override @@ -73,36 +66,39 @@ public ReadPreference getReadPreference() { } @Override - public OperationContext getOperationContext() { - return operationContext; + public ConnectionSource getReadConnectionSource(final OperationContext operationContext) { + return new ClusterBindingConnectionSource( + cluster.selectServer(new ReadPreferenceServerSelector(readPreference), operationContext), + readPreference); } @Override - public ConnectionSource getReadConnectionSource() { - return new ClusterBindingConnectionSource(cluster.selectServer(new ReadPreferenceServerSelector(readPreference), operationContext), readPreference); - } - - @Override - public ConnectionSource getReadConnectionSource(final int minWireVersion, final ReadPreference fallbackReadPreference) { + public ConnectionSource getReadConnectionSource(final int minWireVersion, final ReadPreference fallbackReadPreference, + final OperationContext operationContext) { // Assume 5.0+ for load-balanced mode if (cluster.getSettings().getMode() == ClusterConnectionMode.LOAD_BALANCED) { - return getReadConnectionSource(); + return getReadConnectionSource(operationContext); } else { ReadPreferenceWithFallbackServerSelector readPreferenceWithFallbackServerSelector = new ReadPreferenceWithFallbackServerSelector(readPreference, minWireVersion, fallbackReadPreference); ServerTuple serverTuple = cluster.selectServer(readPreferenceWithFallbackServerSelector, operationContext); - return new ClusterBindingConnectionSource(serverTuple, readPreferenceWithFallbackServerSelector.getAppliedReadPreference()); + return new ClusterBindingConnectionSource(serverTuple, + readPreferenceWithFallbackServerSelector.getAppliedReadPreference()); } } @Override - public ConnectionSource getWriteConnectionSource() { - return new ClusterBindingConnectionSource(cluster.selectServer(new WritableServerSelector(), operationContext), readPreference); + public ConnectionSource getWriteConnectionSource(final OperationContext operationContext) { + return new ClusterBindingConnectionSource( + cluster.selectServer(new WritableServerSelector(), operationContext), + readPreference); } @Override - public ConnectionSource getConnectionSource(final ServerAddress serverAddress) { - return new ClusterBindingConnectionSource(cluster.selectServer(new ServerAddressSelector(serverAddress), operationContext), readPreference); + public ConnectionSource getConnectionSource(final ServerAddress serverAddress, final OperationContext operationContext) { + return new ClusterBindingConnectionSource( + cluster.selectServer(new ServerAddressSelector(serverAddress), operationContext), + readPreference); } private final class ClusterBindingConnectionSource extends AbstractReferenceCounted implements ConnectionSource { @@ -114,7 +110,8 @@ private ClusterBindingConnectionSource(final ServerTuple serverTuple, final Read this.server = serverTuple.getServer(); this.serverDescription = serverTuple.getServerDescription(); this.appliedReadPreference = appliedReadPreference; - operationContext.getTimeoutContext().minRoundTripTimeMS(NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos())); + //TODO THis has to be moved outside of the consutructor to the place where getConnectionSource is called to create a new OperationContet to use further + // operationContext.getTimeoutContext().minRoundTripTimeMS(NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos())); ClusterBinding.this.retain(); } @@ -123,19 +120,18 @@ public ServerDescription getServerDescription() { return serverDescription; } - @Override - public OperationContext getOperationContext() { - return operationContext; - } - @Override public ReadPreference getReadPreference() { return appliedReadPreference; } @Override - public Connection getConnection() { - return server.getConnection(operationContext); + public Connection getConnection(final OperationContext operationContext) { + // The first read in a causally consistent session MUST not send afterClusterTime to the server + // (because the operationTime has not yet been determined). Therefore, we use ReadConcernAwareNoOpSessionContext to + // so that we do not advance clusterTime on ClientSession in given operationContext because it might not be yet set. + ReadConcern readConcern = operationContext.getSessionContext().getReadConcern(); + return server.getConnection(operationContext.withSessionContext(new ReadConcernAwareNoOpSessionContext(readConcern))); } public ConnectionSource retain() { diff --git a/driver-core/src/main/com/mongodb/internal/binding/ConnectionSource.java b/driver-core/src/main/com/mongodb/internal/binding/ConnectionSource.java index 90c8b85cf16..5ba3c562895 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/ConnectionSource.java +++ b/driver-core/src/main/com/mongodb/internal/binding/ConnectionSource.java @@ -19,19 +19,20 @@ import com.mongodb.ReadPreference; import com.mongodb.connection.ServerDescription; import com.mongodb.internal.connection.Connection; +import com.mongodb.internal.connection.OperationContext; /** * A source of connections to a single MongoDB server. * *This class is not part of the public API and may be removed or changed at any time
*/ -public interface ConnectionSource extends BindingContext, ReferenceCounted { +public interface ConnectionSource extends ReferenceCounted { ServerDescription getServerDescription(); ReadPreference getReadPreference(); - Connection getConnection(); + Connection getConnection(OperationContext operationContext); @Override ConnectionSource retain(); diff --git a/driver-core/src/main/com/mongodb/internal/binding/ReadBinding.java b/driver-core/src/main/com/mongodb/internal/binding/ReadBinding.java index ffdde848382..67c10ccbc5c 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/ReadBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/ReadBinding.java @@ -17,6 +17,7 @@ package com.mongodb.internal.binding; import com.mongodb.ReadPreference; +import com.mongodb.internal.connection.OperationContext; /** * A factory of connection sources to servers that can be read from and that satisfy the specified read preference. @@ -30,7 +31,7 @@ public interface ReadBinding extends BindingContext, ReferenceCounted { * Returns a connection source to a server that satisfies the read preference with which this instance is configured. * @return the connection source */ - ConnectionSource getReadConnectionSource(); + ConnectionSource getReadConnectionSource(OperationContext operationContext); /** * Return a connection source that satisfies the read preference with which this instance is configured, if all connected servers have @@ -42,7 +43,7 @@ public interface ReadBinding extends BindingContext, ReferenceCounted { * * @see com.mongodb.internal.operation.AggregateToCollectionOperation */ - ConnectionSource getReadConnectionSource(int minWireVersion, ReadPreference fallbackReadPreference); + ConnectionSource getReadConnectionSource(int minWireVersion, ReadPreference fallbackReadPreference, OperationContext operationContext); @Override ReadBinding retain(); diff --git a/driver-core/src/main/com/mongodb/internal/binding/SingleServerBinding.java b/driver-core/src/main/com/mongodb/internal/binding/SingleServerBinding.java index 7d7e948c344..50497ae2526 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/SingleServerBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/SingleServerBinding.java @@ -35,23 +35,21 @@ public class SingleServerBinding extends AbstractReferenceCounted implements ReadWriteBinding { private final Cluster cluster; private final ServerAddress serverAddress; - private final OperationContext operationContext; /** * Creates an instance, defaulting to {@link com.mongodb.ReadPreference#primary()} for reads. * @param cluster a non-null Cluster which will be used to select a server to bind to * @param serverAddress a non-null address of the server to bind to - * @param operationContext the operation context */ - public SingleServerBinding(final Cluster cluster, final ServerAddress serverAddress, final OperationContext operationContext) { + public SingleServerBinding(final Cluster cluster, final ServerAddress serverAddress) { this.cluster = notNull("cluster", cluster); this.serverAddress = notNull("serverAddress", serverAddress); - this.operationContext = notNull("operationContext", operationContext); } @Override - public ConnectionSource getWriteConnectionSource() { - return new SingleServerBindingConnectionSource(); + public ConnectionSource getWriteConnectionSource(final OperationContext operationContext) { + ServerTuple serverTuple = cluster.selectServer(new ServerAddressSelector(serverAddress), operationContext); + return new SingleServerBindingConnectionSource(serverTuple); } @Override @@ -60,20 +58,17 @@ public ReadPreference getReadPreference() { } @Override - public ConnectionSource getReadConnectionSource() { - return new SingleServerBindingConnectionSource(); + public ConnectionSource getReadConnectionSource(final OperationContext operationContext) { + ServerTuple serverTuple = cluster.selectServer(new ServerAddressSelector(serverAddress), operationContext); + return new SingleServerBindingConnectionSource(serverTuple); } @Override - public ConnectionSource getReadConnectionSource(final int minWireVersion, final ReadPreference fallbackReadPreference) { + public ConnectionSource getReadConnectionSource(final int minWireVersion, final ReadPreference fallbackReadPreference, + final OperationContext operationContext) { throw new UnsupportedOperationException(); } - @Override - public OperationContext getOperationContext() { - return operationContext; - } - @Override public SingleServerBinding retain() { super.retain(); @@ -83,10 +78,9 @@ public SingleServerBinding retain() { private final class SingleServerBindingConnectionSource extends AbstractReferenceCounted implements ConnectionSource { private final ServerDescription serverDescription; - private SingleServerBindingConnectionSource() { + private SingleServerBindingConnectionSource(final ServerTuple serverTuple) { SingleServerBinding.this.retain(); - ServerTuple serverTuple = cluster.selectServer(new ServerAddressSelector(serverAddress), operationContext); - serverDescription = serverTuple.getServerDescription(); + this.serverDescription = serverTuple.getServerDescription(); } @Override @@ -94,18 +88,13 @@ public ServerDescription getServerDescription() { return serverDescription; } - @Override - public OperationContext getOperationContext() { - return operationContext; - } - @Override public ReadPreference getReadPreference() { return ReadPreference.primary(); } @Override - public Connection getConnection() { + public Connection getConnection(final OperationContext operationContext) { return cluster .selectServer(new ServerAddressSelector(serverAddress), operationContext) .getServer() diff --git a/driver-core/src/main/com/mongodb/internal/binding/WriteBinding.java b/driver-core/src/main/com/mongodb/internal/binding/WriteBinding.java index b0ac674489c..beeee4c3bf2 100644 --- a/driver-core/src/main/com/mongodb/internal/binding/WriteBinding.java +++ b/driver-core/src/main/com/mongodb/internal/binding/WriteBinding.java @@ -16,6 +16,8 @@ package com.mongodb.internal.binding; +import com.mongodb.internal.connection.OperationContext; + /** * A factory of connection sources to servers that can be written to, e.g, a standalone, a mongos, or a replica set primary. * @@ -27,7 +29,7 @@ public interface WriteBinding extends BindingContext, ReferenceCounted { * * @return a connection source */ - ConnectionSource getWriteConnectionSource(); + ConnectionSource getWriteConnectionSource(OperationContext operationContext); @Override WriteBinding retain(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/CommandHelper.java b/driver-core/src/main/com/mongodb/internal/connection/CommandHelper.java index fa7c1f0739d..9670db5af2c 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/CommandHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/CommandHelper.java @@ -129,7 +129,6 @@ private static CommandMessage getCommandMessage(final String database, final Bso public static void applyMaxTimeMS(final TimeoutContext timeoutContext, final BsonDocument command) { if (!timeoutContext.hasTimeoutMS()) { command.append("maxTimeMS", new BsonInt64(timeoutContext.getTimeoutSettings().getMaxTimeMS())); - timeoutContext.disableMaxTimeOverride(); } } diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java index 0ef94d559c9..ba2ed0da1de 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java @@ -449,7 +449,7 @@ void doMaintenance() { } private boolean shouldEnsureMinSize() { - return settings.getMinSize() > 0; + return settings.getMinSize() > -1; } private boolean shouldPrune(final UsageTrackingInternalConnection connection) { diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java index c1b12f9f18a..0e9c2681e4f 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java @@ -75,8 +75,8 @@ import static com.mongodb.assertions.Assertions.assertNull; import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; -import static com.mongodb.internal.async.AsyncRunnable.beginAsync; import static com.mongodb.internal.TimeoutContext.createMongoTimeoutException; +import static com.mongodb.internal.async.AsyncRunnable.beginAsync; import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback; import static com.mongodb.internal.connection.Authenticator.shouldAuthenticate; import static com.mongodb.internal.connection.CommandHelper.HELLO; @@ -226,13 +226,15 @@ public int getGeneration() { public void open(final OperationContext originalOperationContext) { isTrue("Open already called", stream == null); stream = streamFactory.create(serverId.getAddress()); + OperationContext operationContext = originalOperationContext; try { - OperationContext operationContext = originalOperationContext - .withTimeoutContext(originalOperationContext.getTimeoutContext().withComputedServerSelectionTimeoutContext()); - + //COMMENT given that we already use serverSelection timeout in SyncOperationHelper, this step is not needed. +// OperationContext operationContext = originalOperationContext +// .withTimeoutContext(originalOperationContext.getTimeoutContext().withComputedServerSelectionTimeoutContext()); stream.open(operationContext); - InternalConnectionInitializationDescription initializationDescription = connectionInitializer.startHandshake(this, operationContext); + + operationContext = operationContext.withTimeoutContextOverride(TimeoutContext::withNewlyStartedTimeoutMaintenanceTimeout); initAfterHandshakeStart(initializationDescription); initializationDescription = connectionInitializer.finishHandshake(this, initializationDescription, operationContext); @@ -250,9 +252,11 @@ public void open(final OperationContext originalOperationContext) { @Override public void openAsync(final OperationContext originalOperationContext, final SingleResultCallbackcallback) { assertNull(stream); + OperationContext operationContext = originalOperationContext; try { - OperationContext operationContext = originalOperationContext - .withTimeoutContext(originalOperationContext.getTimeoutContext().withComputedServerSelectionTimeoutContext()); + //COMMENT given that we already use serverSelection timeout in SyncOperationHelper, this step is not needed. +// OperationContext operationContext = originalOperationContext +// .withTimeoutContext(originalOperationContext.getTimeoutContext().withComputedServerSelectionTimeoutContext()); stream = streamFactory.create(serverId.getAddress()); stream.openAsync(operationContext, new AsyncCompletionHandler () { @@ -268,7 +272,8 @@ public void completed(@Nullable final Void aVoid) { assertNotNull(initialResult); initAfterHandshakeStart(initialResult); connectionInitializer.finishHandshakeAsync(InternalStreamConnection.this, - initialResult, operationContext, (completedResult, completedException) -> { + initialResult, operationContext.withTimeoutContextOverride(TimeoutContext::withNewlyStartedTimeoutMaintenanceTimeout), + (completedResult, completedException) -> { if (completedException != null) { close(); callback.onResult(null, completedException); diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java index 79c21f33356..574a85669d0 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java @@ -144,8 +144,6 @@ private InternalConnectionInitializationDescription initializeConnectionDescript helloResult = executeCommand("admin", helloCommandDocument, clusterConnectionMode, serverApi, internalConnection, operationContext); } catch (MongoException e) { throw mapHelloException(e); - } finally { - operationContext.getTimeoutContext().resetMaintenanceTimeout(); } setSpeculativeAuthenticateResponse(helloResult); return createInitializationDescription(helloResult, internalConnection, start); diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java index 5e6adeff530..14723559f2b 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java +++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java @@ -15,6 +15,7 @@ */ package com.mongodb.internal.connection; +import com.mongodb.Function; import com.mongodb.MongoConnectionPoolClearedException; import com.mongodb.RequestContext; import com.mongodb.ServerAddress; @@ -33,6 +34,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import static java.util.stream.Collectors.toList; @@ -142,6 +144,14 @@ public OperationContext withNewlyStartedTimeout() { return this.withTimeoutContext(tc); } + public OperationContext withMinRoundTripTime(final ServerDescription serverDescription) { + return this.withTimeoutContext(this.timeoutContext.withMinRoundTripTime(TimeUnit.NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos()))); + } + + public OperationContext withTimeoutContextOverride(final Function timeoutContextOverrideFunction) { + return this.withTimeoutContext(timeoutContextOverrideFunction.apply(timeoutContext)); + } + public static final class ServerDeprioritization { @Nullable private ServerAddress candidate; diff --git a/driver-core/src/main/com/mongodb/internal/connection/SaslAuthenticator.java b/driver-core/src/main/com/mongodb/internal/connection/SaslAuthenticator.java index eeee3a31abd..059f810c611 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/SaslAuthenticator.java +++ b/driver-core/src/main/com/mongodb/internal/connection/SaslAuthenticator.java @@ -27,6 +27,7 @@ import com.mongodb.SubjectProvider; import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.connection.ConnectionDescription; +import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.async.SingleResultCallback; import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; @@ -63,12 +64,18 @@ abstract class SaslAuthenticator extends Authenticator implements SpeculativeAut } public void authenticate(final InternalConnection connection, final ConnectionDescription connectionDescription, - final OperationContext operationContext) { + final OperationContext originalOperationContext) { doAsSubject(() -> { + OperationContext operationContext = originalOperationContext; SaslClient saslClient = createSaslClient(connection.getDescription().getServerAddress(), operationContext); throwIfSaslClientIsNull(saslClient); try { - BsonDocument responseDocument = getNextSaslResponse(saslClient, connection, operationContext); + BsonDocument responseDocument = connection.opened() ? null : getSpeculativeAuthenticateResponse(); + if (responseDocument == null) { + responseDocument = getNextSaslResponse(saslClient, connection, operationContext); + operationContext = operationContext.withTimeoutContextOverride(TimeoutContext::withNewlyStartedTimeoutMaintenanceTimeout); + } + BsonInt32 conversationId = responseDocument.getInt32("conversationId"); while (!(responseDocument.getBoolean("done")).getValue()) { @@ -81,7 +88,7 @@ public void authenticate(final InternalConnection connection, final ConnectionDe } responseDocument = sendSaslContinue(conversationId, response, connection, operationContext); - operationContext.getTimeoutContext().resetMaintenanceTimeout(); + operationContext = operationContext.withTimeoutContextOverride(TimeoutContext::withNewlyStartedTimeoutMaintenanceTimeout); } if (!saslClient.isComplete()) { saslClient.evaluateChallenge((responseDocument.getBinary("payload")).getData()); @@ -117,6 +124,9 @@ void authenticateAsync(final InternalConnection connection, final ConnectionDesc public abstract String getMechanismName(); + /** + * Does not send any commands to the server + */ protected abstract SaslClient createSaslClient(ServerAddress serverAddress, OperationContext operationContext); protected void appendSaslStartOptions(final BsonDocument saslStartCommand) { @@ -131,11 +141,6 @@ private void throwIfSaslClientIsNull(@Nullable final SaslClient saslClient) { private BsonDocument getNextSaslResponse(final SaslClient saslClient, final InternalConnection connection, final OperationContext operationContext) { - BsonDocument response = connection.opened() ? null : getSpeculativeAuthenticateResponse(); - if (response != null) { - return response; - } - try { byte[] serverResponse = saslClient.hasInitialResponse() ? saslClient.evaluateChallenge(new byte[0]) : null; return sendSaslStart(serverResponse, connection, operationContext); @@ -160,7 +165,9 @@ private void getNextSaslResponseAsync(final SaslClient saslClient, final Interna if (result.getBoolean("done").getValue()) { verifySaslClientComplete(saslClient, result, errHandlingCallback); } else { - new Continuator(saslClient, result, connection, operationContext, errHandlingCallback).start(); + OperationContext saslContinueOperationContext = + operationContext.withTimeoutContextOverride(TimeoutContext::withNewlyStartedTimeoutMaintenanceTimeout); + new Continuator(saslClient, result, connection, saslContinueOperationContext, errHandlingCallback).start(); } }); } else if (response.getBoolean("done").getValue()) { @@ -232,22 +239,14 @@ private BsonDocument sendSaslStart(@Nullable final byte[] outToken, final Intern final OperationContext operationContext) { BsonDocument startDocument = createSaslStartCommandDocument(outToken); appendSaslStartOptions(startDocument); - try { return executeCommand(getMongoCredential().getSource(), startDocument, getClusterConnectionMode(), getServerApi(), connection, operationContext); - } finally { - operationContext.getTimeoutContext().resetMaintenanceTimeout(); - } } private BsonDocument sendSaslContinue(final BsonInt32 conversationId, final byte[] outToken, final InternalConnection connection, final OperationContext operationContext) { - try { return executeCommand(getMongoCredential().getSource(), createSaslContinueDocument(conversationId, outToken), getClusterConnectionMode(), getServerApi(), connection, operationContext); - } finally { - operationContext.getTimeoutContext().resetMaintenanceTimeout(); - } } private void sendSaslStartAsync(@Nullable final byte[] outToken, final InternalConnection connection, @@ -256,19 +255,13 @@ private void sendSaslStartAsync(@Nullable final byte[] outToken, final InternalC appendSaslStartOptions(startDocument); executeCommandAsync(getMongoCredential().getSource(), startDocument, getClusterConnectionMode(), getServerApi(), connection, - operationContext, (r, t) -> { - operationContext.getTimeoutContext().resetMaintenanceTimeout(); - callback.onResult(r, t); - }); + operationContext, callback::onResult); } private void sendSaslContinueAsync(final BsonInt32 conversationId, final byte[] outToken, final InternalConnection connection, final OperationContext operationContext, final SingleResultCallback callback) { executeCommandAsync(getMongoCredential().getSource(), createSaslContinueDocument(conversationId, outToken), - getClusterConnectionMode(), getServerApi(), connection, operationContext, (r, t) -> { - operationContext.getTimeoutContext().resetMaintenanceTimeout(); - callback.onResult(r, t); - }); + getClusterConnectionMode(), getServerApi(), connection, operationContext, callback::onResult); } protected BsonDocument createSaslStartCommandDocument(@Nullable final byte[] outToken) { @@ -323,7 +316,7 @@ private final class Continuator implements SingleResultCallback { private final SaslClient saslClient; private final BsonDocument saslStartDocument; private final InternalConnection connection; - private final OperationContext operationContext; + private OperationContext operationContext; private final SingleResultCallback callback; Continuator(final SaslClient saslClient, final BsonDocument saslStartDocument, final InternalConnection connection, @@ -347,6 +340,7 @@ public void onResult(@Nullable final BsonDocument result, @Nullable final Throwa verifySaslClientComplete(saslClient, result, callback); disposeOfSaslClient(saslClient); } else { + operationContext = operationContext.withTimeoutContextOverride(TimeoutContext::withNewlyStartedTimeoutMaintenanceTimeout); continueConversation(result); } } diff --git a/driver-core/src/main/com/mongodb/internal/operation/AbortTransactionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/AbortTransactionOperation.java index bbd7ce7300e..f440fc4f293 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AbortTransactionOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AbortTransactionOperation.java @@ -50,9 +50,10 @@ protected String getCommandName() { @Override CommandCreator getCommandCreator() { return (operationContext, serverDescription, connectionDescription) -> { - operationContext.getTimeoutContext().resetToDefaultMaxTime(); BsonDocument command = AbortTransactionOperation.super.getCommandCreator() - .create(operationContext, serverDescription, connectionDescription); + .create(operationContext.withTimeoutContextOverride(TimeoutContext::withDefaultMaxTime), + serverDescription, + connectionDescription); putIfNotNull(command, "recoveryToken", recoveryToken); return command; }; diff --git a/driver-core/src/main/com/mongodb/internal/operation/AbstractWriteSearchIndexOperation.java b/driver-core/src/main/com/mongodb/internal/operation/AbstractWriteSearchIndexOperation.java index 8410a030185..e976a1f0de7 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AbstractWriteSearchIndexOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AbstractWriteSearchIndexOperation.java @@ -22,6 +22,7 @@ import com.mongodb.internal.async.SingleResultCallback; import com.mongodb.internal.binding.AsyncWriteBinding; import com.mongodb.internal.binding.WriteBinding; +import com.mongodb.internal.connection.OperationContext; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; @@ -45,12 +46,12 @@ abstract class AbstractWriteSearchIndexOperation implements AsyncWriteOperation< } @Override - public Void execute(final WriteBinding binding) { - return withConnection(binding, connection -> { + public Void execute(final WriteBinding binding, final OperationContext operationContext) { + return withConnection(binding, operationContext, (connection, operationContextWithMinRtt) -> { try { - executeCommand(binding, namespace.getDatabaseName(), buildCommand(), + executeCommand(binding, operationContextWithMinRtt, namespace.getDatabaseName(), buildCommand(), connection, - writeConcernErrorTransformer(binding.getOperationContext().getTimeoutContext())); + writeConcernErrorTransformer(operationContextWithMinRtt.getTimeoutContext())); } catch (MongoCommandException mongoCommandException) { swallowOrThrow(mongoCommandException); } @@ -59,20 +60,21 @@ public Void execute(final WriteBinding binding) { } @Override - public void executeAsync(final AsyncWriteBinding binding, final SingleResultCallback callback) { - withAsyncSourceAndConnection(binding::getWriteConnectionSource, false, callback, - (connectionSource, connection, cb) -> - executeCommandAsync(binding, namespace.getDatabaseName(), buildCommand(), connection, - writeConcernErrorTransformerAsync(binding.getOperationContext().getTimeoutContext()), (result, commandExecutionError) -> { + public void executeAsync(final AsyncWriteBinding binding, final OperationContext operationContext, final SingleResultCallback callback) { + withAsyncSourceAndConnection(binding::getWriteConnectionSource, false, operationContext, callback, + (connectionSource, connection, operationContextWithMinRtt, cb) -> + executeCommandAsync(binding, operationContextWithMinRtt, namespace.getDatabaseName(), buildCommand(), connection, + writeConcernErrorTransformerAsync(operationContextWithMinRtt.getTimeoutContext()), (result, commandExecutionError) -> { try { swallowOrThrow(commandExecutionError); + //TODO why call callback and not cb? callback.onResult(result, null); } catch (Throwable mongoCommandException) { + //TODO why call callback and not cb? callback.onResult(null, mongoCommandException); } } - ) - ); + )); } /** diff --git a/driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java b/driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java index 1f25bc87bf9..aea1a9c3610 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java @@ -25,6 +25,7 @@ import com.mongodb.internal.binding.AsyncReadBinding; import com.mongodb.internal.binding.ReadBinding; import com.mongodb.internal.client.model.AggregationLevel; +import com.mongodb.internal.connection.OperationContext; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.BsonValue; @@ -136,13 +137,13 @@ public AggregateOperation timeoutMode(@Nullable final TimeoutMode timeoutMode } @Override - public BatchCursor execute(final ReadBinding binding) { - return wrapped.execute(binding); + public BatchCursor execute(final ReadBinding binding, final OperationContext operationContext) { + return wrapped.execute(binding, operationContext); } @Override - public void executeAsync(final AsyncReadBinding binding, final SingleResultCallback > callback) { - wrapped.executeAsync(binding, callback); + public void executeAsync(final AsyncReadBinding binding, final OperationContext operationContext, final SingleResultCallback > callback) { + wrapped.executeAsync(binding, operationContext, callback); } public ReadOperation asExplainableOperation(@Nullable final ExplainVerbosity verbosity, final Decoder resultDecoder) { @@ -155,7 +156,7 @@ public AsyncReadOperation asAsyncExplainableOperation(@Nullable final Exp } CommandReadOperation createExplainableOperation(@Nullable final ExplainVerbosity verbosity, final Decoder resultDecoder) { - return new CommandReadOperation<>(getNamespace().getDatabaseName(), + return new ExplainCommandOperation<>(getNamespace().getDatabaseName(), (operationContext, serverDescription, connectionDescription) -> { BsonDocument command = wrapped.getCommand(operationContext, UNKNOWN_WIRE_VERSION); applyMaxTimeMS(operationContext.getTimeoutContext(), command); diff --git a/driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java b/driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java index 7ba2c56b874..d9c83667b00 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java @@ -47,7 +47,7 @@ import static com.mongodb.internal.operation.AsyncOperationHelper.executeRetryableReadAsync; import static com.mongodb.internal.operation.CommandOperationHelper.CommandCreator; import static com.mongodb.internal.operation.OperationHelper.LOGGER; -import static com.mongodb.internal.operation.OperationHelper.setNonTailableCursorMaxTimeSupplier; +import static com.mongodb.internal.operation.OperationHelper.applyTimeoutModeToOperationContext; import static com.mongodb.internal.operation.OperationReadConcernHelper.appendReadConcernToCommand; import static com.mongodb.internal.operation.SyncOperationHelper.CommandReadTransformer; import static com.mongodb.internal.operation.SyncOperationHelper.executeRetryableRead; @@ -186,16 +186,16 @@ AggregateOperationImpl hint(@Nullable final BsonValue hint) { } @Override - public BatchCursor execute(final ReadBinding binding) { - return executeRetryableRead(binding, namespace.getDatabaseName(), + public BatchCursor execute(final ReadBinding binding, final OperationContext operationContext) { + return executeRetryableRead(binding, applyTimeoutModeToOperationContext(timeoutMode, operationContext), namespace.getDatabaseName(), getCommandCreator(), CommandResultDocumentCodec.create(decoder, FIELD_NAMES_WITH_RESULT), transformer(), retryReads); } @Override - public void executeAsync(final AsyncReadBinding binding, final SingleResultCallback > callback) { + public void executeAsync(final AsyncReadBinding binding, final OperationContext operationContext, final SingleResultCallback > callback) { SingleResultCallback > errHandlingCallback = errorHandlingCallback(callback, LOGGER); - executeRetryableReadAsync(binding, namespace.getDatabaseName(), + executeRetryableReadAsync(binding, applyTimeoutModeToOperationContext(timeoutMode, operationContext), namespace.getDatabaseName(), getCommandCreator(), CommandResultDocumentCodec.create(decoder, FIELD_NAMES_WITH_RESULT), asyncTransformer(), retryReads, errHandlingCallback); @@ -210,7 +210,6 @@ BsonDocument getCommand(final OperationContext operationContext, final int maxWi BsonDocument commandDocument = new BsonDocument("aggregate", aggregateTarget.create()); appendReadConcernToCommand(operationContext.getSessionContext(), maxWireVersion, commandDocument); commandDocument.put("pipeline", pipelineCreator.create()); - setNonTailableCursorMaxTimeSupplier(timeoutMode, operationContext); BsonDocument cursor = new BsonDocument(); if (batchSize != null) { cursor.put("batchSize", new BsonInt32(batchSize)); @@ -236,15 +235,19 @@ BsonDocument getCommand(final OperationContext operationContext, final int maxWi } private CommandReadTransformer > transformer() { - return (result, source, connection) -> - new CommandBatchCursor<>(getTimeoutMode(), result, batchSize != null ? batchSize : 0, - getMaxTimeForCursor(source.getOperationContext().getTimeoutContext()), decoder, comment, source, connection); + return (result, source, connection, operationContext) -> + new CommandBatchCursor<>(getTimeoutMode(), getMaxTimeForCursor(operationContext.getTimeoutContext()), operationContext, new CommandCoreCursor<>( + result, batchSize != null ? batchSize : 0, + decoder, comment, source, connection + )); } private CommandReadTransformerAsync > asyncTransformer() { - return (result, source, connection) -> - new AsyncCommandBatchCursor<>(getTimeoutMode(), result, batchSize != null ? batchSize : 0, - getMaxTimeForCursor(source.getOperationContext().getTimeoutContext()), decoder, comment, source, connection); + return (result, source, connection, operationContext) -> + new AsyncCommandBatchCursor<>(getTimeoutMode(), getMaxTimeForCursor(operationContext.getTimeoutContext()), + operationContext, new AsyncCommandCoreCursor<>( + result, batchSize != null ? batchSize : 0, decoder, comment, source, connection + )); } private TimeoutMode getTimeoutMode() { diff --git a/driver-core/src/main/com/mongodb/internal/operation/AggregateToCollectionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/AggregateToCollectionOperation.java index 904f85042ac..ce429fdd5b0 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AggregateToCollectionOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AggregateToCollectionOperation.java @@ -26,6 +26,7 @@ import com.mongodb.internal.binding.AsyncReadBinding; import com.mongodb.internal.binding.ReadBinding; import com.mongodb.internal.client.model.AggregationLevel; +import com.mongodb.internal.connection.OperationContext; import com.mongodb.lang.Nullable; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -39,8 +40,10 @@ import static com.mongodb.assertions.Assertions.isTrueArgument; import static com.mongodb.assertions.Assertions.notNull; +import static com.mongodb.internal.operation.AsyncOperationHelper.CommandReadTransformerAsync; import static com.mongodb.internal.operation.AsyncOperationHelper.executeRetryableReadAsync; import static com.mongodb.internal.operation.ServerVersionHelper.FIVE_DOT_ZERO_WIRE_VERSION; +import static com.mongodb.internal.operation.SyncOperationHelper.CommandReadTransformer; import static com.mongodb.internal.operation.SyncOperationHelper.executeRetryableRead; import static com.mongodb.internal.operation.WriteConcernHelper.appendWriteConcernToCommand; import static com.mongodb.internal.operation.WriteConcernHelper.throwOnWriteConcernError; @@ -152,30 +155,33 @@ public AggregateToCollectionOperation timeoutMode(@Nullable final TimeoutMode ti } @Override - public Void execute(final ReadBinding binding) { - return executeRetryableRead(binding, - () -> binding.getReadConnectionSource(FIVE_DOT_ZERO_WIRE_VERSION, ReadPreference.primary()), - namespace.getDatabaseName(), - getCommandCreator(), - new BsonDocumentCodec(), (result, source, connection) -> { - throwOnWriteConcernError(result, connection.getDescription().getServerAddress(), - connection.getDescription().getMaxWireVersion(), binding.getOperationContext().getTimeoutContext()); - return null; - }, false); + public Void execute(final ReadBinding binding, final OperationContext operationContext) { + return executeRetryableRead( + operationContext, + (serverSelectionOperationContext) -> + binding.getReadConnectionSource( + FIVE_DOT_ZERO_WIRE_VERSION, + ReadPreference.primary(), + serverSelectionOperationContext), + namespace.getDatabaseName(), + getCommandCreator(), + new BsonDocumentCodec(), transformer(), false); } @Override - public void executeAsync(final AsyncReadBinding binding, final SingleResultCallback callback) { - executeRetryableReadAsync(binding, - (connectionSourceCallback) -> - binding.getReadConnectionSource(FIVE_DOT_ZERO_WIRE_VERSION, ReadPreference.primary(), connectionSourceCallback), - namespace.getDatabaseName(), - getCommandCreator(), - new BsonDocumentCodec(), (result, source, connection) -> { - throwOnWriteConcernError(result, connection.getDescription().getServerAddress(), - connection.getDescription().getMaxWireVersion(), binding.getOperationContext().getTimeoutContext()); - return null; - }, false, callback); + public void executeAsync(final AsyncReadBinding binding, final OperationContext operationContext, + final SingleResultCallback callback) { + executeRetryableReadAsync( + binding, + operationContext, + (serverSelectionOperationContext, connectionSourceCallback) -> + binding.getReadConnectionSource(FIVE_DOT_ZERO_WIRE_VERSION, ReadPreference.primary(), serverSelectionOperationContext, connectionSourceCallback), + namespace.getDatabaseName(), + getCommandCreator(), + new BsonDocumentCodec(), + asyncTransformer(), + false, + callback); } private CommandOperationHelper.CommandCreator getCommandCreator() { @@ -214,4 +220,20 @@ private CommandOperationHelper.CommandCreator getCommandCreator() { return commandDocument; }; } + + private static CommandReadTransformer transformer() { + return (result, source, connection, operationContext) -> { + throwOnWriteConcernError(result, connection.getDescription().getServerAddress(), + connection.getDescription().getMaxWireVersion(), operationContext.getTimeoutContext()); + return null; + }; + } + + private static CommandReadTransformerAsync asyncTransformer() { + return (result, source, connection, operationContext) -> { + throwOnWriteConcernError(result, connection.getDescription().getServerAddress(), + connection.getDescription().getMaxWireVersion(), operationContext.getTimeoutContext()); + return null; + }; + } } diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java index a4cfbafedb6..78b09c36539 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java @@ -1,3 +1,5 @@ +package com.mongodb.internal.operation; + /* * Copyright 2008-present MongoDB, Inc. * @@ -14,14 +16,13 @@ * limitations under the License. */ -package com.mongodb.internal.operation; import com.mongodb.MongoException; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.async.AsyncAggregateResponseBatchCursor; -import com.mongodb.internal.async.AsyncBatchCursor; import com.mongodb.internal.async.SingleResultCallback; import com.mongodb.internal.binding.AsyncReadBinding; +import com.mongodb.internal.connection.OperationContext; import com.mongodb.lang.NonNull; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; @@ -43,7 +44,7 @@ final class AsyncChangeStreamBatchCursor implements AsyncAggregateResponseBatchCursor { private final AsyncReadBinding binding; - private final TimeoutContext timeoutContext; + private OperationContext operationContext; private final ChangeStreamOperation changeStreamOperation; private final int maxWireVersion; @@ -53,40 +54,41 @@ final class AsyncChangeStreamBatchCursor implements AsyncAggregateResponseBat * {@code wrapped} containing {@code null} and {@link #isClosed} being {@code false}. * This represents a situation in which the wrapped object was closed by {@code this} but {@code this} remained open. */ - private final AtomicReference > wrapped; + private final AtomicReference > wrapped; private final AtomicBoolean isClosed; AsyncChangeStreamBatchCursor(final ChangeStreamOperation changeStreamOperation, - final AsyncCommandBatchCursor wrapped, + final AsyncCoreCursor wrapped, final AsyncReadBinding binding, + final OperationContext operationContext, @Nullable final BsonDocument resumeToken, final int maxWireVersion) { this.changeStreamOperation = changeStreamOperation; this.wrapped = new AtomicReference<>(assertNotNull(wrapped)); this.binding = binding; binding.retain(); - this.timeoutContext = binding.getOperationContext().getTimeoutContext(); + this.operationContext = operationContext.withTimeoutContextOverride(TimeoutContext::withMaxTimeAsMaxAwaitTimeOverride); this.resumeToken = resumeToken; this.maxWireVersion = maxWireVersion; isClosed = new AtomicBoolean(); } @NonNull - AsyncCommandBatchCursor getWrapped() { + AsyncCoreCursor getWrapped() { return assertNotNull(wrapped.get()); } @Override public void next(final SingleResultCallback > callback) { - resumeableOperation(AsyncBatchCursor::next, callback, false); + OperationContext operationContext = resetTimeout(); + resumeableOperation(AsyncCoreCursor::next, callback, operationContext, false); } @Override public void close() { - timeoutContext.resetTimeoutIfPresent(); if (isClosed.compareAndSet(false, true)) { try { - nullifyAndCloseWrapped(); + nullifyAndCloseWrapped(operationContext.withNewlyStartedTimeout()); } finally { binding.release(); } @@ -116,7 +118,7 @@ public boolean isClosed() { } private boolean wrappedClosedItself() { - AsyncAggregateResponseBatchCursor
observedWrapped = wrapped.get(); + AsyncCoreCursor observedWrapped = wrapped.get(); return observedWrapped != null && observedWrapped.isClosed(); } @@ -125,10 +127,10 @@ private boolean wrappedClosedItself() { * if {@link #wrappedClosedItself()} observes a {@linkplain AsyncAggregateResponseBatchCursor#isClosed() closed} wrapped object, * then it closed itself as opposed to being closed by {@code this}. */ - private void nullifyAndCloseWrapped() { - AsyncAggregateResponseBatchCursor observedWrapped = wrapped.getAndSet(null); + private void nullifyAndCloseWrapped(final OperationContext operationContext) { + AsyncCoreCursor observedWrapped = wrapped.getAndSet(null); if (observedWrapped != null) { - observedWrapped.close(); + observedWrapped.close(operationContext); } } @@ -137,14 +139,14 @@ private void nullifyAndCloseWrapped() { * {@code setWrappedOrCloseIt(AsyncCommandBatchCursor)} is called concurrently with or after (in the happens-before order) * the method {@link #close()}. */ - private void setWrappedOrCloseIt(final AsyncCommandBatchCursor newValue) { + private void setWrappedOrCloseIt(final AsyncCoreCursor newValue, final OperationContext operationContext) { if (isClosed()) { assertNull(wrapped.get()); - newValue.close(); + newValue.close(operationContext); } else { assertNull(wrapped.getAndSet(newValue)); if (isClosed()) { - nullifyAndCloseWrapped(); + nullifyAndCloseWrapped(operationContext); } } } @@ -169,7 +171,7 @@ public int getMaxWireVersion() { return maxWireVersion; } - private void cachePostBatchResumeToken(final AsyncCommandBatchCursor cursor) { + private void cachePostBatchResumeToken(final AsyncCoreCursor cursor) { BsonDocument resumeToken = cursor.getPostBatchResumeToken(); if (resumeToken != null) { this.resumeToken = resumeToken; @@ -177,19 +179,23 @@ private void cachePostBatchResumeToken(final AsyncCommandBatchCursor cursor, SingleResultCallback > callback); + void apply(AsyncCoreCursor
cursor, OperationContext operationContext, + SingleResultCallback > callback); } - private void resumeableOperation(final AsyncBlock asyncBlock, final SingleResultCallback
> callback, final boolean tryNext) { - timeoutContext.resetTimeoutIfPresent(); + private void resumeableOperation(final AsyncBlock asyncBlock, + final SingleResultCallback
> callback, + final OperationContext operationContext, + final boolean tryNext) { + //timeoutContext.resetTimeoutIfPresent(); //FIXme it was a bug, we reset timeout on retry which is against the spec. Moved to next() method. SingleResultCallback
> errHandlingCallback = errorHandlingCallback(callback, LOGGER); if (isClosed()) { errHandlingCallback.onResult(null, new MongoException(format("%s called after the cursor was closed.", tryNext ? "tryNext()" : "next()"))); return; } - AsyncCommandBatchCursor
wrappedCursor = getWrapped(); - asyncBlock.apply(wrappedCursor, (result, t) -> { + AsyncCoreCursor wrappedCursor = getWrapped(); + asyncBlock.apply(wrappedCursor, operationContext, (result, t) -> { if (t == null) { try { List convertedResults; @@ -206,8 +212,8 @@ private void resumeableOperation(final AsyncBlock asyncBlock, final SingleResult } else { cachePostBatchResumeToken(wrappedCursor); if (isResumableError(t, maxWireVersion)) { - nullifyAndCloseWrapped(); - retryOperation(asyncBlock, errHandlingCallback, tryNext); + nullifyAndCloseWrapped(operationContext); + retryOperation(asyncBlock, errHandlingCallback, operationContext, tryNext); } else { errHandlingCallback.onResult(null, t); } @@ -215,26 +221,29 @@ private void resumeableOperation(final AsyncBlock asyncBlock, final SingleResult }); } - private void retryOperation(final AsyncBlock asyncBlock, final SingleResultCallback > callback, + private void retryOperation(final AsyncBlock asyncBlock, + final SingleResultCallback
> callback, + final OperationContext operationContext, final boolean tryNext) { - withAsyncReadConnectionSource(binding, (source, t) -> { + withAsyncReadConnectionSource(binding, operationContext, (source, t) -> { if (t != null) { callback.onResult(null, t); } else { changeStreamOperation.setChangeStreamOptionsForResume(resumeToken, assertNotNull(source).getServerDescription().getMaxWireVersion()); source.release(); - changeStreamOperation.executeAsync(binding, (asyncBatchCursor, t1) -> { + changeStreamOperation.executeAsync(binding, operationContext, (asyncBatchCursor, t1) -> { if (t1 != null) { callback.onResult(null, t1); } else { try { - setWrappedOrCloseIt(assertNotNull((AsyncChangeStreamBatchCursor
) asyncBatchCursor).getWrapped()); + setWrappedOrCloseIt(assertNotNull((AsyncChangeStreamBatchCursor ) asyncBatchCursor).getWrapped(), + operationContext); } finally { try { binding.release(); // release the new change stream batch cursor's reference to the binding } finally { - resumeableOperation(asyncBlock, callback, tryNext); + resumeableOperation(asyncBlock, callback, operationContext, tryNext); } } } @@ -242,4 +251,10 @@ private void retryOperation(final AsyncBlock asyncBlock, final SingleResultCallb } }); } + + private OperationContext resetTimeout() { + operationContext = operationContext.withNewlyStartedTimeout(); + return operationContext; + } } + diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncCommandBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncCommandBatchCursor.java index a391458ce6f..036d91e1530 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AsyncCommandBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncCommandBatchCursor.java @@ -16,367 +16,94 @@ package com.mongodb.internal.operation; -import com.mongodb.MongoCommandException; -import com.mongodb.MongoException; -import com.mongodb.MongoNamespace; -import com.mongodb.MongoOperationTimeoutException; -import com.mongodb.MongoSocketException; -import com.mongodb.ReadPreference; -import com.mongodb.ServerAddress; -import com.mongodb.ServerCursor; -import com.mongodb.annotations.ThreadSafe; import com.mongodb.client.cursor.TimeoutMode; -import com.mongodb.connection.ConnectionDescription; -import com.mongodb.connection.ServerType; -import com.mongodb.internal.TimeoutContext; -import com.mongodb.internal.VisibleForTesting; import com.mongodb.internal.async.AsyncAggregateResponseBatchCursor; import com.mongodb.internal.async.SingleResultCallback; -import com.mongodb.internal.async.function.AsyncCallbackSupplier; -import com.mongodb.internal.binding.AsyncConnectionSource; -import com.mongodb.internal.connection.AsyncConnection; -import com.mongodb.internal.connection.Connection; import com.mongodb.internal.connection.OperationContext; -import com.mongodb.internal.operation.AsyncOperationHelper.AsyncCallableConnectionWithCallback; -import com.mongodb.internal.validator.NoOpFieldNameValidator; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.BsonTimestamp; -import org.bson.BsonValue; -import org.bson.codecs.BsonDocumentCodec; -import org.bson.codecs.Decoder; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import static com.mongodb.assertions.Assertions.assertNotNull; -import static com.mongodb.assertions.Assertions.assertTrue; -import static com.mongodb.assertions.Assertions.doesNotThrow; -import static com.mongodb.internal.async.AsyncRunnable.beginAsync; -import static com.mongodb.internal.async.SingleResultCallback.THEN_DO_NOTHING; -import static com.mongodb.internal.operation.CommandBatchCursorHelper.FIRST_BATCH; -import static com.mongodb.internal.operation.CommandBatchCursorHelper.MESSAGE_IF_CLOSED_AS_CURSOR; -import static com.mongodb.internal.operation.CommandBatchCursorHelper.NEXT_BATCH; -import static com.mongodb.internal.operation.CommandBatchCursorHelper.getKillCursorsCommand; -import static com.mongodb.internal.operation.CommandBatchCursorHelper.getMoreCommandDocument; -import static com.mongodb.internal.operation.CommandBatchCursorHelper.logCommandCursorResult; -import static com.mongodb.internal.operation.CommandBatchCursorHelper.translateCommandException; -import static java.util.Collections.emptyList; +public class AsyncCommandBatchCursor implements AsyncAggregateResponseBatchCursor { -class AsyncCommandBatchCursor implements AsyncAggregateResponseBatchCursor { - - private final MongoNamespace namespace; - private final Decoder decoder; - @Nullable - private final BsonValue comment; - private final int maxWireVersion; - private final boolean firstBatchEmpty; - private final ResourceManager resourceManager; private final TimeoutMode timeoutMode; private OperationContext operationContext; - private final AtomicBoolean processedInitial = new AtomicBoolean(); - private int batchSize; - private volatile CommandCursorResult commandCursorResult; - private boolean resetTimeoutWhenClosing; + + private AsyncCoreCursor wrapped; AsyncCommandBatchCursor( final TimeoutMode timeoutMode, - final BsonDocument commandCursorDocument, - final int batchSize, final long maxTimeMS, - final Decoder decoder, - @Nullable final BsonValue comment, - final AsyncConnectionSource connectionSource, - final AsyncConnection connection) { - ConnectionDescription connectionDescription = connection.getDescription(); - this.commandCursorResult = toCommandCursorResult(connectionDescription.getServerAddress(), FIRST_BATCH, commandCursorDocument); - this.namespace = commandCursorResult.getNamespace(); - this.batchSize = batchSize; - this.decoder = decoder; - this.comment = comment; - this.maxWireVersion = connectionDescription.getMaxWireVersion(); - this.firstBatchEmpty = commandCursorResult.getResults().isEmpty(); + final long maxTimeMs, + final OperationContext operationContext, + final AsyncCoreCursor wrapped) { + this.operationContext = operationContext.withTimeoutContextOverride(timeoutContext -> + timeoutContext.withMaxTimeOverride(maxTimeMs)); this.timeoutMode = timeoutMode; - - operationContext = connectionSource.getOperationContext(); - operationContext.getTimeoutContext().setMaxTimeOverride(maxTimeMS); // TODO-JAVA-5640 with? - - AsyncConnection connectionToPin = connectionSource.getServerDescription().getType() == ServerType.LOAD_BALANCER - ? connection : null; - resourceManager = new ResourceManager(namespace, connectionSource, connectionToPin, commandCursorResult.getServerCursor()); - resetTimeoutWhenClosing = true; + this.wrapped = wrapped; } @Override public void next(final SingleResultCallback > callback) { - resourceManager.execute(funcCallback -> { - checkTimeoutModeAndResetTimeoutContextIfIteration(); - ServerCursor localServerCursor = resourceManager.getServerCursor(); - boolean serverCursorIsNull = localServerCursor == null; - List
batchResults = emptyList(); - if (!processedInitial.getAndSet(true) && !firstBatchEmpty) { - batchResults = commandCursorResult.getResults(); - } - - if (serverCursorIsNull || !batchResults.isEmpty()) { - funcCallback.onResult(batchResults, null); - } else { - getMore(localServerCursor, funcCallback); - } - }, callback); + resetTimeout(); + wrapped.next(operationContext, callback); } @Override - public boolean isClosed() { - return !resourceManager.operable(); + public void setBatchSize(final int batchSize) { + wrapped.setBatchSize(batchSize); } @Override - public void setBatchSize(final int batchSize) { - this.batchSize = batchSize; + public int getBatchSize() { + return wrapped.getBatchSize(); } @Override - public int getBatchSize() { - return batchSize; + public boolean isClosed() { + return wrapped.isClosed(); } @Override public void close() { - resourceManager.close(); + wrapped.close(operationContext + .withTimeoutContextOverride(timeoutContext -> timeoutContext + .withNewlyStartedTimeout() + .withDefaultMaxTime() + )); } @Nullable - @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) - ServerCursor getServerCursor() { - if (!resourceManager.operable()) { - return null; - } - return resourceManager.getServerCursor(); - } - @Override public BsonDocument getPostBatchResumeToken() { - return commandCursorResult.getPostBatchResumeToken(); + return wrapped.getPostBatchResumeToken(); } + @Nullable @Override public BsonTimestamp getOperationTime() { - return commandCursorResult.getOperationTime(); + return wrapped.getOperationTime(); } @Override public boolean isFirstBatchEmpty() { - return firstBatchEmpty; + return wrapped.isFirstBatchEmpty(); } @Override public int getMaxWireVersion() { - return maxWireVersion; + return wrapped.getMaxWireVersion(); } - void checkTimeoutModeAndResetTimeoutContextIfIteration() { + private void resetTimeout() { if (timeoutMode == TimeoutMode.ITERATION) { operationContext = operationContext.withNewlyStartedTimeout(); } } - private void getMore(final ServerCursor cursor, final SingleResultCallback > callback) { - resourceManager.executeWithConnection((connection, wrappedCallback) -> - getMoreLoop(assertNotNull(connection), cursor, wrappedCallback), callback); - } - - private void getMoreLoop(final AsyncConnection connection, final ServerCursor serverCursor, - final SingleResultCallback
> callback) { - connection.commandAsync(namespace.getDatabaseName(), - getMoreCommandDocument(serverCursor.getId(), connection.getDescription(), namespace, batchSize, comment), - NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), - CommandResultDocumentCodec.create(decoder, NEXT_BATCH), - assertNotNull(resourceManager.getConnectionSource()).getOperationContext(), - (commandResult, t) -> { - if (t != null) { - Throwable translatedException = - t instanceof MongoCommandException - ? translateCommandException((MongoCommandException) t, serverCursor) - : t; - callback.onResult(null, translatedException); - return; - } - commandCursorResult = toCommandCursorResult( - connection.getDescription().getServerAddress(), NEXT_BATCH, assertNotNull(commandResult)); - ServerCursor nextServerCursor = commandCursorResult.getServerCursor(); - resourceManager.setServerCursor(nextServerCursor); - List
nextBatch = commandCursorResult.getResults(); - if (nextServerCursor == null || !nextBatch.isEmpty()) { - callback.onResult(nextBatch, null); - return; - } - - if (!resourceManager.operable()) { - callback.onResult(emptyList(), null); - return; - } - - getMoreLoop(connection, nextServerCursor, callback); - }); - } - - private CommandCursorResult toCommandCursorResult(final ServerAddress serverAddress, final String fieldNameContainingBatch, - final BsonDocument commandCursorDocument) { - CommandCursorResult commandCursorResult = new CommandCursorResult<>(serverAddress, fieldNameContainingBatch, - commandCursorDocument); - logCommandCursorResult(commandCursorResult); - return commandCursorResult; - } - - /** - * Configures the cursor to {@link #close()} - * without {@linkplain TimeoutContext#withNewlyStartedTimeout() starting a new} - * {@linkplain TimeoutContext#getTimeout() timeout}. - * This is useful when managing the {@link #close()} behavior externally. - */ - AsyncCommandBatchCursor disableTimeoutResetWhenClosing() { - resetTimeoutWhenClosing = false; - return this; - } - - @ThreadSafe - private final class ResourceManager extends CursorResourceManager { - ResourceManager( - final MongoNamespace namespace, - final AsyncConnectionSource connectionSource, - @Nullable final AsyncConnection connectionToPin, - @Nullable final ServerCursor serverCursor) { - super(namespace, connectionSource, connectionToPin, serverCursor); - } - - /** - * Thread-safe. - * Executes {@code operation} within the {@link #tryStartOperation()}/{@link #endOperation()} bounds. - */ - void execute(final AsyncCallbackSupplier operation, final SingleResultCallback callback) { - boolean canStartOperation = doesNotThrow(this::tryStartOperation); - if (!canStartOperation) { - callback.onResult(null, new IllegalStateException(MESSAGE_IF_CLOSED_AS_CURSOR)); - } else { - operation.whenComplete(() -> { - endOperation(); - if (super.getServerCursor() == null) { - // At this point all resources have been released, - // but `isClose` may still be returning `false` if `close` have not been called. - // Self-close to update the state managed by `ResourceManger`, and so that `isClosed` return `true`. - close(); - } - }).get(callback); - } - } - - @Override - void markAsPinned(final AsyncConnection connectionToPin, final Connection.PinningMode pinningMode) { - connectionToPin.markAsPinned(pinningMode); - } - - @Override - void doClose() { - TimeoutContext timeoutContext = operationContext.getTimeoutContext(); - timeoutContext.resetToDefaultMaxTime(); - if (resetTimeoutWhenClosing) { - releaseResourcesAsync(operationContext.withNewlyStartedTimeout(), THEN_DO_NOTHING); - } else { - releaseResourcesAsync(operationContext, THEN_DO_NOTHING); - } - } - - private void releaseResourcesAsync(final OperationContext operationContext, final SingleResultCallback callback) { - beginAsync().thenRunTryCatchAsyncBlocks(c -> { - if (isSkipReleasingServerResourcesOnClose()) { - unsetServerCursor(); - } - if (super.getServerCursor() != null) { - beginAsync(). thenSupply(c2 -> { - getConnection(c2); - }).thenConsume((connection, c3) -> { - beginAsync().thenRun(c4 -> { - releaseServerResourcesAsync(connection, operationContext, c4); - }).thenAlwaysRunAndFinish(() -> { - connection.release(); - }, c3); - }).finish(c); - } else { - c.complete(c); - } - }, MongoException.class, (e, c5) -> { - c5.complete(c5); // ignore exceptions when releasing server resources - }).thenAlwaysRunAndFinish(() -> { - // guarantee that regardless of exceptions, `serverCursor` is null and client resources are released - unsetServerCursor(); - releaseClientResources(); - }, callback); - } - - void executeWithConnection(final AsyncCallableConnectionWithCallback callable, final SingleResultCallback callback) { - getConnection((connection, t) -> { - if (t != null) { - callback.onResult(null, t); - return; - } - callable.call(assertNotNull(connection), (result, t1) -> { - if (t1 != null) { - handleException(connection, t1); - } - connection.release(); - callback.onResult(result, t1); - }); - }); - } - - private void handleException(final AsyncConnection connection, final Throwable exception) { - if (exception instanceof MongoOperationTimeoutException && exception.getCause() instanceof MongoSocketException) { - onCorruptedConnection(connection, (MongoSocketException) exception.getCause()); - } else if (exception instanceof MongoSocketException) { - onCorruptedConnection(connection, (MongoSocketException) exception); - } - } - - private void getConnection(final SingleResultCallback callback) { - assertTrue(getState() != State.IDLE); - AsyncConnection pinnedConnection = getPinnedConnection(); - if (pinnedConnection != null) { - callback.onResult(assertNotNull(pinnedConnection).retain(), null); - } else { - assertNotNull(getConnectionSource()).getConnection(callback); - } - } - - private void releaseServerResourcesAsync(final AsyncConnection connection, final OperationContext operationContext, - final SingleResultCallback callback) { - beginAsync().thenRun((c) -> { - ServerCursor localServerCursor = super.getServerCursor(); - if (localServerCursor != null) { - killServerCursorAsync(getNamespace(), localServerCursor, connection, operationContext, callback); - } else { - c.complete(c); - } - }).thenAlwaysRunAndFinish(() -> { - unsetServerCursor(); - }, callback); - } - - private void killServerCursorAsync( - final MongoNamespace namespace, - final ServerCursor localServerCursor, - final AsyncConnection localConnection, - final OperationContext operationContext, - final SingleResultCallback callback) { - beginAsync().thenRun(c -> { - localConnection.commandAsync( - namespace.getDatabaseName(), - getKillCursorsCommand(namespace, localServerCursor), - NoOpFieldNameValidator.INSTANCE, - ReadPreference.primary(), - new BsonDocumentCodec(), - operationContext, - (r, t) -> c.complete(c)); - }).finish(callback); - } + AsyncCoreCursor getWrapped() { + return wrapped; } } + diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncCommandCoreCursor.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncCommandCoreCursor.java new file mode 100644 index 00000000000..754c63208fd --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncCommandCoreCursor.java @@ -0,0 +1,348 @@ +package com.mongodb.internal.operation; + +/* + * 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. + */ + + +import com.mongodb.MongoCommandException; +import com.mongodb.MongoException; +import com.mongodb.MongoNamespace; +import com.mongodb.MongoOperationTimeoutException; +import com.mongodb.MongoSocketException; +import com.mongodb.ReadPreference; +import com.mongodb.ServerAddress; +import com.mongodb.ServerCursor; +import com.mongodb.annotations.ThreadSafe; +import com.mongodb.connection.ConnectionDescription; +import com.mongodb.connection.ServerType; +import com.mongodb.internal.async.SingleResultCallback; +import com.mongodb.internal.async.function.AsyncCallbackSupplier; +import com.mongodb.internal.binding.AsyncConnectionSource; +import com.mongodb.internal.connection.AsyncConnection; +import com.mongodb.internal.connection.Connection; +import com.mongodb.internal.connection.OperationContext; +import com.mongodb.internal.operation.AsyncOperationHelper.AsyncCallableConnectionWithCallback; +import com.mongodb.internal.validator.NoOpFieldNameValidator; +import com.mongodb.lang.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonTimestamp; +import org.bson.BsonValue; +import org.bson.codecs.BsonDocumentCodec; +import org.bson.codecs.Decoder; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.mongodb.assertions.Assertions.assertNotNull; +import static com.mongodb.assertions.Assertions.assertTrue; +import static com.mongodb.assertions.Assertions.doesNotThrow; +import static com.mongodb.internal.async.AsyncRunnable.beginAsync; +import static com.mongodb.internal.async.SingleResultCallback.THEN_DO_NOTHING; +import static com.mongodb.internal.operation.CommandBatchCursorHelper.FIRST_BATCH; +import static com.mongodb.internal.operation.CommandBatchCursorHelper.MESSAGE_IF_CLOSED_AS_CURSOR; +import static com.mongodb.internal.operation.CommandBatchCursorHelper.NEXT_BATCH; +import static com.mongodb.internal.operation.CommandBatchCursorHelper.getKillCursorsCommand; +import static com.mongodb.internal.operation.CommandBatchCursorHelper.getMoreCommandDocument; +import static com.mongodb.internal.operation.CommandBatchCursorHelper.logCommandCursorResult; +import static com.mongodb.internal.operation.CommandBatchCursorHelper.translateCommandException; +import static java.util.Collections.emptyList; + +class AsyncCommandCoreCursor implements AsyncCoreCursor { + + private final MongoNamespace namespace; + private final Decoder decoder; + @Nullable + private final BsonValue comment; + private final int maxWireVersion; + private final boolean firstBatchEmpty; + private final ResourceManager resourceManager; + private final AtomicBoolean processedInitial = new AtomicBoolean(); + private int batchSize; + private volatile CommandCursorResult commandCursorResult; + + AsyncCommandCoreCursor( + final BsonDocument commandCursorDocument, + final int batchSize, + final Decoder decoder, + @Nullable final BsonValue comment, + final AsyncConnectionSource connectionSource, + final AsyncConnection connection) { + ConnectionDescription connectionDescription = connection.getDescription(); + this.commandCursorResult = toCommandCursorResult(connectionDescription.getServerAddress(), FIRST_BATCH, commandCursorDocument); + this.namespace = commandCursorResult.getNamespace(); + this.batchSize = batchSize; + this.decoder = decoder; + this.comment = comment; + this.maxWireVersion = connectionDescription.getMaxWireVersion(); + this.firstBatchEmpty = commandCursorResult.getResults().isEmpty(); + AsyncConnection connectionToPin = connectionSource.getServerDescription().getType() == ServerType.LOAD_BALANCER + ? connection : null; + resourceManager = new ResourceManager(namespace, connectionSource, connectionToPin, commandCursorResult.getServerCursor()); + } + + @Override + public void next(final OperationContext operationContext, final SingleResultCallback > callback) { + resourceManager.execute(funcCallback -> { + //checkTimeoutModeAndResetTimeoutContextIfIteration(); //FIXME it was a bug? we should have reset the timeout when next was request to execute connection checkout and subsequent read wait on one timeout + ServerCursor localServerCursor = resourceManager.getServerCursor(); + boolean serverCursorIsNull = localServerCursor == null; + List
batchResults = emptyList(); + if (!processedInitial.getAndSet(true) && !firstBatchEmpty) { + batchResults = commandCursorResult.getResults(); + } + + if (serverCursorIsNull || !batchResults.isEmpty()) { + funcCallback.onResult(batchResults, null); + } else { + getMore(localServerCursor, operationContext, funcCallback); + } + }, operationContext, callback); + } + + @Override + public boolean isClosed() { + return !resourceManager.operable(); + } + + @Override + public void setBatchSize(final int batchSize) { + this.batchSize = batchSize; + } + + @Override + public int getBatchSize() { + return batchSize; + } + + @Override + public void close(final OperationContext operationContext) { + resourceManager.close(operationContext); + } + + @Nullable + @Override + public ServerCursor getServerCursor() { + if (!resourceManager.operable()) { + return null; + } + return resourceManager.getServerCursor(); + } + + @Override + public BsonDocument getPostBatchResumeToken() { + return commandCursorResult.getPostBatchResumeToken(); + } + + @Override + public BsonTimestamp getOperationTime() { + return commandCursorResult.getOperationTime(); + } + + @Override + public boolean isFirstBatchEmpty() { + return firstBatchEmpty; + } + + @Override + public int getMaxWireVersion() { + return maxWireVersion; + } + + private void getMore(final ServerCursor cursor, final OperationContext operationContext, final SingleResultCallback > callback) { + resourceManager.executeWithConnection(operationContext, (connection, wrappedCallback) -> + getMoreLoop(assertNotNull(connection), cursor, operationContext, wrappedCallback), callback); + } + + private void getMoreLoop(final AsyncConnection connection, final ServerCursor serverCursor, + final OperationContext operationContext, + final SingleResultCallback
> callback) { + connection.commandAsync(namespace.getDatabaseName(), + getMoreCommandDocument(serverCursor.getId(), connection.getDescription(), namespace, batchSize, comment), + NoOpFieldNameValidator.INSTANCE, ReadPreference.primary(), + CommandResultDocumentCodec.create(decoder, NEXT_BATCH), + operationContext, + (commandResult, t) -> { + if (t != null) { + Throwable translatedException = + t instanceof MongoCommandException + ? translateCommandException((MongoCommandException) t, serverCursor) + : t; + callback.onResult(null, translatedException); + return; + } + commandCursorResult = toCommandCursorResult( + connection.getDescription().getServerAddress(), NEXT_BATCH, assertNotNull(commandResult)); + ServerCursor nextServerCursor = commandCursorResult.getServerCursor(); + resourceManager.setServerCursor(nextServerCursor); + List
nextBatch = commandCursorResult.getResults(); + if (nextServerCursor == null || !nextBatch.isEmpty()) { + callback.onResult(nextBatch, null); + return; + } + + if (!resourceManager.operable()) { + callback.onResult(emptyList(), null); + return; + } + + getMoreLoop(connection, nextServerCursor, operationContext, callback); + }); + } + + private CommandCursorResult toCommandCursorResult(final ServerAddress serverAddress, final String fieldNameContainingBatch, + final BsonDocument commandCursorDocument) { + CommandCursorResult commandCursorResult = new CommandCursorResult<>(serverAddress, fieldNameContainingBatch, + commandCursorDocument); + logCommandCursorResult(commandCursorResult); + return commandCursorResult; + } + + @ThreadSafe + private final class ResourceManager extends CursorResourceManagerNew { + ResourceManager( + final MongoNamespace namespace, + final AsyncConnectionSource connectionSource, + @Nullable final AsyncConnection connectionToPin, + @Nullable final ServerCursor serverCursor) { + super(namespace, connectionSource, connectionToPin, serverCursor); + } + + /** + * Thread-safe. + */ + void execute(final AsyncCallbackSupplier operation, final OperationContext operationContext, final SingleResultCallback callback) { + boolean canStartOperation = doesNotThrow(this::tryStartOperation); + if (!canStartOperation) { + callback.onResult(null, new IllegalStateException(MESSAGE_IF_CLOSED_AS_CURSOR)); + } else { + operation.whenComplete(() -> { + endOperation(operationContext); + if (super.getServerCursor() == null) { + // At this point all resources have been released, + // but `isClose` may still be returning `false` if `close` have not been called. + // Self-close to update the state managed by `ResourceManger`, and so that `isClosed` return `true`. + close(operationContext); + } + }).get(callback); + } + } + + @Override + void markAsPinned(final AsyncConnection connectionToPin, final Connection.PinningMode pinningMode) { + connectionToPin.markAsPinned(pinningMode); + } + + @Override + void doClose(final OperationContext operationContext) { + releaseResourcesAsync(operationContext, THEN_DO_NOTHING); + } + + private void releaseResourcesAsync(final OperationContext operationContext, final SingleResultCallback callback) { + beginAsync().thenRunTryCatchAsyncBlocks(c -> { + if (isSkipReleasingServerResourcesOnClose()) { + unsetServerCursor(); + } + if (super.getServerCursor() != null) { + beginAsync(). thenSupply(c2 -> { + getConnection(operationContext, c2); + }).thenConsume((connection, c3) -> { + beginAsync().thenRun(c4 -> { + releaseServerResourcesAsync(connection, operationContext, c4); + }).thenAlwaysRunAndFinish(() -> { + connection.release(); + }, c3); + }).finish(c); + } else { + c.complete(c); + } + }, MongoException.class, (e, c5) -> { + c5.complete(c5); // ignore exceptions when releasing server resources + }).thenAlwaysRunAndFinish(() -> { + // guarantee that regardless of exceptions, `serverCursor` is null and client resources are released + unsetServerCursor(); + releaseClientResources(); + }, callback); + } + + void executeWithConnection(final OperationContext operationContext, final AsyncCallableConnectionWithCallback callable, + final SingleResultCallback callback) { + getConnection(operationContext, (connection, t) -> { + if (t != null) { + callback.onResult(null, t); + return; + } + callable.call(assertNotNull(connection), (result, t1) -> { + if (t1 != null) { + handleException(connection, t1); + } + connection.release(); + callback.onResult(result, t1); + }); + }); + } + + private void handleException(final AsyncConnection connection, final Throwable exception) { + if (exception instanceof MongoOperationTimeoutException && exception.getCause() instanceof MongoSocketException) { + onCorruptedConnection(connection, (MongoSocketException) exception.getCause()); + } else if (exception instanceof MongoSocketException) { + onCorruptedConnection(connection, (MongoSocketException) exception); + } + } + + private void getConnection(final OperationContext operationContext, final SingleResultCallback callback) { + assertTrue(getState() != State.IDLE); + AsyncConnection pinnedConnection = getPinnedConnection(); + if (pinnedConnection != null) { + callback.onResult(assertNotNull(pinnedConnection).retain(), null); + } else { + assertNotNull(getConnectionSource()).getConnection(operationContext, callback); + } + } + + private void releaseServerResourcesAsync(final AsyncConnection connection, final OperationContext operationContext, + final SingleResultCallback callback) { + beginAsync().thenRun((c) -> { + ServerCursor localServerCursor = super.getServerCursor(); + if (localServerCursor != null) { + killServerCursorAsync(getNamespace(), localServerCursor, connection, operationContext, callback); + } else { + c.complete(c); + } + }).thenAlwaysRunAndFinish(() -> { + unsetServerCursor(); + }, callback); + } + + private void killServerCursorAsync( + final MongoNamespace namespace, + final ServerCursor localServerCursor, + final AsyncConnection localConnection, + final OperationContext operationContext, + final SingleResultCallback callback) { + beginAsync().thenRun(c -> { + localConnection.commandAsync( + namespace.getDatabaseName(), + getKillCursorsCommand(namespace, localServerCursor), + NoOpFieldNameValidator.INSTANCE, + ReadPreference.primary(), + new BsonDocumentCodec(), + operationContext, + (r, t) -> c.complete(c)); + }).finish(callback); + } + } +} + diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncCoreCursor.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncCoreCursor.java new file mode 100644 index 00000000000..c689d48f7df --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncCoreCursor.java @@ -0,0 +1,68 @@ +/* + * 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.operation; + +import com.mongodb.ServerCursor; +import com.mongodb.internal.async.AsyncBatchCursor; +import com.mongodb.internal.async.SingleResultCallback; +import com.mongodb.internal.connection.OperationContext; +import com.mongodb.lang.Nullable; +import org.bson.BsonDocument; +import org.bson.BsonTimestamp; + +import java.util.List; + +public interface AsyncCoreCursor { + void close(OperationContext operationContext); + void next(OperationContext operationContext, SingleResultCallback > callback); + + /** + * Sets the batch size to use when requesting the next batch. This is the number of documents to request in the next batch. + * + * @param batchSize the non-negative batch size. 0 means to use the server default. + */ + void setBatchSize(int batchSize); + + /** + * Gets the batch size to use when requesting the next batch. This is the number of documents to request in the next batch. + * + * @return the non-negative batch size. 0 means to use the server default. + */ + int getBatchSize(); + + @Nullable + ServerCursor getServerCursor(); + + + @Nullable + BsonDocument getPostBatchResumeToken(); + + @Nullable + BsonTimestamp getOperationTime(); + + boolean isFirstBatchEmpty(); + + int getMaxWireVersion(); + + + /** + * Implementations of {@link AsyncBatchCursor} are allowed to close themselves, see {@link #close()} for more details. + * + * @return {@code true} if {@code this} has been closed or has closed itself. + */ + boolean isClosed(); +} diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncOperationHelper.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncOperationHelper.java index f158b3944ae..7b134df2ee8 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AsyncOperationHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncOperationHelper.java @@ -21,12 +21,13 @@ import com.mongodb.ReadPreference; import com.mongodb.assertions.Assertions; import com.mongodb.client.cursor.TimeoutMode; +import com.mongodb.connection.ServerDescription; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.async.AsyncBatchCursor; import com.mongodb.internal.async.SingleResultCallback; -import com.mongodb.internal.async.function.AsyncCallbackBiFunction; import com.mongodb.internal.async.function.AsyncCallbackFunction; import com.mongodb.internal.async.function.AsyncCallbackSupplier; +import com.mongodb.internal.async.function.AsyncCallbackTriFunction; import com.mongodb.internal.async.function.RetryState; import com.mongodb.internal.async.function.RetryingAsyncCallbackSupplier; import com.mongodb.internal.binding.AsyncConnectionSource; @@ -62,7 +63,7 @@ final class AsyncOperationHelper { interface AsyncCallableWithConnection { - void call(@Nullable AsyncConnection connection, @Nullable Throwable t); + void call(@Nullable AsyncConnection connection, OperationContext operationContext, @Nullable Throwable t); } interface AsyncCallableConnectionWithCallback
{ @@ -94,39 +95,69 @@ interface CommandReadTransformerAsync { * @return the function result */ @Nullable - R apply(T t, AsyncConnectionSource source, AsyncConnection connection); + R apply(T t, AsyncConnectionSource source, AsyncConnection connection, OperationContext operationContext); } - static void withAsyncReadConnectionSource(final AsyncReadBinding binding, final AsyncCallableWithSource callable) { - binding.getReadConnectionSource(errorHandlingCallback(new AsyncCallableWithSourceCallback(callable), OperationHelper.LOGGER)); + static void withAsyncReadConnectionSource(final AsyncReadBinding binding, final OperationContext operationContext, + final AsyncCallableWithSource callable) { + binding.getReadConnectionSource(operationContext, + errorHandlingCallback(new AsyncCallableWithSourceCallback(callable), OperationHelper.LOGGER)); } - static void withAsyncConnection(final AsyncWriteBinding binding, final AsyncCallableWithConnection callable) { - binding.getWriteConnectionSource(errorHandlingCallback(new AsyncCallableWithConnectionCallback(callable), OperationHelper.LOGGER)); + static void withAsyncConnection(final AsyncWriteBinding binding, + final OperationContext originalOperationContext, + final AsyncCallableWithConnection callable) { + OperationContext serverSelectionOperationContext = originalOperationContext.withTimeoutContextOverride(TimeoutContext::withComputedServerSelectionTimeoutContextNew); + binding.getWriteConnectionSource( + serverSelectionOperationContext, + errorHandlingCallback( + new AsyncCallableWithConnectionCallback(callable, serverSelectionOperationContext, originalOperationContext), + OperationHelper.LOGGER)); } /** - * @see #withAsyncSuppliedResource(AsyncCallbackSupplier, boolean, SingleResultCallback, AsyncCallbackFunction) + * @see #withAsyncSuppliedResource(AsyncCallbackFunction, boolean, OperationContext, SingleResultCallback, AsyncCallbackFunction) */ - static void withAsyncSourceAndConnection(final AsyncCallbackSupplier sourceSupplier, - final boolean wrapConnectionSourceException, final SingleResultCallback callback, - final AsyncCallbackBiFunction asyncFunction) + static void withAsyncSourceAndConnection( + final AsyncCallbackFunction sourceAsyncFunction, + final boolean wrapConnectionSourceException, + final OperationContext operationContext, + final SingleResultCallback callback, + final AsyncCallbackTriFunction asyncFunction) throws OperationHelper.ResourceSupplierInternalException { SingleResultCallback errorHandlingCallback = errorHandlingCallback(callback, OperationHelper.LOGGER); - withAsyncSuppliedResource(sourceSupplier, wrapConnectionSourceException, errorHandlingCallback, + + OperationContext serverSelectionOperationContext = + operationContext.withTimeoutContextOverride(TimeoutContext::withComputedServerSelectionTimeoutContextNew); + withAsyncSuppliedResource( + sourceAsyncFunction, + wrapConnectionSourceException, + serverSelectionOperationContext, + errorHandlingCallback, (source, sourceReleasingCallback) -> - withAsyncSuppliedResource(source::getConnection, wrapConnectionSourceException, sourceReleasingCallback, + withAsyncSuppliedResource( + source::getConnection, + wrapConnectionSourceException, + serverSelectionOperationContext.withMinRoundTripTime(source.getServerDescription()), + sourceReleasingCallback, (connection, connectionAndSourceReleasingCallback) -> - asyncFunction.apply(source, connection, connectionAndSourceReleasingCallback))); + asyncFunction.apply( + source, + connection, + operationContext.withMinRoundTripTime(source.getServerDescription()), + connectionAndSourceReleasingCallback))); } - static void withAsyncSuppliedResource(final AsyncCallbackSupplier resourceSupplier, - final boolean wrapSourceConnectionException, final SingleResultCallback callback, - final AsyncCallbackFunction function) throws OperationHelper.ResourceSupplierInternalException { + static void withAsyncSuppliedResource(final AsyncCallbackFunction resourceSupplier, + final boolean wrapSourceConnectionException, + final OperationContext operationContext, + final SingleResultCallback callback, + final AsyncCallbackFunction function) + throws OperationHelper.ResourceSupplierInternalException { SingleResultCallback errorHandlingCallback = errorHandlingCallback(callback, OperationHelper.LOGGER); - resourceSupplier.get((resource, supplierException) -> { + resourceSupplier.apply(operationContext, (resource, supplierException) -> { if (supplierException != null) { if (wrapSourceConnectionException) { supplierException = new OperationHelper.ResourceSupplierInternalException(supplierException); @@ -144,57 +175,47 @@ static void withAsyncSuppliedResource(final Asyn }); } - static void withAsyncConnectionSourceCallableConnection(final AsyncConnectionSource source, - final AsyncCallableWithConnection callable) { - source.getConnection((connection, t) -> { - source.release(); - if (t != null) { - callable.call(null, t); - } else { - callable.call(connection, null); - } - }); - } - - static void withAsyncConnectionSource(final AsyncConnectionSource source, final AsyncCallableWithSource callable) { + static void withAsyncConnectionSource(final AsyncConnectionSource source, + final AsyncCallableWithSource callable) { callable.call(source, null); } static void executeRetryableReadAsync( final AsyncReadBinding binding, + final OperationContext operationContext, final String database, final CommandCreator commandCreator, final Decoder decoder, final CommandReadTransformerAsync transformer, final boolean retryReads, final SingleResultCallback callback) { - executeRetryableReadAsync(binding, binding::getReadConnectionSource, database, commandCreator, + executeRetryableReadAsync(binding, operationContext, binding::getReadConnectionSource, database, commandCreator, decoder, transformer, retryReads, callback); } static void executeRetryableReadAsync( final AsyncReadBinding binding, - final AsyncCallbackSupplier sourceAsyncSupplier, + final OperationContext operationContext, + final AsyncCallbackFunction sourceAsyncFunction, final String database, final CommandCreator commandCreator, final Decoder decoder, final CommandReadTransformerAsync