1# vim:set ft= ts=4 sw=4 et fdm=marker:
2
3our $SkipReason;
4
5BEGIN {
6    if ($ENV{TEST_NGINX_CHECK_LEAK}) {
7        $SkipReason = "unavailable for the hup tests";
8
9    } else {
10        $ENV{TEST_NGINX_USE_HUP} = 1;
11        undef $ENV{TEST_NGINX_USE_STAP};
12    }
13}
14
15use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : ();
16
17
18use t::StapThread;
19
20our $GCScript = $t::StapThread::GCScript;
21our $StapScript = $t::StapThread::StapScript;
22
23#worker_connections(1014);
24#master_on();
25#workers(2);
26#log_level('warn');
27
28repeat_each(2);
29
30plan tests => repeat_each() * 81;
31
32#no_diff();
33no_long_string();
34
35our $HtmlDir = html_dir;
36
37$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
38$ENV{TEST_NGINX_HTML_DIR} = $HtmlDir;
39
40worker_connections(1024);
41run_tests();
42
43__DATA__
44
45=== TEST 1: single timer
46--- config
47    location /t {
48        content_by_lua '
49            local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r")
50            if not f then
51                ngx.say("failed to open nginx.pid: ", err)
52                return
53            end
54
55            local pid = f:read()
56            -- ngx.say("master pid: [", pid, "]")
57
58            f:close()
59
60            local i = 0
61            local function f(premature)
62                i = i + 1
63                print("timer prematurely expired: ", premature)
64                print("in callback: hello, ", i)
65            end
66            local ok, err = ngx.timer.at(3, f)
67            if not ok then
68                ngx.say("failed to set timer: ", err)
69                return
70            end
71            ngx.say("registered timer")
72            os.execute("kill -HUP " .. pid)
73        ';
74    }
75--- request
76GET /t
77
78--- response_body
79registered timer
80
81--- wait: 0.3
82--- no_error_log
83[error]
84[alert]
85[crit]
86in callback: hello, 2
87timer prematurely expired: false
88
89--- error_log
90lua abort pending timers
91lua ngx.timer expired
92http lua close fake http connection
93in callback: hello, 1
94timer prematurely expired: true
95
96
97
98=== TEST 2: multiple timers
99--- config
100    location /t {
101        content_by_lua '
102            local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r")
103            if not f then
104                ngx.say("failed to open nginx.pid: ", err)
105                return
106            end
107
108            local pid = f:read()
109            -- ngx.say("master pid: [", pid, "]")
110
111            f:close()
112
113            local i = 0
114            local function f(premature)
115                i = i + 1
116                print("timer prematurely expired: ", premature)
117                print("in callback: hello, ", i, "!")
118            end
119            for i = 1, 10 do
120                local ok, err = ngx.timer.at(3, f)
121                if not ok then
122                    ngx.say("failed to set timer: ", err)
123                    return
124                end
125            end
126            ngx.say("registered timers")
127            os.execute("kill -HUP " .. pid)
128        ';
129    }
130--- request
131GET /t
132
133--- response_body
134registered timers
135
136--- wait: 0.3
137--- no_error_log
138[error]
139[alert]
140[crit]
141in callback: hello, 11!
142timer prematurely expired: false
143
144--- error_log
145lua abort pending timers
146lua ngx.timer expired
147http lua close fake http connection
148in callback: hello, 1!
149in callback: hello, 2!
150in callback: hello, 3!
151in callback: hello, 4!
152in callback: hello, 5!
153in callback: hello, 6!
154in callback: hello, 7!
155in callback: hello, 8!
156in callback: hello, 9!
157in callback: hello, 10!
158timer prematurely expired: true
159
160
161
162=== TEST 3: trying to add new timer after HUP reload
163--- config
164    location /t {
165        content_by_lua '
166            local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r")
167            if not f then
168                ngx.say("failed to open nginx.pid: ", err)
169                return
170            end
171
172            local pid = f:read()
173            -- ngx.say("master pid: [", pid, "]")
174
175            f:close()
176
177            local function f(premature)
178                print("timer prematurely expired: ", premature)
179                local ok, err = ngx.timer.at(3, f)
180                if not ok then
181                    print("failed to register a new timer after reload: ", err)
182                else
183                    print("registered a new timer after reload")
184                end
185            end
186            local ok, err = ngx.timer.at(3, f)
187            if not ok then
188                ngx.say("failed to set timer: ", err)
189                return
190            end
191            ngx.say("registered timer")
192            os.execute("kill -HUP " .. pid)
193        ';
194    }
195--- request
196GET /t
197
198--- response_body
199registered timer
200
201--- wait: 0.2
202--- no_error_log
203[error]
204[alert]
205[crit]
206in callback: hello, 2
207timer prematurely expired: false
208
209--- error_log
210lua abort pending timers
211lua ngx.timer expired
212http lua close fake http connection
213timer prematurely expired: true
214failed to register a new timer after reload: process exiting, context: ngx.timer
215
216
217
218=== TEST 4: trying to add new timer after HUP reload
219--- config
220    location /t {
221        content_by_lua '
222            local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r")
223            if not f then
224                ngx.say("failed to open nginx.pid: ", err)
225                return
226            end
227
228            local pid = f:read()
229            -- ngx.say("master pid: [", pid, "]")
230
231            f:close()
232
233            local function g(premature)
234                print("g: timer prematurely expired: ", premature)
235                print("g: exiting=", ngx.worker.exiting())
236            end
237
238            local function f(premature)
239                print("f: timer prematurely expired: ", premature)
240                print("f: exiting=", ngx.worker.exiting())
241                local ok, err = ngx.timer.at(0, g)
242                if not ok then
243                    print("f: failed to register a new timer after reload: ", err)
244                else
245                    print("f: registered a new timer after reload")
246                end
247            end
248            local ok, err = ngx.timer.at(3, f)
249            if not ok then
250                ngx.say("failed to set timer: ", err)
251                return
252            end
253            ngx.say("registered timer")
254            os.execute("kill -HUP " .. pid)
255        ';
256    }
257--- request
258GET /t
259
260--- response_body
261registered timer
262
263--- wait: 0.2
264--- no_error_log
265[error]
266[alert]
267[crit]
268in callback: hello, 2
269failed to register a new timer after reload
270
271--- error_log
272lua abort pending timers
273lua ngx.timer expired
274http lua close fake http connection
275f: timer prematurely expired: true
276f: registered a new timer after reload
277f: exiting=true
278g: timer prematurely expired: false
279g: exiting=true
280
281
282
283=== TEST 5: HUP reload should abort pending timers
284--- config
285    location /t {
286        content_by_lua '
287            local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r")
288            if not f then
289                ngx.say("failed to open nginx.pid: ", err)
290                return
291            end
292
293            local pid = f:read()
294            -- ngx.say("master pid: [", pid, "]")
295
296            f:close()
297
298            local function f(premature)
299                print("f: timer prematurely expired: ", premature)
300                print("f: exiting=", ngx.worker.exiting())
301            end
302
303            for i = 1, 100 do
304                local ok, err = ngx.timer.at(3 + i, f)
305                if not ok then
306                    ngx.say("failed to set timer: ", err)
307                    return
308                end
309            end
310            ngx.say("ok")
311            os.execute("kill -HUP " .. pid)
312        ';
313    }
314--- request
315GET /t
316
317--- response_body
318ok
319
320--- wait: 0.5
321--- no_error_log
322[error]
323[alert]
324[crit]
325in callback: hello, 2
326failed to register a new timer after reload
327
328--- grep_error_log eval: qr/lua found \d+ pending timers/
329--- grep_error_log_out
330lua found 100 pending timers
331
332
333
334=== TEST 6: HUP reload should abort pending timers (coroutine + cosocket)
335--- http_config
336    lua_shared_dict test_dict 1m;
337
338    server {
339        listen 12355;
340        location = /foo {
341            echo 'foo';
342        }
343    }
344
345--- config
346    location /t {
347        content_by_lua '
348            local http_req = {"GET /foo HTTP/1.1", "Host: localhost:1234", "", ""}
349            http_req = table.concat(http_req, "\\r\\n")
350
351            -- Connect the socket
352            local sock = ngx.socket.tcp()
353            local ok,err = sock:connect("127.0.0.1", 12355)
354            if not ok then
355                ngx.log(ngx.ERR, err)
356            end
357
358            -- Send the request
359            local ok,err = sock:send(http_req)
360
361            -- Get Headers
362            repeat
363                local line, err = sock:receive("*l")
364            until not line or string.find(line, "^%s*$")
365
366            local function foo()
367                repeat
368                    -- Get and read chunk
369                    local line, err = sock:receive("*l")
370                    local len = tonumber(line)
371                    if len > 0 then
372                        local chunk, err = sock:receive(len)
373                        coroutine.yield(chunk)
374                        sock:receive(2)
375                    else
376                        -- Read last newline
377                        sock:receive(2)
378                    end
379                until len == 0
380            end
381
382            local co = coroutine.create(foo)
383            repeat
384                local chunk = select(2,coroutine.resume(co))
385            until chunk == nil
386
387            -- Breaks the timer
388            sock:setkeepalive()
389            ngx.say("ok")
390        ';
391
392        log_by_lua '
393            local background_thread
394            background_thread = function(premature)
395                ngx.log(ngx.DEBUG, premature)
396                if premature then
397                    ngx.shared["test_dict"]:delete("background_flag")
398                    return
399                end
400                local ok, err = ngx.timer.at(1, background_thread)
401
402                local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r")
403                if not f then
404                    ngx.say("failed to open nginx.pid: ", err)
405                    return
406                end
407                local pid = f:read()
408                -- ngx.say("master pid: [", pid, "]")
409                f:close()
410
411                os.execute("kill -HUP " .. pid)
412            end
413            local dict = ngx.shared["test_dict"]
414
415            if dict:get("background_flag") == nil then
416                local ok, err = ngx.timer.at(0, background_thread)
417                if ok then
418                    dict:set("test_dict", 1)
419                end
420            end
421        ';
422    }
423--- request
424GET /t
425
426--- response_body
427ok
428
429--- wait: 0.3
430--- no_error_log
431[error]
432[alert]
433[crit]
434in callback: hello, 2
435failed to register a new timer after reload
436
437--- grep_error_log eval: qr/lua found \d+ pending timers/
438--- grep_error_log_out
439lua found 1 pending timers
440
441
442
443=== TEST 7: HUP reload should abort pending timers (fuzz test)
444--- http_config
445    lua_max_pending_timers 8192;
446
447--- config
448    location /t {
449        content_by_lua '
450            local job = function(premature, kill)
451                if premature then
452                    return
453                end
454
455                if kill then
456                    local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r")
457                    if not f then
458                        ngx.log(ngx.ERR, "failed to open nginx.pid: ", err)
459                        return
460                    end
461                    local pid = f:read()
462                    -- ngx.say("master pid: [", pid, "]")
463                    f:close()
464
465                    os.execute("kill -HUP " .. pid)
466                end
467            end
468
469            math.randomseed(ngx.time())
470            local rand = math.random
471            local newtimer = ngx.timer.at
472            for i = 1, 8191 do
473                local delay = rand(4096)
474                local ok, err = newtimer(delay, job, false)
475                if not ok then
476                    ngx.say("failed to create timer at ", delay, ": ", err)
477                    return
478                end
479            end
480            local ok, err = newtimer(0, job, true)
481            if not ok then
482                ngx.say("failed to create the killer timer: ", err)
483                return
484            end
485            ngx.say("ok")
486        ';
487    }
488--- request
489GET /t
490
491--- response_body
492ok
493
494--- wait: 0.3
495--- no_error_log
496[error]
497[alert]
498
499--- grep_error_log eval: qr/lua found \d+ pending timers/
500--- grep_error_log_out
501lua found 8191 pending timers
502--- timeout: 20
503