From 2cf5adccf82f1fbbcee8f06c8877dd5dd5c57354 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 4 Dec 2025 11:39:30 -0500 Subject: [PATCH 01/27] Add failing test --- src/libmongoc/tests/test-mongoc-client.c | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index 04ba91a023e..7bc0447fec2 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -3836,6 +3836,46 @@ test_killCursors(void) mongoc_client_destroy(client); } +static void +test_socketTimeoutMS_zero(void) +{ + mongoc_uri_t *const uri = test_framework_get_uri(); + mongoc_uri_set_option_as_int32(uri, MONGOC_URI_SOCKETTIMEOUTMS, 0); + + mongoc_client_t *const client = test_framework_client_new_from_uri(uri, NULL); + + // Configure a failpoint to block on "ping" for 500ms. + bson_error_t error; + bool ok = mongoc_client_command_simple( + client, + "admin", + tmp_bson(BSON_STR({ + "configureFailPoint" : "failCommand", + "mode" : {"times" : 1}, + "data" : {"failCommands" : ["ping"], "blockTimeMS" : 500, "blockConnection" : true} + })), + NULL, + NULL, + &error); + ASSERT_OR_PRINT(ok, error); + + // Expect "ping" to take 500ms. + const mlib_time_point start = mlib_now(); + + // Send "ping": + ok = mongoc_client_command_simple(client, "admin", tmp_bson(BSON_STR({"ping" : 1})), NULL, NULL, &error); + ASSERT_OR_PRINT(ok, error); + + const mlib_time_point end = mlib_now(); + + const mlib_duration elapsed = mlib_time_difference(end, start); + + ASSERT_CMPINT64(mlib_microseconds_count(elapsed), >=, mlib_microseconds_count(mlib_duration(500, ms))); + + mongoc_client_destroy(client); + mongoc_uri_destroy(uri); +} + void test_client_install(TestSuite *suite) { @@ -4051,4 +4091,5 @@ test_client_install(TestSuite *suite) test_framework_skip_if_no_server_ssl); #endif TestSuite_AddLive(suite, "/Client/killCursors", test_killCursors); + TestSuite_AddLive(suite, "/Client/socketTimeoutMS_zero", test_socketTimeoutMS_zero); } From 6ed9323d7615bdebe9b5cba071168b5bc4b8ce6c Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 4 Dec 2025 11:40:17 -0500 Subject: [PATCH 02/27] Fix socketTimeoutMS=0 ignored in URI --- src/libmongoc/src/mongoc/mongoc-client.h | 13 ---------- src/libmongoc/src/mongoc/mongoc-cluster.c | 3 +-- src/libmongoc/src/mongoc/mongoc-uri-private.h | 3 +++ src/libmongoc/src/mongoc/mongoc-uri.c | 25 ++++++++++++++----- src/libmongoc/src/mongoc/mongoc-uri.h | 12 +++++++++ 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-client.h b/src/libmongoc/src/mongoc/mongoc-client.h index 2b53c91b8b0..3c89c0e70a4 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.h +++ b/src/libmongoc/src/mongoc/mongoc-client.h @@ -54,19 +54,6 @@ BSON_BEGIN_DECLS #endif -#ifndef MONGOC_DEFAULT_SOCKETTIMEOUTMS -/* - * NOTE: The default socket timeout for connections is 5 minutes. This - * means that if your MongoDB server dies or becomes unavailable - * it will take 5 minutes to detect this. - * - * You can change this by providing sockettimeoutms= in your - * connection URI. - */ -#define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) -#endif - - /** * mongoc_client_t: * diff --git a/src/libmongoc/src/mongoc/mongoc-cluster.c b/src/libmongoc/src/mongoc/mongoc-cluster.c index 0519ec0a179..3a2e6171f58 100644 --- a/src/libmongoc/src/mongoc/mongoc-cluster.c +++ b/src/libmongoc/src/mongoc/mongoc-cluster.c @@ -2502,8 +2502,7 @@ void mongoc_cluster_reset_sockettimeoutms(mongoc_cluster_t *cluster) { BSON_ASSERT_PARAM(cluster); - cluster->sockettimeoutms = - mongoc_uri_get_option_as_int32(cluster->uri, MONGOC_URI_SOCKETTIMEOUTMS, MONGOC_DEFAULT_SOCKETTIMEOUTMS); + cluster->sockettimeoutms = mongoc_uri_get_sockettimeoutms_option(cluster->uri); } static uint32_t diff --git a/src/libmongoc/src/mongoc/mongoc-uri-private.h b/src/libmongoc/src/mongoc/mongoc-uri-private.h index 5e9dc2ac38f..ae759ed0932 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri-private.h +++ b/src/libmongoc/src/mongoc/mongoc-uri-private.h @@ -55,6 +55,9 @@ _mongoc_uri_apply_query_string(mongoc_uri_t *uri, mstr_view options, bool from_d int32_t mongoc_uri_get_local_threshold_option(const mongoc_uri_t *uri); +int32_t +mongoc_uri_get_sockettimeoutms_option(const mongoc_uri_t *uri); + bool _mongoc_uri_requires_auth_negotiation(const mongoc_uri_t *uri); diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index d444cfdfd2f..c9e77557ea4 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -2573,27 +2573,40 @@ mongoc_uri_get_compressors(const mongoc_uri_t *uri) } -/* can't use mongoc_uri_get_option_as_int32, it treats 0 specially */ int32_t -mongoc_uri_get_local_threshold_option(const mongoc_uri_t *uri) +_mongoc_uri_get_option_as_int32_0_is_valid(const mongoc_uri_t *uri, const char *option, int32_t fallback) { const bson_t *options; bson_iter_t iter; - int32_t retval = MONGOC_TOPOLOGY_LOCAL_THRESHOLD_MS; + int32_t retval = fallback; - if ((options = mongoc_uri_get_options(uri)) && bson_iter_init_find_case(&iter, options, "localthresholdms") && + if ((options = mongoc_uri_get_options(uri)) && bson_iter_init_find_case(&iter, options, option) && BSON_ITER_HOLDS_INT32(&iter)) { retval = bson_iter_int32(&iter); if (retval < 0) { - MONGOC_WARNING("Invalid localThresholdMS: %d", retval); - retval = MONGOC_TOPOLOGY_LOCAL_THRESHOLD_MS; + MONGOC_WARNING("Invalid %s: %d", option, retval); + retval = fallback; } } return retval; } +/* can't use mongoc_uri_get_option_as_int32, it treats 0 specially */ +int32_t +mongoc_uri_get_local_threshold_option(const mongoc_uri_t *uri) +{ + return _mongoc_uri_get_option_as_int32_0_is_valid( + uri, MONGOC_URI_LOCALTHRESHOLDMS, MONGOC_TOPOLOGY_LOCAL_THRESHOLD_MS); +} + +/* can't use mongoc_uri_get_option_as_int32, it treats 0 specially */ +int32_t +mongoc_uri_get_sockettimeoutms_option(const mongoc_uri_t *uri) +{ + return _mongoc_uri_get_option_as_int32_0_is_valid(uri, MONGOC_URI_SOCKETTIMEOUTMS, MONGOC_DEFAULT_SOCKETTIMEOUTMS); +} const char * mongoc_uri_get_srv_hostname(const mongoc_uri_t *uri) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.h b/src/libmongoc/src/mongoc/mongoc-uri.h index ecf689d6ae7..c788a244014 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.h +++ b/src/libmongoc/src/mongoc/mongoc-uri.h @@ -33,6 +33,18 @@ #define MONGOC_DEFAULT_PORT 27017 #endif +#ifndef MONGOC_DEFAULT_SOCKETTIMEOUTMS +/* + * NOTE: The default socket timeout for connections is 5 minutes. This + * means that if your MongoDB server dies or becomes unavailable + * it will take 5 minutes to detect this. + * + * You can change this by providing sockettimeoutms= in your + * connection URI. + */ +#define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) +#endif + #define MONGOC_URI_APPNAME "appname" #define MONGOC_URI_AUTHMECHANISM "authmechanism" #define MONGOC_URI_AUTHMECHANISMPROPERTIES "authmechanismproperties" From 596727c96696b294c3984abc515c4c057ae351c7 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 4 Dec 2025 11:51:45 -0500 Subject: [PATCH 03/27] Treat 0 timeout as infinite --- src/libmongoc/src/mongoc/mongoc-stream-socket.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-socket.c b/src/libmongoc/src/mongoc/mongoc-stream-socket.c index 2c020f45170..9be3b3237af 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-socket.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-socket.c @@ -36,10 +36,8 @@ struct _mongoc_stream_socket_t { static BSON_INLINE int64_t get_expiration(int32_t timeout_msec) { - if (timeout_msec < 0) { + if (timeout_msec <= 0) { return -1; - } else if (timeout_msec == 0) { - return 0; } else { return (bson_get_monotonic_time() + ((int64_t)timeout_msec * 1000L)); } From 543260846557bfd03b8bec505ac483bc493920a6 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 4 Dec 2025 14:49:32 -0500 Subject: [PATCH 04/27] Fix meaning of 0 timeout for TLS --- src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c index 71bc8a1579b..9f84a56510b 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c @@ -121,7 +121,7 @@ _mongoc_stream_tls_secure_transport_write(mongoc_stream_t *stream, char *buf, si ENTRY; BSON_ASSERT(secure_transport); - if (tls->timeout_msec >= 0) { + if (tls->timeout_msec > 0) { expire = bson_get_monotonic_time() + (tls->timeout_msec * 1000UL); } @@ -300,7 +300,7 @@ _mongoc_stream_tls_secure_transport_readv( tls->timeout_msec = timeout_msec; - if (timeout_msec >= 0) { + if (timeout_msec > 0) { expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); } From bb23cbda1e7f12bbfe85806378f060b4cdd0457f Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 4 Dec 2025 15:04:57 -0500 Subject: [PATCH 05/27] Revert "Fix socketTimeoutMS=0 ignored in URI" This reverts commit 6ed9323d7615bdebe9b5cba071168b5bc4b8ce6c. --- src/libmongoc/src/mongoc/mongoc-client.h | 13 ++++++++++ src/libmongoc/src/mongoc/mongoc-cluster.c | 3 ++- src/libmongoc/src/mongoc/mongoc-uri-private.h | 3 --- src/libmongoc/src/mongoc/mongoc-uri.c | 25 +++++-------------- src/libmongoc/src/mongoc/mongoc-uri.h | 12 --------- 5 files changed, 21 insertions(+), 35 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-client.h b/src/libmongoc/src/mongoc/mongoc-client.h index 3c89c0e70a4..2b53c91b8b0 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.h +++ b/src/libmongoc/src/mongoc/mongoc-client.h @@ -54,6 +54,19 @@ BSON_BEGIN_DECLS #endif +#ifndef MONGOC_DEFAULT_SOCKETTIMEOUTMS +/* + * NOTE: The default socket timeout for connections is 5 minutes. This + * means that if your MongoDB server dies or becomes unavailable + * it will take 5 minutes to detect this. + * + * You can change this by providing sockettimeoutms= in your + * connection URI. + */ +#define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) +#endif + + /** * mongoc_client_t: * diff --git a/src/libmongoc/src/mongoc/mongoc-cluster.c b/src/libmongoc/src/mongoc/mongoc-cluster.c index 3a2e6171f58..0519ec0a179 100644 --- a/src/libmongoc/src/mongoc/mongoc-cluster.c +++ b/src/libmongoc/src/mongoc/mongoc-cluster.c @@ -2502,7 +2502,8 @@ void mongoc_cluster_reset_sockettimeoutms(mongoc_cluster_t *cluster) { BSON_ASSERT_PARAM(cluster); - cluster->sockettimeoutms = mongoc_uri_get_sockettimeoutms_option(cluster->uri); + cluster->sockettimeoutms = + mongoc_uri_get_option_as_int32(cluster->uri, MONGOC_URI_SOCKETTIMEOUTMS, MONGOC_DEFAULT_SOCKETTIMEOUTMS); } static uint32_t diff --git a/src/libmongoc/src/mongoc/mongoc-uri-private.h b/src/libmongoc/src/mongoc/mongoc-uri-private.h index ae759ed0932..5e9dc2ac38f 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri-private.h +++ b/src/libmongoc/src/mongoc/mongoc-uri-private.h @@ -55,9 +55,6 @@ _mongoc_uri_apply_query_string(mongoc_uri_t *uri, mstr_view options, bool from_d int32_t mongoc_uri_get_local_threshold_option(const mongoc_uri_t *uri); -int32_t -mongoc_uri_get_sockettimeoutms_option(const mongoc_uri_t *uri); - bool _mongoc_uri_requires_auth_negotiation(const mongoc_uri_t *uri); diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index c9e77557ea4..d444cfdfd2f 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -2573,40 +2573,27 @@ mongoc_uri_get_compressors(const mongoc_uri_t *uri) } +/* can't use mongoc_uri_get_option_as_int32, it treats 0 specially */ int32_t -_mongoc_uri_get_option_as_int32_0_is_valid(const mongoc_uri_t *uri, const char *option, int32_t fallback) +mongoc_uri_get_local_threshold_option(const mongoc_uri_t *uri) { const bson_t *options; bson_iter_t iter; - int32_t retval = fallback; + int32_t retval = MONGOC_TOPOLOGY_LOCAL_THRESHOLD_MS; - if ((options = mongoc_uri_get_options(uri)) && bson_iter_init_find_case(&iter, options, option) && + if ((options = mongoc_uri_get_options(uri)) && bson_iter_init_find_case(&iter, options, "localthresholdms") && BSON_ITER_HOLDS_INT32(&iter)) { retval = bson_iter_int32(&iter); if (retval < 0) { - MONGOC_WARNING("Invalid %s: %d", option, retval); - retval = fallback; + MONGOC_WARNING("Invalid localThresholdMS: %d", retval); + retval = MONGOC_TOPOLOGY_LOCAL_THRESHOLD_MS; } } return retval; } -/* can't use mongoc_uri_get_option_as_int32, it treats 0 specially */ -int32_t -mongoc_uri_get_local_threshold_option(const mongoc_uri_t *uri) -{ - return _mongoc_uri_get_option_as_int32_0_is_valid( - uri, MONGOC_URI_LOCALTHRESHOLDMS, MONGOC_TOPOLOGY_LOCAL_THRESHOLD_MS); -} - -/* can't use mongoc_uri_get_option_as_int32, it treats 0 specially */ -int32_t -mongoc_uri_get_sockettimeoutms_option(const mongoc_uri_t *uri) -{ - return _mongoc_uri_get_option_as_int32_0_is_valid(uri, MONGOC_URI_SOCKETTIMEOUTMS, MONGOC_DEFAULT_SOCKETTIMEOUTMS); -} const char * mongoc_uri_get_srv_hostname(const mongoc_uri_t *uri) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.h b/src/libmongoc/src/mongoc/mongoc-uri.h index c788a244014..ecf689d6ae7 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.h +++ b/src/libmongoc/src/mongoc/mongoc-uri.h @@ -33,18 +33,6 @@ #define MONGOC_DEFAULT_PORT 27017 #endif -#ifndef MONGOC_DEFAULT_SOCKETTIMEOUTMS -/* - * NOTE: The default socket timeout for connections is 5 minutes. This - * means that if your MongoDB server dies or becomes unavailable - * it will take 5 minutes to detect this. - * - * You can change this by providing sockettimeoutms= in your - * connection URI. - */ -#define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) -#endif - #define MONGOC_URI_APPNAME "appname" #define MONGOC_URI_AUTHMECHANISM "authmechanism" #define MONGOC_URI_AUTHMECHANISMPROPERTIES "authmechanismproperties" From 5678eec14653229b7c90f92ec2d40a5d0ef397eb Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Mon, 8 Dec 2025 16:08:53 -0500 Subject: [PATCH 06/27] Support socketTimeoutMS=inf --- src/libmongoc/src/mongoc/mongoc-client.h | 13 ----- src/libmongoc/src/mongoc/mongoc-cluster.c | 3 +- src/libmongoc/src/mongoc/mongoc-uri-private.h | 3 ++ src/libmongoc/src/mongoc/mongoc-uri.c | 23 ++++++++- src/libmongoc/src/mongoc/mongoc-uri.h | 14 +++++ src/libmongoc/tests/test-mongoc-client.c | 6 +-- src/libmongoc/tests/test-mongoc-uri.c | 51 +++++++++++++++++++ 7 files changed, 93 insertions(+), 20 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-client.h b/src/libmongoc/src/mongoc/mongoc-client.h index 2b53c91b8b0..3c89c0e70a4 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.h +++ b/src/libmongoc/src/mongoc/mongoc-client.h @@ -54,19 +54,6 @@ BSON_BEGIN_DECLS #endif -#ifndef MONGOC_DEFAULT_SOCKETTIMEOUTMS -/* - * NOTE: The default socket timeout for connections is 5 minutes. This - * means that if your MongoDB server dies or becomes unavailable - * it will take 5 minutes to detect this. - * - * You can change this by providing sockettimeoutms= in your - * connection URI. - */ -#define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) -#endif - - /** * mongoc_client_t: * diff --git a/src/libmongoc/src/mongoc/mongoc-cluster.c b/src/libmongoc/src/mongoc/mongoc-cluster.c index 0519ec0a179..580eb40de5d 100644 --- a/src/libmongoc/src/mongoc/mongoc-cluster.c +++ b/src/libmongoc/src/mongoc/mongoc-cluster.c @@ -2502,8 +2502,7 @@ void mongoc_cluster_reset_sockettimeoutms(mongoc_cluster_t *cluster) { BSON_ASSERT_PARAM(cluster); - cluster->sockettimeoutms = - mongoc_uri_get_option_as_int32(cluster->uri, MONGOC_URI_SOCKETTIMEOUTMS, MONGOC_DEFAULT_SOCKETTIMEOUTMS); + cluster->sockettimeoutms = mongoc_uri_get_socket_timeout_ms_option(cluster->uri); } static uint32_t diff --git a/src/libmongoc/src/mongoc/mongoc-uri-private.h b/src/libmongoc/src/mongoc/mongoc-uri-private.h index 5e9dc2ac38f..887b3e3ef89 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri-private.h +++ b/src/libmongoc/src/mongoc/mongoc-uri-private.h @@ -55,6 +55,9 @@ _mongoc_uri_apply_query_string(mongoc_uri_t *uri, mstr_view options, bool from_d int32_t mongoc_uri_get_local_threshold_option(const mongoc_uri_t *uri); +int32_t +mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri); + bool _mongoc_uri_requires_auth_negotiation(const mongoc_uri_t *uri); diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index d444cfdfd2f..5276fd7dab0 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -22,6 +22,8 @@ #include #include +#include "mongoc/mongoc.h" + /* strcasecmp on windows */ #include #include @@ -733,7 +735,9 @@ mongoc_uri_option_is_utf8(const char *key) /* deprecated options with canonical equivalents */ !strcasecmp(key, MONGOC_URI_SSLCLIENTCERTIFICATEKEYFILE) || !strcasecmp(key, MONGOC_URI_SSLCLIENTCERTIFICATEKEYPASSWORD) || - !strcasecmp(key, MONGOC_URI_SSLCERTIFICATEAUTHORITYFILE); + !strcasecmp(key, MONGOC_URI_SSLCERTIFICATEAUTHORITYFILE) || + // temporarily allow `socketTimeoutMS=inf` to support unlimited timeouts + !strcasecmp(key, MONGOC_URI_SOCKETTIMEOUTMS); } const char * @@ -997,7 +1001,10 @@ mongoc_uri_apply_options(mongoc_uri_t *uri, const bson_t *options, bool from_dns MONGOC_WARNING("Empty value provided for \"%s\"", key); } } else if (mongoc_uri_option_is_int32(key)) { - if (0 < strlen(value)) { + // temporarily allow `socketTimeoutMS=inf` to support unlimited timeouts + if (strcasecmp(key, MONGOC_URI_SOCKETTIMEOUTMS) == 0 && strcasecmp(value, "inf") == 0) { + _bson_upsert_utf8_icase(&uri->options, mstr_cstring(MONGOC_URI_SOCKETTIMEOUTMS), "inf"); + } else if (0 < strlen(value)) { int32_t i32 = 42424242; if (mlib_i32_parse(mstr_cstring(value), &i32)) { goto UNSUPPORTED_VALUE; @@ -2594,6 +2601,18 @@ mongoc_uri_get_local_threshold_option(const mongoc_uri_t *uri) return retval; } +int32_t +mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri) +{ + const char *const str_maybe = mongoc_uri_get_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, NULL); + + if (str_maybe && strcasecmp(str_maybe, "inf") == 0) { + // TODO: log and refer to ticket number + return 0; + } + + return mongoc_uri_get_option_as_int32(uri, MONGOC_URI_SOCKETTIMEOUTMS, MONGOC_DEFAULT_SOCKETTIMEOUTMS); +} const char * mongoc_uri_get_srv_hostname(const mongoc_uri_t *uri) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.h b/src/libmongoc/src/mongoc/mongoc-uri.h index ecf689d6ae7..fd654268930 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.h +++ b/src/libmongoc/src/mongoc/mongoc-uri.h @@ -33,6 +33,20 @@ #define MONGOC_DEFAULT_PORT 27017 #endif +#ifndef MONGOC_DEFAULT_SOCKETTIMEOUTMS +/* + * NOTE: The default socket timeout for connections is 5 minutes. This + * means that if your MongoDB server dies or becomes unavailable + * it will take 5 minutes to detect this. + * + * You can change this by providing sockettimeoutms= in your + * connection URI. + * + * This default may be changed to 0 (unlimited timeout) + */ +#define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) +#endif + #define MONGOC_URI_APPNAME "appname" #define MONGOC_URI_AUTHMECHANISM "authmechanism" #define MONGOC_URI_AUTHMECHANISMPROPERTIES "authmechanismproperties" diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index 7bc0447fec2..717e94013a3 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -3837,10 +3837,10 @@ test_killCursors(void) } static void -test_socketTimeoutMS_zero(void) +test_socketTimeoutMS_unlimited(void) { mongoc_uri_t *const uri = test_framework_get_uri(); - mongoc_uri_set_option_as_int32(uri, MONGOC_URI_SOCKETTIMEOUTMS, 0); + mongoc_uri_set_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, "inf"); mongoc_client_t *const client = test_framework_client_new_from_uri(uri, NULL); @@ -4091,5 +4091,5 @@ test_client_install(TestSuite *suite) test_framework_skip_if_no_server_ssl); #endif TestSuite_AddLive(suite, "/Client/killCursors", test_killCursors); - TestSuite_AddLive(suite, "/Client/socketTimeoutMS_zero", test_socketTimeoutMS_zero); + TestSuite_AddLive(suite, "/Client/socketTimeoutMS_unlimited", test_socketTimeoutMS_unlimited); } diff --git a/src/libmongoc/tests/test-mongoc-uri.c b/src/libmongoc/tests/test-mongoc-uri.c index dd4d1036199..2b19b7b66d0 100644 --- a/src/libmongoc/tests/test-mongoc-uri.c +++ b/src/libmongoc/tests/test-mongoc-uri.c @@ -2610,6 +2610,56 @@ test_mongoc_uri_local_threshold_ms(void) mongoc_uri_destroy(uri); } +static void +test_mongoc_uri_socket_timeout_ms(void) +{ + mongoc_uri_t *uri = mongoc_uri_new("mongodb://localhost/"); + ASSERT(uri); + + // If sockettimeoutms is not set, return the C Driver's default + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, MONGOC_DEFAULT_SOCKETTIMEOUTMS); + + ASSERT(mongoc_uri_set_option_as_int32(uri, MONGOC_URI_SOCKETTIMEOUTMS, 99)); + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, 99); + + // TODO:link to ticket explaining "inf" + ASSERT(mongoc_uri_set_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, "inf")); + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, 0); + + mongoc_uri_destroy(uri); + + uri = mongoc_uri_new("mongodb://localhost/?" MONGOC_URI_SOCKETTIMEOUTMS "=99"); + ASSERT(uri); + + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, 99); + + mongoc_uri_destroy(uri); + + uri = mongoc_uri_new("mongodb://localhost/?" MONGOC_URI_SOCKETTIMEOUTMS "=inf"); + ASSERT(uri); + + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, 0); + + mongoc_uri_destroy(uri); + + uri = mongoc_uri_new("mongodb://localhost/?" MONGOC_URI_SOCKETTIMEOUTMS "=0"); + ASSERT(uri); + + // TODO:link to ticket explaining "inf" + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), !=, 0); + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, MONGOC_DEFAULT_SOCKETTIMEOUTMS); + + mongoc_uri_destroy(uri); + + capture_logs(true); + + uri = mongoc_uri_new("mongodb://localhost/?" MONGOC_URI_SOCKETTIMEOUTMS "=garbage"); + ASSERT(!uri); + ASSERT_CAPTURED_LOG("mongoc_uri_get_socket_timeout_ms_option", + MONGOC_LOG_LEVEL_WARNING, + "Unsupported value for \"sockettimeoutms\": \"garbage\""); +} + #define INVALID(_uri, _host) \ ASSERT_WITH_MSG(!mongoc_uri_upsert_host((_uri), (_host), 1, &error), "expected host upsert to fail"); \ @@ -3323,6 +3373,7 @@ test_uri_install(TestSuite *suite) TestSuite_Add(suite, "/Uri/compound_setters", test_mongoc_uri_compound_setters); TestSuite_Add(suite, "/Uri/long_hostname", test_mongoc_uri_long_hostname); TestSuite_Add(suite, "/Uri/local_threshold_ms", test_mongoc_uri_local_threshold_ms); + TestSuite_Add(suite, "/Uri/socket_timeout_ms", test_mongoc_uri_socket_timeout_ms); TestSuite_Add(suite, "/Uri/srv", test_mongoc_uri_srv); TestSuite_Add(suite, "/Uri/dns_options", test_mongoc_uri_dns_options); TestSuite_Add(suite, "/Uri/utf8", test_mongoc_uri_utf8); From d4d1f45ae004e75bfa91b1e58d5f527853ecc550 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Tue, 9 Dec 2025 10:07:50 -0500 Subject: [PATCH 07/27] Set SSL opts on test client --- src/libmongoc/tests/test-mongoc-client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index 717e94013a3..53dcf7c85d7 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -3843,6 +3843,7 @@ test_socketTimeoutMS_unlimited(void) mongoc_uri_set_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, "inf"); mongoc_client_t *const client = test_framework_client_new_from_uri(uri, NULL); + test_framework_set_ssl_opts(client); // Configure a failpoint to block on "ping" for 500ms. bson_error_t error; From 4fb27cbd7fd9b23b990ad8ebb4cc65191b309878 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Tue, 9 Dec 2025 10:19:02 -0500 Subject: [PATCH 08/27] Remove include --- src/libmongoc/src/mongoc/mongoc-uri.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index 5276fd7dab0..fc71931592e 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -22,8 +22,6 @@ #include #include -#include "mongoc/mongoc.h" - /* strcasecmp on windows */ #include #include From d9811a769189e18ada1af9e9f084fe2500b5f748 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Wed, 10 Dec 2025 15:50:18 -0500 Subject: [PATCH 09/27] Apply timeout changes to OpenSSL TLS --- src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c index 298e0fe48d6..0bb8d954569 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c @@ -209,7 +209,7 @@ _mongoc_stream_tls_openssl_write(mongoc_stream_tls_t *tls, char *buf, size_t buf BSON_ASSERT(buf); BSON_ASSERT(buf_len); - if (tls->timeout_msec >= 0) { + if (tls->timeout_msec > 0) { expire = bson_get_monotonic_time() + (tls->timeout_msec * 1000); } @@ -421,7 +421,7 @@ _mongoc_stream_tls_openssl_readv( tls->timeout_msec = timeout_msec; - if (timeout_msec >= 0) { + if (timeout_msec > 0) { expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); } From 969fe2606b6cd02b9ba9332628767b703c858b0a Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 11 Dec 2025 11:17:47 -0500 Subject: [PATCH 10/27] Handle case where immediate timeout is desired --- src/libmongoc/src/mongoc/mongoc-async-cmd.c | 4 ++-- src/libmongoc/src/mongoc/mongoc-stream-socket.c | 4 +++- src/libmongoc/src/mongoc/mongoc-stream.h | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-async-cmd.c b/src/libmongoc/src/mongoc/mongoc-async-cmd.c index 3ca7acbb163..d5baea7882d 100644 --- a/src/libmongoc/src/mongoc/mongoc-async-cmd.c +++ b/src/libmongoc/src/mongoc/mongoc-async-cmd.c @@ -85,8 +85,8 @@ mongoc_async_cmd_tls_setup(mongoc_stream_t *stream, int *events, void *ctx, mlib // Try to do a non-blocking operation, if our backend allows it const mlib_duration_rep_t remain_ms = // use_non_blocking - // Pass 0 for the timeout to begin / continue a non-blocking handshake - ? 0 + // Pass sentinel value for the timeout to begin / continue a non-blocking handshake + ? MONGOC_SOCKET_TIMEOUT_NON_BLOCKING // Otherwise, use the deadline : mlib_milliseconds_count(mlib_timer_remaining(deadline)); if (mongoc_stream_tls_handshake(tls_stream, host, mlib_assert_narrow(int32_t, remain_ms), &retry_events, error)) { diff --git a/src/libmongoc/src/mongoc/mongoc-stream-socket.c b/src/libmongoc/src/mongoc/mongoc-stream-socket.c index 9be3b3237af..1ab60dee857 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-socket.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-socket.c @@ -36,7 +36,9 @@ struct _mongoc_stream_socket_t { static BSON_INLINE int64_t get_expiration(int32_t timeout_msec) { - if (timeout_msec <= 0) { + if (timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { + return 0; + } else if (timeout_msec <= 0) { return -1; } else { return (bson_get_monotonic_time() + ((int64_t)timeout_msec * 1000L)); diff --git a/src/libmongoc/src/mongoc/mongoc-stream.h b/src/libmongoc/src/mongoc/mongoc-stream.h index 723f5febd43..c4ccd4d0db3 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream.h +++ b/src/libmongoc/src/mongoc/mongoc-stream.h @@ -23,10 +23,14 @@ #include #include +#include + BSON_BEGIN_DECLS +#define MONGOC_SOCKET_TIMEOUT_NON_BLOCKING INT32_MIN + typedef struct _mongoc_stream_t mongoc_stream_t; typedef struct _mongoc_stream_poll_t { From 6e1a6db86ff3fd14433d87017fe46a8dd5275a0a Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 11 Dec 2025 14:27:24 -0500 Subject: [PATCH 11/27] Apply timeout channels to secure channel --- src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c index 0d4b70d616e..f35b1775931 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c @@ -695,7 +695,7 @@ _mongoc_stream_tls_secure_channel_readv( tls->timeout_msec = timeout_msec; - if (timeout_msec >= 0) { + if (timeout_msec > 0) { expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); } From 52c1672353dd0607c3a5a231f690e7c2cc1cc421 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Tue, 16 Dec 2025 14:21:23 -0500 Subject: [PATCH 12/27] Fix non-blocking timeout handling in TLS --- src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c | 8 ++++++-- .../src/mongoc/mongoc-stream-tls-secure-channel.c | 4 +++- .../src/mongoc/mongoc-stream-tls-secure-transport.c | 8 ++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c index 0bb8d954569..65238baf335 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c @@ -211,6 +211,8 @@ _mongoc_stream_tls_openssl_write(mongoc_stream_tls_t *tls, char *buf, size_t buf if (tls->timeout_msec > 0) { expire = bson_get_monotonic_time() + (tls->timeout_msec * 1000); + } else if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { + expire = bson_get_monotonic_time(); } BSON_ASSERT(mlib_in_range(int, buf_len)); @@ -228,7 +230,7 @@ _mongoc_stream_tls_openssl_write(mongoc_stream_tls_t *tls, char *buf, size_t buf mongoc_counter_streams_timeout_inc(); } - tls->timeout_msec = 0; + tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { tls->timeout_msec = (expire - now) / 1000; } @@ -423,6 +425,8 @@ _mongoc_stream_tls_openssl_readv( if (timeout_msec > 0) { expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); + } else if (timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { + expire = bson_get_monotonic_time(); } for (i = 0; i < iovcnt; i++) { @@ -457,7 +461,7 @@ _mongoc_stream_tls_openssl_readv( RETURN(-1); } - tls->timeout_msec = 0; + tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { tls->timeout_msec = (expire - now) / 1000L; } diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c index f35b1775931..d44789318ec 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c @@ -697,6 +697,8 @@ _mongoc_stream_tls_secure_channel_readv( if (timeout_msec > 0) { expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); + } else if (timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { + expire = bson_get_monotonic_time(); } for (i = 0; i < iovcnt; i++) { @@ -726,7 +728,7 @@ _mongoc_stream_tls_secure_channel_readv( RETURN(-1); } - tls->timeout_msec = 0; + tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { tls->timeout_msec = (expire - now) / 1000L; } diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c index 9f84a56510b..432e40f7daf 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c @@ -123,6 +123,8 @@ _mongoc_stream_tls_secure_transport_write(mongoc_stream_t *stream, char *buf, si if (tls->timeout_msec > 0) { expire = bson_get_monotonic_time() + (tls->timeout_msec * 1000UL); + } else if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { + expire = bson_get_monotonic_time(); } status = SSLWrite(secure_transport->ssl_ctx_ref, buf, buf_len, (size_t *)&write_ret); @@ -147,7 +149,7 @@ _mongoc_stream_tls_secure_transport_write(mongoc_stream_t *stream, char *buf, si mongoc_counter_streams_timeout_inc(); } - tls->timeout_msec = 0; + tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { tls->timeout_msec = (expire - now) / 1000L; } @@ -302,6 +304,8 @@ _mongoc_stream_tls_secure_transport_readv( if (timeout_msec > 0) { expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); + } else if (timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { + expire = bson_get_monotonic_time(); } for (i = 0; i < iovcnt; i++) { @@ -338,7 +342,7 @@ _mongoc_stream_tls_secure_transport_readv( RETURN(-1); } - tls->timeout_msec = 0; + tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { tls->timeout_msec = (expire - now) / 1000L; } From 6d0b80989e371032a518c71c4929089610bf8d84 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Tue, 16 Dec 2025 15:31:43 -0500 Subject: [PATCH 13/27] Fix handling of sub-millisecond timeouts --- src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c | 12 ++++++++---- .../src/mongoc/mongoc-stream-tls-secure-channel.c | 6 ++++-- .../src/mongoc/mongoc-stream-tls-secure-transport.c | 12 ++++++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c index 65238baf335..41340f9f87c 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c @@ -225,14 +225,16 @@ _mongoc_stream_tls_openssl_write(mongoc_stream_tls_t *tls, char *buf, size_t buf if (expire) { now = bson_get_monotonic_time(); - if ((expire - now) < 0) { + const int64_t remaining_msec = (expire - now) / 1000L; + + if (remaining_msec <= 0) { if (mlib_cmp(ret, <, buf_len)) { mongoc_counter_streams_timeout_inc(); } tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { - tls->timeout_msec = (expire - now) / 1000; + tls->timeout_msec = remaining_msec; } } @@ -450,7 +452,9 @@ _mongoc_stream_tls_openssl_readv( if (expire) { now = bson_get_monotonic_time(); - if ((expire - now) < 0) { + const int64_t remaining_msec = (expire - now) / 1000L; + + if (remaining_msec <= 0) { if (read_ret == 0) { mongoc_counter_streams_timeout_inc(); #ifdef _WIN32 @@ -463,7 +467,7 @@ _mongoc_stream_tls_openssl_readv( tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { - tls->timeout_msec = (expire - now) / 1000L; + tls->timeout_msec = remaining_msec; } } diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c index d44789318ec..1d25734c56f 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c @@ -721,7 +721,9 @@ _mongoc_stream_tls_secure_channel_readv( if (expire) { now = bson_get_monotonic_time(); - if ((expire - now) < 0) { + const int64_t remaining_msec = (expire - now) / 1000L; + + if (remaining_msec <= 0) { if (read_ret == 0) { mongoc_counter_streams_timeout_inc(); errno = ETIMEDOUT; @@ -730,7 +732,7 @@ _mongoc_stream_tls_secure_channel_readv( tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { - tls->timeout_msec = (expire - now) / 1000L; + tls->timeout_msec = remaining_msec; } } diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c index 432e40f7daf..15647b943d8 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c @@ -144,14 +144,16 @@ _mongoc_stream_tls_secure_transport_write(mongoc_stream_t *stream, char *buf, si if (expire) { now = bson_get_monotonic_time(); - if ((expire - now) < 0) { + const int64_t remaining_msec = (expire - now) / 1000L; + + if (remaining_msec <= 0) { if (write_ret < (ssize_t)buf_len) { mongoc_counter_streams_timeout_inc(); } tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { - tls->timeout_msec = (expire - now) / 1000L; + tls->timeout_msec = remaining_msec; } } @@ -335,7 +337,9 @@ _mongoc_stream_tls_secure_transport_readv( if (expire) { now = bson_get_monotonic_time(); - if ((expire - now) < 0) { + const int64_t remaining_msec = (expire - now) / 1000L; + + if (remaining_msec <= 0) { if (read_ret == 0) { mongoc_counter_streams_timeout_inc(); errno = ETIMEDOUT; @@ -344,7 +348,7 @@ _mongoc_stream_tls_secure_transport_readv( tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; } else { - tls->timeout_msec = (expire - now) / 1000L; + tls->timeout_msec = remaining_msec; } } From f8056fd8f7e6a63bdb822e47d1c01ad6f8198e81 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Wed, 17 Dec 2025 11:10:43 -0500 Subject: [PATCH 14/27] Refactor timeout handling to be less error-prone --- src/libmongoc/src/mongoc/mongoc-async-cmd.c | 2 +- .../src/mongoc/mongoc-stream-socket.c | 2 +- .../src/mongoc/mongoc-stream-tls-openssl.c | 54 ++++--------------- .../src/mongoc/mongoc-stream-tls-private.h | 8 +++ .../mongoc/mongoc-stream-tls-secure-channel.c | 28 +++------- .../mongoc-stream-tls-secure-transport.c | 52 ++++-------------- src/libmongoc/src/mongoc/mongoc-stream-tls.c | 30 +++++++++++ src/libmongoc/src/mongoc/mongoc-stream.h | 3 +- src/libmongoc/src/mongoc/mongoc-uri.c | 2 +- src/libmongoc/tests/test-mongoc-uri.c | 6 +-- 10 files changed, 73 insertions(+), 114 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-async-cmd.c b/src/libmongoc/src/mongoc/mongoc-async-cmd.c index d5baea7882d..e713b35c7c0 100644 --- a/src/libmongoc/src/mongoc/mongoc-async-cmd.c +++ b/src/libmongoc/src/mongoc/mongoc-async-cmd.c @@ -86,7 +86,7 @@ mongoc_async_cmd_tls_setup(mongoc_stream_t *stream, int *events, void *ctx, mlib const mlib_duration_rep_t remain_ms = // use_non_blocking // Pass sentinel value for the timeout to begin / continue a non-blocking handshake - ? MONGOC_SOCKET_TIMEOUT_NON_BLOCKING + ? MONGOC_SOCKET_TIMEOUT_IMMEDIATE // Otherwise, use the deadline : mlib_milliseconds_count(mlib_timer_remaining(deadline)); if (mongoc_stream_tls_handshake(tls_stream, host, mlib_assert_narrow(int32_t, remain_ms), &retry_events, error)) { diff --git a/src/libmongoc/src/mongoc/mongoc-stream-socket.c b/src/libmongoc/src/mongoc/mongoc-stream-socket.c index 1ab60dee857..a6bfaabba86 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-socket.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-socket.c @@ -36,7 +36,7 @@ struct _mongoc_stream_socket_t { static BSON_INLINE int64_t get_expiration(int32_t timeout_msec) { - if (timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { + if (timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE) { return 0; } else if (timeout_msec <= 0) { return -1; diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c index 41340f9f87c..106099b1e15 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c @@ -201,19 +201,13 @@ _mongoc_stream_tls_openssl_write(mongoc_stream_tls_t *tls, char *buf, size_t buf { mongoc_stream_tls_openssl_t *openssl = (mongoc_stream_tls_openssl_t *)tls->ctx; ssize_t ret; - int64_t now; - int64_t expire = 0; ENTRY; BSON_ASSERT(tls); BSON_ASSERT(buf); BSON_ASSERT(buf_len); - if (tls->timeout_msec > 0) { - expire = bson_get_monotonic_time() + (tls->timeout_msec * 1000); - } else if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { - expire = bson_get_monotonic_time(); - } + const mlib_timer timer = _mongoc_stream_tls_timer_from_timeout_msec(tls->timeout_msec); BSON_ASSERT(mlib_in_range(int, buf_len)); ret = BIO_write(openssl->bio, buf, (int)buf_len); @@ -222,20 +216,10 @@ _mongoc_stream_tls_openssl_write(mongoc_stream_tls_t *tls, char *buf, size_t buf return ret; } - if (expire) { - now = bson_get_monotonic_time(); - - const int64_t remaining_msec = (expire - now) / 1000L; - - if (remaining_msec <= 0) { - if (mlib_cmp(ret, <, buf_len)) { - mongoc_counter_streams_timeout_inc(); - } + tls->timeout_msec = _mongoc_stream_tls_timer_to_timeout_msec(timer); - tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; - } else { - tls->timeout_msec = remaining_msec; - } + if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE && mlib_cmp(ret, <, buf_len)) { + mongoc_counter_streams_timeout_inc(); } RETURN(ret); @@ -415,8 +399,6 @@ _mongoc_stream_tls_openssl_readv( size_t i; int read_ret; size_t iov_pos = 0; - int64_t now; - int64_t expire = 0; ENTRY; BSON_ASSERT(tls); @@ -425,11 +407,7 @@ _mongoc_stream_tls_openssl_readv( tls->timeout_msec = timeout_msec; - if (timeout_msec > 0) { - expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); - } else if (timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { - expire = bson_get_monotonic_time(); - } + const mlib_timer timer = _mongoc_stream_tls_timer_from_timeout_msec(timeout_msec); for (i = 0; i < iovcnt; i++) { iov_pos = 0; @@ -449,26 +427,16 @@ _mongoc_stream_tls_openssl_readv( return -1; } - if (expire) { - now = bson_get_monotonic_time(); - - const int64_t remaining_msec = (expire - now) / 1000L; + tls->timeout_msec = _mongoc_stream_tls_timer_to_timeout_msec(timer); - if (remaining_msec <= 0) { - if (read_ret == 0) { - mongoc_counter_streams_timeout_inc(); + if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE && read_ret == 0) { + mongoc_counter_streams_timeout_inc(); #ifdef _WIN32 - errno = WSAETIMEDOUT; + errno = WSAETIMEDOUT; #else - errno = ETIMEDOUT; + errno = ETIMEDOUT; #endif - RETURN(-1); - } - - tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; - } else { - tls->timeout_msec = remaining_msec; - } + RETURN(-1); } ret += read_ret; diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-private.h b/src/libmongoc/src/mongoc/mongoc-stream-tls-private.h index c17e992ff28..f7624492aab 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-private.h +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-private.h @@ -28,6 +28,8 @@ #include +#include + #ifdef MONGOC_ENABLE_SSL_OPENSSL #include #endif @@ -67,6 +69,12 @@ mongoc_stream_tls_new_with_secure_channel_cred(mongoc_stream_t *base_stream, mongoc_shared_ptr secure_channel_cred_ptr) BSON_GNUC_WARN_UNUSED_RESULT; #endif // MONGOC_ENABLE_SSL_SECURE_CHANNEL +mlib_timer +_mongoc_stream_tls_timer_from_timeout_msec(int64_t timeout_msec); + +int64_t +_mongoc_stream_tls_timer_to_timeout_msec(mlib_timer timer); + BSON_END_DECLS #endif /* MONGOC_STREAM_TLS_PRIVATE_H */ diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c index 1d25734c56f..79dd8a6aaf6 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-channel.c @@ -685,8 +685,6 @@ _mongoc_stream_tls_secure_channel_readv( ssize_t ret = 0; size_t i; size_t iov_pos = 0; - int64_t now; - int64_t expire = 0; BSON_ASSERT(iov); BSON_ASSERT(iovcnt); @@ -695,11 +693,7 @@ _mongoc_stream_tls_secure_channel_readv( tls->timeout_msec = timeout_msec; - if (timeout_msec > 0) { - expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); - } else if (timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { - expire = bson_get_monotonic_time(); - } + const mlib_timer timer = _mongoc_stream_tls_timer_from_timeout_msec(timeout_msec); for (i = 0; i < iovcnt; i++) { iov_pos = 0; @@ -718,22 +712,12 @@ _mongoc_stream_tls_secure_channel_readv( RETURN(-1); } - if (expire) { - now = bson_get_monotonic_time(); - - const int64_t remaining_msec = (expire - now) / 1000L; - - if (remaining_msec <= 0) { - if (read_ret == 0) { - mongoc_counter_streams_timeout_inc(); - errno = ETIMEDOUT; - RETURN(-1); - } + tls->timeout_msec = _mongoc_stream_tls_timer_to_timeout_msec(timer); - tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; - } else { - tls->timeout_msec = remaining_msec; - } + if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE && read_ret == 0) { + mongoc_counter_streams_timeout_inc(); + errno = ETIMEDOUT; + RETURN(-1); } ret += read_ret; diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c index 15647b943d8..5b246694106 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c @@ -115,17 +115,11 @@ _mongoc_stream_tls_secure_transport_write(mongoc_stream_t *stream, char *buf, si mongoc_stream_tls_t *tls = (mongoc_stream_tls_t *)stream; mongoc_stream_tls_secure_transport_t *secure_transport = (mongoc_stream_tls_secure_transport_t *)tls->ctx; ssize_t write_ret; - int64_t now; - int64_t expire = 0; ENTRY; BSON_ASSERT(secure_transport); - if (tls->timeout_msec > 0) { - expire = bson_get_monotonic_time() + (tls->timeout_msec * 1000UL); - } else if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { - expire = bson_get_monotonic_time(); - } + const mlib_timer timer = _mongoc_stream_tls_timer_from_timeout_msec(tls->timeout_msec); status = SSLWrite(secure_transport->ssl_ctx_ref, buf, buf_len, (size_t *)&write_ret); @@ -141,20 +135,10 @@ _mongoc_stream_tls_secure_transport_write(mongoc_stream_t *stream, char *buf, si RETURN(-1); } - if (expire) { - now = bson_get_monotonic_time(); - - const int64_t remaining_msec = (expire - now) / 1000L; + tls->timeout_msec = _mongoc_stream_tls_timer_to_timeout_msec(timer); - if (remaining_msec <= 0) { - if (write_ret < (ssize_t)buf_len) { - mongoc_counter_streams_timeout_inc(); - } - - tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; - } else { - tls->timeout_msec = remaining_msec; - } + if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE && write_ret < (ssize_t)buf_len) { + mongoc_counter_streams_timeout_inc(); } RETURN(write_ret); @@ -291,8 +275,6 @@ _mongoc_stream_tls_secure_transport_readv( size_t i; size_t read_ret; size_t iov_pos = 0; - int64_t now; - int64_t expire = 0; size_t to_read; size_t remaining_buf_size; size_t remaining_to_read; @@ -304,11 +286,7 @@ _mongoc_stream_tls_secure_transport_readv( tls->timeout_msec = timeout_msec; - if (timeout_msec > 0) { - expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); - } else if (timeout_msec == MONGOC_SOCKET_TIMEOUT_NON_BLOCKING) { - expire = bson_get_monotonic_time(); - } + const mlib_timer timer = _mongoc_stream_tls_timer_from_timeout_msec(timeout_msec); for (i = 0; i < iovcnt; i++) { iov_pos = 0; @@ -334,22 +312,12 @@ _mongoc_stream_tls_secure_transport_readv( RETURN(-1); } - if (expire) { - now = bson_get_monotonic_time(); - - const int64_t remaining_msec = (expire - now) / 1000L; - - if (remaining_msec <= 0) { - if (read_ret == 0) { - mongoc_counter_streams_timeout_inc(); - errno = ETIMEDOUT; - RETURN(-1); - } + tls->timeout_msec = _mongoc_stream_tls_timer_to_timeout_msec(timer); - tls->timeout_msec = MONGOC_SOCKET_TIMEOUT_NON_BLOCKING; - } else { - tls->timeout_msec = remaining_msec; - } + if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE && read_ret == 0) { + mongoc_counter_streams_timeout_inc(); + errno = ETIMEDOUT; + RETURN(-1); } ret += read_ret; diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls.c b/src/libmongoc/src/mongoc/mongoc-stream-tls.c index 3c2494f410c..4a5ad7f666e 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls.c @@ -237,4 +237,34 @@ mongoc_stream_tls_new_with_secure_channel_cred(mongoc_stream_t *base_stream, } #endif // MONGOC_ENABLE_SSL_SECURE_CHANNEL +mlib_timer +_mongoc_stream_tls_timer_from_timeout_msec(int64_t timeout_msec) +{ + if (timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE) { + return mlib_expires_after(0, ms); + } else if (timeout_msec <= 0) { + return mlib_expires_never(); + } else { + return mlib_expires_after(timeout_msec, ms); + } +} + +int64_t +_mongoc_stream_tls_timer_to_timeout_msec(mlib_timer timer) +{ + const mlib_timer never = mlib_expires_never(); + + if (mlib_time_cmp(timer.expires_at, never.expires_at) == mlib_equal) { + return MONGOC_SOCKET_TIMEOUT_INFINITE; + } + + const int64_t remaining_msec = mlib_milliseconds_count(mlib_timer_remaining(timer)); + + if (remaining_msec <= 0) { + return MONGOC_SOCKET_TIMEOUT_IMMEDIATE; + } else { + return remaining_msec; + } +} + #endif diff --git a/src/libmongoc/src/mongoc/mongoc-stream.h b/src/libmongoc/src/mongoc/mongoc-stream.h index c4ccd4d0db3..07058787af8 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream.h +++ b/src/libmongoc/src/mongoc/mongoc-stream.h @@ -29,7 +29,8 @@ BSON_BEGIN_DECLS -#define MONGOC_SOCKET_TIMEOUT_NON_BLOCKING INT32_MIN +#define MONGOC_SOCKET_TIMEOUT_INFINITE 0 +#define MONGOC_SOCKET_TIMEOUT_IMMEDIATE INT32_MIN typedef struct _mongoc_stream_t mongoc_stream_t; diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index 9e2b607b52e..7556be19e71 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -2611,7 +2611,7 @@ mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri) if (str_maybe && strcasecmp(str_maybe, "inf") == 0) { // TODO: log and refer to ticket number - return 0; + return MONGOC_SOCKET_TIMEOUT_INFINITE; } return mongoc_uri_get_option_as_int32(uri, MONGOC_URI_SOCKETTIMEOUTMS, MONGOC_DEFAULT_SOCKETTIMEOUTMS); diff --git a/src/libmongoc/tests/test-mongoc-uri.c b/src/libmongoc/tests/test-mongoc-uri.c index 7ae3c0f2204..4168718091d 100644 --- a/src/libmongoc/tests/test-mongoc-uri.c +++ b/src/libmongoc/tests/test-mongoc-uri.c @@ -2624,7 +2624,7 @@ test_mongoc_uri_socket_timeout_ms(void) // TODO:link to ticket explaining "inf" ASSERT(mongoc_uri_set_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, "inf")); - ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, 0); + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, MONGOC_SOCKET_TIMEOUT_INFINITE); mongoc_uri_destroy(uri); @@ -2638,7 +2638,7 @@ test_mongoc_uri_socket_timeout_ms(void) uri = mongoc_uri_new("mongodb://localhost/?" MONGOC_URI_SOCKETTIMEOUTMS "=inf"); ASSERT(uri); - ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, 0); + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, MONGOC_SOCKET_TIMEOUT_INFINITE); mongoc_uri_destroy(uri); @@ -2646,7 +2646,7 @@ test_mongoc_uri_socket_timeout_ms(void) ASSERT(uri); // TODO:link to ticket explaining "inf" - ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), !=, 0); + ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), !=, MONGOC_SOCKET_TIMEOUT_INFINITE); ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, MONGOC_DEFAULT_SOCKETTIMEOUTMS); mongoc_uri_destroy(uri); From 2a458ef95faefe922d93753dd61d67ff98c8b41a Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Wed, 17 Dec 2025 12:38:49 -0500 Subject: [PATCH 15/27] Rename test case --- src/libmongoc/tests/test-mongoc-client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index 53dcf7c85d7..fec13397e01 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -3837,7 +3837,7 @@ test_killCursors(void) } static void -test_socketTimeoutMS_unlimited(void) +test_socketTimeoutMS_infinite(void) { mongoc_uri_t *const uri = test_framework_get_uri(); mongoc_uri_set_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, "inf"); @@ -4092,5 +4092,5 @@ test_client_install(TestSuite *suite) test_framework_skip_if_no_server_ssl); #endif TestSuite_AddLive(suite, "/Client/killCursors", test_killCursors); - TestSuite_AddLive(suite, "/Client/socketTimeoutMS_unlimited", test_socketTimeoutMS_unlimited); + TestSuite_AddLive(suite, "/Client/socketTimeoutMS_infinite", test_socketTimeoutMS_infinite); } From 2261cfd2df90c812e3ae68fdd18e608037e45b08 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Wed, 17 Dec 2025 12:42:24 -0500 Subject: [PATCH 16/27] Remove timing checks from test --- src/libmongoc/tests/test-mongoc-client.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index fec13397e01..9ffd8321ccd 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -3860,19 +3860,10 @@ test_socketTimeoutMS_infinite(void) &error); ASSERT_OR_PRINT(ok, error); - // Expect "ping" to take 500ms. - const mlib_time_point start = mlib_now(); - - // Send "ping": + // Ensure we can send a ping without timing out: ok = mongoc_client_command_simple(client, "admin", tmp_bson(BSON_STR({"ping" : 1})), NULL, NULL, &error); ASSERT_OR_PRINT(ok, error); - const mlib_time_point end = mlib_now(); - - const mlib_duration elapsed = mlib_time_difference(end, start); - - ASSERT_CMPINT64(mlib_microseconds_count(elapsed), >=, mlib_microseconds_count(mlib_duration(500, ms))); - mongoc_client_destroy(client); mongoc_uri_destroy(uri); } From 93b32a82574aae77c11cc3e75aacadd39952189c Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Wed, 17 Dec 2025 13:44:04 -0500 Subject: [PATCH 17/27] Refer to future fix ticket number --- src/libmongoc/src/mongoc/mongoc-uri.c | 7 ++++--- src/libmongoc/src/mongoc/mongoc-uri.h | 2 +- src/libmongoc/tests/test-mongoc-client.c | 1 + src/libmongoc/tests/test-mongoc-uri.c | 7 +++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index 7556be19e71..776148a6a54 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -734,7 +734,7 @@ mongoc_uri_option_is_utf8(const char *key) !strcasecmp(key, MONGOC_URI_SSLCLIENTCERTIFICATEKEYFILE) || !strcasecmp(key, MONGOC_URI_SSLCLIENTCERTIFICATEKEYPASSWORD) || !strcasecmp(key, MONGOC_URI_SSLCERTIFICATEAUTHORITYFILE) || - // temporarily allow `socketTimeoutMS=inf` to support unlimited timeouts + // CDRIVER-6177: Temporarily allow `socketTimeoutMS=inf` to support unlimited timeouts. !strcasecmp(key, MONGOC_URI_SOCKETTIMEOUTMS); } @@ -1004,7 +1004,7 @@ mongoc_uri_apply_options(mongoc_uri_t *uri, const bson_t *options, bool from_dns MONGOC_WARNING("Empty value provided for \"%s\"", key); } } else if (mongoc_uri_option_is_int32(key)) { - // temporarily allow `socketTimeoutMS=inf` to support unlimited timeouts + // CDRIVER-6177: Temporarily allow `socketTimeoutMS=inf` to support unlimited timeouts. if (strcasecmp(key, MONGOC_URI_SOCKETTIMEOUTMS) == 0 && strcasecmp(value, "inf") == 0) { _bson_upsert_utf8_icase(&uri->options, mstr_cstring(MONGOC_URI_SOCKETTIMEOUTMS), "inf"); } else if (0 < strlen(value)) { @@ -2610,7 +2610,8 @@ mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri) const char *const str_maybe = mongoc_uri_get_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, NULL); if (str_maybe && strcasecmp(str_maybe, "inf") == 0) { - // TODO: log and refer to ticket number + // CDRIVER-6177: To avoid a breaking change, use `socketTimeoutMS=inf` to specify an infinite timeout instead of + // `socketTimeoutMS=0`. return MONGOC_SOCKET_TIMEOUT_INFINITE; } diff --git a/src/libmongoc/src/mongoc/mongoc-uri.h b/src/libmongoc/src/mongoc/mongoc-uri.h index fd654268930..bd87243fb24 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.h +++ b/src/libmongoc/src/mongoc/mongoc-uri.h @@ -42,7 +42,7 @@ * You can change this by providing sockettimeoutms= in your * connection URI. * - * This default may be changed to 0 (unlimited timeout) + * CDRIVER-6177: This default is not spec compliant. */ #define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) #endif diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index 9ffd8321ccd..f2481b6d70c 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -3840,6 +3840,7 @@ static void test_socketTimeoutMS_infinite(void) { mongoc_uri_t *const uri = test_framework_get_uri(); + // CDRIVER-6177: We must use "inf" instead of 0 to disable the timeout. mongoc_uri_set_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, "inf"); mongoc_client_t *const client = test_framework_client_new_from_uri(uri, NULL); diff --git a/src/libmongoc/tests/test-mongoc-uri.c b/src/libmongoc/tests/test-mongoc-uri.c index 4168718091d..84f133ff966 100644 --- a/src/libmongoc/tests/test-mongoc-uri.c +++ b/src/libmongoc/tests/test-mongoc-uri.c @@ -2616,13 +2616,13 @@ test_mongoc_uri_socket_timeout_ms(void) mongoc_uri_t *uri = mongoc_uri_new("mongodb://localhost/"); ASSERT(uri); - // If sockettimeoutms is not set, return the C Driver's default + // If `socketTimeoutMS` is not set, return the C Driver's default. ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, MONGOC_DEFAULT_SOCKETTIMEOUTMS); ASSERT(mongoc_uri_set_option_as_int32(uri, MONGOC_URI_SOCKETTIMEOUTMS, 99)); ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, 99); - // TODO:link to ticket explaining "inf" + // CDRIVER-6177: `socketTimeoutMS=inf` is used to specify an infinite timeout instead of `socketTimeoutMS=0`. ASSERT(mongoc_uri_set_option_as_utf8(uri, MONGOC_URI_SOCKETTIMEOUTMS, "inf")); ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, MONGOC_SOCKET_TIMEOUT_INFINITE); @@ -2645,8 +2645,7 @@ test_mongoc_uri_socket_timeout_ms(void) uri = mongoc_uri_new("mongodb://localhost/?" MONGOC_URI_SOCKETTIMEOUTMS "=0"); ASSERT(uri); - // TODO:link to ticket explaining "inf" - ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), !=, MONGOC_SOCKET_TIMEOUT_INFINITE); + // CDRIVER-6177: `socketTimeoutMS=0` is treated as unset, so the default is used instead. ASSERT_CMPINT32(mongoc_uri_get_socket_timeout_ms_option(uri), ==, MONGOC_DEFAULT_SOCKETTIMEOUTMS); mongoc_uri_destroy(uri); From dfac42d8360ec5d49a6f943d39fcfbb69321fb15 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Wed, 17 Dec 2025 14:02:35 -0500 Subject: [PATCH 18/27] Warn if socketTimeoutMS=0 is specified --- src/libmongoc/src/mongoc/mongoc-uri.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index 776148a6a54..e35f29b28c2 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -2615,7 +2615,24 @@ mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri) return MONGOC_SOCKET_TIMEOUT_INFINITE; } - return mongoc_uri_get_option_as_int32(uri, MONGOC_URI_SOCKETTIMEOUTMS, MONGOC_DEFAULT_SOCKETTIMEOUTMS); + const bson_t *const options = mongoc_uri_get_options(uri); + bson_iter_t iter; + + if (options && bson_iter_init_find_case(&iter, options, MONGOC_URI_SOCKETTIMEOUTMS) && + BSON_ITER_HOLDS_INT32(&iter)) { + int32_t const value = bson_iter_int32(&iter); + + if (value == 0) { + MONGOC_WARNING("`socketTimeoutMS=0` cannot be used to disable socket timeouts. The default of %" PRId64 + " will be used instead. To disable socket timeouts, use `socketTimeoutMS=inf`.", + (int64_t)MONGOC_DEFAULT_SOCKETTIMEOUTMS); + return MONGOC_DEFAULT_SOCKETTIMEOUTMS; + } else { + return value; + } + } + + return MONGOC_DEFAULT_SOCKETTIMEOUTMS; } const char * From 3c59307ef31e161e677fdf2ee4556b2dd085a94d Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Wed, 17 Dec 2025 14:48:16 -0500 Subject: [PATCH 19/27] Add comment with ticket number --- src/libmongoc/src/mongoc/mongoc-uri.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index e35f29b28c2..aa9c4004df4 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -2623,6 +2623,7 @@ mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri) int32_t const value = bson_iter_int32(&iter); if (value == 0) { + // See CDRIVER-6177. MONGOC_WARNING("`socketTimeoutMS=0` cannot be used to disable socket timeouts. The default of %" PRId64 " will be used instead. To disable socket timeouts, use `socketTimeoutMS=inf`.", (int64_t)MONGOC_DEFAULT_SOCKETTIMEOUTMS); From 7f4a63c4254be51128a980c68f361c5a1ad8a43b Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Wed, 17 Dec 2025 15:04:11 -0500 Subject: [PATCH 20/27] Document socketTimeoutMS=inf --- src/libmongoc/doc/mongoc_uri_t.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libmongoc/doc/mongoc_uri_t.rst b/src/libmongoc/doc/mongoc_uri_t.rst index fd2ba609a7d..6a1ddcf996d 100644 --- a/src/libmongoc/doc/mongoc_uri_t.rst +++ b/src/libmongoc/doc/mongoc_uri_t.rst @@ -115,6 +115,8 @@ MONGOC_URI_SRVMAXHOSTS srvmaxhosts 0 The meaning of a timeout of ``0`` or a negative value may vary depending on the operation being executed, even when specified by the same URI option. To specify the documented default value for a \*timeoutMS option, use the `MONGOC_DEFAULT_*` constants defined in ``mongoc-client.h`` instead. + In the case of socketTimeoutMS, to disable the timeout completely (i.e., an infinite timeout), use ``socketTimeoutMS=inf``. + Authentication Options ---------------------- From 34f63a54048bb8dda1915676f15e87b9a1f4ac6d Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 18 Dec 2025 08:38:24 -0500 Subject: [PATCH 21/27] Document socketTimeoutMS=inf as a C Driver extension --- src/libmongoc/doc/mongoc_uri_t.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libmongoc/doc/mongoc_uri_t.rst b/src/libmongoc/doc/mongoc_uri_t.rst index 6a1ddcf996d..9404c29208b 100644 --- a/src/libmongoc/doc/mongoc_uri_t.rst +++ b/src/libmongoc/doc/mongoc_uri_t.rst @@ -115,7 +115,7 @@ MONGOC_URI_SRVMAXHOSTS srvmaxhosts 0 The meaning of a timeout of ``0`` or a negative value may vary depending on the operation being executed, even when specified by the same URI option. To specify the documented default value for a \*timeoutMS option, use the `MONGOC_DEFAULT_*` constants defined in ``mongoc-client.h`` instead. - In the case of socketTimeoutMS, to disable the timeout completely (i.e., an infinite timeout), use ``socketTimeoutMS=inf``. + In the case of socketTimeoutMS, to disable the timeout completely (i.e., an infinite timeout), use the C Driver extension ``socketTimeoutMS=inf``. Authentication Options ---------------------- From 6ff501c4b9d5d113e5c6fc7ae72c411c2a228f3d Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 18 Dec 2025 08:49:20 -0500 Subject: [PATCH 22/27] Use more familiar time point comparison syntax --- src/libmongoc/src/mongoc/mongoc-stream-tls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls.c b/src/libmongoc/src/mongoc/mongoc-stream-tls.c index 4a5ad7f666e..52c39312816 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls.c @@ -254,7 +254,7 @@ _mongoc_stream_tls_timer_to_timeout_msec(mlib_timer timer) { const mlib_timer never = mlib_expires_never(); - if (mlib_time_cmp(timer.expires_at, never.expires_at) == mlib_equal) { + if (mlib_time_cmp(timer.expires_at, ==, never.expires_at)) { return MONGOC_SOCKET_TIMEOUT_INFINITE; } From f65bb766aac51a2cdd21baf28d30f14d58d67e16 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 18 Dec 2025 08:58:54 -0500 Subject: [PATCH 23/27] Move new constants into private header --- src/libmongoc/src/mongoc/mongoc-stream-private.h | 3 +++ src/libmongoc/src/mongoc/mongoc-stream.h | 3 --- src/libmongoc/src/mongoc/mongoc-uri.c | 1 + src/libmongoc/tests/test-mongoc-uri.c | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-private.h b/src/libmongoc/src/mongoc/mongoc-stream-private.h index 66a50e75627..adfddb9a1a1 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-private.h +++ b/src/libmongoc/src/mongoc/mongoc-stream-private.h @@ -39,6 +39,9 @@ BSON_BEGIN_DECLS #define MONGOC_STREAM_GRIDFS_UPLOAD 6 #define MONGOC_STREAM_GRIDFS_DOWNLOAD 7 +#define MONGOC_SOCKET_TIMEOUT_INFINITE 0 +#define MONGOC_SOCKET_TIMEOUT_IMMEDIATE INT32_MIN + bool mongoc_stream_wait(mongoc_stream_t *stream, int64_t expire_at); diff --git a/src/libmongoc/src/mongoc/mongoc-stream.h b/src/libmongoc/src/mongoc/mongoc-stream.h index 07058787af8..de8901712d4 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream.h +++ b/src/libmongoc/src/mongoc/mongoc-stream.h @@ -29,9 +29,6 @@ BSON_BEGIN_DECLS -#define MONGOC_SOCKET_TIMEOUT_INFINITE 0 -#define MONGOC_SOCKET_TIMEOUT_IMMEDIATE INT32_MIN - typedef struct _mongoc_stream_t mongoc_stream_t; typedef struct _mongoc_stream_poll_t { diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index aa9c4004df4..e16eaec1639 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/src/libmongoc/tests/test-mongoc-uri.c b/src/libmongoc/tests/test-mongoc-uri.c index 84f133ff966..24dc34e7be9 100644 --- a/src/libmongoc/tests/test-mongoc-uri.c +++ b/src/libmongoc/tests/test-mongoc-uri.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include From fb8f40fa96decbdf1a345a799ed23eced72f5b50 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 18 Dec 2025 09:04:47 -0500 Subject: [PATCH 24/27] Fix inconsistent integer widths --- src/libmongoc/src/mongoc/mongoc-uri.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index e16eaec1639..6036a5e107a 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -2616,6 +2616,8 @@ mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri) return MONGOC_SOCKET_TIMEOUT_INFINITE; } + const int32_t fallback = MONGOC_DEFAULT_SOCKETTIMEOUTMS; + const bson_t *const options = mongoc_uri_get_options(uri); bson_iter_t iter; @@ -2625,16 +2627,16 @@ mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri) if (value == 0) { // See CDRIVER-6177. - MONGOC_WARNING("`socketTimeoutMS=0` cannot be used to disable socket timeouts. The default of %" PRId64 + MONGOC_WARNING("`socketTimeoutMS=0` cannot be used to disable socket timeouts. The default of %" PRId32 " will be used instead. To disable socket timeouts, use `socketTimeoutMS=inf`.", - (int64_t)MONGOC_DEFAULT_SOCKETTIMEOUTMS); - return MONGOC_DEFAULT_SOCKETTIMEOUTMS; + fallback); + return fallback; } else { return value; } } - return MONGOC_DEFAULT_SOCKETTIMEOUTMS; + return fallback; } const char * From 1773b8a32fe1882362089c83a77a121f8265c22d Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 18 Dec 2025 09:05:29 -0500 Subject: [PATCH 25/27] Fix inconsistent east/west const --- src/libmongoc/src/mongoc/mongoc-uri.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index 6036a5e107a..8f9cf697d87 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -2623,7 +2623,7 @@ mongoc_uri_get_socket_timeout_ms_option(const mongoc_uri_t *uri) if (options && bson_iter_init_find_case(&iter, options, MONGOC_URI_SOCKETTIMEOUTMS) && BSON_ITER_HOLDS_INT32(&iter)) { - int32_t const value = bson_iter_int32(&iter); + const int32_t value = bson_iter_int32(&iter); if (value == 0) { // See CDRIVER-6177. From 5314d229adcff33464162dd678d10b89238a20a9 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 18 Dec 2025 09:17:59 -0500 Subject: [PATCH 26/27] Move MONGOC_DEFAULT_SOCKETTIMEOUTMS back to mongoc-client.h --- src/libmongoc/src/mongoc/mongoc-client.h | 15 +++++++++++++++ src/libmongoc/src/mongoc/mongoc-uri.c | 2 ++ src/libmongoc/src/mongoc/mongoc-uri.h | 14 -------------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-client.h b/src/libmongoc/src/mongoc/mongoc-client.h index 3c89c0e70a4..55b74d0f610 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.h +++ b/src/libmongoc/src/mongoc/mongoc-client.h @@ -54,6 +54,21 @@ BSON_BEGIN_DECLS #endif +#ifndef MONGOC_DEFAULT_SOCKETTIMEOUTMS +/* + * NOTE: The default socket timeout for connections is 5 minutes. This + * means that if your MongoDB server dies or becomes unavailable + * it will take 5 minutes to detect this. + * + * You can change this by providing sockettimeoutms= in your + * connection URI. + * + * CDRIVER-6177: This default is not spec compliant. + */ +#define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) +#endif + + /** * mongoc_client_t: * diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index 8f9cf697d87..965d54f14d6 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -38,6 +38,8 @@ #include #include +// CDRIVER-6179: Including mongoc-client.h for MONGOC_DEFAULT_SOCKETTIMEOUTMS. +#include #include #include #include diff --git a/src/libmongoc/src/mongoc/mongoc-uri.h b/src/libmongoc/src/mongoc/mongoc-uri.h index bd87243fb24..ecf689d6ae7 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.h +++ b/src/libmongoc/src/mongoc/mongoc-uri.h @@ -33,20 +33,6 @@ #define MONGOC_DEFAULT_PORT 27017 #endif -#ifndef MONGOC_DEFAULT_SOCKETTIMEOUTMS -/* - * NOTE: The default socket timeout for connections is 5 minutes. This - * means that if your MongoDB server dies or becomes unavailable - * it will take 5 minutes to detect this. - * - * You can change this by providing sockettimeoutms= in your - * connection URI. - * - * CDRIVER-6177: This default is not spec compliant. - */ -#define MONGOC_DEFAULT_SOCKETTIMEOUTMS (1000L * 60L * 5L) -#endif - #define MONGOC_URI_APPNAME "appname" #define MONGOC_URI_AUTHMECHANISM "authmechanism" #define MONGOC_URI_AUTHMECHANISMPROPERTIES "authmechanismproperties" From 4b7dce8fef62172cdafbc824e90546cfcea544e8 Mon Sep 17 00:00:00 2001 From: Connor MacDonald Date: Thu, 18 Dec 2025 09:23:10 -0500 Subject: [PATCH 27/27] Fix fixed-width integer includes --- src/libmongoc/src/mongoc/mongoc-stream-private.h | 2 ++ src/libmongoc/src/mongoc/mongoc-stream.h | 2 -- src/libmongoc/src/mongoc/mongoc-uri.c | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-private.h b/src/libmongoc/src/mongoc/mongoc-stream-private.h index adfddb9a1a1..9fd71ccf575 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-private.h +++ b/src/libmongoc/src/mongoc/mongoc-stream-private.h @@ -27,6 +27,8 @@ #include +#include + BSON_BEGIN_DECLS diff --git a/src/libmongoc/src/mongoc/mongoc-stream.h b/src/libmongoc/src/mongoc/mongoc-stream.h index de8901712d4..723f5febd43 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream.h +++ b/src/libmongoc/src/mongoc/mongoc-stream.h @@ -23,8 +23,6 @@ #include #include -#include - BSON_BEGIN_DECLS diff --git a/src/libmongoc/src/mongoc/mongoc-uri.c b/src/libmongoc/src/mongoc/mongoc-uri.c index 965d54f14d6..70fc4a3284b 100644 --- a/src/libmongoc/src/mongoc/mongoc-uri.c +++ b/src/libmongoc/src/mongoc/mongoc-uri.c @@ -18,7 +18,9 @@ #include #include +#include #include +#include #include #include