Skip to content

Commit 9bdda56

Browse files
committed
feature: added new API function ngx.thread.kill() for killing a user "light thread". thanks aviramc for the original patch in #288.
1 parent 9f0f9ea commit 9bdda56

File tree

5 files changed

+336
-4
lines changed

5 files changed

+336
-4
lines changed

src/ngx_http_lua_socket_tcp.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4354,6 +4354,9 @@ ngx_http_lua_tcp_resolve_cleanup(void *data)
43544354
ngx_http_lua_socket_tcp_upstream_t *u;
43554355
ngx_http_lua_co_ctx_t *coctx = data;
43564356

4357+
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
4358+
"lua tcp socket abort resolver");
4359+
43574360
u = coctx->data;
43584361
if (u == NULL) {
43594362
return;

src/ngx_http_lua_uthread.c

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,24 @@
2424

2525
static int ngx_http_lua_uthread_spawn(lua_State *L);
2626
static int ngx_http_lua_uthread_wait(lua_State *L);
27+
static int ngx_http_lua_uthread_kill(lua_State *L);
2728

2829

2930
void
3031
ngx_http_lua_inject_uthread_api(ngx_log_t *log, lua_State *L)
3132
{
3233
/* new thread table */
33-
lua_createtable(L, 0 /* narr */, 2 /* nrec */);
34+
lua_createtable(L, 0 /* narr */, 3 /* nrec */);
3435

3536
lua_pushcfunction(L, ngx_http_lua_uthread_spawn);
3637
lua_setfield(L, -2, "spawn");
3738

3839
lua_pushcfunction(L, ngx_http_lua_uthread_wait);
3940
lua_setfield(L, -2, "wait");
4041

42+
lua_pushcfunction(L, ngx_http_lua_uthread_kill);
43+
lua_setfield(L, -2, "kill");
44+
4145
lua_setfield(L, -2, "thread");
4246
}
4347

@@ -181,7 +185,7 @@ ngx_http_lua_uthread_wait(lua_State *L)
181185

182186
/* being the last one */
183187
lua_pushnil(L);
184-
lua_pushliteral(L, "already waited");
188+
lua_pushliteral(L, "already waited or killed");
185189
return 2;
186190

187191
default:
@@ -197,4 +201,75 @@ ngx_http_lua_uthread_wait(lua_State *L)
197201
return lua_yield(L, 0);
198202
}
199203

204+
205+
static int
206+
ngx_http_lua_uthread_kill(lua_State *L)
207+
{
208+
lua_State *sub_co;
209+
ngx_http_request_t *r;
210+
ngx_http_lua_ctx_t *ctx;
211+
ngx_http_lua_co_ctx_t *coctx, *sub_coctx;
212+
213+
r = ngx_http_lua_get_req(L);
214+
if (r == NULL) {
215+
return luaL_error(L, "no request found");
216+
}
217+
218+
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
219+
if (ctx == NULL) {
220+
return luaL_error(L, "no request ctx found");
221+
}
222+
223+
ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE
224+
| NGX_HTTP_LUA_CONTEXT_ACCESS
225+
| NGX_HTTP_LUA_CONTEXT_CONTENT
226+
| NGX_HTTP_LUA_CONTEXT_TIMER);
227+
228+
coctx = ctx->cur_co_ctx;
229+
230+
sub_co = lua_tothread(L, 1);
231+
luaL_argcheck(L, sub_co, 1, "lua thread expected");
232+
233+
sub_coctx = ngx_http_lua_get_co_ctx(sub_co, ctx);
234+
235+
if (sub_coctx == NULL) {
236+
return luaL_error(L, "no co ctx found");
237+
}
238+
239+
if (!sub_coctx->is_uthread) {
240+
return luaL_error(L, "attempt to wait on a coroutine that is "
241+
"not a user thread");
242+
}
243+
244+
if (sub_coctx->parent_co_ctx != coctx) {
245+
return luaL_error(L, "only the parent coroutine can wait on the "
246+
"thread");
247+
}
248+
249+
switch (sub_coctx->co_status) {
250+
case NGX_HTTP_LUA_CO_ZOMBIE:
251+
ngx_http_lua_del_thread(r, L, ctx, sub_coctx);
252+
ctx->uthreads--;
253+
254+
lua_pushnil(L);
255+
lua_pushliteral(L, "already terminated");
256+
return 2;
257+
258+
case NGX_HTTP_LUA_CO_DEAD:
259+
lua_pushnil(L);
260+
lua_pushliteral(L, "already waited or killed");
261+
return 2;
262+
263+
default:
264+
ngx_http_lua_cleanup_pending_operation(sub_coctx);
265+
ngx_http_lua_del_thread(r, L, ctx, sub_coctx);
266+
ctx->uthreads--;
267+
268+
lua_pushinteger(L, 1);
269+
return 1;
270+
}
271+
272+
/* not reacheable */
273+
}
274+
200275
/* vi:set ft=c ts=4 sw=4 et fdm=marker: */

