From: Dmitriy Alekseev <1865999+dragoangel@users.noreply.github.com> Date: Fri, 22 May 2026 11:44:43 +0000 (+0200) Subject: [Fix] Properly populate timeout read in tcp_lua.c X-Git-Tag: 4.1.0~31^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1df4e7fe56f3629ebde2e6a4aae1fe4aa5818877;p=thirdparty%2Frspamd.git [Fix] Properly populate timeout read in tcp_lua.c --- diff --git a/src/lua/lua_tcp.c b/src/lua/lua_tcp.c index fe7e3f1452..27b21d1475 100644 --- a/src/lua/lua_tcp.c +++ b/src/lua/lua_tcp.c @@ -846,16 +846,18 @@ lua_tcp_connect_helper(struct lua_tcp_cbdata *cbd) if (cbd->thread == NULL) { /* - * Async pure-connect probe: the LUA_WANT_CONNECT marker was queued only - * to keep plan_handler_event from finishing the session before the - * socket completes the dial. The on_connect callback (if any) was - * already fired in lua_tcp_handler when LUA_TCP_FLAG_CONNECTED got - * set; shift the marker and re-plan so the now-empty queue triggers - * the FINISHED tear-down. + * Async path: the LUA_WANT_CONNECT marker was queued either as a + * pure-connect probe (queue empty after shift -> FINISHED tear-down) + * or ahead of a LUA_WANT_READ for connect-phase detection on a + * read-without-write request (queue head is now the read handler). + * The on_connect callback (if any) was already fired in + * lua_tcp_handler when LUA_TCP_FLAG_CONNECTED got set; shift the + * marker and re-plan with can_read/can_write enabled so any remaining + * handler gets armed correctly. */ msg_debug_tcp("tcp connected (async probe)"); lua_tcp_shift_handler(cbd); - lua_tcp_plan_handler_event(cbd, FALSE, FALSE); + lua_tcp_plan_handler_event(cbd, TRUE, TRUE); return; } @@ -2023,19 +2025,40 @@ lua_tcp_request(lua_State *L) } /* - * Pure-probe shape: caller wants to verify TCP connectivity (and/or be - * notified of connect-phase errors via on_error) without queueing any - * read or write. Push a LUA_WANT_CONNECT marker so plan_handler_event - * arms the EV_WRITE watcher; the marker is shifted in - * lua_tcp_connect_helper once the connect resolves. Without this, - * plan_handler_event would see an empty queue and tear the session down - * before the dial ever completes. + * 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: + * + * 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. + * + * 2. Read-without-prior-write (queue head is 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. + * + * In both shapes the marker is shifted in lua_tcp_connect_helper once + * the connect resolves. */ - if (g_queue_get_length(cbd->handlers) == 0 && - (conn_cbref != -1 || error_cbref != -1)) { - struct lua_tcp_handler *ch = g_malloc0(sizeof(*ch)); - ch->type = LUA_WANT_CONNECT; - g_queue_push_tail(cbd->handlers, ch); + if (g_queue_get_length(cbd->handlers) == 0) { + if (conn_cbref != -1 || error_cbref != -1) { + struct lua_tcp_handler *ch = g_malloc0(sizeof(*ch)); + ch->type = LUA_WANT_CONNECT; + g_queue_push_tail(cbd->handlers, ch); + } + } + 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); + } } cbd->connect_cb = conn_cbref;