Skip to content

Commit 01aff5a

Browse files
katcharovrozzajyeminvbabaninstIncMale
authored
Implement Client Side Operation Timeout (CSOT) feature (#1215)
Introduce a unified Client Side Operation Timeout (CSOT) as an Alpha feature to simplify the multitude of existing timeout settings. This consolidation merges various complex timeout settings into a single, overarching operation timeout option, enhancing usability and predictability. Existing timeout options will continue to be honored if the operation timeout is not set. The options `serverSelectionTimeoutMS` and `connectTimeoutMS` are still honored even if the operation timeout is set. Key Changes: - Implement CSOT across various layers of the driver, applying the new operation timeout configuration to govern execution time in areas such as Authentication, Connection Monitoring and Pooling, Server Discovery and Monitoring, CRUD, GridFS, CSFLE, Transactions, Handshake, and Server Selection. - Annotate CSOT API as Alpha, indicating that this public API is in the early stages of development and may undergo incompatible changes. - Centralize the setting of maxTimeMS just before sending commands to the server. This optimization enhances performance and ensures precise enforcement of timeouts. - Introduce TimeoutContext to centralize timeout-related logic, enhancing the clarity and ease of maintenance for timeout management within the driver. - Enable a new retry mode when CSOT is active, allowing operations to retry until the timeout is reached. This improves resiliency compared to the previous behavior of retrying only once. - Enhance MongoClient, MongoDatabase, MongoCollection, and ClientSession to support setting and inheriting the operation timeout. - Introduce MongoCluster, a new conceptual entity representing server deployments, configurable with specific writeConcern, timeout, and read preference. - Account for minimum Round Trip Time (RTT) for socket reads and writes when CSOT is enabled to ensure timeouts are adjusted for network latency, avoiding connection churn. - Standardize CSOT error handling by transforming timeout errors into the new `MongoOperationTimeoutException`. --------- Co-authored-by: Ross Lawley <[email protected]> Co-authored-by: Maxim Katcharov <[email protected]> Co-authored-by: Jeff Yemin <[email protected]> Co-authored-by: Viacheslav Babanin <[email protected]> Co-authored-by: Valentin Kovalenko <[email protected]>
1 parent 7d80cef commit 01aff5a

File tree

711 files changed

+64266
-8947
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

711 files changed

+64266
-8947
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ almost always be binary compatible with prior minor releases from the same major
5454
Patch 5.x.y increments (such as 5.0.0 -> 5.0.1, 5.1.1 -> 5.1.2, etc) will occur for bug fixes only and will always be binary compatible
5555
with prior patch releases of the same minor release branch.
5656

57+
#### @Alpha
58+
59+
APIs marked with the `@Alpha` annotation are in the early stages of development, subject to incompatible changes,
60+
or even removal, in a future release and may lack some intended features. An APIs bearing `@Alpha` annotation may
61+
contain known issues affecting functionality, performance, and stability. They are also exempt from any compatibility
62+
guarantees made by its containing library.
63+
64+
It is inadvisable for <i>applications</i> to use Alpha APIs in production environments or for <i>libraries</i>
65+
(which get included on users' CLASSPATHs, outside the library developers' control) to depend on these APIs. Alpha APIs
66+
are intended for <b>experimental purposes</b> only.
67+
5768
#### @Beta
5869

5970
APIs marked with the `@Beta` annotation at the class or method level are subject to change. They can be modified in any way, or even

THIRD-PARTY-NOTICES

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ https://github.com/mongodb/mongo-java-driver.
3737
See the License for the specific language governing permissions and
3838
limitations under the License.
3939

40-
3) The following files: Beta.java
40+
3) The following files:
41+
42+
Alpha.java (formerly Beta.java)
43+
Beta.java
4144

4245
Copyright 2010 The Guava Authors
4346
Copyright 2011 The Guava Authors

