Skip to content

bugfix: clean child coroutine ops when killing uthreads.#2514

Merged
zhuizhuhaomeng merged 2 commits into
openresty:masterfrom
oowl:fix/uthread-child-coroutine-cleanup
Jul 11, 2026
Merged

bugfix: clean child coroutine ops when killing uthreads.#2514
zhuizhuhaomeng merged 2 commits into
openresty:masterfrom
oowl:fix/uthread-child-coroutine-cleanup

Conversation

@oowl

@oowl oowl commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary:

  • Clean pending operations owned by ordinary descendant coroutines before deleting a killed user thread.
  • Prevent delayed cosocket events from resuming coroutine state after the parent uthread has been killed and Lua GC has collected related objects.

Why this matters:

This can happen when ngx.thread.kill is mixed with libraries that hide cosocket reads behind ordinary Lua coroutines. lua-resty-http is a concrete example:

The crash sequence is:

gdb-peda$ bt
#0  lj_debug_getinfo (L=L@entry=0x7fe6b5c34a60, what=what@entry=0x55e9d05abc72 "Snl", 
    ar=ar@entry=0x7ffc72e56010, ext=ext@entry=0x0) at lj_debug.c:465
#1  0x000055e9d00ac9cb in lua_getinfo (L=L@entry=0x7fe6b5c34a60, what=what@entry=0x55e9d05abc72 "Snl", 
    ar=ar@entry=0x7ffc72e56010) at lj_debug.c:544
#2  0x000055e9cffb78d1 in ngx_http_lua_thread_traceback (coctx=0x55e9d28ab6d0, co=0x7fe6b5c34a60, L=0x1)
    at ../ngx_lua-0.10.31rc2/src/ngx_http_lua_util.c:3059
#3  ngx_http_lua_run_thread (L=L@entry=0x7fe6b5c39380, r=r@entry=0x55e9d28aa1b0, 
    ctx=ctx@entry=0x55e9d28aaf38, nrets=<optimized out>)
    at ../ngx_lua-0.10.31rc2/src/ngx_http_lua_util.c:1534
#4  0x000055e9cffc6961 in ngx_http_lua_socket_tcp_resume_helper (r=0x55e9d28aa1b0, 
    socket_op=<optimized out>) at ../ngx_lua-0.10.31rc2/src/ngx_http_lua_socket_tcp.c:6481
#5  0x000055e9cffbf43f in ngx_http_lua_access_handler (r=0x55e9d28aa1b0)
    at ../ngx_lua-0.10.31rc2/src/ngx_http_lua_accessby.c:101
#6  0x000055e9cfefa959 in ngx_http_core_access_phase (r=0x55e9d28aa1b0, ph=0x55e9d28a0d38)
    at src/http/ngx_http_core_module.c:1147
#7  0x000055e9cfef579d in ngx_http_core_run_phases (r=0x55e9d28aa1b0)
    at src/http/ngx_http_core_module.c:911
#8  0x000055e9cffca93a in ngx_http_lua_socket_handle_read_success (u=0x7fe6b5c0f3c0, r=0x55e9d28aa1b0)
    at ../ngx_lua-0.10.31rc2/src/ngx_http_lua_socket_tcp.c:3922
#9  ngx_http_lua_socket_tcp_read (r=0x55e9d28aa1b0, u=0x7fe6b5c0f3c0)
    at ../ngx_lua-0.10.31rc2/src/ngx_http_lua_socket_tcp.c:2937
#10 0x000055e9cffc47dc in ngx_http_lua_socket_tcp_handler (ev=0x55e9d280e4f0)
    at ../ngx_lua-0.10.31rc2/src/ngx_http_lua_socket_tcp.c:3676
#11 0x000055e9cfec9213 in ngx_epoll_process_events (cycle=0x55e9d27feaa0, timer=<optimized out>, 
    flags=<optimized out>) at src/event/modules/ngx_epoll_module.c:901
#12 0x000055e9cfebc3ee in ngx_process_events_and_timers (cycle=cycle@entry=0x55e9d27feaa0)
    at src/event/ngx_event.c:258
#13 0x000055e9cfec7ad4 in ngx_single_process_cycle (cycle=cycle@entry=0x55e9d27feaa0)
    at src/os/unix/ngx_process_cycle.c:325
#14 0x000055e9cfe96844 in main (argc=<optimized out>, argc@entry=0x5, argv=argv@entry=0x7ffc72e56738)
    at src/core/nginx.c:384
#15 0x00007fe6b5ca3d90 in __libc_start_call_main (main=main@entry=0x55e9cfe95b90 <main>, 
    argc=argc@entry=0x5, argv=argv@entry=0x7ffc72e56738) at ../sysdeps/nptl/libc_start_call_main.h:58
#16 0x00007fe6b5ca3e40 in __libc_start_main_impl (main=0x55e9cfe95b90 <main>, argc=0x5, 
    argv=0x7ffc72e56738, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, 
    stack_end=0x7ffc72e56728) at ../csu/libc-start.c:392
#17 0x000055e9cfe96935 in _start ()
  1. A user thread created by ngx.thread.spawn calls lua-resty-http, or another library with the same pattern.
  2. The user thread resumes an ordinary child coroutine created by that library.
  3. The child coroutine blocks in sock:receive while reading the response body, so ngx_lua stores the pending cosocket operation on the child coroutine's coctx.
  4. The parent kills the user thread with ngx.thread.kill. Before this patch, kill only cleaned the killed uthread's own coctx. It did not clean pending operations owned by ordinary descendant coroutines.
  5. The Lua socket object can then be collected, or the killed uthread can be deleted, while the nginx socket event still points at the child coroutine/upstream state.
  6. When the delayed read event fires, ngx_lua tries to resume or clean up stale coroutine/cosocket state, which can dereference freed or collected memory and crash the worker.

This patch treats ordinary descendant coroutines as part of the killed uthread's execution tree for cleanup purposes. Before deleting the uthread, it walks user coroutine contexts, finds non-uthread descendants of the killed uthread, and cancels their pending operations. This keeps lua-resty-http-style body readers and other coroutine-wrapped cosocket users from leaving stale socket callbacks behind when mixed with ngx.thread.kill.

I hereby granted the copyright of the changes in this pull request
to the authors of this lua-nginx-module project.

oowl and others added 2 commits July 11, 2026 14:54
Cancel descendant coroutine operations before deleting killed user threads so delayed cosocket events cannot resume collected Lua states.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@mergify

mergify Bot commented Jul 11, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@zhuizhuhaomeng
zhuizhuhaomeng merged commit de7e57f into openresty:master Jul 11, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants