# vim:set ft= ts=4 sw=4 et fdm=marker: use Test::Nginx::Socket::Lua; use t::StapThread; our $GCScript = $t::StapThread::GCScript; our $StapScript = $t::StapThread::StapScript; repeat_each(2); plan tests => repeat_each() * (blocks() * 4); $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211'; #no_shuffle(); no_long_string(); run_tests(); __DATA__ === TEST 1: simple user thread wait without I/O --- config location /lua { content_by_lua ' function f() ngx.say("hello in thread") return "done" end local t, err = ngx.thread.spawn(f) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) collectgarbage() local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to run thread: ", res) return end ngx.say(res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: ok delete thread 2 terminate 1: ok delete thread 1 --- response_body hello in thread thread created: zombie done --- no_error_log [error] === TEST 2: simple user thread wait with I/O --- config location /lua { content_by_lua ' function f() ngx.sleep(0.1) ngx.say("hello in thread") return "done" end local t, err = ngx.thread.spawn(f) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to wait thread: ", res) return end ngx.say(res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: ok delete thread 2 terminate 1: ok delete thread 1 --- response_body thread created: running hello in thread done --- no_error_log [error] === TEST 3: wait on uthreads on the reversed order of their termination --- config location /lua { content_by_lua ' function f() ngx.sleep(0.1) ngx.say("f: hello") return "done" end function g() ngx.sleep(0.2) ngx.say("g: hello") return "done" end local tf, err = ngx.thread.spawn(f) if not tf then ngx.say("failed to spawn thread: ", err) return end ngx.say("f thread created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then ngx.say("failed to spawn thread: ", err) return end ngx.say("g thread created: ", coroutine.status(tg)) local ok, res = ngx.thread.wait(tg) if not ok then ngx.say("failed to wait g: ", res) return end ngx.say("g: ", res) ngx.say("f thread status: ", coroutine.status(tf)) ok, res = ngx.thread.wait(tf) if not ok then ngx.say("failed to wait f: ", res) return end ngx.say("f: ", res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 2: ok terminate 3: ok delete thread 3 delete thread 2 terminate 1: ok delete thread 1 --- response_body f thread created: running g thread created: running f: hello g: hello g: done f thread status: zombie f: done --- no_error_log [error] === TEST 4: wait on uthreads on the exact order of their termination --- config location /lua { content_by_lua ' function f() ngx.sleep(0.1) ngx.say("f: hello") return "done" end function g() ngx.sleep(0.2) ngx.say("g: hello") return "done" end local tf, err = ngx.thread.spawn(f) if not tf then ngx.say("failed to spawn thread: ", err) return end ngx.say("f thread created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then ngx.say("failed to spawn thread: ", err) return end ngx.say("g thread created: ", coroutine.status(tg)) ok, res = ngx.thread.wait(tf) if not ok then ngx.say("failed to wait f: ", res) return end ngx.say("f: ", res) ngx.say("g thread status: ", coroutine.status(tg)) local ok, res = ngx.thread.wait(tg) if not ok then ngx.say("failed to wait g: ", res) return end ngx.say("g: ", res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 2: ok delete thread 2 terminate 3: ok delete thread 3 terminate 1: ok delete thread 1 --- wait: 0.1 --- response_body f thread created: running g thread created: running f: hello f: done g thread status: running g: hello g: done --- no_error_log [error] === TEST 5: simple user thread wait without I/O (return multiple values) --- config location /lua { content_by_lua ' function f() ngx.say("hello in thread") return "done", 3.14 end local t, err = ngx.thread.spawn(f) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) collectgarbage() local ok, res1, res2 = ngx.thread.wait(t) if not ok then ngx.say("failed to run thread: ", res1) return end ngx.say("res: ", res1, " ", res2) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: ok delete thread 2 terminate 1: ok delete thread 1 --- response_body hello in thread thread created: zombie res: done 3.14 --- no_error_log [error] === TEST 6: simple user thread wait with I/O, return multiple values --- config location /lua { content_by_lua ' function f() ngx.sleep(0.1) ngx.say("hello in thread") return "done", 3.14 end local t, err = ngx.thread.spawn(f) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) local ok, res1, res2 = ngx.thread.wait(t) if not ok then ngx.say("failed to wait thread: ", res1) return end ngx.say("res: ", res1, " ", res2) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: ok delete thread 2 terminate 1: ok delete thread 1 --- response_body thread created: running hello in thread res: done 3.14 --- no_error_log [error] === TEST 7: simple user thread wait without I/O, throw errors --- config location /lua { content_by_lua ' function f() ngx.say("hello in thread") error("bad bad!") end local t, err = ngx.thread.spawn(f) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) collectgarbage() local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to wait thread: ", res) return end ngx.say(res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: fail delete thread 2 terminate 1: ok delete thread 1 --- response_body hello in thread thread created: zombie failed to wait thread: bad bad! --- error_log eval qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):4: bad bad!/ === TEST 8: simple user thread wait with I/O, throw errors --- config location /lua { content_by_lua ' function f() ngx.sleep(0.1) ngx.say("hello in thread") error("bad bad!") end local t, err = ngx.thread.spawn(f) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) collectgarbage() local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to wait thread: ", res) return end ngx.say(res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: fail delete thread 2 terminate 1: ok delete thread 1 --- response_body thread created: running hello in thread failed to wait thread: bad bad! --- error_log eval qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):5: bad bad!/ === TEST 9: simple user thread wait without I/O (in a user coroutine) --- config location /lua { content_by_lua ' function g() ngx.say("hello in thread") return "done" end function f() local t, err = ngx.thread.spawn(g) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) collectgarbage() local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to run thread: ", res) return end ngx.say(res) end local co = coroutine.create(f) coroutine.resume(co) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 create 3 in 2 spawn user thread 3 in 2 terminate 3: ok delete thread 3 terminate 2: ok terminate 1: ok delete thread 1 --- response_body hello in thread thread created: zombie done --- no_error_log [error] === TEST 10: simple user thread wait with I/O (in a user coroutine) --- config location /lua { content_by_lua ' function g() ngx.sleep(0.1) ngx.say("hello in thread") return "done" end function f() local t, err = ngx.thread.spawn(g) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) collectgarbage() local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to run thread: ", res) return end ngx.say(res) end local co = coroutine.create(f) coroutine.resume(co) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 create 3 in 2 spawn user thread 3 in 2 terminate 3: ok delete thread 3 terminate 2: ok terminate 1: ok delete thread 1 --- response_body thread created: running hello in thread done --- no_error_log [error] === TEST 11: waiting on two simple user threads without I/O --- config location /lua { content_by_lua ' -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say function f() out("f: hello") return "f done" end function g() out("g: hello") return "g done" end local tf, err = ngx.thread.spawn(f) if not tf then out("failed to spawn thread f: ", err) return end out("thread f created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then out("failed to spawn thread g: ", err) return end out("thread g created: ", coroutine.status(tg)) local ok, res = ngx.thread.wait(tf, tg) if not ok then out("failed to wait thread: ", res) return end out("res: ", res) out("f status: ", coroutine.status(tf)) out("g status: ", coroutine.status(tg)) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: ok create 3 in 1 spawn user thread 3 in 1 terminate 3: ok delete thread 2 terminate 1: ok delete thread 3 delete thread 1 --- response_body f: hello thread f created: zombie g: hello thread g created: zombie res: f done f status: dead g status: zombie --- no_error_log [error] === TEST 12: waiting on two simple user threads with I/O --- config location /lua { content_by_lua ' -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say function f() ngx.sleep(0.1) out("f: hello") return "f done" end function g() ngx.sleep(0.2) out("g: hello") return "g done" end local tf, err = ngx.thread.spawn(f) if not tf then out("failed to spawn thread f: ", err) return end out("thread f created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then out("failed to spawn thread g: ", err) return end out("thread g created: ", coroutine.status(tg)) local ok, res = ngx.thread.wait(tf, tg) if not ok then out("failed to wait thread: ", res) return end out("res: ", res) out("f status: ", coroutine.status(tf)) out("g status: ", coroutine.status(tg)) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 2: ok delete thread 2 terminate 1: ok delete thread 1 terminate 3: ok delete thread 3 --- response_body thread f created: running thread g created: running f: hello res: f done f status: dead g status: running g: hello --- no_error_log [error] === TEST 13: waiting on two simple user threads with I/O (uthreads completed in reversed order) --- config location /lua { content_by_lua ' -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say function f() ngx.sleep(0.2) out("f: hello") return "f done" end function g() ngx.sleep(0.1) out("g: hello") return "g done" end local tf, err = ngx.thread.spawn(f) if not tf then out("failed to spawn thread f: ", err) return end out("thread f created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then out("failed to spawn thread g: ", err) return end out("thread g created: ", coroutine.status(tg)) local ok, res = ngx.thread.wait(tf, tg) if not ok then out("failed to wait thread: ", res) return end out("res: ", res) out("f status: ", coroutine.status(tf)) out("g status: ", coroutine.status(tg)) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 3: ok delete thread 3 terminate 1: ok delete thread 1 terminate 2: ok delete thread 2 --- response_body thread f created: running thread g created: running g: hello res: g done f status: running g status: dead f: hello --- no_error_log [error] === TEST 14: waiting on two simple user threads without I/O, both aborted by errors --- config location /lua { content_by_lua ' -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say function f() out("f: hello") error("f done") end function g() out("g: hello") error("g done") end local tf, err = ngx.thread.spawn(f) if not tf then out("failed to spawn thread f: ", err) return end out("thread f created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then out("failed to spawn thread g: ", err) return end out("thread g created: ", coroutine.status(tg)) local ok, res = ngx.thread.wait(tf, tg) if not ok then out("failed to wait thread: ", res) else out("res: ", res) end out("f status: ", coroutine.status(tf)) out("g status: ", coroutine.status(tg)) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: fail create 3 in 1 spawn user thread 3 in 1 terminate 3: fail delete thread 2 terminate 1: ok delete thread 3 delete thread 1 --- response_body f: hello thread f created: zombie g: hello thread g created: zombie failed to wait thread: f done f status: dead g status: zombie --- error_log eval qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):7: f done/ === TEST 15: waiting on two simple user threads with I/O, both aborted by errors --- config location /lua { content_by_lua ' -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say function f() ngx.sleep(0.1) out("f: hello") error("f done") end function g() ngx.sleep(0.2) out("g: hello") error("g done") end local tf, err = ngx.thread.spawn(f) if not tf then out("failed to spawn thread f: ", err) return end out("thread f created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then out("failed to spawn thread g: ", err) return end out("thread g created: ", coroutine.status(tg)) local ok, res = ngx.thread.wait(tf, tg) if not ok then out("failed to wait thread: ", res) else out("res: ", res) end out("f status: ", coroutine.status(tf)) out("g status: ", coroutine.status(tg)) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 2: fail delete thread 2 terminate 1: ok delete thread 1 terminate 3: fail delete thread 3 --- response_body thread f created: running thread g created: running f: hello failed to wait thread: f done f status: dead g status: running g: hello --- error_log eval qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):8: f done/ === TEST 16: wait on uthreads on the exact order of their termination, but exit the world early --- config location /lua { content_by_lua ' function f() ngx.sleep(0.1) ngx.say("f: hello") return "done" end function g() ngx.sleep(0.2) ngx.say("g: hello") return "done" end local tf, err = ngx.thread.spawn(f) if not tf then ngx.say("failed to spawn thread: ", err) return end ngx.say("f thread created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then ngx.say("failed to spawn thread: ", err) return end ngx.say("g thread created: ", coroutine.status(tg)) ok, res = ngx.thread.wait(tf, tg) if not ok then ngx.say("failed to wait: ", res) return end ngx.say("res: ", res) ngx.exit(200) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 2: ok delete thread 2 terminate 1: ok delete thread 3 delete thread 1 --- response_body f thread created: running g thread created: running f: hello res: done --- no_error_log [error] === TEST 17: wait on uthreads on the reversed order of their termination, but exit the world early --- config location /lua { content_by_lua ' function f() ngx.sleep(0.2) ngx.say("f: hello") return "f done" end function g() ngx.sleep(0.1) ngx.say("g: hello") return "g done" end local tf, err = ngx.thread.spawn(f) if not tf then ngx.say("failed to spawn thread f: ", err) return end ngx.say("f thread created: ", coroutine.status(tf)) local tg, err = ngx.thread.spawn(g) if not tg then ngx.say("failed to spawn thread g: ", err) return end ngx.say("g thread created: ", coroutine.status(tg)) ok, res = ngx.thread.wait(tf, tg) if not ok then ngx.say("failed to wait: ", res) return end ngx.say("res: ", res) ngx.exit(200) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 3: ok delete thread 3 terminate 1: ok delete thread 2 delete thread 1 --- response_body f thread created: running g thread created: running g: hello res: g done --- no_error_log [error] === TEST 18: entry coroutine waiting on a thread not created by itself --- config location /lua { content_by_lua ' local t function f() ngx.sleep(0.1) return "done" end function g() t = ngx.thread.spawn(f) end local co = coroutine.create(g) coroutine.resume(co) local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to run thread: ", res) return end ngx.say(res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 create 3 in 2 spawn user thread 3 in 2 terminate 2: ok terminate 1: fail delete thread 3 delete thread 1 --- response_body_like: 500 Internal Server Error --- error_code: 500 --- error_log only the parent coroutine can wait on the thread === TEST 19: entry coroutine waiting on a user coroutine --- config location /lua { content_by_lua ' function f() ngx.sleep(0.1) coroutine.yield() return "done" end local co = coroutine.create(f) coroutine.resume(co) local ok, res = ngx.thread.wait(co) if not ok then ngx.say("failed to run thread: ", res) return end ngx.say(res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 terminate 1: fail delete thread 1 --- response_body_like: 500 Internal Server Error --- error_code: 500 --- error_log eval qr/lua entry thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):11: attempt to wait on a coroutine that is not a user thread/ === TEST 20: lua backtrace dumper may access dead parent coroutines --- config location /lua { content_by_lua ' function f() ngx.sleep(0.1) collectgarbage() error("f done") end ngx.thread.spawn(f) ngx.say("ok") collectgarbage() '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 1: ok delete thread 1 terminate 2: fail delete thread 2 --- response_body ok --- error_log eval qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):5: f done/ === TEST 21: waiting on a dead coroutine --- config location /lua { content_by_lua ' function f() ngx.say("hello in thread") return "done" end local t, err = ngx.thread.spawn(f) if not t then ngx.say("failed to spawn thread: ", err) return end ngx.say("thread created: ", coroutine.status(t)) collectgarbage() local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to run thread: ", res) return end local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to run thread: ", res) return end ngx.say(res) '; } --- request GET /lua --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 spawn user thread 2 in 1 terminate 2: ok delete thread 2 terminate 1: ok delete thread 1 --- response_body hello in thread thread created: zombie failed to run thread: already waited or killed --- no_error_log [error] === TEST 22: spawn and wait uthreads for many times --- config location /lua { content_by_lua ' function f() -- ngx.say("hello in thread") return "done" end for i = 1, 100 do local t, err = ngx.thread.spawn(f) if not t then ngx.say("failed to spawn thread: ", err) break end -- ngx.say("thread created: ", coroutine.status(t)) collectgarbage() local ok, res = ngx.thread.wait(t) if not ok then ngx.say("failed to run thread: ", res) break end ngx.say(i, ": ", res) end '; } --- request GET /lua --- response_body eval my $s = ''; for my $i (1..100) { $s .= "$i: done\n"; } $s; --- no_error_log [error] [alert]