# vim:set ft= ts=4 sw=4 et fdm=marker:

use Test::Nginx::Socket::Lua::Stream;use t::StapThread;

our $GCScript = $t::StapThread::GCScript;
our $StapScript = $t::StapThread::StapScript;

repeat_each(2);

plan tests => repeat_each() * (blocks() * 5 + 1) - 2;

$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: kill pending sleep
--- stream_server_config
    content_by_lua_block {
        function f()
            ngx.say("hello from f()")
            ngx.sleep(1)
        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, err = ngx.thread.kill(t)
        if not ok then
            ngx.say("failed to kill thread: ", err)
            return
        end

        ngx.say("killed")

        local ok, err = ngx.thread.kill(t)
        if not ok then
            ngx.say("failed to kill thread: ", err)
            return
        end
    }
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
delete thread 2
terminate 1: ok
delete thread 1

--- stream_response
hello from f()
thread created: running
killed
failed to kill thread: already waited or killed

--- no_error_log
[error]
--- error_log
lua clean up the timer for pending ngx.sleep



=== TEST 2: already waited
--- stream_server_config
    content_by_lua_block {
        function f()
            ngx.say("hello from f()")
            ngx.sleep(0.001)
            return 32
        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 kill thread: ", res)
            return
        end

        ngx.say("waited: ", res)

        local ok, err = ngx.thread.kill(t)
        if not ok then
            ngx.say("failed to kill thread: ", err)
            return
        end
    }
--- 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

--- stream_response
hello from f()
thread created: running
waited: 32
failed to kill thread: already waited or killed

--- no_error_log
[error]
lua clean up the timer for pending ngx.sleep



=== TEST 3: kill pending resolver
--- stream_server_config
    resolver 127.0.0.2:12345;
    resolver_timeout 5ms;
    content_by_lua_block {
        function f()
            local sock = ngx.socket.tcp()
            sock:connect("some.agentzh.org", 80)
        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, err = ngx.thread.kill(t)
        if not ok then
            ngx.say("failed to kill thread: ", err)
            return
        end

        ngx.say("killed")
    }
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
delete thread 2
terminate 1: ok
delete thread 1

--- stream_response
thread created: running
killed

--- no_error_log
[error]
--- error_log
lua tcp socket abort resolver



=== TEST 4: kill pending connect
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        local ready = false
        function f()
            local sock = ngx.socket.tcp()
            sock:connect("agentzh.org", 80)
            sock:close()
            ready = true
            sock:settimeout(10000)
            sock:connect("127.0.0.2", 12345)
        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()

        while not ready do
            ngx.sleep(0.001)
        end

        local ok, err = ngx.thread.kill(t)
        if not ok then
            ngx.say("failed to kill thread: ", err)
            return
        end

        ngx.say("killed")
    }
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
delete thread 2
terminate 1: ok
delete thread 1

--- stream_response
thread created: running
killed

--- no_error_log
[error]
lua tcp socket abort resolver
--- grep_error_log: stream lua finalize socket
--- grep_error_log_out
stream lua finalize socket
stream lua finalize socket

--- error_log



=== TEST 5: kill a thread already terminated
--- stream_server_config
    content_by_lua_block {
        function f()
            return
        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, err = ngx.thread.kill(t)
        if not ok then
            ngx.say("failed to kill thread: ", err)
            return
        end

        ngx.say("killed")
    }
--- stap2 eval: $::StapScript
--- stream_response
thread created: zombie
failed to kill thread: already terminated

--- no_error_log
[error]
[alert]
lua tcp socket abort resolver
--- error_log



=== TEST 6: kill self
--- stream_server_config
    content_by_lua_block {
        local ok, err = ngx.thread.kill(coroutine.running())
        if not ok then
            ngx.say("failed to kill main thread: ", err)
        else
            ngx.say("killed main thread.")
        end

        function f()
            local ok, err = ngx.thread.kill(coroutine.running())
            if not ok then
                ngx.say("failed to kill user thread: ", err)
            else
                ngx.say("user thread thread.")
            end

        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))
    }
--- stap2 eval: $::StapScript
--- stream_response
failed to kill main thread: not user thread
failed to kill user thread: killer not parent
thread created: zombie

--- no_error_log
[error]
[alert]
stream lua tcp socket abort resolver
--- error_log



=== TEST 7: no phantom uthreads decrement after killing parent uthread
--- stream_server_config
    content_by_lua_block {
        local redis_port = $TEST_NGINX_REDIS_PORT
        local dns_threads = {}

        dns_threads[1] = ngx.thread.spawn(function()
            local sock = ngx.socket.tcp()
            sock:settimeout(2000)
            local ok, err = sock:connect("127.0.0.1", redis_port)
            if not ok then
                return nil, err
            end

            sock:send("PING\r\n")
            local line = sock:receive()
            sock:setkeepalive()
            return line
        end)

        dns_threads[2] = ngx.thread.spawn(function()
            local child = coroutine.create(function()
                local sock = ngx.socket.tcp()
                sock:settimeout(2000)
                local ok, err = sock:connect("127.0.0.1", redis_port)
                if not ok then
                    return nil, err
                end

                sock:send("PING\r\n")
                local line = sock:receive()
                sock:setkeepalive()
                return line
            end)

            local ok, res = coroutine.resume(child)
            return res
        end)

        ngx.thread.wait(dns_threads[1], dns_threads[2])

        for _, t in ipairs(dns_threads) do
            ngx.thread.kill(t)
        end

        local probe_threads = {}
        for i = 1, 10 do
            probe_threads[i] = ngx.thread.spawn(function()
                local sock = ngx.socket.tcp()
                sock:settimeout(2000)
                local ok, err = sock:connect("127.0.0.1", redis_port)
                if not ok then
                    return nil, err
                end

                sock:send("PING\r\n")
                local line = sock:receive()
                sock:setkeepalive()
                return line
            end)
        end

        local ok_count = 0
        for i = 1, #probe_threads do
            local ok, res = ngx.thread.wait(probe_threads[i])
            if ok and res then
                ok_count = ok_count + 1
            end
        end

        ngx.say("ok_count=", ok_count)
    }
--- stream_response
ok_count=10
--- no_error_log
[error]
[alert]
