diff --git a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java index 2a886704cd..88317524e8 100644 --- a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java +++ b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java @@ -446,7 +446,10 @@ public TimeoutContext withComputedServerSelectionTimeoutContext() { return this; } - public Timeout startWaitQueueTimeout(final StartTime checkoutStart) { + public Timeout startMaxWaitTimeout(final StartTime checkoutStart) { + if (hasTimeoutMS()) { + return assertNotNull(timeout); + } final long ms = getTimeoutSettings().getMaxWaitTimeMS(); return checkoutStart.timeoutAfterOrInfiniteIfNegative(ms, MILLISECONDS); } 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 0ef94d559c..c62d527f86 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java @@ -42,6 +42,7 @@ import com.mongodb.event.ConnectionPoolListener; import com.mongodb.event.ConnectionPoolReadyEvent; import com.mongodb.event.ConnectionReadyEvent; +import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.VisibleForTesting; import com.mongodb.internal.async.SingleResultCallback; import com.mongodb.internal.connection.SdamServerDescriptionManager.SdamIssue; @@ -98,6 +99,7 @@ import static com.mongodb.event.ConnectionClosedEvent.Reason.ERROR; import static com.mongodb.internal.Locks.lockInterruptibly; import static com.mongodb.internal.Locks.withLock; +import static com.mongodb.internal.TimeoutContext.createMongoTimeoutException; import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback; import static com.mongodb.internal.connection.ConcurrentPool.INFINITE_SIZE; @@ -110,12 +112,12 @@ import static com.mongodb.internal.logging.LogMessage.Entry.Name.MAX_CONNECTING; import static com.mongodb.internal.logging.LogMessage.Entry.Name.MAX_IDLE_TIME_MS; import static com.mongodb.internal.logging.LogMessage.Entry.Name.MAX_POOL_SIZE; +import static com.mongodb.internal.logging.LogMessage.Entry.Name.MAX_WAIT_TIMEOUT_MS; import static com.mongodb.internal.logging.LogMessage.Entry.Name.MIN_POOL_SIZE; import static com.mongodb.internal.logging.LogMessage.Entry.Name.REASON_DESCRIPTION; import static com.mongodb.internal.logging.LogMessage.Entry.Name.SERVER_HOST; import static com.mongodb.internal.logging.LogMessage.Entry.Name.SERVER_PORT; import static com.mongodb.internal.logging.LogMessage.Entry.Name.SERVICE_ID; -import static com.mongodb.internal.logging.LogMessage.Entry.Name.WAIT_QUEUE_TIMEOUT_MS; import static com.mongodb.internal.logging.LogMessage.Level.DEBUG; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -190,12 +192,12 @@ public int getGeneration(@NonNull final ObjectId serviceId) { @Override public InternalConnection get(final OperationContext operationContext) { StartTime checkoutStart = connectionCheckoutStarted(operationContext); - Timeout waitQueueTimeout = operationContext.getTimeoutContext().startWaitQueueTimeout(checkoutStart); + Timeout maxWaitTimeout = operationContext.getTimeoutContext().startMaxWaitTimeout(checkoutStart); try { stateAndGeneration.throwIfClosedOrPaused(); - PooledConnection connection = getPooledConnection(waitQueueTimeout, checkoutStart); + PooledConnection connection = getPooledConnection(maxWaitTimeout, checkoutStart, operationContext.getTimeoutContext()); if (!connection.opened()) { - connection = openConcurrencyLimiter.openOrGetAvailable(operationContext, connection, waitQueueTimeout, checkoutStart); + connection = openConcurrencyLimiter.openOrGetAvailable(operationContext, connection, maxWaitTimeout, checkoutStart); } connection.checkedOutForOperation(operationContext); connectionCheckedOut(operationContext, connection, checkoutStart); @@ -208,7 +210,7 @@ public InternalConnection get(final OperationContext operationContext) { @Override public void getAsync(final OperationContext operationContext, final SingleResultCallback callback) { StartTime checkoutStart = connectionCheckoutStarted(operationContext); - Timeout maxWaitTimeout = checkoutStart.timeoutAfterOrInfiniteIfNegative(settings.getMaxWaitTime(NANOSECONDS), NANOSECONDS); + Timeout maxWaitTimeout = operationContext.getTimeoutContext().startMaxWaitTimeout(checkoutStart); SingleResultCallback eventSendingCallback = (connection, failure) -> { SingleResultCallback errHandlingCallback = errorHandlingCallback(callback, LOGGER); if (failure == null) { @@ -225,13 +227,13 @@ public void getAsync(final OperationContext operationContext, final SingleResult eventSendingCallback.onResult(null, e); return; } - asyncWorkManager.enqueue(new Task(maxWaitTimeout, checkoutStart, t -> { + asyncWorkManager.enqueue(new Task(maxWaitTimeout, checkoutStart, operationContext.getTimeoutContext(), t -> { if (t != null) { eventSendingCallback.onResult(null, t); } else { PooledConnection connection; try { - connection = getPooledConnection(maxWaitTimeout, checkoutStart); + connection = getPooledConnection(maxWaitTimeout, checkoutStart, operationContext.getTimeoutContext()); } catch (Exception e) { eventSendingCallback.onResult(null, e); return; @@ -330,22 +332,24 @@ public int getGeneration() { return stateAndGeneration.generation(); } - private PooledConnection getPooledConnection(final Timeout waitQueueTimeout, final StartTime startTime) throws MongoTimeoutException { + private PooledConnection getPooledConnection(final Timeout maxWaitTimeout, + final StartTime startTime, + final TimeoutContext timeoutContext) throws MongoTimeoutException { try { - UsageTrackingInternalConnection internalConnection = waitQueueTimeout.call(NANOSECONDS, + UsageTrackingInternalConnection internalConnection = maxWaitTimeout.call(NANOSECONDS, () -> pool.get(-1L, NANOSECONDS), (ns) -> pool.get(ns, NANOSECONDS), () -> pool.get(0L, NANOSECONDS)); while (shouldPrune(internalConnection)) { pool.release(internalConnection, true); - internalConnection = waitQueueTimeout.call(NANOSECONDS, + internalConnection = maxWaitTimeout.call(NANOSECONDS, () -> pool.get(-1L, NANOSECONDS), (ns) -> pool.get(ns, NANOSECONDS), () -> pool.get(0L, NANOSECONDS)); } return new PooledConnection(internalConnection); } catch (MongoTimeoutException e) { - throw createTimeoutException(startTime); + throw createTimeoutException(startTime, timeoutContext); } } @@ -359,13 +363,15 @@ private PooledConnection getPooledConnectionImmediate() { return internalConnection == null ? null : new PooledConnection(internalConnection); } - private MongoTimeoutException createTimeoutException(final StartTime startTime) { + private MongoTimeoutException createTimeoutException(final StartTime startTime, final TimeoutContext timeoutContext) { long elapsedMs = startTime.elapsed().toMillis(); int numPinnedToCursor = pinnedStatsManager.getNumPinnedToCursor(); int numPinnedToTransaction = pinnedStatsManager.getNumPinnedToTransaction(); + String errorMessage; + if (numPinnedToCursor == 0 && numPinnedToTransaction == 0) { - return new MongoTimeoutException(format("Timed out after %d ms while waiting for a connection to server %s.", - elapsedMs, serverId.getAddress())); + errorMessage = format("Timed out after %d ms while waiting for a connection to server %s.", + elapsedMs, serverId.getAddress()); } else { int maxSize = pool.getMaxSize(); int numInUse = pool.getInUseCount(); @@ -394,13 +400,15 @@ private MongoTimeoutException createTimeoutException(final StartTime startTime) int numOtherInUse = numInUse - numPinnedToCursor - numPinnedToTransaction; assertTrue(numOtherInUse >= 0); assertTrue(numPinnedToCursor + numPinnedToTransaction + numOtherInUse <= maxSize); - return new MongoTimeoutException(format("Timed out after %d ms while waiting for a connection to server %s. Details: " + errorMessage = format("Timed out after %d ms while waiting for a connection to server %s. Details: " + "maxPoolSize: %s, connections in use by cursors: %d, connections in use by transactions: %d, " + "connections in use by other operations: %d", elapsedMs, serverId.getAddress(), sizeToString(maxSize), numPinnedToCursor, numPinnedToTransaction, - numOtherInUse)); + numOtherInUse); } + + return timeoutContext.hasTimeoutMS() ? createMongoTimeoutException(errorMessage) : new MongoTimeoutException(errorMessage); } @VisibleForTesting(otherwise = PRIVATE) @@ -497,7 +505,7 @@ private void connectionPoolCreated(final ConnectionPoolListener connectionPoolLi entries.add(new LogMessage.Entry(MIN_POOL_SIZE, settings.getMinSize())); entries.add(new LogMessage.Entry(MAX_POOL_SIZE, settings.getMaxSize())); entries.add(new LogMessage.Entry(MAX_CONNECTING, settings.getMaxConnecting())); - entries.add(new LogMessage.Entry(WAIT_QUEUE_TIMEOUT_MS, settings.getMaxWaitTime(MILLISECONDS))); + entries.add(new LogMessage.Entry(MAX_WAIT_TIMEOUT_MS, settings.getMaxWaitTime(MILLISECONDS))); logMessage("Connection pool created", clusterId, message, entries); } @@ -903,11 +911,11 @@ private final class OpenConcurrencyLimiter { } PooledConnection openOrGetAvailable(final OperationContext operationContext, final PooledConnection connection, - final Timeout waitQueueTimeout, final StartTime startTime) + final Timeout maxWaitTimeout, final StartTime startTime) throws MongoTimeoutException { PooledConnection result = openWithConcurrencyLimit( operationContext, connection, OpenWithConcurrencyLimitMode.TRY_GET_AVAILABLE, - waitQueueTimeout, startTime); + maxWaitTimeout, startTime); return assertNotNull(result); } @@ -950,7 +958,7 @@ void openImmediatelyAndTryHandOverOrRelease(final OperationContext operationCont * * * @param operationContext the operation context - * @param waitQueueTimeout Applies only to the first phase. + * @param maxWaitTimeout Applies only to the first phase. * @return An {@linkplain PooledConnection#opened() opened} connection which is either the specified * {@code connection}, or potentially a different one if {@code mode} is * {@link OpenWithConcurrencyLimitMode#TRY_GET_AVAILABLE}, or {@code null} if {@code mode} is @@ -959,13 +967,14 @@ void openImmediatelyAndTryHandOverOrRelease(final OperationContext operationCont */ @Nullable private PooledConnection openWithConcurrencyLimit(final OperationContext operationContext, - final PooledConnection connection, final OpenWithConcurrencyLimitMode mode, - final Timeout waitQueueTimeout, final StartTime startTime) + final PooledConnection connection, final OpenWithConcurrencyLimitMode mode, + final Timeout maxWaitTimeout, final StartTime startTime) throws MongoTimeoutException { PooledConnection availableConnection; try {//phase one availableConnection = acquirePermitOrGetAvailableOpenedConnection( - mode == OpenWithConcurrencyLimitMode.TRY_GET_AVAILABLE, waitQueueTimeout, startTime); + mode == OpenWithConcurrencyLimitMode.TRY_GET_AVAILABLE, maxWaitTimeout, startTime, + operationContext.getTimeoutContext()); } catch (Exception e) { connection.closeSilently(); throw e; @@ -1007,7 +1016,8 @@ void openWithConcurrencyLimitAsync( final SingleResultCallback callback) { PooledConnection availableConnection; try {//phase one - availableConnection = acquirePermitOrGetAvailableOpenedConnection(true, maxWaitTimeout, startTime); + availableConnection = + acquirePermitOrGetAvailableOpenedConnection(true, maxWaitTimeout, startTime, operationContext.getTimeoutContext()); } catch (Exception e) { connection.closeSilently(); callback.onResult(null, e); @@ -1038,7 +1048,8 @@ void openWithConcurrencyLimitAsync( */ @Nullable private PooledConnection acquirePermitOrGetAvailableOpenedConnection(final boolean tryGetAvailable, - final Timeout waitQueueTimeout, final StartTime startTime) + final Timeout maxWaitTimeout, final StartTime startTime, + final TimeoutContext timeoutContext) throws MongoTimeoutException, MongoInterruptedException { PooledConnection availableConnection = null; boolean expressedDesireToGetAvailableConnection = false; @@ -1066,10 +1077,10 @@ private PooledConnection acquirePermitOrGetAvailableOpenedConnection(final boole & !stateAndGeneration.throwIfClosedOrPaused() & (availableConnection = tryGetAvailable ? tryGetAvailableConnection() : null) == null) { - Timeout.onExistsAndExpired(waitQueueTimeout, () -> { - throw createTimeoutException(startTime); + Timeout.onExistsAndExpired(maxWaitTimeout, () -> { + throw createTimeoutException(startTime, timeoutContext); }); - waitQueueTimeout.awaitOn(permitAvailableOrHandedOverOrClosedOrPausedCondition, + maxWaitTimeout.awaitOn(permitAvailableOrHandedOverOrClosedOrPausedCondition, () -> "acquiring permit or getting available opened connection"); } if (availableConnection == null) { @@ -1389,10 +1400,15 @@ final class Task { private final Timeout timeout; private final StartTime startTime; private final Consumer action; + private final TimeoutContext timeoutContext; private boolean completed; - Task(final Timeout timeout, final StartTime startTime, final Consumer action) { + Task(final Timeout timeout, + final StartTime startTime, + final TimeoutContext timeoutContext, + final Consumer action) { this.timeout = timeout; + this.timeoutContext = timeoutContext; this.startTime = startTime; this.action = action; } @@ -1406,7 +1422,7 @@ void failAsClosed() { } void failAsTimedOut() { - doComplete(() -> createTimeoutException(startTime)); + doComplete(() -> createTimeoutException(startTime, timeoutContext)); } private void doComplete(final Supplier failureSupplier) { diff --git a/driver-core/src/main/com/mongodb/internal/logging/LogMessage.java b/driver-core/src/main/com/mongodb/internal/logging/LogMessage.java index cfd97f713e..100abf0270 100644 --- a/driver-core/src/main/com/mongodb/internal/logging/LogMessage.java +++ b/driver-core/src/main/com/mongodb/internal/logging/LogMessage.java @@ -122,7 +122,7 @@ public enum Name { MIN_POOL_SIZE("minPoolSize"), MAX_POOL_SIZE("maxPoolSize"), MAX_CONNECTING("maxConnecting"), - WAIT_QUEUE_TIMEOUT_MS("waitQueueTimeoutMS"), + MAX_WAIT_TIMEOUT_MS("waitQueueTimeoutMS"), SELECTOR("selector"), TOPOLOGY_DESCRIPTION("topologyDescription"), REMAINING_TIME_MS("remainingTimeMS"), diff --git a/driver-core/src/main/com/mongodb/internal/time/StartTime.java b/driver-core/src/main/com/mongodb/internal/time/StartTime.java index 905af2265d..1d8f186ab6 100644 --- a/driver-core/src/main/com/mongodb/internal/time/StartTime.java +++ b/driver-core/src/main/com/mongodb/internal/time/StartTime.java @@ -22,6 +22,8 @@ * A point in time used to track how much time has elapsed. In contrast to a * Timeout, it is guaranteed to not be in the future, and is never infinite. * + * Implementations of this interface must be immutable. + * * @see TimePoint */ public interface StartTime { diff --git a/driver-core/src/main/com/mongodb/internal/time/Timeout.java b/driver-core/src/main/com/mongodb/internal/time/Timeout.java index 3dba42e580..c497f08945 100644 --- a/driver-core/src/main/com/mongodb/internal/time/Timeout.java +++ b/driver-core/src/main/com/mongodb/internal/time/Timeout.java @@ -21,8 +21,8 @@ import com.mongodb.internal.function.CheckedFunction; import com.mongodb.internal.function.CheckedRunnable; import com.mongodb.internal.function.CheckedSupplier; -import com.mongodb.lang.Nullable; import com.mongodb.lang.NonNull; +import com.mongodb.lang.Nullable; import java.util.Arrays; import java.util.Collections; @@ -40,6 +40,8 @@ /** * A Timeout is a "deadline", point in time by which something must happen. * + * Implementations of this interface must be immutable. + * * @see TimePoint */ public interface Timeout { diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java index 56122ec64a..fc5926b3ba 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java @@ -66,11 +66,13 @@ import static com.mongodb.ClusterFixture.createOperationContext; import static com.mongodb.internal.time.Timeout.ZeroSemantics.ZERO_DURATION_MEANS_EXPIRED; import static java.lang.Long.MAX_VALUE; +import static java.lang.Thread.sleep; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -125,7 +127,7 @@ public void shouldThrowOnTimeout() throws InterruptedException { // when TimeoutTrackingConnectionGetter connectionGetter = new TimeoutTrackingConnectionGetter(provider, timeoutSettings); - new Thread(connectionGetter).start(); + cachedExecutor.submit(connectionGetter); connectionGetter.getLatch().await(); @@ -133,6 +135,33 @@ public void shouldThrowOnTimeout() throws InterruptedException { assertTrue(connectionGetter.isGotTimeout()); } + @Test + public void shouldNotUseMaxAwaitTimeMSWhenTimeoutMsIsSet() throws InterruptedException { + // given + provider = new DefaultConnectionPool(SERVER_ID, connectionFactory, + ConnectionPoolSettings.builder() + .maxSize(1) + .build(), + mockSdamProvider(), OPERATION_CONTEXT_FACTORY); + provider.ready(); + TimeoutSettings timeoutSettings = TIMEOUT_SETTINGS + .withTimeout(100L, MILLISECONDS) + .withMaxWaitTimeMS(50); + + InternalConnection internalConnection = provider.get(createOperationContext(timeoutSettings)); + + // when + TimeoutTrackingConnectionGetter connectionGetter = new TimeoutTrackingConnectionGetter(provider, timeoutSettings); + cachedExecutor.submit(connectionGetter); + + sleep(70); // wait for more than maxWaitTimeMS but less than timeoutMs. + internalConnection.close(); + connectionGetter.getLatch().await(); + + // then + assertFalse(connectionGetter.isGotTimeout()); + } + @Test public void shouldThrowOnPoolClosed() { provider = new DefaultConnectionPool(SERVER_ID, connectionFactory, @@ -166,7 +195,7 @@ public void shouldExpireConnectionAfterMaxLifeTime() throws InterruptedException // when provider.get(OPERATION_CONTEXT).close(); - Thread.sleep(100); + sleep(100); provider.doMaintenance(); provider.get(OPERATION_CONTEXT); @@ -187,7 +216,7 @@ public void shouldExpireConnectionAfterLifeTimeOnClose() throws InterruptedExcep // when InternalConnection connection = provider.get(OPERATION_CONTEXT); - Thread.sleep(50); + sleep(50); connection.close(); // then @@ -208,7 +237,7 @@ public void shouldExpireConnectionAfterMaxIdleTime() throws InterruptedException // when provider.get(OPERATION_CONTEXT).close(); - Thread.sleep(100); + sleep(100); provider.doMaintenance(); provider.get(OPERATION_CONTEXT); @@ -230,7 +259,7 @@ public void shouldCloseConnectionAfterExpiration() throws InterruptedException { // when provider.get(OPERATION_CONTEXT).close(); - Thread.sleep(50); + sleep(50); provider.doMaintenance(); provider.get(OPERATION_CONTEXT); @@ -252,7 +281,7 @@ public void shouldCreateNewConnectionAfterExpiration() throws InterruptedExcepti // when provider.get(OPERATION_CONTEXT).close(); - Thread.sleep(50); + sleep(50); provider.doMaintenance(); InternalConnection secondConnection = provider.get(OPERATION_CONTEXT); @@ -277,7 +306,7 @@ public void shouldPruneAfterMaintenanceTaskRuns() throws InterruptedException { // when - Thread.sleep(10); + sleep(10); provider.doMaintenance(); // then @@ -594,7 +623,7 @@ private static void useConcurrently(final DefaultConnectionPool pool, final int */ private static void sleepMillis(final long millis) { try { - Thread.sleep(millis); + sleep(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java index 75a19536cb..91355ab39c 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java @@ -522,7 +522,7 @@ public void setUp() { @Override @AfterEach - public void tearDown() { + public void tearDown() throws InterruptedException { super.tearDown(); SyncMongoClient.disableSleep(); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 8eb47aa0a6..ed626e5202 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -72,6 +72,8 @@ import java.time.Instant; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -87,10 +89,12 @@ import static com.mongodb.ClusterFixture.sleep; import static com.mongodb.client.Fixture.getDefaultDatabaseName; import static com.mongodb.client.Fixture.getPrimary; +import static java.lang.Long.MAX_VALUE; import static java.lang.String.join; import static java.util.Arrays.asList; import static java.util.Collections.nCopies; import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -112,6 +116,7 @@ public abstract class AbstractClientSideOperationsTimeoutProseTest { protected static final String FAIL_COMMAND_NAME = "failCommand"; protected static final String GRID_FS_BUCKET_NAME = "db.fs"; private static final AtomicInteger COUNTER = new AtomicInteger(); + private ExecutorService executor; protected MongoNamespace namespace; protected MongoNamespace gridFsFileNamespace; @@ -781,6 +786,111 @@ public void shouldIgnoreWtimeoutMsOfWriteConcernToInitialAndSubsequentCommitTran }}); } + /** + * Not a prose spec test. However, it is additional test case for better coverage. + */ + @Test + @DisplayName("Should ignore waitQueueTimeoutMS when timeoutMS is set") + public void shouldIgnoreWaitQueueTimeoutMSWhenTimeoutMsIsSet() { + assumeTrue(serverVersionAtLeast(4, 4)); + + //given + try (MongoClient mongoClient = createMongoClient(getMongoClientSettingsBuilder() + .timeout(500, TimeUnit.MILLISECONDS) + .applyToConnectionPoolSettings(builder -> builder + .maxWaitTime(1, TimeUnit.MILLISECONDS) + .maxSize(1) + ))) { + MongoCollection collection = mongoClient.getDatabase(namespace.getDatabaseName()) + .getCollection(namespace.getCollectionName()); + + collectionHelper.runAdminCommand("{" + + " configureFailPoint: \"failCommand\"," + + " mode: { times: 1}," + + " data: {" + + " failCommands: [\"find\" ]," + + " blockConnection: true," + + " blockTimeMS: " + 300 + + " }" + + "}"); + + executor.submit(() -> collection.find().first()); + sleep(100); + + //when && then + assertDoesNotThrow(() -> collection.find().first()); + } + } + + /** + * Not a prose spec test. However, it is additional test case for better coverage. + */ + @Test + @DisplayName("Should throw MongoOperationTimeoutException when connection is not available and timeoutMS is set") + public void shouldThrowOperationTimeoutExceptionWhenConnectionIsNotAvailableAndTimeoutMSIsSet() { + assumeTrue(serverVersionAtLeast(4, 4)); + + //given + try (MongoClient mongoClient = createMongoClient(getMongoClientSettingsBuilder() + .timeout(100, TimeUnit.MILLISECONDS) + .applyToConnectionPoolSettings(builder -> builder + .maxSize(1) + ))) { + MongoCollection collection = mongoClient.getDatabase(namespace.getDatabaseName()) + .getCollection(namespace.getCollectionName()); + + collectionHelper.runAdminCommand("{" + + " configureFailPoint: \"failCommand\"," + + " mode: { times: 1}," + + " data: {" + + " failCommands: [\"find\" ]," + + " blockConnection: true," + + " blockTimeMS: " + 500 + + " }" + + "}"); + + executor.submit(() -> collection.withTimeout(0, TimeUnit.MILLISECONDS).find().first()); + sleep(100); + + //when && then + assertThrows(MongoOperationTimeoutException.class, () -> collection.find().first()); + } + } + + /** + * Not a prose spec test. However, it is additional test case for better coverage. + */ + @Test + @DisplayName("Should use waitQueueTimeoutMS when timeoutMS is not set") + public void shouldUseWaitQueueTimeoutMSWhenTimeoutIsNotSet() { + assumeTrue(serverVersionAtLeast(4, 4)); + + //given + try (MongoClient mongoClient = createMongoClient(getMongoClientSettingsBuilder() + .applyToConnectionPoolSettings(builder -> builder + .maxWaitTime(100, TimeUnit.MILLISECONDS) + .maxSize(1) + ))) { + MongoCollection collection = mongoClient.getDatabase(namespace.getDatabaseName()) + .getCollection(namespace.getCollectionName()); + + collectionHelper.runAdminCommand("{" + + " configureFailPoint: \"failCommand\"," + + " mode: { times: 1}," + + " data: {" + + " failCommands: [\"find\" ]," + + " blockConnection: true," + + " blockTimeMS: " + 300 + + " }" + + "}"); + + executor.submit(() -> collection.find().first()); + sleep(100); + + //when & then + assertThrows(MongoTimeoutException.class, () -> collection.find().first()); + } + } /** * Not a prose spec test. However, it is additional test case for better coverage. @@ -956,6 +1066,7 @@ protected MongoClientSettings.Builder getMongoClientSettingsBuilder() { @BeforeEach public void setUp() { namespace = generateNamespace(); + executor = Executors.newSingleThreadExecutor(); gridFsFileNamespace = new MongoNamespace(getDefaultDatabaseName(), GRID_FS_BUCKET_NAME + ".files"); gridFsChunksNamespace = new MongoNamespace(getDefaultDatabaseName(), GRID_FS_BUCKET_NAME + ".chunks"); @@ -966,7 +1077,7 @@ public void setUp() { } @AfterEach - public void tearDown() { + public void tearDown() throws InterruptedException { ClusterFixture.disableFailPoint(FAIL_COMMAND_NAME); if (collectionHelper != null) { collectionHelper.drop(); @@ -979,6 +1090,10 @@ public void tearDown() { // ignore } } + + executor.shutdownNow(); + //noinspection ResultOfMethodCallIgnored + executor.awaitTermination(MAX_VALUE, NANOSECONDS); } @AfterAll