Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2cf5adc
Add failing test
connorsmacd Dec 4, 2025
6ed9323
Fix socketTimeoutMS=0 ignored in URI
connorsmacd Dec 4, 2025
596727c
Treat 0 timeout as infinite
connorsmacd Dec 4, 2025
5432608
Fix meaning of 0 timeout for TLS
connorsmacd Dec 4, 2025
bb23cbd
Revert "Fix socketTimeoutMS=0 ignored in URI"
connorsmacd Dec 4, 2025
5678eec
Support socketTimeoutMS=inf
connorsmacd Dec 8, 2025
d4d1f45
Set SSL opts on test client
connorsmacd Dec 9, 2025
4fb27cb
Remove include
connorsmacd Dec 9, 2025
8c12d39
Merge branch 'master' into sockettimeoutms_0.CDRIVER-5815
connorsmacd Dec 9, 2025
d9811a7
Apply timeout changes to OpenSSL TLS
connorsmacd Dec 10, 2025
969fe26
Handle case where immediate timeout is desired
connorsmacd Dec 11, 2025
6e1a6db
Apply timeout channels to secure channel
connorsmacd Dec 11, 2025
52c1672
Fix non-blocking timeout handling in TLS
connorsmacd Dec 16, 2025
6d0b809
Fix handling of sub-millisecond timeouts
connorsmacd Dec 16, 2025
f8056fd
Refactor timeout handling to be less error-prone
connorsmacd Dec 17, 2025
2a458ef
Rename test case
connorsmacd Dec 17, 2025
2261cfd
Remove timing checks from test
connorsmacd Dec 17, 2025
93b32a8
Refer to future fix ticket number
connorsmacd Dec 17, 2025
dfac42d
Warn if socketTimeoutMS=0 is specified
connorsmacd Dec 17, 2025
3c59307
Add comment with ticket number
connorsmacd Dec 17, 2025
7f4a63c
Document socketTimeoutMS=inf
connorsmacd Dec 17, 2025
34f63a5
Document socketTimeoutMS=inf as a C Driver extension
connorsmacd Dec 18, 2025
6ff501c
Use more familiar time point comparison syntax
connorsmacd Dec 18, 2025
f65bb76
Move new constants into private header
connorsmacd Dec 18, 2025
fb8f40f
Fix inconsistent integer widths
connorsmacd Dec 18, 2025
1773b8a
Fix inconsistent east/west const
connorsmacd Dec 18, 2025
5314d22
Move MONGOC_DEFAULT_SOCKETTIMEOUTMS back to mongoc-client.h
connorsmacd Dec 18, 2025
4b7dce8
Fix fixed-width integer includes
connorsmacd Dec 18, 2025
20f7232
Fix timeout convention API break
connorsmacd Jan 5, 2026
18ed8c2
Fix stream timeout test
connorsmacd Jan 5, 2026
ef1b546
Fix incorrect function name in assert
connorsmacd Jan 5, 2026
61594eb
Capture log in test
connorsmacd Jan 5, 2026
718dbbe
Disable log capturing before ending test case
connorsmacd Jan 5, 2026
94bf2aa
Reduce log noise
connorsmacd Jan 5, 2026
24c8588
Fix typo
connorsmacd Jan 5, 2026
4a992df
Document magic number in get_expiration
connorsmacd Jan 5, 2026
2ced1f6
Reduce variable scope
connorsmacd Jan 5, 2026
ed42d5a
Remove unnecessary null check
connorsmacd Jan 5, 2026
84640dc
Zero-intilize bson_iter_t
connorsmacd Jan 5, 2026
2ed7d2d
Fix missing include
connorsmacd Jan 5, 2026
1e0ada2
Use string duplication instead of fixed-sized buffer
connorsmacd Jan 6, 2026
e12a859
Use POSIX convention for streams
connorsmacd Jan 8, 2026
93f670d
Merge branch 'master' into sockettimeoutms_0.CDRIVER-5815
connorsmacd Jan 8, 2026
c7a3a90
Fix misplaced line from merge
connorsmacd Jan 8, 2026
2192c3c
Fix accidentally-added includes
connorsmacd Jan 8, 2026
cedded7
Revert documentation change
connorsmacd Jan 8, 2026
6eda124
Fix stray MONGOC_SOCKET_TIMEOUT_IMMEDIATE
connorsmacd Jan 8, 2026
1f319e2
Restore timeout_msec==0 case
connorsmacd Jan 9, 2026
d45bd4c
Use POSIX convention for socketTimeoutMS rep
connorsmacd Jan 9, 2026
23aedde
Remove reference to socket timeout convention
connorsmacd Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libmongoc/doc/mongoc_uri_t.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------------

Expand Down
11 changes: 6 additions & 5 deletions src/libmongoc/src/mongoc/mongoc-buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <mongoc/mongoc-buffer-private.h>
#include <mongoc/mongoc-error-private.h>
#include <mongoc/mongoc-stream-private.h>
#include <mongoc/mongoc-trace-private.h>

#include <bson/bson.h>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/libmongoc/src/mongoc/mongoc-client.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions src/libmongoc/src/mongoc/mongoc-cluster.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/libmongoc/src/mongoc/mongoc-crypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 4 additions & 2 deletions src/libmongoc/src/mongoc/mongoc-secure-channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <mongoc/mongoc-errno-private.h>
#include <mongoc/mongoc-error-private.h>
#include <mongoc/mongoc-secure-channel-private.h>
#include <mongoc/mongoc-stream-private.h>
#include <mongoc/mongoc-stream-tls-private.h>
#include <mongoc/mongoc-stream-tls-secure-channel-private.h>
#include <mongoc/mongoc-trace-private.h>
Expand Down Expand Up @@ -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);

Expand All @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/libmongoc/src/mongoc/mongoc-secure-transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <common-macros-private.h>
#include <common-string-private.h>
#include <mongoc/mongoc-secure-transport-private.h>
#include <mongoc/mongoc-stream-private.h>
#include <mongoc/mongoc-stream-tls-private.h>
#include <mongoc/mongoc-stream-tls-secure-transport-private.h>
#include <mongoc/mongoc-trace-private.h>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
36 changes: 34 additions & 2 deletions src/libmongoc/src/mongoc/mongoc-stream-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

#include <mlib/timer.h>

#include <stdint.h>


BSON_BEGIN_DECLS

Expand All @@ -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
Expand All @@ -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


Expand Down
2 changes: 1 addition & 1 deletion src/libmongoc/src/mongoc/mongoc-stream-socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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) {
Expand Down
48 changes: 12 additions & 36 deletions src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions src/libmongoc/src/mongoc/mongoc-stream-tls-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

#include <bson/bson.h>

#include <mlib/timer.h>

#ifdef MONGOC_ENABLE_SSL_OPENSSL
#include <openssl/ssl.h>
#endif
Expand Down Expand Up @@ -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 */
Loading