Skip to content

Commit c005354

Browse files
committed
bugfix: timers created by ngx.timer.at() might not be aborted prematurely upon nginx worker exit. thanks Hamish Forbes for the report.
1 parent 1d85a61 commit c005354

File tree

2 files changed

+171
-3
lines changed

2 files changed

+171
-3
lines changed

src/ngx_http_lua_timer.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,8 @@ ngx_http_lua_abort_pending_timers(ngx_event_t *ev)
462462

463463
sentinel = ngx_event_timer_rbtree.sentinel;
464464

465-
prev = NULL;
466465
cur = ngx_event_timer_rbtree.root;
466+
prev = cur->parent;
467467

468468
events = ngx_pcalloc(ngx_cycle->pool,
469469
lmcf->pending_timers * sizeof(ngx_event_t));
@@ -475,14 +475,17 @@ ngx_http_lua_abort_pending_timers(ngx_event_t *ev)
475475

476476
dd("root: %p, root parent: %p, sentinel: %p", cur, cur->parent, sentinel);
477477

478-
while (lmcf->pending_timers > n) {
478+
while (n < lmcf->pending_timers) {
479479
if (cur == sentinel || cur == NULL) {
480480
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
481481
"lua pending timer counter got out of sync: %i",
482482
lmcf->pending_timers);
483483
break;
484484
}
485485

486+
dd("prev: %p, cur: %p, cur parent: %p, cur left: %p, cur right: %p",
487+
prev, cur, cur->parent, cur->left, cur->right);
488+
486489
if (prev == cur->parent) {
487490
next = cur->left;
488491
if (next == sentinel) {
@@ -519,6 +522,10 @@ ngx_http_lua_abort_pending_timers(ngx_event_t *ev)
519522
cur = next;
520523
}
521524

525+
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
526+
"lua found %i pending timers to be aborted prematurely",
527+
n);
528+
522529
for (i = 0; i < n; i++) {
523530
ev = events[i];
524531

@@ -537,6 +544,7 @@ ngx_http_lua_abort_pending_timers(ngx_event_t *ev)
537544
tctx = ev->data;
538545
tctx->premature = 1;
539546

547+
dd("calling timer handler prematurely");
540548
ev->handler(ev);
541549
}
542550

t/109-timer-hup.t

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ our $StapScript = $t::StapThread::StapScript;
2828

2929
repeat_each(2);
3030

31-
plan tests => repeat_each() * 60;
31+
plan tests => repeat_each() * 76;
3232

3333
#no_diff();
3434
no_long_string();
@@ -279,3 +279,163 @@ f: exiting=true
279279
g: timer prematurely expired: false
280280
g: exiting=true
281281
282+
283+
284+
=== TEST 5: HUP reload should abort pending timers
285+
--- config
286+
location /t {
287+
content_by_lua '
288+
local f, err = io.open("t/servroot/logs/nginx.pid", "r")
289+
if not f then
290+
ngx.say("failed to open nginx.pid: ", err)
291+
return
292+
end
293+
294+
local pid = f:read()
295+
-- ngx.say("master pid: [", pid, "]")
296+
297+
f:close()
298+
299+
local function f(premature)
300+
print("f: timer prematurely expired: ", premature)
301+
print("f: exiting=", ngx.worker.exiting())
302+
end
303+
304+
for i = 1, 100 do
305+
local ok, err = ngx.timer.at(3 + i, f)
306+
if not ok then
307+
ngx.say("failed to set timer: ", err)
308+
return
309+
end
310+
end
311+
ngx.say("ok")
312+
os.execute("kill -HUP " .. pid)
313+
';
314+
}
315+
--- request
316+
GET /t
317+
318+
--- response_body
319+
ok
320+
321+
--- wait: 0.3
322+
--- no_error_log
323+
[error]
324+
[alert]
325+
[crit]
326+
in callback: hello, 2
327+
failed to register a new timer after reload
328+
329+
--- grep_error_log eval: qr/lua found \d+ pending timers/
330+
--- grep_error_log_out
331+
lua found 100 pending timers
332+
333+
334+
335+
=== TEST 6: HUP reload should abort pending timers (coroutine + cosocket)
336+
--- http_config
337+
lua_shared_dict test_dict 1m;
338+
339+
server {
340+
listen 54123;
341+
location = /foo {
342+
echo 'foo';
343+
}
344+
}
345+
346+
--- config
347+
location /t {
348+
content_by_lua '
349+
local http_req = {"GET /foo HTTP/1.1", "Host: localhost:1234", "", ""}
350+
http_req = table.concat(http_req, "\\r\\n")
351+
352+
-- Connect the socket
353+
local sock = ngx.socket.tcp()
354+
local ok,err = sock:connect("127.0.0.1", 54123)
355+
if not ok then
356+
ngx.log(ngx.ERR, err)
357+
end
358+
359+
-- Send the request
360+
local ok,err = sock:send(http_req)
361+
362+
-- Get Headers
363+
repeat
364+
local line, err = sock:receive("*l")
365+
until string.find(line, "^%s*$")
366+
367+
function foo()
368+
repeat
369+
-- Get and read chunk
370+
local line, err = sock:receive("*l")
371+
local len = tonumber(line)
372+
if len > 0 then
373+
local chunk, err = sock:receive(len)
374+
coroutine.yield(chunk)
375+
sock:receive(2)
376+
else
377+
-- Read last newline
378+
sock:receive(2)
379+
end
380+
until len == 0
381+
end
382+
383+
co = coroutine.create(foo)
384+
repeat
385+
local chunk = select(2,coroutine.resume(co))
386+
until chunk == nil
387+
388+
-- Breaks the timer
389+
sock:setkeepalive()
390+
ngx.say("ok")
391+
';
392+
393+
log_by_lua '
394+
local background_thread
395+
background_thread = function(premature)
396+
ngx.log(ngx.DEBUG, premature)
397+
if premature then
398+
ngx.shared["test_dict"]:delete("background_flag")
399+
return
400+
end
401+
local ok, err = ngx.timer.at(1, background_thread)
402+
403+
local f, err = io.open("t/servroot/logs/nginx.pid", "r")
404+
if not f then
405+
ngx.say("failed to open nginx.pid: ", err)
406+
return
407+
end
408+
local pid = f:read()
409+
-- ngx.say("master pid: [", pid, "]")
410+
f:close()
411+
412+
os.execute("kill -HUP " .. pid)
413+
end
414+
local dict = ngx.shared["test_dict"]
415+
416+
if dict:get("background_flag") == nil then
417+
local ok, err = ngx.timer.at(0, background_thread)
418+
if ok then
419+
dict:set("test_dict", 1)
420+
end
421+
end
422+
';
423+
}
424+
--- request
425+
GET /t
426+
427+
--- response_body
428+
ok
429+
430+
--- wait: 0.3
431+
--- no_error_log
432+
[error]
433+
[alert]
434+
[crit]
435+
in callback: hello, 2
436+
failed to register a new timer after reload
437+
438+
--- grep_error_log eval: qr/lua found \d+ pending timers/
439+
--- grep_error_log_out
440+
lua found 1 pending timers
441+

0 commit comments

Comments
 (0)