# vim:set ft= ts=4 sw=4 et fdm=marker: BEGIN { if ($ENV{TEST_NGINX_USE_HTTP3}) { $SkipReason = "client abort detect does not support in http3"; } elsif ($ENV{TEST_NGINX_USE_HTTP2}) { $SkipReason = "client abort detect does not support in http2"; } } use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); use t::StapThread; our $GCScript = <<_EOC_; $t::StapThread::GCScript F(ngx_http_lua_check_broken_connection) { println("lua check broken conn") } F(ngx_http_lua_request_cleanup) { println("lua req cleanup") } _EOC_ our $StapScript = $t::StapThread::StapScript; repeat_each(2); plan tests => repeat_each() * (blocks() * 4 + 19); $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211'; $ENV{TEST_NGINX_REDIS_PORT} ||= '6379'; #no_shuffle(); no_long_string(); run_tests(); __DATA__ === TEST 1: ignore the client abort event in the user callback --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") end) if not ok then error("cannot set on_abort: " .. err) end ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out_like chop ^create 2 in 1 lua check broken conn terminate 2: ok (?:lua check broken conn )?terminate 1: ok delete thread 2 delete thread 1 lua req cleanup --- timeout: 0.2 --- abort --- wait: 0.7 --- ignore_response --- no_error_log [error] --- error_log client prematurely closed connection on abort called main handler done === TEST 2: abort in the user callback --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(444) end) if not ok then error("cannot set on_abort: " .. err) end ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 1 --- wait: 0.1 --- timeout: 0.2 --- abort --- ignore_response --- no_error_log [error] main handler done --- error_log client prematurely closed connection on abort called === TEST 3: ngx.exit(499) with pending subrequest --- config location = /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(499) end) if not ok then error("cannot set on_abort: " .. err) end ngx.location.capture("/sleep") '; } location = /sleep { echo_sleep 1; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 1 --- timeout: 0.2 --- abort --- ignore_response --- no_error_log [error] --- error_log client prematurely closed connection on abort called === TEST 4: ngx.exit(408) with pending subrequest --- config location = /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(408) end) if not ok then error("cannot set on_abort: " .. err) end ngx.location.capture("/sleep") '; } location = /sleep { echo_sleep 1; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 1 --- timeout: 0.2 --- abort --- wait: 0.1 --- ignore_response --- no_error_log [error] --- error_log client prematurely closed connection on abort called === TEST 5: ngx.exit(-1) with pending subrequest --- config location = /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(-1) end) if not ok then error("cannot set on_abort: " .. err) end ngx.location.capture("/sleep") '; } location = /sleep { echo_sleep 1; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 1 --- timeout: 0.2 --- abort --- ignore_response --- no_error_log [error] --- error_log client prematurely closed connection on abort called === TEST 6: ngx.exit(0) with pending subrequest --- config location = /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(0) end) if not ok then error("cannot set on_abort: " .. err) end ngx.location.capture("/sleep") ngx.log(ngx.ERR, "main handler done") '; } location = /sleep { echo_sleep 0.7; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: fail terminate 1: ok delete thread 2 delete thread 1 lua req cleanup --- timeout: 0.2 --- abort --- wait: 0.7 --- ignore_response --- error_log eval [ 'client prematurely closed connection', 'on abort called', qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):4: attempt to abort with pending subrequests/, 'main handler done', ] === TEST 7: accessing cosocket in callback --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") local sock = ngx.socket.tcp() local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) if not ok then ngx.log(ngx.ERR, "failed to connect to redis: ", err) ngx.exit(499) end local bytes, err = sock:send("flushall\\r\\n") if not bytes then ngx.log(ngx.ERR, "failed to send query: ", err) ngx.exit(499) end local res, err = sock:receive() if not res then ngx.log(ngx.ERR, "failed to receive: ", err) ngx.exit(499) end ngx.log(ngx.NOTICE, "callback done: ", res) ngx.exit(499) end) if not ok then error("cannot set on_abort: " .. err) end ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 1 --- timeout: 0.2 --- abort --- wait: 0.5 --- ignore_response --- no_error_log [error] main handler done --- error_log client prematurely closed connection on abort called callback done: +OK === TEST 8: ignore the client abort event in the user callback (no check) --- config location /t { lua_check_client_abort off; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") end) if not ok then ngx.say("cannot set on_abort: ", err) return end ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out terminate 1: ok delete thread 1 lua req cleanup --- timeout: 0.2 --- abort --- response_body cannot set on_abort: lua_check_client_abort is off --- no_error_log client prematurely closed connection on abort called main handler done === TEST 9: register on_abort callback but no client abortion --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") end) if not ok then error("cannot set on_abort: " .. err) end ngx.say("done") '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 terminate 1: ok delete thread 1 lua req cleanup delete thread 2 --- response_body done --- no_error_log [error] client prematurely closed connection on abort called main handler done === TEST 10: ignore the client abort event in the user callback (uthread) --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") end) if not ok then error("cannot set on_abort: " .. err) end ngx.thread.spawn(function () ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") end) '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 1: ok delete thread 1 lua check broken conn terminate 2: ok delete thread 2 terminate 3: ok delete thread 3 lua req cleanup --- timeout: 0.2 --- abort --- wait: 0.7 --- ignore_response --- no_error_log [error] --- error_log client prematurely closed connection on abort called main handler done === TEST 11: abort in the user callback (uthread) --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(444) end) if not ok then error("cannot set on_abort: " .. err) end ngx.thread.spawn(function () ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") end) '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 1: ok delete thread 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 3 --- timeout: 0.2 --- wait: 0.1 --- abort --- ignore_response --- no_error_log [error] main handler done --- error_log client prematurely closed connection on abort called === TEST 12: register on_abort callback but no client abortion (uthread) --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") end) if not ok then error("cannot set on_abort: " .. err) end ngx.thread.spawn(function () ngx.sleep(0.1) ngx.say("done") end) '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 create 3 in 1 spawn user thread 3 in 1 terminate 1: ok delete thread 1 terminate 3: ok delete thread 3 lua req cleanup delete thread 2 --- response_body done --- no_error_log [error] client prematurely closed connection on abort called main handler done === TEST 13: register on_abort callback multiple times --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") end) if not ok then ngx.say("1: cannot set on_abort: " .. err) return end local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") end) if not ok then ngx.say("2: cannot set on_abort: " .. err) return end ngx.thread.spawn(function () ngx.sleep(0.1) ngx.say("done") end) '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 terminate 1: ok delete thread 1 lua req cleanup delete thread 2 --- response_body 2: cannot set on_abort: duplicate call --- no_error_log [error] === TEST 14: abort with 499 in the user callback, but the header is already sent --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(499) end) if not ok then error("cannot set on_abort: " .. err) end ngx.send_headers() ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 1 --- timeout: 0.2 --- abort --- ignore_response --- no_error_log [error] main handler done --- error_log client prematurely closed connection on abort called === TEST 15: abort with 444 in the user callback, but the header is already sent --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(444) end) if not ok then error("cannot set on_abort: " .. err) end ngx.send_headers() ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 1 --- timeout: 0.2 --- abort --- ignore_response --- no_error_log [error] main handler done --- error_log client prematurely closed connection on abort called === TEST 16: abort with 408 in the user callback, but the header is already sent --- config location /t { lua_check_client_abort on; content_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") ngx.exit(408) end) if not ok then error("cannot set on_abort: " .. err) end ngx.send_headers() ngx.sleep(0.7) ngx.log(ngx.NOTICE, "main handler done") '; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 lua check broken conn terminate 2: ok lua req cleanup delete thread 2 delete thread 1 --- timeout: 0.2 --- wait: 0.1 --- abort --- ignore_response --- no_error_log [error] main handler done --- error_log client prematurely closed connection on abort called === TEST 17: GC issue with the on_abort thread object --- config location = /t { lua_check_client_abort on; content_by_lua ' ngx.on_abort(function () end) collectgarbage() ngx.sleep(60) '; } --- request GET /t --- abort --- timeout: 0.2 --- ignore_response --- no_error_log [error] [alert] === TEST 18: register on_abort callback but no client abortion (2 uthreads and 1 pending) --- config location /t { lua_check_client_abort on; rewrite_by_lua ' local ok, err = ngx.on_abort(function () ngx.log(ngx.NOTICE, "on abort called") end) if not ok then error("cannot set on_abort: " .. err) end ngx.thread.spawn(function () ngx.sleep(0.1) ngx.say("done") ngx.exit(200) end) ngx.thread.spawn(function () ngx.sleep(100) end) '; content_by_lua return; } --- request GET /t --- stap2 eval: $::StapScript --- stap eval: $::GCScript --- stap_out create 2 in 1 create 3 in 1 spawn user thread 3 in 1 create 4 in 1 spawn user thread 4 in 1 terminate 1: ok delete thread 1 terminate 3: ok lua req cleanup delete thread 2 delete thread 3 delete thread 4 --- wait: 0.5 --- response_body done --- no_error_log [error] client prematurely closed connection on abort called main handler done