From 78f357e9a23fb5fe132078ea963fa487384031c4 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Sat, 11 Apr 2026 12:38:57 -0700 Subject: [PATCH] Change CSOT exception hierarchy - Make MongoOperationTimeoutException extend MongoClientException instead of MongoTimeoutException to avoid breaking the semantic guarantee that MongoTimeoutException implies the operation was not executed - Handle MongoOperationTimeoutException explicitly in instanceof MongoTimeoutException checks to preserve existing behavior for transaction labeling, connection pool events, and logging JAVA-5438 --- .../com/mongodb/MongoOperationTimeoutException.java | 2 +- .../mongodb/internal/connection/BaseCluster.java | 4 ++-- .../internal/connection/DefaultConnectionPool.java | 13 ++++++++----- .../internal/connection/LoadBalancedCluster.java | 4 ++-- .../operation/CommitTransactionOperation.java | 2 ++ .../client/internal/OperationExecutorImpl.java | 2 ++ .../mongodb/client/internal/MongoClusterImpl.java | 2 ++ 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/driver-core/src/main/com/mongodb/MongoOperationTimeoutException.java b/driver-core/src/main/com/mongodb/MongoOperationTimeoutException.java index 9d46814c3da..428563edb1e 100644 --- a/driver-core/src/main/com/mongodb/MongoOperationTimeoutException.java +++ b/driver-core/src/main/com/mongodb/MongoOperationTimeoutException.java @@ -40,7 +40,7 @@ * @since 5.2 */ @Alpha(Reason.CLIENT) -public final class MongoOperationTimeoutException extends MongoTimeoutException { +public final class MongoOperationTimeoutException extends MongoClientException { private static final long serialVersionUID = 1L; /** diff --git a/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java b/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java index 4146d06c22e..eca7c210253 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java @@ -427,7 +427,7 @@ private void logAndThrowTimeoutException( "Timed out while waiting for a server that matches %s. Client view of cluster state is %s", serverSelector, clusterDescription.getShortDescription()); - MongoTimeoutException exception = operationContext.getTimeoutContext().hasTimeoutMS() + MongoClientException exception = operationContext.getTimeoutContext().hasTimeoutMS() ? new MongoOperationTimeoutException(message) : new MongoTimeoutException(message); logServerSelectionFailed(operationContext, clusterId, exception, serverSelector, clusterDescription); @@ -590,7 +590,7 @@ private static void logServerSelectionFailed( final ServerSelector serverSelector, final ClusterDescription clusterDescription) { if (STRUCTURED_LOGGER.isRequired(DEBUG, clusterId)) { - String failureDescription = failure instanceof MongoTimeoutException + String failureDescription = failure instanceof MongoTimeoutException || failure instanceof MongoOperationTimeoutException // This hardcoded message guarantees that the `FAILURE` entry for `MongoTimeoutException` does not include // any information that is specified via other entries, e.g., `SELECTOR` and `TOPOLOGY_DESCRIPTION`. // The logging spec requires us to avoid such duplication of information. 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 2339cf18b86..9e3d8265da3 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java @@ -16,9 +16,11 @@ package com.mongodb.internal.connection; +import com.mongodb.MongoClientException; import com.mongodb.MongoConnectionPoolClearedException; import com.mongodb.MongoException; import com.mongodb.MongoInterruptedException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.MongoServerUnavailableException; import com.mongodb.MongoTimeoutException; import com.mongodb.annotations.NotThreadSafe; @@ -256,7 +258,7 @@ public void getAsync(final OperationContext operationContext, final SingleResult private Throwable checkOutFailed(final Throwable t, final OperationContext operationContext, final StartTime checkoutStart) { Throwable result = t; Reason reason; - if (t instanceof MongoTimeoutException) { + if (t instanceof MongoTimeoutException || t instanceof MongoOperationTimeoutException) { reason = Reason.TIMEOUT; } else if (t instanceof MongoOpenConnectionInternalException) { reason = Reason.CONNECTION_ERROR; @@ -334,7 +336,7 @@ public int getGeneration() { private PooledConnection getPooledConnection(final Timeout maxWaitTimeout, final StartTime startTime, - final TimeoutContext timeoutContext) throws MongoTimeoutException { + final TimeoutContext timeoutContext) throws MongoClientException { try { UsageTrackingInternalConnection internalConnection = maxWaitTimeout.call(NANOSECONDS, () -> pool.get(-1L, NANOSECONDS), @@ -363,9 +365,9 @@ private PooledConnection getPooledConnectionImmediate() { return internalConnection == null ? null : new PooledConnection(internalConnection); } - private MongoTimeoutException createTimeoutException(final StartTime startTime, - @Nullable final MongoTimeoutException cause, - final TimeoutContext timeoutContext) { + private MongoClientException createTimeoutException(final StartTime startTime, + @Nullable final MongoTimeoutException cause, + final TimeoutContext timeoutContext) { long elapsedMs = startTime.elapsed().toMillis(); int numPinnedToCursor = pinnedStatsManager.getNumPinnedToCursor(); int numPinnedToTransaction = pinnedStatsManager.getNumPinnedToTransaction(); @@ -425,6 +427,7 @@ ConcurrentPool getPool() { void doMaintenance() { Predicate silentlyComplete = e -> e instanceof MongoInterruptedException || e instanceof MongoTimeoutException + || e instanceof MongoOperationTimeoutException || e instanceof MongoConnectionPoolClearedException || ConcurrentPool.isPoolClosedException(e); try { pool.prune(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java index 2401a9e014a..38bb0524ac6 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java @@ -322,7 +322,7 @@ private MongoClientException createResolvedToMultipleHostsException() { + "to multiple hosts"); } - private MongoTimeoutException createTimeoutException(final TimeoutContext timeoutContext) { + private MongoClientException createTimeoutException(final TimeoutContext timeoutContext) { MongoException localSrvResolutionException = srvResolutionException; String message; if (localSrvResolutionException == null) { @@ -334,7 +334,7 @@ private MongoTimeoutException createTimeoutException(final TimeoutContext timeou return createTimeoutException(timeoutContext, message); } - private static MongoTimeoutException createTimeoutException(final TimeoutContext timeoutContext, final String message) { + private static MongoClientException createTimeoutException(final TimeoutContext timeoutContext, final String message) { return timeoutContext.hasTimeoutMS() ? new MongoOperationTimeoutException(message) : new MongoTimeoutException(message); } diff --git a/driver-core/src/main/com/mongodb/internal/operation/CommitTransactionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CommitTransactionOperation.java index ca3c8ac5e6f..b176efeadf9 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/CommitTransactionOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/CommitTransactionOperation.java @@ -21,6 +21,7 @@ import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoNamespace; import com.mongodb.MongoNodeIsRecoveringException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.MongoNotPrimaryException; import com.mongodb.MongoSocketException; import com.mongodb.MongoTimeoutException; @@ -103,6 +104,7 @@ private void addErrorLabels(final MongoException e) { private static boolean shouldAddUnknownTransactionCommitResultLabel(final MongoException e) { if (e instanceof MongoSocketException || e instanceof MongoTimeoutException + || e instanceof MongoOperationTimeoutException || e instanceof MongoNotPrimaryException || e instanceof MongoNodeIsRecoveringException || e instanceof MongoExecutionTimeoutException) { return true; diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java index 62a4431cc9a..de2e86f741b 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 @@ -18,6 +18,7 @@ import com.mongodb.MongoClientException; import com.mongodb.MongoException; import com.mongodb.MongoInternalException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.MongoQueryException; import com.mongodb.MongoSocketException; import com.mongodb.MongoTimeoutException; @@ -203,6 +204,7 @@ private RequestContext getContext(final Subscriber subscriber) { private void labelException(@Nullable final ClientSession session, @Nullable final Throwable t) { if (session != null && session.hasActiveTransaction() && (t instanceof MongoSocketException || t instanceof MongoTimeoutException + || t instanceof MongoOperationTimeoutException || (t instanceof MongoQueryException && ((MongoQueryException) t).getErrorCode() == 91)) && !((MongoException) t).hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) { ((MongoException) t).addLabel(TRANSIENT_TRANSACTION_ERROR_LABEL); 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 eb36678761a..4af069cc47e 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java @@ -22,6 +22,7 @@ import com.mongodb.MongoClientException; import com.mongodb.MongoException; import com.mongodb.MongoInternalException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.MongoQueryException; import com.mongodb.MongoSocketException; import com.mongodb.MongoTimeoutException; @@ -535,6 +536,7 @@ private RequestContext getRequestContext() { private void labelException(final ClientSession session, final MongoException e) { if (session.hasActiveTransaction() && (e instanceof MongoSocketException || e instanceof MongoTimeoutException + || e instanceof MongoOperationTimeoutException || e instanceof MongoQueryException && e.getCode() == 91) && !e.hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) { e.addLabel(TRANSIENT_TRANSACTION_ERROR_LABEL);