From d8b898fd248d0e91a7b3687484b4c569e5008fc2 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:35 +0300 Subject: [PATCH] net: websocket client connection in windows (fixes #24715) --- vlib/net/mbedtls/mbedtls_read_timeout_test.v | 4 ++ vlib/net/mbedtls/ssl_connection.c.v | 43 ++++++++++----- vlib/net/openssl/ssl_connection.c.v | 55 ++++++++++++------- vlib/net/websocket/websocket_client.v | 2 +- ...bsocket_client_default_read_timeout_test.v | 10 ++++ 5 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 vlib/v/tests/websocket_client_default_read_timeout_test.v diff --git a/vlib/net/mbedtls/mbedtls_read_timeout_test.v b/vlib/net/mbedtls/mbedtls_read_timeout_test.v index a39f63c17..ab78eda49 100644 --- a/vlib/net/mbedtls/mbedtls_read_timeout_test.v +++ b/vlib/net/mbedtls/mbedtls_read_timeout_test.v @@ -1,6 +1,7 @@ // vtest build: !(windows && tinyc) // TODO: fix these by adding declarations for the missing functions in the prebuilt tcc module mbedtls +import net import time fn test_ssl_conn_read_timeout_can_be_configured_at_runtime() ! { @@ -13,4 +14,7 @@ fn test_ssl_conn_read_timeout_can_be_configured_at_runtime() ! { timeout := 5 * time.minute conn.set_read_timeout(timeout) assert conn.read_timeout() == timeout + + conn.set_read_timeout(net.infinite_timeout) + assert conn.read_timeout() == net.infinite_timeout } diff --git a/vlib/net/mbedtls/ssl_connection.c.v b/vlib/net/mbedtls/ssl_connection.c.v index ab4321d09..b54099fdf 100644 --- a/vlib/net/mbedtls/ssl_connection.c.v +++ b/vlib/net/mbedtls/ssl_connection.c.v @@ -360,16 +360,30 @@ pub: } fn ssl_read_timeout_ms(timeout time.Duration) u32 { - timeout_ms := timeout.milliseconds() - if timeout_ms <= 0 { + if timeout <= 0 || timeout == net.infinite_timeout { return 0 } + timeout_ms := timeout.milliseconds() if timeout_ms > i64(max_u32) { return max_u32 } return u32(timeout_ms) } +fn ssl_timeout_deadline(timeout time.Duration) time.Time { + if timeout <= 0 || timeout == net.infinite_timeout { + return time.unix(0) + } + return time.now().add(timeout) +} + +fn ssl_remaining_timeout(deadline time.Time) time.Duration { + if deadline.unix() == 0 { + return net.infinite_timeout + } + return deadline - time.now() +} + // read_timeout returns the current SSL read timeout. pub fn (s &SSLConn) read_timeout() time.Duration { return s.read_timeout @@ -608,7 +622,7 @@ pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int { } } - deadline := time.now().add(s.duration) + deadline := ssl_timeout_deadline(s.duration) // s.wait_for_read(deadline - time.now())! for { res = C.mbedtls_ssl_read(&s.ssl, buf_ptr, len) @@ -622,7 +636,7 @@ pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int { } else { match res { C.MBEDTLS_ERR_SSL_WANT_READ { - s.wait_for_read(deadline - time.now()) or { + s.wait_for_read(ssl_remaining_timeout(deadline)) or { $if trace_ssl ? { eprintln('${@METHOD} ---> res: ${err}, C.MBEDTLS_ERR_SSL_WANT_READ') } @@ -630,7 +644,7 @@ pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int { } } C.MBEDTLS_ERR_SSL_WANT_WRITE { - s.wait_for_write(deadline - time.now()) or { + s.wait_for_write(ssl_remaining_timeout(deadline)) or { $if trace_ssl ? { eprintln('${@METHOD} ---> res: ${err}, C.MBEDTLS_ERR_SSL_WANT_WRITE') } @@ -686,7 +700,7 @@ pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int { } } - deadline := time.now().add(s.duration) + deadline := ssl_timeout_deadline(s.duration) unsafe { mut ptr_base := bytes for total_sent < len { @@ -696,11 +710,11 @@ pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int { if sent <= 0 { match sent { C.MBEDTLS_ERR_SSL_WANT_READ { - s.wait_for_read(deadline - time.now())! + s.wait_for_read(ssl_remaining_timeout(deadline))! continue } C.MBEDTLS_ERR_SSL_WANT_WRITE { - s.wait_for_write(deadline - time.now())! + s.wait_for_write(ssl_remaining_timeout(deadline))! continue } else { @@ -740,9 +754,10 @@ fn select(handle int, test Select, timeout time.Duration) !bool { C.FD_ZERO(&set) C.FD_SET(handle, &set) - deadline := time.now().add(timeout) - mut remaining_time := timeout.milliseconds() - for remaining_time > 0 { + is_infinite := timeout <= 0 || timeout == net.infinite_timeout + deadline := ssl_timeout_deadline(timeout) + mut remaining_time := if is_infinite { i64(0) } else { timeout.milliseconds() } + for is_infinite || remaining_time > 0 { seconds := remaining_time / 1000 microseconds := (remaining_time % 1000) * 1000 @@ -750,7 +765,7 @@ fn select(handle int, test Select, timeout time.Duration) !bool { tv_sec: u64(seconds) tv_usec: u64(microseconds) } - timeval_timeout := if timeout < 0 { &C.timeval(unsafe { nil }) } else { &tt } + timeval_timeout := if is_infinite { &C.timeval(unsafe { nil }) } else { &tt } mut res := -1 match test { @@ -767,7 +782,9 @@ fn select(handle int, test Select, timeout time.Duration) !bool { if res < 0 { if C.errno == C.EINTR { // errno is 4, Spurious wakeup from signal, keep waiting - remaining_time = (deadline - time.now()).milliseconds() + if !is_infinite { + remaining_time = ssl_remaining_timeout(deadline).milliseconds() + } continue } cerr := C.errno diff --git a/vlib/net/openssl/ssl_connection.c.v b/vlib/net/openssl/ssl_connection.c.v index d5864ed6b..508e0f8bf 100644 --- a/vlib/net/openssl/ssl_connection.c.v +++ b/vlib/net/openssl/ssl_connection.c.v @@ -51,6 +51,20 @@ enum Select { except } +fn ssl_timeout_deadline(timeout time.Duration) time.Time { + if timeout <= 0 || timeout == net.infinite_timeout { + return time.unix(0) + } + return time.now().add(timeout) +} + +fn ssl_remaining_timeout(deadline time.Time) time.Duration { + if deadline.unix() == 0 { + return net.infinite_timeout + } + return deadline - time.now() +} + // close closes the ssl connection and does cleanup pub fn (mut s SSLConn) close() ! { s.shutdown()! @@ -63,7 +77,7 @@ pub fn (mut s SSLConn) shutdown() ! { } if s.ssl != 0 { - deadline := time.now().add(s.duration) + deadline := ssl_timeout_deadline(s.duration) for { mut res := C.SSL_shutdown(voidptr(s.ssl)) if res == 1 { @@ -74,10 +88,10 @@ pub fn (mut s SSLConn) shutdown() ! { break // We break to free rest of resources } if err_res == .ssl_error_want_read { - s.wait_for_read(deadline - time.now())! + s.wait_for_read(ssl_remaining_timeout(deadline))! continue } else if err_res == .ssl_error_want_write { - s.wait_for_write(deadline - time.now())! + s.wait_for_write(ssl_remaining_timeout(deadline))! continue } if s.ssl != 0 { @@ -200,7 +214,7 @@ fn (mut s SSLConn) complete_connect() ! { eprintln(@METHOD) } - deadline := time.now().add(s.duration) + deadline := ssl_timeout_deadline(s.duration) for { mut res := C.SSL_connect(voidptr(s.ssl)) if res == 1 { @@ -209,11 +223,11 @@ fn (mut s SSLConn) complete_connect() ! { err_res := ssl_error(res, s.ssl)! if err_res == .ssl_error_want_read { - s.wait_for_read(deadline - time.now())! + s.wait_for_read(ssl_remaining_timeout(deadline))! continue } if err_res == .ssl_error_want_write { - s.wait_for_write(deadline - time.now())! + s.wait_for_write(ssl_remaining_timeout(deadline))! continue } return error('net.openssl SSLConn.complete_connect, could not connect using SSL. (${err_res}),err') @@ -229,10 +243,10 @@ fn (mut s SSLConn) complete_connect() ! { err_res := ssl_error(res, s.ssl)! if err_res == .ssl_error_want_read { - s.wait_for_read(deadline - time.now())! + s.wait_for_read(ssl_remaining_timeout(deadline))! continue } else if err_res == .ssl_error_want_write { - s.wait_for_write(deadline - time.now())! + s.wait_for_write(ssl_remaining_timeout(deadline))! continue } return error('net.openssl SSLConn.complete_connect, could not validate SSL certificate. (${err_res}),err') @@ -270,7 +284,7 @@ pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int { } } - deadline := time.now().add(s.duration) + deadline := ssl_timeout_deadline(s.duration) // s.wait_for_read(deadline - time.now())! for { res = C.SSL_read(voidptr(s.ssl), buf_ptr, len) @@ -285,7 +299,7 @@ pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int { err_res := ssl_error(res, s.ssl)! match err_res { .ssl_error_want_read { - s.wait_for_read(deadline - time.now()) or { + s.wait_for_read(ssl_remaining_timeout(deadline)) or { $if trace_ssl ? { eprintln('${@METHOD} ---> res: ${err} .ssl_error_want_read') } @@ -293,7 +307,7 @@ pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int { } } .ssl_error_want_write { - s.wait_for_write(deadline - time.now()) or { + s.wait_for_write(ssl_remaining_timeout(deadline)) or { $if trace_ssl ? { eprintln('${@METHOD} ---> res: ${err} .ssl_error_want_write') } @@ -336,7 +350,7 @@ pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int { } } - deadline := time.now().add(s.duration) + deadline := ssl_timeout_deadline(s.duration) unsafe { mut ptr_base := bytes for total_sent < len { @@ -346,10 +360,10 @@ pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int { if sent <= 0 { err_res := ssl_error(sent, s.ssl)! if err_res == .ssl_error_want_read { - s.wait_for_read(deadline - time.now())! + s.wait_for_read(ssl_remaining_timeout(deadline))! continue } else if err_res == .ssl_error_want_write { - s.wait_for_write(deadline - time.now())! + s.wait_for_write(ssl_remaining_timeout(deadline))! continue } else if err_res == .ssl_error_zero_return { $if trace_ssl ? { @@ -391,9 +405,10 @@ fn select(handle int, test Select, timeout time.Duration) !bool { C.FD_ZERO(&set) C.FD_SET(handle, &set) - deadline := time.now().add(timeout) - mut remaining_time := timeout.milliseconds() - for remaining_time > 0 { + is_infinite := timeout <= 0 || timeout == net.infinite_timeout + deadline := ssl_timeout_deadline(timeout) + mut remaining_time := if is_infinite { i64(0) } else { timeout.milliseconds() } + for is_infinite || remaining_time > 0 { seconds := remaining_time / 1000 microseconds := (remaining_time % 1000) * 1000 @@ -401,7 +416,7 @@ fn select(handle int, test Select, timeout time.Duration) !bool { tv_sec: u64(seconds) tv_usec: u64(microseconds) } - timeval_timeout := if timeout < 0 { + timeval_timeout := if is_infinite { &C.timeval(unsafe { nil }) } else { &tt @@ -422,7 +437,9 @@ fn select(handle int, test Select, timeout time.Duration) !bool { if res < 0 { if C.errno == C.EINTR { // errno is 4, Spurious wakeup from signal, keep waiting - remaining_time = (deadline - time.now()).milliseconds() + if !is_infinite { + remaining_time = ssl_remaining_timeout(deadline).milliseconds() + } continue } cerr := C.errno diff --git a/vlib/net/websocket/websocket_client.v b/vlib/net/websocket/websocket_client.v index 11fe6f3a6..ede1b760b 100644 --- a/vlib/net/websocket/websocket_client.v +++ b/vlib/net/websocket/websocket_client.v @@ -84,7 +84,7 @@ pub enum OPCode { @[params] pub struct ClientOpt { pub: - read_timeout i64 = 30 * time.second + read_timeout i64 = net.infinite_timeout write_timeout i64 = 30 * time.second logger &log.Logger = default_logger proxy_url string // optional proxy URL used to open the websocket TCP tunnel diff --git a/vlib/v/tests/websocket_client_default_read_timeout_test.v b/vlib/v/tests/websocket_client_default_read_timeout_test.v new file mode 100644 index 000000000..a77b19dd6 --- /dev/null +++ b/vlib/v/tests/websocket_client_default_read_timeout_test.v @@ -0,0 +1,10 @@ +import net +import net.websocket + +fn test_websocket_client_default_read_timeout_is_infinite() ! { + opt := websocket.ClientOpt{} + assert opt.read_timeout == net.infinite_timeout + + mut client := websocket.new_client('ws://127.0.0.1:12345')! + assert client.read_timeout == net.infinite_timeout +} -- 2.39.5