bson/src/main/org/bson/assertions/Assertions.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ public static <T> T assertNotNull(@Nullable final T value) throws AssertionError
116116
return value;
117117
}
118118

119+
/**
120+
* Throw AssertionError if the condition if false.
121+
*
122+
* @param name the name of the state that is being checked
123+
* @param condition the condition about the parameter to check
124+
* @throws AssertionError if the condition is false
125+
*/
126+
public static void assertTrue(final String name, final boolean condition) {
127+
if (!condition) {
128+
throw new AssertionError("state should be: " + assertNotNull(name));
129+
}
130+
}
131+
119132
/**
120133
* Cast an object to the given class and return it, or throw IllegalArgumentException if it's not assignable to that class.
121134
*

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ configure(scalaProjects) {
158158
"-unchecked",
159159
"-language:reflectiveCalls",
160160
"-Wconf:cat=deprecation:ws,any:e",
161+
"-Wconf:msg=While parsing annotations in:silent",
161162
"-Xlint:strict-unsealed-patmat"
162163
]
163164
}

config/checkstyle/suppressions.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<suppress checks="MethodLength" files="QuickTour"/>
3030
<suppress checks="Regexp" files="Tour"/>
3131

32+
<suppress checks="FileLength" files="UnifiedCrudHelper"/>
3233
<suppress checks="MethodLength" files="PojoRoundTripTest"/>
3334
<suppress checks="MethodLength" files="AbstractUnifiedTest"/>
3435
<suppress checks="MethodLength" files="AbstractClientSideEncryptionTest"/>
@@ -87,6 +88,7 @@
8788
<suppress checks="ParameterNumber" files="Operations"/>
8889
<suppress checks="ParameterNumber" files="ChangeStreamDocument"/>
8990
<suppress checks="ParameterNumber" files="StructuredLogger"/>
91+
<suppress checks="ParameterNumber" files="MongoClusterImpl"/>
9092

9193
<!--Legacy code that has not yet been cleaned-->
9294
<suppress checks="FinalClass" files="AggregationOptions"/>

config/spotbugs/exclude.xml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@
229229
-->
230230
<Match>
231231
<!-- MongoDB status: "False Positive", SpotBugs rank: 13 -->
232-
<Class name="com.mongodb.kotlin.client.coroutine.MongoClient"/>
232+
<Class name="com.mongodb.kotlin.client.coroutine.MongoCluster"/>
233233
<Method name="startSession"/>
234234
<Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"/>
235235
</Match>
@@ -239,4 +239,25 @@
239239
<Bug pattern="NP_NONNULL_PARAM_VIOLATION"/>
240240
</Match>
241241

242+
<!-- Ignoring await return; intended to be used in a loop -->
243+
<Match>
244+
<Class name="com.mongodb.internal.time.Timeout"/>
245+
<Bug pattern="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE"/>
246+
</Match>
247+
<Match>
248+
<Class name="com.mongodb.internal.time.Timeout"/>
249+
<Bug pattern="RV_RETURN_VALUE_IGNORED"/>
250+
</Match>
251+
<Match>
252+
<Class name="com.mongodb.internal.time.Timeout"/>
253+
<Bug pattern="WA_AWAIT_NOT_IN_LOOP"/>
254+
</Match>
255+
256+
<!-- Void method returning null but @NotNull API -->
257+
<Match>
258+
<Class name="com.mongodb.internal.operation.DropIndexOperation"/>
259+
<Method name="execute"/>
260+
<Bug pattern="NP_NONNULL_RETURN_VIOLATION"/>
261+
</Match>
262+
242263
</FindBugsFilter>

driver-core/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ dependencies {
5858
implementation "org.mongodb:mongodb-crypt:$mongoCryptVersion", optional
5959

6060
testImplementation project(':bson').sourceSets.test.output
61+
testImplementation('org.junit.jupiter:junit-jupiter-api')
6162
testRuntimeOnly "io.netty:netty-tcnative-boringssl-static"
6263

6364
classifiers.forEach {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.mongodb;
1818

1919
import com.mongodb.annotations.Beta;
20+
import com.mongodb.annotations.Reason;
2021
import com.mongodb.lang.Nullable;
2122

2223
import static com.mongodb.assertions.Assertions.notNull;
@@ -28,7 +29,7 @@
2829
* @see MongoCredential#AWS_CREDENTIAL_PROVIDER_KEY
2930
* @since 4.4
3031
*/
31-
@Beta(Beta.Reason.CLIENT)
32+
@Beta(Reason.CLIENT)
3233
public final class AwsCredential {
3334
private final String accessKeyId;
3435
private final String secretAccessKey;

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,21 @@
1616

1717
package com.mongodb;
1818

19+
import com.mongodb.annotations.Alpha;
1920
import com.mongodb.annotations.NotThreadSafe;
21+
import com.mongodb.annotations.Reason;
22+
import com.mongodb.lang.Nullable;
2023

2124
import javax.net.ssl.SSLContext;
2225
import java.util.HashMap;
2326
import java.util.Map;
27+
import java.util.concurrent.TimeUnit;
2428
import java.util.function.Supplier;
2529

2630
import static com.mongodb.assertions.Assertions.notNull;
31+
import static com.mongodb.internal.TimeoutSettings.convertAndValidateTimeout;
2732
import static java.util.Collections.unmodifiableMap;
33+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2834

2935
/**
3036
* The client-side settings for data key creation and explicit encryption.
@@ -42,6 +48,8 @@ public final class ClientEncryptionSettings {
4248
private final Map<String, Map<String, Object>> kmsProviders;
4349
private final Map<String, Supplier<Map<String, Object>>> kmsProviderPropertySuppliers;
4450
private final Map<String, SSLContext> kmsProviderSslContextMap;
51+
@Nullable
52+
private final Long timeoutMS;
4553
/**
4654
* A builder for {@code ClientEncryptionSettings} so that {@code ClientEncryptionSettings} can be immutable, and to support easier
4755
* construction through chaining.
@@ -53,6 +61,8 @@ public static final class Builder {
5361
private Map<String, Map<String, Object>> kmsProviders;
5462
private Map<String, Supplier<Map<String, Object>>> kmsProviderPropertySuppliers = new HashMap<>();
5563
private Map<String, SSLContext> kmsProviderSslContextMap = new HashMap<>();
64+
@Nullable
65+
private Long timeoutMS;
5666

5767
/**
5868
* Sets the {@link MongoClientSettings} that will be used to access the key vault.
@@ -120,6 +130,43 @@ public Builder kmsProviderSslContextMap(final Map<String, SSLContext> kmsProvide
120130
return this;
121131
}
122132

133+
/**
134+
* Sets the time limit for the full execution of an operation.
135+
*
136+
* <ul>
137+
* <li>{@code null} means that the timeout mechanism for operations will defer to using:
138+
* <ul>
139+
* <li>{@code waitQueueTimeoutMS}: The maximum wait time in milliseconds that a thread may wait for a connection to become
140+
* available</li>
141+
* <li>{@code socketTimeoutMS}: How long a send or receive on a socket can take before timing out.</li>
142+
* <li>{@code wTimeoutMS}: How long the server will wait for the write concern to be fulfilled before timing out.</li>
143+
* <li>{@code maxTimeMS}: The cumulative time limit for processing operations on a cursor.
144+
* See: <a href="https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS">cursor.maxTimeMS</a>.</li>
145+
* <li>{@code maxCommitTimeMS}: The maximum amount of time to allow a single {@code commitTransaction} command to execute.
146+
* See: {@link TransactionOptions#getMaxCommitTime}.</li>
147+
* </ul>
148+
* </li>
149+
* <li>{@code 0} means infinite timeout.</li>
150+
* <li>{@code > 0} The time limit to use for the full execution of an operation.</li>
151+
* </ul>
152+
*
153+
* <p><strong>Note:</strong> The timeout set through this method overrides the timeout defined in the key vault client settings
154+
* specified in {@link #keyVaultMongoClientSettings(MongoClientSettings)}.
155+
* Essentially, for operations that require accessing the key vault, the remaining timeout from the initial operation
156+
* determines the duration allowed for key vault access.</p>
157+
*
158+
* @param timeout the timeout
159+
* @param timeUnit the time unit
160+
* @return this
161+
* @since 5.2
162+
* @see #getTimeout
163+
*/
164+
@Alpha(Reason.CLIENT)
165+
public ClientEncryptionSettings.Builder timeout(final long timeout, final TimeUnit timeUnit) {
166+
this.timeoutMS = convertAndValidateTimeout(timeout, timeUnit);
167+
return this;
168+
}
169+
123170
/**
124171
* Build an instance of {@code ClientEncryptionSettings}.
125172
*
@@ -253,12 +300,46 @@ public Map<String, SSLContext> getKmsProviderSslContextMap() {
253300
return unmodifiableMap(kmsProviderSslContextMap);
254301
}
255302

303+
/**
304+
* The time limit for the full execution of an operation.
305+
*
306+
* <p>If set the following deprecated options will be ignored:
307+
* {@code waitQueueTimeoutMS}, {@code socketTimeoutMS}, {@code wTimeoutMS}, {@code maxTimeMS} and {@code maxCommitTimeMS}</p>
308+
*
309+
* <ul>
310+
* <li>{@code null} means that the timeout mechanism for operations will defer to using:
311+
* <ul>
312+
* <li>{@code waitQueueTimeoutMS}: The maximum wait time in milliseconds that a thread may wait for a connection to become
313+
* available</li>
314+
* <li>{@code socketTimeoutMS}: How long a send or receive on a socket can take before timing out.</li>
315+
* <li>{@code wTimeoutMS}: How long the server will wait for the write concern to be fulfilled before timing out.</li>
316+
* <li>{@code maxTimeMS}: The cumulative time limit for processing operations on a cursor.
317+
* See: <a href="https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS">cursor.maxTimeMS</a>.</li>
318+
* <li>{@code maxCommitTimeMS}: The maximum amount of time to allow a single {@code commitTransaction} command to execute.
319+
* See: {@link TransactionOptions#getMaxCommitTime}.</li>
320+
* </ul>
321+
* </li>
322+
* <li>{@code 0} means infinite timeout.</li>
323+
* <li>{@code > 0} The time limit to use for the full execution of an operation.</li>
324+
* </ul>
325+
*
326+
* @param timeUnit the time unit
327+
* @return the timeout in the given time unit
328+
* @since 5.2
329+
*/
330+
@Alpha(Reason.CLIENT)
331+
@Nullable
332+
public Long getTimeout(final TimeUnit timeUnit) {
333+
return timeoutMS == null ? null : timeUnit.convert(timeoutMS, MILLISECONDS);
334+
}
335+
256336
private ClientEncryptionSettings(final Builder builder) {
257337
this.keyVaultMongoClientSettings = notNull("keyVaultMongoClientSettings", builder.keyVaultMongoClientSettings);
258338
this.keyVaultNamespace = notNull("keyVaultNamespace", builder.keyVaultNamespace);
259339
this.kmsProviders = notNull("kmsProviders", builder.kmsProviders);
260340
this.kmsProviderPropertySuppliers = notNull("kmsProviderPropertySuppliers", builder.kmsProviderPropertySuppliers);
261341
this.kmsProviderSslContextMap = notNull("kmsProviderSslContextMap", builder.kmsProviderSslContextMap);
342+
this.timeoutMS = builder.timeoutMS;
262343
}
263344

264345
}

0 commit comments

Comments
 (0)