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-buffer.c b/src/libmongoc/src/mongoc/mongoc-buffer.c index b7ea1b2234..a6a37a09c4 100644 --- a/src/libmongoc/src/mongoc/mongoc-buffer.c +++ b/src/libmongoc/src/mongoc/mongoc-buffer.c @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -158,7 +159,7 @@ _mongoc_buffer_append(mongoc_buffer_t *buffer, const uint8_t *data, size_t data_ * @buffer; A mongoc_buffer_t. * @stream: The stream to read from. * @size: The number of bytes to read. - * @timeout_msec: The number of milliseconds to wait or -1 for the default + * @timeout_msec: The number of milliseconds to wait * @error: A location for a bson_error_t, or NULL. * * Reads from stream @size bytes and stores them in @buffer. This can be used @@ -198,7 +199,7 @@ _mongoc_buffer_append_from_stream( RETURN(false); } - ret = mongoc_stream_read(stream, buf, size, size, (int32_t)timeout_msec); + ret = _mongoc_stream_read_impl(stream, buf, size, size, (int32_t)timeout_msec); if (mlib_cmp(ret, !=, size)) { _mongoc_set_error(error, MONGOC_ERROR_STREAM, @@ -261,7 +262,7 @@ _mongoc_buffer_fill( RETURN(false); } - ret = mongoc_stream_read(stream, &buffer->data[buffer->len], avail_bytes, min_bytes, (int32_t)timeout_msec); + ret = _mongoc_stream_read_impl(stream, &buffer->data[buffer->len], avail_bytes, min_bytes, (int32_t)timeout_msec); if (ret < 0) { _mongoc_set_error( @@ -291,7 +292,7 @@ _mongoc_buffer_fill( * @buffer; A mongoc_buffer_t. * @stream: The stream to read from. * @size: The number of bytes to read. - * @timeout_msec: The number of milliseconds to wait or -1 for the default + * @timeout_msec: The number of milliseconds to wait * * Reads from stream @size bytes and stores them in @buffer. This can be used * in conjunction with reading RPCs from a stream. You read from the stream @@ -328,7 +329,7 @@ _mongoc_buffer_try_append_from_stream(mongoc_buffer_t *buffer, RETURN(-1); } - ret = mongoc_stream_read(stream, buf, size, 0, (int32_t)timeout_msec); + ret = _mongoc_stream_read_impl(stream, buf, size, 0, (int32_t)timeout_msec); if (ret > 0) { buffer->len += (size_t)ret; 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..e2215c4a49 100644 --- a/src/libmongoc/src/mongoc/mongoc-cluster.c +++ b/src/libmongoc/src/mongoc/mongoc-cluster.c @@ -296,7 +296,7 @@ _mongoc_cluster_run_command_opquery_send( } mcd_rpc_message_egress(rpc); - if (!_mongoc_stream_writev_full(stream, iovecs, num_iovecs, cluster->sockettimeoutms, error)) { + if (!_mongoc_stream_writev_full_impl(stream, iovecs, num_iovecs, cluster->sockettimeoutms, error)) { RUN_CMD_ERR_DECORATE; _handle_network_error(cluster, cmd->server_stream, error); goto done; @@ -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 @@ -3015,7 +3014,7 @@ mongoc_cluster_legacy_rpc_sendv_to_server(mongoc_cluster_t *cluster, BSON_ASSERT(iovecs); mcd_rpc_message_egress(rpc); - if (!_mongoc_stream_writev_full(server_stream->stream, iovecs, num_iovecs, cluster->sockettimeoutms, error)) { + if (!_mongoc_stream_writev_full_impl(server_stream->stream, iovecs, num_iovecs, cluster->sockettimeoutms, error)) { GOTO(done); } @@ -3235,7 +3234,7 @@ _mongoc_cluster_run_opmsg_send( mcd_rpc_message_egress(rpc); const bool res = - _mongoc_stream_writev_full(server_stream->stream, iovecs, num_iovecs, cluster->sockettimeoutms, error); + _mongoc_stream_writev_full_impl(server_stream->stream, iovecs, num_iovecs, cluster->sockettimeoutms, error); if (!res) { RUN_CMD_ERR_DECORATE; diff --git a/src/libmongoc/src/mongoc/mongoc-crypt.c b/src/libmongoc/src/mongoc/mongoc-crypt.c index 60b9a4def7..a0d6d078c5 100644 --- a/src/libmongoc/src/mongoc/mongoc-crypt.c +++ b/src/libmongoc/src/mongoc/mongoc-crypt.c @@ -621,7 +621,7 @@ _state_need_kms(_state_machine_t *state_machine, bson_error_t *error) iov.iov_base = (char *)mongocrypt_binary_data(http_req); iov.iov_len = mongocrypt_binary_len(http_req); - if (!_mongoc_stream_writev_full(tls_stream, &iov, 1, sockettimeout, error)) { + if (!_mongoc_stream_writev_full_impl(tls_stream, &iov, 1, sockettimeout, error)) { if (mongocrypt_kms_ctx_fail(kms_ctx)) { continue; } else { @@ -649,7 +649,7 @@ _state_need_kms(_state_machine_t *state_machine, bson_error_t *error) bytes_needed = BUFFER_SIZE; } - read_ret = mongoc_stream_read(tls_stream, buf, bytes_needed, 1 /* min_bytes. */, sockettimeout); + read_ret = _mongoc_stream_read_impl(tls_stream, buf, bytes_needed, 1 /* min_bytes. */, sockettimeout); if (read_ret <= 0) { if (mongocrypt_kms_ctx_fail(kms_ctx)) { break; // Stop reading reply. diff --git a/src/libmongoc/src/mongoc/mongoc-secure-channel.c b/src/libmongoc/src/mongoc/mongoc-secure-channel.c index ed46b370e7..65263b2984 100644 --- a/src/libmongoc/src/mongoc/mongoc-secure-channel.c +++ b/src/libmongoc/src/mongoc/mongoc-secure-channel.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -716,7 +717,7 @@ mongoc_secure_channel_read(mongoc_stream_tls_t *tls, void *data, size_t data_len * size of the buffer. We are totally fine with just one TLS record (few *bytes) **/ - const ssize_t length = mongoc_stream_read(tls->base_stream, data, data_length, 0, (int32_t)tls->timeout_msec); + const ssize_t length = _mongoc_stream_read_impl(tls->base_stream, data, data_length, 0, (int32_t)tls->timeout_msec); TRACE("Got %zd", length); @@ -740,7 +741,8 @@ mongoc_secure_channel_write(mongoc_stream_tls_t *tls, const void *data, size_t d errno = 0; TRACE("Wanting to write: %zu", data_length); - const ssize_t length = mongoc_stream_write(tls->base_stream, (void *)data, data_length, (int32_t)tls->timeout_msec); + const ssize_t length = + _mongoc_stream_write_impl(tls->base_stream, (void *)data, data_length, (int32_t)tls->timeout_msec); TRACE("Wrote: %zd", length); return length; diff --git a/src/libmongoc/src/mongoc/mongoc-secure-transport.c b/src/libmongoc/src/mongoc/mongoc-secure-transport.c index 233cfcfd70..f5ee451e0d 100644 --- a/src/libmongoc/src/mongoc/mongoc-secure-transport.c +++ b/src/libmongoc/src/mongoc/mongoc-secure-transport.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -329,7 +330,7 @@ mongoc_secure_transport_read(SSLConnectionRef connection, void *data, size_t *da /* 4 arguments is *min_bytes* -- This is not a negotiation. * Secure Transport wants all or nothing. We must continue reading until * we get this amount, or timeout */ - length = mongoc_stream_read(tls->base_stream, data, *data_length, *data_length, tls->timeout_msec); + length = _mongoc_stream_readv_impl(tls->base_stream, data, *data_length, *data_length, tls->timeout_msec); if (length > 0) { *data_length = length; @@ -364,7 +365,7 @@ mongoc_secure_transport_write(SSLConnectionRef connection, const void *data, siz ENTRY; errno = 0; - length = mongoc_stream_write(tls->base_stream, (void *)data, *data_length, tls->timeout_msec); + length = _mongoc_stream_write_impl(tls->base_stream, (void *)data, *data_length, tls->timeout_msec); if (length >= 0) { *data_length = length; diff --git a/src/libmongoc/src/mongoc/mongoc-stream-private.h b/src/libmongoc/src/mongoc/mongoc-stream-private.h index 66a50e7562..b7794033a9 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,15 +41,42 @@ BSON_BEGIN_DECLS #define MONGOC_STREAM_GRIDFS_UPLOAD 6 #define MONGOC_STREAM_GRIDFS_DOWNLOAD 7 + bool mongoc_stream_wait(mongoc_stream_t *stream, int64_t expire_at); +mongoc_stream_t * +mongoc_stream_get_root_stream(mongoc_stream_t *stream); + bool _mongoc_stream_writev_full( mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, int64_t timeout_msec, bson_error_t *error); -mongoc_stream_t * -mongoc_stream_get_root_stream(mongoc_stream_t *stream); +/** + * The public stream API's timeout convention has no means of specifying an infinite timeout. The following "impl" + * functions do the same thing as their similarly-named counterparts, but the timeout arguments are in POSIX convention, + * i.e., negative values are interpreted as infinite rather than replaced with the default timeout. Custom stream + * implementations that wrap other streams should use these functions internally in order to ensure infinite timeouts + * are correctly propagated to underlying streams. + */ + +ssize_t +_mongoc_stream_writev_impl(mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, int32_t timeout_msec); + +ssize_t +_mongoc_stream_write_impl(mongoc_stream_t *stream, void *buf, size_t count, int32_t timeout_msec); + +ssize_t +_mongoc_stream_readv_impl( + mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, size_t min_bytes, int32_t timeout_msec); + +ssize_t +_mongoc_stream_read_impl(mongoc_stream_t *stream, void *buf, size_t count, size_t min_bytes, int32_t timeout_msec); + +bool +_mongoc_stream_writev_full_impl( + mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, int64_t timeout_msec, bson_error_t *error); + /** * @brief Poll the given set of streams @@ -59,6 +88,9 @@ mongoc_stream_get_root_stream(mongoc_stream_t *stream); ssize_t _mongoc_stream_poll_internal(mongoc_stream_poll_t *streams, size_t nstreams, mlib_timer until); +int32_t +_mongoc_stream_timeout_ms_to_posix_timeout_convention(int32_t timeout_msec); + BSON_END_DECLS diff --git a/src/libmongoc/src/mongoc/mongoc-stream-socket.c b/src/libmongoc/src/mongoc/mongoc-stream-socket.c index 2c020f4517..e213a3b487 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-socket.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-socket.c @@ -37,7 +37,7 @@ static BSON_INLINE int64_t get_expiration(int32_t timeout_msec) { if (timeout_msec < 0) { - return -1; + return -1; // Infinite. } else if (timeout_msec == 0) { return 0; } else { diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c index 0bcfee3394..dd5fcc350d 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c @@ -226,7 +226,7 @@ mongoc_stream_tls_openssl_bio_read(BIO *b, char *buf, int len) openssl = (mongoc_stream_tls_openssl_t *)tls->ctx; errno = 0; - const ssize_t ret = mongoc_stream_read(tls->base_stream, buf, (size_t)len, 0, (int32_t)tls->timeout_msec); + const ssize_t ret = _mongoc_stream_read_impl(tls->base_stream, buf, (size_t)len, 0, (int32_t)tls->timeout_msec); BIO_clear_retry_flags(b); if ((ret <= 0) && MONGOC_ERRNO_IS_AGAIN(errno)) { @@ -292,8 +292,8 @@ mongoc_stream_tls_openssl_bio_write(BIO *b, const char *buf, int len) } errno = 0; - TRACE("mongoc_stream_writev is expected to write: %d", len); - const ssize_t ret = mongoc_stream_writev(tls->base_stream, &iov, 1, (int32_t)tls->timeout_msec); + TRACE("_mongoc_stream_writev_impl is expected to write: %d", len); + const ssize_t ret = _mongoc_stream_writev_impl(tls->base_stream, &iov, 1, (int32_t)tls->timeout_msec); BIO_clear_retry_flags(b); if (len > ret) { diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c index 00c198f27c..545ffe49f1 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 == 0 && 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); @@ -422,9 +408,7 @@ _mongoc_stream_tls_openssl_readv( tls->timeout_msec = timeout_msec; tls->timed_out = false; - 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; @@ -444,25 +428,17 @@ _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(); - tls->timed_out = true; + if (tls->timeout_msec == 0 && read_ret == 0) { + mongoc_counter_streams_timeout_inc(); + tls->timed_out = true; #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 f99041ea2d..558d9cb97c 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 @@ -69,6 +71,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 8a4ab60313..2beebeb144 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); @@ -696,9 +694,7 @@ _mongoc_stream_tls_secure_channel_readv( tls->timeout_msec = timeout_msec; tls->timed_out = false; - 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; @@ -717,21 +713,13 @@ _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(); - tls->timed_out = true; - 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 == 0 && read_ret == 0) { + mongoc_counter_streams_timeout_inc(); + tls->timed_out = true; + 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 9724050927..50a9c57cd2 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 == 0 && 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; @@ -301,9 +287,7 @@ _mongoc_stream_tls_secure_transport_readv( tls->timeout_msec = timeout_msec; tls->timed_out = false; - 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; @@ -329,21 +313,13 @@ _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(); - tls->timed_out = true; - 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 == 0 && read_ret == 0) { + mongoc_counter_streams_timeout_inc(); + tls->timed_out = true; + 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..777c83d8e6 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls.c @@ -66,7 +66,7 @@ mongoc_stream_tls_handshake( BSON_ASSERT(stream_tls); BSON_ASSERT(stream_tls->handshake); - stream_tls->timeout_msec = timeout_msec; + stream_tls->timeout_msec = _mongoc_stream_timeout_ms_to_posix_timeout_convention(timeout_msec); return stream_tls->handshake(stream, host, events, error); } @@ -237,4 +237,26 @@ 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 < 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 -1; + } + + return mlib_milliseconds_count(mlib_timer_remaining(timer)); +} + #endif diff --git a/src/libmongoc/src/mongoc/mongoc-stream.c b/src/libmongoc/src/mongoc/mongoc-stream.c index 515de537bb..194583ecf9 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream.c +++ b/src/libmongoc/src/mongoc/mongoc-stream.c @@ -36,6 +36,8 @@ #include #include +#include + #undef MONGOC_LOG_DOMAIN #define MONGOC_LOG_DOMAIN "stream" @@ -150,23 +152,10 @@ mongoc_stream_flush(mongoc_stream_t *stream) ssize_t mongoc_stream_writev(mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, int32_t timeout_msec) { - ssize_t ret; - ENTRY; - BSON_ASSERT_PARAM(stream); - BSON_ASSERT_PARAM(iov); - BSON_ASSERT(iovcnt); - - BSON_ASSERT(stream->writev); - - // CDRIVER-4781: for backward compatibility. - if (timeout_msec < 0) { - timeout_msec = MONGOC_DEFAULT_TIMEOUT_MSEC; - } - - DUMP_IOVEC(writev, iov, iovcnt); - ret = stream->writev(stream, iov, iovcnt, timeout_msec); + const ssize_t ret = _mongoc_stream_writev_impl( + stream, iov, iovcnt, _mongoc_stream_timeout_ms_to_posix_timeout_convention(timeout_msec)); RETURN(ret); } @@ -185,20 +174,10 @@ mongoc_stream_writev(mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt ssize_t mongoc_stream_write(mongoc_stream_t *stream, void *buf, size_t count, int32_t timeout_msec) { - mongoc_iovec_t iov; - ssize_t ret; - ENTRY; - BSON_ASSERT_PARAM(stream); - BSON_ASSERT_PARAM(buf); - - iov.iov_base = buf; - iov.iov_len = count; - - BSON_ASSERT(stream->writev); - - ret = mongoc_stream_writev(stream, &iov, 1, timeout_msec); + const ssize_t ret = _mongoc_stream_write_impl( + stream, buf, count, _mongoc_stream_timeout_ms_to_posix_timeout_convention(timeout_msec)); RETURN(ret); } @@ -221,20 +200,10 @@ mongoc_stream_write(mongoc_stream_t *stream, void *buf, size_t count, int32_t ti ssize_t mongoc_stream_readv(mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, size_t min_bytes, int32_t timeout_msec) { - ssize_t ret; - ENTRY; - BSON_ASSERT_PARAM(stream); - BSON_ASSERT_PARAM(iov); - BSON_ASSERT(iovcnt); - - BSON_ASSERT(stream->readv); - - ret = stream->readv(stream, iov, iovcnt, min_bytes, timeout_msec); - if (ret >= 0) { - DUMP_IOVEC(readv, iov, iovcnt); - } + const ssize_t ret = _mongoc_stream_readv_impl( + stream, iov, iovcnt, min_bytes, _mongoc_stream_timeout_ms_to_posix_timeout_convention(timeout_msec)); RETURN(ret); } @@ -258,20 +227,10 @@ mongoc_stream_readv(mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, ssize_t mongoc_stream_read(mongoc_stream_t *stream, void *buf, size_t count, size_t min_bytes, int32_t timeout_msec) { - mongoc_iovec_t iov; - ssize_t ret; - ENTRY; - BSON_ASSERT_PARAM(stream); - BSON_ASSERT_PARAM(buf); - - iov.iov_base = buf; - iov.iov_len = count; - - BSON_ASSERT(stream->readv); - - ret = mongoc_stream_readv(stream, &iov, 1, min_bytes, timeout_msec); + const ssize_t ret = _mongoc_stream_read_impl( + stream, buf, count, min_bytes, _mongoc_stream_timeout_ms_to_posix_timeout_convention(timeout_msec)); RETURN(ret); } @@ -420,6 +379,18 @@ mongoc_stream_should_retry(mongoc_stream_t *stream) bool _mongoc_stream_writev_full( mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, int64_t timeout_msec, bson_error_t *error) +{ + ENTRY; + + const bool ret = _mongoc_stream_writev_full_impl( + stream, iov, iovcnt, _mongoc_stream_timeout_ms_to_posix_timeout_convention(timeout_msec), error); + + RETURN(ret); +} + +bool +_mongoc_stream_writev_full_impl( + mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, int64_t timeout_msec, bson_error_t *error) { size_t total_bytes = 0; ssize_t r; @@ -439,7 +410,7 @@ _mongoc_stream_writev_full( RETURN(false); } - r = mongoc_stream_writev(stream, iov, iovcnt, (int32_t)timeout_msec); + r = _mongoc_stream_writev_impl(stream, iov, iovcnt, (int32_t)timeout_msec); TRACE("writev returned: %zd", r); if (r < 0) { @@ -461,17 +432,116 @@ _mongoc_stream_writev_full( } if (mlib_cmp(r, !=, total_bytes)) { + char *timeout_str = NULL; + + if (timeout_msec == 0) { + timeout_str = bson_strdup("with an immediate timeout"); + } else if (timeout_msec < 0) { + timeout_str = bson_strdup("with an infinite timeout"); + } else { + timeout_str = bson_strdup_printf("in %" PRId64 "ms", timeout_msec); + } + _mongoc_set_error(error, MONGOC_ERROR_STREAM, MONGOC_ERROR_STREAM_SOCKET, - "Failure to send all requested bytes (only sent: %" PRIu64 "/%zu in %" PRId64 - "ms) during socket delivery", + "Failure to send all requested bytes (only sent: %" PRIu64 "/%zu %s) during socket delivery", (uint64_t)r, total_bytes, - timeout_msec); + timeout_str); + + bson_free(timeout_str); RETURN(false); } RETURN(true); } + +ssize_t +_mongoc_stream_writev_impl(mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, int32_t timeout_msec) +{ + ssize_t ret; + + ENTRY; + + BSON_ASSERT_PARAM(stream); + BSON_ASSERT_PARAM(iov); + BSON_ASSERT(iovcnt); + BSON_ASSERT(stream->writev); + + DUMP_IOVEC(writev, iov, iovcnt); + ret = stream->writev(stream, iov, iovcnt, timeout_msec); + + RETURN(ret); +} + +ssize_t +_mongoc_stream_write_impl(mongoc_stream_t *stream, void *buf, size_t count, int32_t timeout_msec) +{ + mongoc_iovec_t iov; + ssize_t ret; + + ENTRY; + + BSON_ASSERT_PARAM(stream); + BSON_ASSERT_PARAM(buf); + + iov.iov_base = buf; + iov.iov_len = count; + + BSON_ASSERT(stream->writev); + + ret = _mongoc_stream_writev_impl(stream, &iov, 1, timeout_msec); + + RETURN(ret); +} + +ssize_t +_mongoc_stream_readv_impl( + mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, size_t min_bytes, int32_t timeout_msec) +{ + ssize_t ret; + + ENTRY; + + BSON_ASSERT_PARAM(stream); + BSON_ASSERT_PARAM(iov); + BSON_ASSERT(iovcnt); + + BSON_ASSERT(stream->readv); + + ret = stream->readv(stream, iov, iovcnt, min_bytes, timeout_msec); + if (ret >= 0) { + DUMP_IOVEC(readv, iov, iovcnt); + } + + RETURN(ret); +} + +ssize_t +_mongoc_stream_read_impl(mongoc_stream_t *stream, void *buf, size_t count, size_t min_bytes, int32_t timeout_msec) +{ + mongoc_iovec_t iov; + ssize_t ret; + + ENTRY; + + BSON_ASSERT_PARAM(stream); + BSON_ASSERT_PARAM(buf); + + iov.iov_base = buf; + iov.iov_len = count; + + BSON_ASSERT(stream->readv); + + ret = _mongoc_stream_readv_impl(stream, &iov, 1, min_bytes, timeout_msec); + + RETURN(ret); +} + +int32_t +_mongoc_stream_timeout_ms_to_posix_timeout_convention(int32_t timeout_msec) +{ + return timeout_msec >= 0 ? timeout_msec : MONGOC_DEFAULT_TIMEOUT_MSEC; +} 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..01c76b377a 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,32 @@ 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 -1; + } + } + + const int32_t fallback = MONGOC_DEFAULT_SOCKETTIMEOUTMS; + + bson_iter_t iter = {0}; + + if (bson_iter_init_find_case(&iter, mongoc_uri_get_options(uri), MONGOC_URI_SOCKETTIMEOUTMS) && + BSON_ITER_HOLDS_INT32(&iter)) { + const int32_t value = bson_iter_int32(&iter); + + return value == 0 ? fallback : value; + } + + return fallback; +} const char * mongoc_uri_get_srv_hostname(const mongoc_uri_t *uri) @@ -3006,6 +3042,13 @@ _mongoc_uri_set_option_as_int32_with_error(mongoc_uri_t *uri, return false; } + if (strcmp(option_lowercase, MONGOC_URI_SOCKETTIMEOUTMS) == 0 && 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`.", + (int32_t)MONGOC_DEFAULT_SOCKETTIMEOUTMS); + } + bson_free(option_lowercase); return true; } diff --git a/src/libmongoc/tests/debug-stream.c b/src/libmongoc/tests/debug-stream.c index 578638b966..b8e9431c82 100644 --- a/src/libmongoc/tests/debug-stream.c +++ b/src/libmongoc/tests/debug-stream.c @@ -15,6 +15,7 @@ */ #include +#include #include @@ -78,14 +79,14 @@ static ssize_t _mongoc_stream_debug_readv( mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, size_t min_bytes, int32_t timeout_msec) { - return mongoc_stream_readv(((mongoc_stream_debug_t *)stream)->wrapped, iov, iovcnt, min_bytes, timeout_msec); + return _mongoc_stream_readv_impl(((mongoc_stream_debug_t *)stream)->wrapped, iov, iovcnt, min_bytes, timeout_msec); } static ssize_t _mongoc_stream_debug_writev(mongoc_stream_t *stream, mongoc_iovec_t *iov, size_t iovcnt, int32_t timeout_msec) { - return mongoc_stream_writev(((mongoc_stream_debug_t *)stream)->wrapped, iov, iovcnt, timeout_msec); + return _mongoc_stream_writev_impl(((mongoc_stream_debug_t *)stream)->wrapped, iov, iovcnt, timeout_msec); } 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..f615db7734 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,62 @@ 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), ==, -1); + + 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), ==, -1); + + mongoc_uri_destroy(uri); + + capture_logs(true); + + uri = mongoc_uri_new("mongodb://localhost/?" MONGOC_URI_SOCKETTIMEOUTMS "=0"); + ASSERT(uri); + ASSERT_CAPTURED_LOG( + "mongoc_uri_new", MONGOC_LOG_LEVEL_WARNING, "`socketTimeoutMS=0` cannot be used to disable socket timeouts"); + + capture_logs(false); + + // 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_new", MONGOC_LOG_LEVEL_WARNING, "Unsupported value for \"sockettimeoutms\": \"garbage\""); + + capture_logs(false); +} + #define INVALID(_uri, _host) \ ASSERT_WITH_MSG(!mongoc_uri_upsert_host((_uri), (_host), 1, &error), "expected host upsert to fail"); \ @@ -3335,6 +3392,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);