From a2cba07a6b629a718906401adaad13114fdb318c Mon Sep 17 00:00:00 2001 From: Gabriel Clima Date: Thu, 7 May 2026 08:00:10 +0000 Subject: [PATCH] bugfix: prevent SIGSEGV in event timer rbtree during worker shutdown. When a worker exits, ngx_worker_process_exit() calls ngx_destroy_pool(cycle->pool), which fires the cycle's cleanup handlers. One of these is ngx_http_lua_cleanup_vm, which calls lua_close() on the worker's Lua VM. lua_close() runs LuaJIT GC finalizers (__gc) on every live userdata, including TCP socket cosocket userdata still bound to in-flight requests. The __gc finalizers ngx_http_lua_socket_tcp_upstream_destroy and ngx_http_lua_socket_downstream_destroy reach ngx_http_lua_socket_tcp_finalize_{read,write}_part, which call ngx_del_timer() on connection events. By that point the event timer rbtree may already have been partially torn down by sibling finalizers, so ngx_del_timer() corrupts the tree and the next deletion segfaults inside ngx_rbtree_min / ngx_rbtree_delete. Skip the cleanup when ngx_terminate or ngx_exiting is set: the cycle pool is about to be destroyed and the kernel reclaims fds, so unwinding events and timers from the __gc path is unnecessary and unsafe. The same guard is already used in ngx_http_lua_socket_tcp_setkeepalive() for the analogous case. Reproduces under SIGTERM and SIGQUIT shutdown when there are in-flight ngx.socket.tcp() / ngx.req.socket() coroutines, e.g. long-lived ngx.sleep + tcpsock:receive() loops. Crash signature: ngx_rbtree_min src/core/ngx_rbtree.h ngx_rbtree_delete src/core/ngx_rbtree.c ngx_event_del_timer ngx_http_lua_socket_tcp_finalize_read_part ngx_http_lua_socket_tcp_finalize ngx_http_lua_socket_tcp_cleanup ngx_http_lua_socket_tcp_upstream_destroy (Lua __gc) ... LuaJIT GC sweep ... ngx_http_lua_cleanup_vm ngx_destroy_pool(cycle->pool) ngx_worker_process_exit --- src/ngx_http_lua_socket_tcp.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c index b6af9f8f8f..4b1cdde950 100644 --- a/src/ngx_http_lua_socket_tcp.c +++ b/src/ngx_http_lua_socket_tcp.c @@ -6137,6 +6137,18 @@ ngx_http_lua_socket_tcp_upstream_destroy(lua_State *L) return 0; } + /* + * During worker shutdown the Lua VM is closed by the cycle pool + * cleanup; by then the event timer rbtree may already have been + * partially torn down by sibling finalizers. Calling ngx_del_timer + * from here corrupts the rbtree. The cycle pool is about to + * disappear and the kernel reclaims fds, so the cleanup is not + * needed at this point. + */ + if (ngx_terminate || ngx_exiting) { + return 0; + } + if (u->cleanup) { ngx_http_lua_socket_tcp_cleanup(u); /* it will clear u->cleanup */ } @@ -6158,6 +6170,10 @@ ngx_http_lua_socket_downstream_destroy(lua_State *L) return 0; } + if (ngx_terminate || ngx_exiting) { + return 0; + } + if (u->cleanup) { ngx_http_lua_socket_tcp_cleanup(u); /* it will clear u->cleanup */ }