Skip to content

Commit de7e57f

Browse files
authored
bugfix: clean child coroutine ops when killing uthreads.
Cancel descendant coroutine operations before deleting killed user threads so delayed cosocket events cannot resume collected Lua states.
1 parent bbed32a commit de7e57f

2 files changed

Lines changed: 163 additions & 0 deletions

File tree

src/ngx_http_lua_uthread.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,49 @@
2525
static int ngx_http_lua_uthread_spawn(lua_State *L);
2626
static int ngx_http_lua_uthread_wait(lua_State *L);
2727
static int ngx_http_lua_uthread_kill(lua_State *L);
28+
static void ngx_http_lua_uthread_cleanup_descendants(ngx_http_lua_ctx_t *ctx,
29+
ngx_http_lua_co_ctx_t *parent);
30+
31+
32+
static void
33+
ngx_http_lua_uthread_cleanup_descendants(ngx_http_lua_ctx_t *ctx,
34+
ngx_http_lua_co_ctx_t *parent)
35+
{
36+
ngx_uint_t i;
37+
ngx_list_part_t *part;
38+
ngx_http_lua_co_ctx_t *coctx, *cur;
39+
40+
if (ctx->user_co_ctx == NULL) {
41+
return;
42+
}
43+
44+
part = &ctx->user_co_ctx->part;
45+
coctx = part->elts;
46+
47+
for (i = 0; /* void */; i++) {
48+
49+
if (i >= part->nelts) {
50+
if (part->next == NULL) {
51+
break;
52+
}
53+
54+
part = part->next;
55+
coctx = part->elts;
56+
i = 0;
57+
}
58+
59+
if (&coctx[i] == parent || coctx[i].is_uthread) {
60+
continue;
61+
}
62+
63+
for (cur = coctx[i].parent_co_ctx; cur; cur = cur->parent_co_ctx) {
64+
if (cur == parent) {
65+
ngx_http_lua_cleanup_pending_operation(&coctx[i]);
66+
break;
67+
}
68+
}
69+
}
70+
}
2871

2972

3073
void
@@ -264,6 +307,7 @@ ngx_http_lua_uthread_kill(lua_State *L)
264307

265308
default:
266309
ngx_http_lua_cleanup_pending_operation(sub_coctx);
310+
ngx_http_lua_uthread_cleanup_descendants(ctx, sub_coctx);
267311
ngx_http_lua_del_thread(r, L, ctx, sub_coctx);
268312
ctx->uthreads--;
269313

t/127-uthread-kill.t

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,3 +617,122 @@ GET /t
617617
ok_count=10
618618
--- no_error_log
619619
[error]
620+
621+
622+
623+
=== TEST 11: kill uthread with child coroutine pending cosocket read after GC
624+
--- config
625+
lua_socket_log_errors off;
626+
627+
location = /slow-body {
628+
content_by_lua_block {
629+
local body = string.rep("x", 8192)
630+
631+
ngx.header["Content-Type"] = "text/plain"
632+
ngx.header["Content-Length"] = #body
633+
634+
ngx.print(body:sub(1, 64))
635+
ngx.flush(true)
636+
ngx.sleep(0.2)
637+
ngx.print(body:sub(65))
638+
}
639+
}
640+
641+
location = /t {
642+
access_by_lua_block {
643+
local port = tonumber(ngx.var.server_port)
644+
local pinned_sockets = {}
645+
local slow_read_pending = false
646+
647+
local function read_content_length(sock)
648+
local line, err = sock:receive("*l")
649+
if not line then
650+
return nil, err
651+
end
652+
653+
local content_length
654+
while true do
655+
line, err = sock:receive("*l")
656+
if not line then
657+
return nil, err
658+
end
659+
660+
if line == "" then
661+
return content_length
662+
end
663+
664+
local name, value = line:match("^([^:]+):%s*(.*)$")
665+
if name and name:lower() == "content-length" then
666+
content_length = tonumber(value)
667+
end
668+
end
669+
end
670+
671+
local fast = ngx.thread.spawn(function()
672+
for _ = 1, 1000 do
673+
if slow_read_pending then
674+
break
675+
end
676+
ngx.sleep(0.001)
677+
end
678+
679+
return "ok"
680+
end)
681+
682+
local slow = ngx.thread.spawn(function()
683+
local child = coroutine.create(function()
684+
local sock = ngx.socket.tcp()
685+
pinned_sockets[#pinned_sockets + 1] = sock
686+
687+
sock:settimeout(10000)
688+
local ok, err = sock:connect("127.0.0.1", port)
689+
if not ok then
690+
return nil, err
691+
end
692+
693+
ok, err = sock:send("GET /slow-body HTTP/1.1\r\nHost: localhost\r\n\r\n")
694+
if not ok then
695+
return nil, err
696+
end
697+
698+
local content_length
699+
content_length, err = read_content_length(sock)
700+
if not content_length then
701+
return nil, err
702+
end
703+
704+
slow_read_pending = true
705+
return sock:receive(content_length)
706+
end)
707+
708+
local ok, body, err = coroutine.resume(child)
709+
if not ok then
710+
return nil, body
711+
end
712+
713+
return body, err
714+
end)
715+
716+
ngx.thread.wait(fast, slow)
717+
ngx.thread.kill(fast)
718+
ngx.thread.kill(slow)
719+
720+
fast = nil
721+
slow = nil
722+
collectgarbage("collect")
723+
collectgarbage("collect")
724+
725+
ngx.sleep(0.3)
726+
727+
ngx.say("survived pinned=", #pinned_sockets)
728+
}
729+
730+
content_by_lua_block {
731+
}
732+
}
733+
--- request
734+
GET /t
735+
--- response_body
736+
survived pinned=1
737+
--- no_error_log
738+
[alert]

0 commit comments

Comments
 (0)