From: Dmitriy Alekseev <1865999+dragoangel@users.noreply.github.com> Date: Fri, 22 May 2026 12:52:49 +0000 (+0200) Subject: [Feature] lua_tcp: bound the dial under connect_timeout for all queue shapes X-Git-Tag: 4.1.0~31^2~1 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=3333f0fb5eb62d539bba0e0a6e07ccc1a09cc6cf;p=thirdparty%2Frspamd.git [Feature] lua_tcp: bound the dial under connect_timeout for all queue shapes Seat a LUA_WANT_CONNECT marker at the head of every non-empty queue, not only when the head is LUA_WANT_READ. A LUA_WANT_WRITE-headed request was already routing connect errors correctly (EV_WRITE naturally armed by the write handler, SO_ERROR check fires before LUA_TCP_FLAG_CONNECTED), but the timer was armed under write_timeout, not connect_timeout: a black-holed SYN sat under the write budget and the caller's connect_timeout was silently ignored. After this change the prepended marker re-arms EV_WRITE under connect_timeout for the dial; once CONNECTED is set, plan_handler_event re-arms EV_WRITE under write_timeout for the actual write. Read-only shapes continue to work as fixed in the previous commit. Legacy single-budget callers (only `timeout` set, use_deduction = TRUE) are unaffected: plan_handler_event gates per-phase timer re-arms on !use_deduction, so the single budget rides through all phases via the elapsed-time deduction in lua_tcp_handler. The extra LUA_WANT_CONNECT phase costs one event-loop trip; total budget is preserved. Signed-off-by: Dmitriy Alekseev <1865999+dragoangel@users.noreply.github.com> --- diff --git a/src/lua/lua_tcp.c b/src/lua/lua_tcp.c index 27b21d1475..cf42527fae 100644 --- a/src/lua/lua_tcp.c +++ b/src/lua/lua_tcp.c @@ -2025,24 +2025,34 @@ lua_tcp_request(lua_State *L) } /* - * Two shapes need an explicit LUA_WANT_CONNECT marker so that EV_WRITE - * is armed for the dial and LUA_TCP_FLAG_CONNECTED is set in the proper - * connect-phase path: + * Always seat an explicit LUA_WANT_CONNECT marker at the queue head so + * the dial gets its own EV_WRITE arming under `connect_timeout`, and + * LUA_TCP_FLAG_CONNECTED is set in the proper connect-phase path. Three + * shapes need this: * * 1. Pure probe (empty queue, only on_connect/on_error registered): - * without the marker plan_handler_event would tear the session down - * before the dial completes. + * without the marker plan_handler_event would tear the session + * down before the dial completes. * - * 2. Read-without-prior-write (queue head is LUA_WANT_READ): without + * 2. Read-without-prior-write (queue head LUA_WANT_READ): without * the marker plan_handler_event arms EV_READ with read_timeout * straight away, the socket-writable connect signal is never * consumed, LUA_TCP_FLAG_CONNECTED stays unset, and any pre-byte * error/timeout misroutes to on_error (looking like a connect * failure) -- plus FINISHED|CONNECTED gates on conn:close() leak - * refcounts. The natural connect detection that LUA_WANT_WRITE - * gets for free (writes require socket-writable) is missing here. + * refcounts. * - * In both shapes the marker is shifted in lua_tcp_connect_helper once + * 3. Write-first (queue head LUA_WANT_WRITE): EV_WRITE is naturally + * armed (writes require socket-writable), so connect-error + * *routing* already worked. But the timer was armed with + * `write_timeout`, not `connect_timeout` -- so a black-holed SYN + * sat under the write budget and the caller's `connect_timeout` + * setting was silently ignored. The marker re-arms the timer + * under `connect_timeout`; after the dial resolves, + * plan_handler_event re-arms EV_WRITE under `write_timeout` for + * the actual write. + * + * In every shape the marker is shifted in lua_tcp_connect_helper once * the connect resolves. */ if (g_queue_get_length(cbd->handlers) == 0) { @@ -2053,12 +2063,9 @@ lua_tcp_request(lua_State *L) } } else { - struct lua_tcp_handler *first = g_queue_peek_head(cbd->handlers); - if (first != NULL && first->type == LUA_WANT_READ) { - struct lua_tcp_handler *ch = g_malloc0(sizeof(*ch)); - ch->type = LUA_WANT_CONNECT; - g_queue_push_head(cbd->handlers, ch); - } + struct lua_tcp_handler *ch = g_malloc0(sizeof(*ch)); + ch->type = LUA_WANT_CONNECT; + g_queue_push_head(cbd->handlers, ch); } cbd->connect_cb = conn_cbref;