From b61a502a4c309d5a092cf0d8f50e790aa9fc38e9 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Thu, 2 Oct 2025 17:21:37 -0700 Subject: [PATCH] Use connectTimeoutMS for connection establishment in maintenance TimeoutContext. (#1777) JAVA-5927 --------- Co-authored-by: Ross Lawley Co-authored-by: Ross Lawley (cherry picked from commit cf416b3916d525155e4ffaab4c43cb71f76aba8d) --- .../com/mongodb/internal/TimeoutContext.java | 4 ++ .../internal/connection/SocksSocket.java | 6 +-- .../SocketStreamHelperSpecification.groovy | 27 +++++++++++++ ...tClientSideOperationsTimeoutProseTest.java | 39 +++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java index a079d9e3431..16b4a1a87a7 100644 --- a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java +++ b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java @@ -289,6 +289,10 @@ public long getWriteTimeoutMS() { public int getConnectTimeoutMs() { final long connectTimeoutMS = getTimeoutSettings().getConnectTimeoutMS(); + if (isMaintenanceContext) { + return (int) connectTimeoutMS; + } + return Math.toIntExact(Timeout.nullAsInfinite(timeout).call(MILLISECONDS, () -> connectTimeoutMS, (ms) -> connectTimeoutMS == 0 ? ms : Math.min(ms, connectTimeoutMS), diff --git a/driver-core/src/main/com/mongodb/internal/connection/SocksSocket.java b/driver-core/src/main/com/mongodb/internal/connection/SocksSocket.java index 8a0152c9423..2619a3c2c10 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/SocksSocket.java +++ b/driver-core/src/main/com/mongodb/internal/connection/SocksSocket.java @@ -81,11 +81,11 @@ public SocksSocket(@Nullable final Socket socket, final ProxySettings proxySetti } @Override - public void connect(final SocketAddress endpoint, final int timeoutMs) throws IOException { + public void connect(final SocketAddress endpoint, final int connectTimeoutMs) throws IOException { // `Socket` requires `IllegalArgumentException` - isTrueArgument("timeoutMs", timeoutMs >= 0); + isTrueArgument("connectTimeoutMs", connectTimeoutMs >= 0); try { - Timeout timeout = Timeout.expiresIn(timeoutMs, MILLISECONDS, ZERO_DURATION_MEANS_INFINITE); + Timeout timeout = Timeout.expiresIn(connectTimeoutMs, MILLISECONDS, ZERO_DURATION_MEANS_INFINITE); InetSocketAddress unresolvedAddress = (InetSocketAddress) endpoint; assertTrue(unresolvedAddress.isUnresolved()); this.remoteAddress = unresolvedAddress; diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy index f652c2a0771..68a82fcbf74 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/SocketStreamHelperSpecification.groovy @@ -18,8 +18,11 @@ package com.mongodb.internal.connection import com.mongodb.ClusterFixture import com.mongodb.MongoInternalException +import com.mongodb.MongoOperationTimeoutException import com.mongodb.connection.SocketSettings import com.mongodb.connection.SslSettings +import com.mongodb.internal.TimeoutContext +import com.mongodb.internal.TimeoutSettings import jdk.net.ExtendedSocketOptions import spock.lang.IgnoreIf import spock.lang.Specification @@ -78,6 +81,30 @@ class SocketStreamHelperSpecification extends Specification { socket?.close() } + def 'should throw MongoOperationTimeoutException during initialization when timeoutMS expires'() { + given: + Socket socket = SocketFactory.default.createSocket() + + when: + SocketStreamHelper.initialize( + OPERATION_CONTEXT.withTimeoutContext(new TimeoutContext( + new TimeoutSettings( + 1, + 100, + 100, + 1, + 100))), + socket, getSocketAddresses(getPrimary(), new DefaultInetAddressResolver()).get(0), + SocketSettings.builder().build(), SslSettings.builder().build()) + + then: + thrown(MongoOperationTimeoutException) + + cleanup: + socket?.close() + } + + def 'should connect socket()'() { given: Socket socket = SocketFactory.default.createSocket() 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 c1f2f88c1b4..3ef1cefa105 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -47,6 +47,7 @@ import com.mongodb.event.ConnectionClosedEvent; import com.mongodb.event.ConnectionCreatedEvent; import com.mongodb.event.ConnectionReadyEvent; +import com.mongodb.internal.connection.InternalStreamConnection; import com.mongodb.internal.connection.ServerHelper; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.internal.connection.TestConnectionPoolListener; @@ -1018,6 +1019,44 @@ public void shouldThrowTimeoutExceptionForSubsequentCommitTransaction() { assertEquals(1, failedEvents.size()); } + /** + * Not a prose spec test. However, it is additional test case for better coverage. + *

+ * From the spec: + * - When doing `minPoolSize` maintenance, `connectTimeoutMS` is used as the timeout for socket establishment. + */ + @Test + @DisplayName("Should use connectTimeoutMS when establishing connection in background") + public void shouldUseConnectTimeoutMsWhenEstablishingConnectionInBackground() { + assumeTrue(serverVersionAtLeast(4, 4)); + + collectionHelper.runAdminCommand("{" + + "configureFailPoint: \"" + FAIL_COMMAND_NAME + "\"," + + "mode: \"alwaysOn\"," + + " data: {" + + " failCommands: [\"hello\", \"isMaster\"]," + + " blockConnection: true," + + " blockTimeMS: " + 500 + + " }" + + "}"); + + try (MongoClient ignored = createMongoClient(getMongoClientSettingsBuilder() + .applyToConnectionPoolSettings(builder -> builder.minSize(1)) + // Use a very short timeout to ensure that the connection establishment will fail on the first handshake command. + .timeout(10, TimeUnit.MILLISECONDS))) { + InternalStreamConnection.setRecordEverything(true); + + // Wait for the connection to start establishment in the background. + sleep(1000); + } finally { + InternalStreamConnection.setRecordEverything(false); + } + + List commandFailedEvents = commandListener.getCommandFailedEvents("isMaster"); + assertEquals(1, commandFailedEvents.size()); + assertInstanceOf(MongoOperationTimeoutException.class, commandFailedEvents.get(0).getThrowable()); + } + private static Stream test8ServerSelectionArguments() { return Stream.of( Arguments.of(Named.of("serverSelectionTimeoutMS honored if timeoutMS is not set",