diff --git a/src/libmongoc/doc/mongoc_uri_t.rst b/src/libmongoc/doc/mongoc_uri_t.rst index fd2ba609a7..9404c29208 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 the C Driver extension ``socketTimeoutMS=inf``. + Authentication Options ---------------------- diff --git a/src/libmongoc/src/mongoc/mongoc-async-cmd.c b/src/libmongoc/src/mongoc/mongoc-async-cmd.c index 3ca7acbb16..e713b35c7c 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_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-client.h b/src/libmongoc/src/mongoc/mongoc-client.h index 2b53c91b8b..55b74d0f61 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.h +++ b/src/libmongoc/src/mongoc/mongoc-client.h @@ -62,6 +62,8 @@ BSON_BEGIN_DECLS * * 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 diff --git a/src/libmongoc/src/mongoc/mongoc-cluster.c b/src/libmongoc/src/mongoc/mongoc-cluster.c index 0519ec0a17..580eb40de5 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-stream-private.h b/src/libmongoc/src/mongoc/mongoc-stream-private.h index 66a50e7562..9fd71ccf57 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 @@ -39,6 +41,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-socket.c b/src/libmongoc/src/mongoc/mongoc-stream-socket.c index 2c020f4517..a6bfaabba8 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-socket.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-socket.c @@ -36,10 +36,10 @@ struct _mongoc_stream_socket_t { static BSON_INLINE int64_t get_expiration(int32_t timeout_msec) { - if (timeout_msec < 0) { - return -1; - } else if (timeout_msec == 0) { + if (timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE) { 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-tls-openssl.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c index 298e0fe48d..106099b1e1 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c @@ -201,17 +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); - } + 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); @@ -220,18 +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(); + tls->timeout_msec = _mongoc_stream_tls_timer_to_timeout_msec(timer); - if ((expire - now) < 0) { - if (mlib_cmp(ret, <, buf_len)) { - mongoc_counter_streams_timeout_inc(); - } - - tls->timeout_msec = 0; - } else { - tls->timeout_msec = (expire - now) / 1000; - } + if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE && mlib_cmp(ret, <, buf_len)) { + mongoc_counter_streams_timeout_inc(); } RETURN(ret); @@ -411,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); @@ -421,9 +407,7 @@ _mongoc_stream_tls_openssl_readv( tls->timeout_msec = timeout_msec; - if (timeout_msec >= 0) { - expire = bson_get_monotonic_time() + (timeout_msec * 1000UL); - } + const mlib_timer timer = _mongoc_stream_tls_timer_from_timeout_msec(timeout_msec); for (i = 0; i < iovcnt; i++) { iov_pos = 0; @@ -443,24 +427,16 @@ _mongoc_stream_tls_openssl_readv( return -1; } - if (expire) { - now = bson_get_monotonic_time(); + tls->timeout_msec = _mongoc_stream_tls_timer_to_timeout_msec(timer); - if ((expire - now) < 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 = 0; - } else { - tls->timeout_msec = (expire - now) / 1000L; - } + 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 c17e992ff2..f7624492aa 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 0d4b70d616..79dd8a6aaf 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,9 +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); - } + const mlib_timer timer = _mongoc_stream_tls_timer_from_timeout_msec(timeout_msec); for (i = 0; i < iovcnt; i++) { iov_pos = 0; @@ -716,20 +712,12 @@ _mongoc_stream_tls_secure_channel_readv( RETURN(-1); } - if (expire) { - now = bson_get_monotonic_time(); - - if ((expire - now) < 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 = 0; - } else { - tls->timeout_msec = (expire - now) / 1000L; - } + 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 71bc8a1579..5b24669410 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-secure-transport.c @@ -115,15 +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); - } + 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); @@ -139,18 +135,10 @@ _mongoc_stream_tls_secure_transport_write(mongoc_stream_t *stream, char *buf, si RETURN(-1); } - if (expire) { - now = bson_get_monotonic_time(); - - if ((expire - now) < 0) { - if (write_ret < (ssize_t)buf_len) { - mongoc_counter_streams_timeout_inc(); - } + tls->timeout_msec = _mongoc_stream_tls_timer_to_timeout_msec(timer); - tls->timeout_msec = 0; - } else { - tls->timeout_msec = (expire - now) / 1000L; - } + if (tls->timeout_msec == MONGOC_SOCKET_TIMEOUT_IMMEDIATE && write_ret < (ssize_t)buf_len) { + mongoc_counter_streams_timeout_inc(); } RETURN(write_ret); @@ -287,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; @@ -300,9 +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); - } + const mlib_timer timer = _mongoc_stream_tls_timer_from_timeout_msec(timeout_msec); for (i = 0; i < iovcnt; i++) { iov_pos = 0; @@ -328,20 +312,12 @@ _mongoc_stream_tls_secure_transport_readv( RETURN(-1); } - if (expire) { - now = bson_get_monotonic_time(); - - if ((expire - now) < 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 = 0; - } else { - tls->timeout_msec = (expire - now) / 1000L; - } + 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 3c2494f410..52c3931281 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)) { + 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-uri-private.h b/src/libmongoc/src/mongoc/mongoc-uri-private.h index 5e9dc2ac38..887b3e3ef8 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 dcc98eabb6..70fc4a3284 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 @@ -31,12 +33,15 @@ #include #include #include +#include #include #include #include #include #include +// CDRIVER-6179: Including mongoc-client.h for MONGOC_DEFAULT_SOCKETTIMEOUTMS. +#include #include #include #include @@ -733,7 +738,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) || + // CDRIVER-6177: Temporarily allow `socketTimeoutMS=inf` to support unlimited timeouts. + !strcasecmp(key, MONGOC_URI_SOCKETTIMEOUTMS); } const char * @@ -1002,7 +1009,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)) { + // 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)) { int32_t i32 = 42424242; if (mlib_i32_parse(mstr_cstring(value), &i32)) { goto UNSUPPORTED_VALUE; @@ -2599,6 +2609,39 @@ 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) { + // CDRIVER-6177: To avoid a breaking change, use `socketTimeoutMS=inf` to specify an infinite timeout instead of + // `socketTimeoutMS=0`. + 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; + + if (options && bson_iter_init_find_case(&iter, options, MONGOC_URI_SOCKETTIMEOUTMS) && + BSON_ITER_HOLDS_INT32(&iter)) { + const int32_t 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 %" PRId32 + " will be used instead. To disable socket timeouts, use `socketTimeoutMS=inf`.", + fallback); + return fallback; + } else { + return value; + } + } + + return fallback; +} const char * mongoc_uri_get_srv_hostname(const mongoc_uri_t *uri) diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index 04ba91a023..f2481b6d70 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -3836,6 +3836,39 @@ test_killCursors(void) mongoc_client_destroy(client); } +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); + test_framework_set_ssl_opts(client); + + // 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); + + // 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); + + mongoc_client_destroy(client); + mongoc_uri_destroy(uri); +} + void test_client_install(TestSuite *suite) { @@ -4051,4 +4084,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_infinite", test_socketTimeoutMS_infinite); } diff --git a/src/libmongoc/tests/test-mongoc-uri.c b/src/libmongoc/tests/test-mongoc-uri.c index 9dc98d361a..24dc34e7be 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 @@ -2610,6 +2611,55 @@ 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); + + // 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); + + 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), ==, MONGOC_SOCKET_TIMEOUT_INFINITE); + + mongoc_uri_destroy(uri); + + uri = mongoc_uri_new("mongodb://localhost/?" MONGOC_URI_SOCKETTIMEOUTMS "=0"); + ASSERT(uri); + + // 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); + + 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"); \ @@ -3335,6 +3385,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);