t/062-count.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ probe process("$LIBLUA_PATH").function("rehashtab") {
414414
}
415415
--- stap_out2
416416
--- response_body
417-
thread: 2
417+
thread: 3
418418
--- no_error_log
419419
[error]
420420

t/098-uthread-wait.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1274,7 +1274,7 @@ delete thread 1
12741274
--- response_body
12751275
hello in thread
12761276
thread created: zombie
1277-
failed to run thread: already waited
1277+
failed to run thread: already waited or killed
12781278
--- no_error_log
12791279
[error]
12801280

t/127-uthread-kill.t

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# vim:set ft= ts=4 sw=4 et fdm=marker:
2+
3+
use lib 'lib';
4+
use Test::Nginx::Socket::Lua;
5+
use t::StapThread;
6+
7+
our $GCScript = $t::StapThread::GCScript;
8+
our $StapScript = $t::StapThread::StapScript;
9+
10+
repeat_each(2);
11+
12+
plan tests => repeat_each() * (blocks() * 5 + 2);
13+
14+
$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';
15+
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211';
16+
17+
#no_shuffle();
18+
no_long_string();
19+
run_tests();
20+
21+
__DATA__
22+
23+
=== TEST 1: kill pending sleep
24+
--- config
25+
location /lua {
26+
content_by_lua '
27+
function f()
28+
ngx.say("hello from f()")
29+
ngx.sleep(1)
30+
end
31+
32+
local t, err = ngx.thread.spawn(f)
33+
if not t then
34+
ngx.say("failed to spawn thread: ", err)
35+
return
36+
end
37+
38+
ngx.say("thread created: ", coroutine.status(t))
39+
40+
collectgarbage()
41+
42+
local ok, err = ngx.thread.kill(t)
43+
if not ok then
44+
ngx.say("failed to kill thread: ", err)
45+
return
46+
end
47+
48+
ngx.say("killed")
49+
50+
local ok, err = ngx.thread.kill(t)
51+
if not ok then
52+
ngx.say("failed to kill thread: ", err)
53+
return
54+
end
55+
';
56+
}
57+
--- request
58+
GET /lua
59+
--- stap2 eval: $::StapScript
60+
--- stap eval: $::GCScript
61+
--- stap_out
62+
create 2 in 1
63+
spawn user thread 2 in 1
64+
delete thread 2
65+
terminate 1: ok
66+
delete thread 1
67+
68+
--- response_body
69+
hello from f()
70+
thread created: running
71+
killed
72+
failed to kill thread: already waited or killed
73+
74+
--- no_error_log
75+
[error]
76+
--- error_log
77+
lua clean up the timer for pending ngx.sleep
78+
79+
80+
81+
=== TEST 2: already waited
82+
--- config
83+
location /lua {
84+
content_by_lua '
85+
function f()
86+
ngx.say("hello from f()")
87+
ngx.sleep(0.001)
88+
return 32
89+
end
90+
91+
local t, err = ngx.thread.spawn(f)
92+
if not t then
93+
ngx.say("failed to spawn thread: ", err)
94+
return
95+
end
96+
97+
ngx.say("thread created: ", coroutine.status(t))
98+
99+
collectgarbage()
100+
101+
local ok, res = ngx.thread.wait(t)
102+
if not ok then
103+
ngx.say("failed to kill thread: ", res)
104+
return
105+
end
106+
107+
ngx.say("waited: ", res)
108+
109+
local ok, err = ngx.thread.kill(t)
110+
if not ok then
111+
ngx.say("failed to kill thread: ", err)
112+
return
113+
end
114+
';
115+
}
116+
--- request
117+
GET /lua
118+
--- stap2 eval: $::StapScript
119+
--- stap eval: $::GCScript
120+
--- stap_out
121+
create 2 in 1
122+
spawn user thread 2 in 1
123+
terminate 2: ok
124+
delete thread 2
125+
terminate 1: ok
126+
delete thread 1
127+
128+
--- response_body
129+
hello from f()
130+
thread created: running
131+
waited: 32
132+
failed to kill thread: already waited or killed
133+
134+
--- no_error_log
135+
[error]
136+
lua clean up the timer for pending ngx.sleep
137+
138+
139+
140+
=== TEST 3: kill pending resolver
141+
--- config
142+
resolver agentzh.org:12345;
143+
location /lua {
144+
content_by_lua '
145+
function f()
146+
local sock = ngx.socket.tcp()
147+
sock:connect("some.agentzh.org", 12345)
148+
end
149+
150+
local t, err = ngx.thread.spawn(f)
151+
if not t then
152+
ngx.say("failed to spawn thread: ", err)
153+
return
154+
end
155+
156+
ngx.say("thread created: ", coroutine.status(t))
157+
158+
collectgarbage()
159+
160+
local ok, err = ngx.thread.kill(t)
161+
if not ok then
162+
ngx.say("failed to kill thread: ", err)
163+
return
164+
end
165+
166+
ngx.say("killed")
167+
';
168+
}
169+
--- request
170+
GET /lua
171+
--- stap2 eval: $::StapScript
172+
--- stap eval: $::GCScript
173+
--- stap_out
174+
create 2 in 1
175+
spawn user thread 2 in 1
176+
delete thread 2
177+
terminate 1: ok
178+
delete thread 1
179+
180+
--- response_body
181+
thread created: running
182+
killed
183+
184+
--- no_error_log
185+
[error]
186+
--- error_log
187+
lua tcp socket abort resolver
188+
resolve name done: -2
189+
190+
191+
192+
=== TEST 4: kill pending connect
193+
--- config
194+
resolver $TEST_NGINX_RESOLVER;
195+
location /lua {
196+
content_by_lua '
197+
local ready = false
198+
function f()
199+
local sock = ngx.socket.tcp()
200+
sock:connect("agentzh.org", 80)
201+
sock:close()
202+
ready = true
203+
sock:settimeout(10000)
204+
sock:connect("agentzh.org", 12345)
205+
end
206+
207+
local t, err = ngx.thread.spawn(f)
208+
if not t then
209+
ngx.say("failed to spawn thread: ", err)
210+
return
211+
end
212+
213+
ngx.say("thread created: ", coroutine.status(t))
214+
215+
collectgarbage()
216+
217+
while not ready do
218+
ngx.sleep(0.001)
219+
end
220+
221+
local ok, err = ngx.thread.kill(t)
222+
if not ok then
223+
ngx.say("failed to kill thread: ", err)
224+
return
225+
end
226+
227+
ngx.say("killed")
228+
';
229+
}
230+
--- request
231+
GET /lua
232+
--- stap2 eval: $::StapScript
233+
--- stap eval: $::GCScript
234+
--- stap_out
235+
create 2 in 1
236+
spawn user thread 2 in 1
237+
delete thread 2
238+
terminate 1: ok
239+
delete thread 1
240+
241+
--- response_body
242+
thread created: running
243+
killed
244+
245+
--- no_error_log
246+
[error]
247+
lua tcp socket abort resolver
248+
--- grep_error_log: lua finalize socket
249+
--- grep_error_log_out
250+
lua finalize socket
251+
lua finalize socket
252+
253+
--- error_log
254+

0 commit comments

Comments
 (0)