# 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();
worker_connections(256);
no_long_string();
run_tests();

__DATA__

=== TEST 1: simple user thread without I/O
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("hello in thread")
            end

            ngx.say("before")
            ngx.thread.spawn(f)
            ngx.say("after")
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
terminate 2: ok
terminate 1: ok
delete thread 2
delete thread 1

--- response_body
before
hello in thread
after
--- no_error_log
[error]



=== TEST 2: two simple user threads without I/O
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("in thread 1")
            end

            local function g()
                ngx.say("in thread 2")
            end

            ngx.say("before 1")
            ngx.thread.spawn(f)
            ngx.say("after 1")

            ngx.say("before 2")
            ngx.thread.spawn(g)
            ngx.say("after 2")
        ';
    }
--- 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
terminate 1: ok
delete thread 2
delete thread 3
delete thread 1

--- response_body
before 1
in thread 1
after 1
before 2
in thread 2
after 2
--- no_error_log
[error]



=== TEST 3: simple user thread with sleep
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("before sleep")
                ngx.sleep(0.1)
                ngx.say("after sleep")
            end

            ngx.say("before thread create")
            ngx.thread.spawn(f)
            ngx.say("after thread create")
        ';
    }
--- 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: ok
delete thread 2

--- response_body
before thread create
before sleep
after thread create
after sleep
--- no_error_log
[error]



=== TEST 4: two simple user threads with sleep
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("1: before sleep")
                ngx.sleep(0.2)
                ngx.say("1: after sleep")
            end

            local function g()
                ngx.say("2: before sleep")
                ngx.sleep(0.1)
                ngx.say("2: after sleep")
            end

            ngx.say("1: before thread create")
            ngx.thread.spawn(f)
            ngx.say("1: after thread create")

            ngx.say("2: before thread create")
            ngx.thread.spawn(g)
            ngx.say("2: after thread create")
        ';
    }
--- 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 1: ok
delete thread 1
terminate 3: ok
delete thread 3
terminate 2: ok
delete thread 2

--- wait: 0.1
--- response_body
1: before thread create
1: before sleep
1: after thread create
2: before thread create
2: before sleep
2: after thread create
2: after sleep
1: after sleep
--- no_error_log
[error]



=== TEST 5: error in user thread
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.blah()
            end

            ngx.thread.spawn(f)
            ngx.say("after")
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
terminate 2: fail
terminate 1: ok
delete thread 2
delete thread 1

--- response_body
after
--- error_log eval
qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):3: attempt to call field 'blah' \(a nil value\)/



=== TEST 6: simple user threads doing a single subrequest (entry quits early)
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("before capture")
                local res = ngx.location.capture("/proxy")
                ngx.say("after capture: ", res.body)
            end

            ngx.say("before thread create")
            ngx.thread.spawn(f)
            ngx.say("after thread create")
        ';
    }

    location /proxy {
        proxy_pass http://127.0.0.1:$server_port/foo;
    }

    location /foo {
        echo_sleep 0.1;
        echo -n hello world;
    }
--- 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: ok
delete thread 2

--- response_body
before thread create
before capture
after thread create
after capture: hello world
--- no_error_log
[error]



=== TEST 7: simple user threads doing a single subrequest (entry also does a subrequest and quits early)
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("before capture")
                local res = ngx.location.capture("/proxy?foo")
                ngx.say("after capture: ", res.body)
            end

            ngx.say("before thread create")
            ngx.thread.spawn(f)
            ngx.say("after thread create")
            local res = ngx.location.capture("/proxy?bar")
            ngx.say("capture: ", res.body)
        ';
    }

    location /proxy {
        proxy_pass http://127.0.0.1:$server_port/$args;
    }

    location /foo {
        echo_sleep 0.1;
        echo -n hello foo;
    }

    location /bar {
        echo -n hello bar;
    }
--- 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: ok
delete thread 2

--- response_body
before thread create
before capture
after thread create
capture: hello bar
after capture: hello foo
--- no_error_log
[error]



=== TEST 8: simple user threads doing a single subrequest (entry also does a subrequest and quits late)
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("before capture")
                local res = ngx.location.capture("/proxy?foo")
                ngx.say("after capture: ", res.body)
            end

            ngx.say("before thread create")
            ngx.thread.spawn(f)
            ngx.say("after thread create")
            local res = ngx.location.capture("/proxy?bar")
            ngx.say("capture: ", res.body)
        ';
    }

    location /proxy {
        proxy_pass http://127.0.0.1:$server_port/$args;
    }

    location /foo {
        echo_sleep 0.1;
        echo -n hello foo;
    }

    location /bar {
        echo_sleep 0.2;
        echo -n hello bar;
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
terminate 2: ok
terminate 1: ok
delete thread 2
delete thread 1

--- response_body
before thread create
before capture
after thread create
after capture: hello foo
capture: hello bar
--- no_error_log
[error]



=== TEST 9: two simple user threads doing single subrequests (entry also does a subrequest and quits between)
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("f: before capture")
                local res = ngx.location.capture("/proxy?foo")
                ngx.say("f: after capture: ", res.body)
            end

            local function g()
                ngx.say("g: before capture")
                local res = ngx.location.capture("/proxy?bah")
                ngx.say("g: after capture: ", res.body)
            end

            ngx.say("before thread 1 create")
            ngx.thread.spawn(f)
            ngx.say("after thread 1 create")

            ngx.say("before thread 2 create")
            ngx.thread.spawn(g)
            ngx.say("after thread 2 create")

            local res = ngx.location.capture("/proxy?bar")
            ngx.say("capture: ", res.body)
        ';
    }

    location /proxy {
        proxy_pass http://127.0.0.1:$server_port/$args;
    }

    location /foo {
        echo_sleep 0.1;
        echo -n hello foo;
    }

    location /bar {
        echo_sleep 0.2;
        echo -n hello bar;
    }

    location /bah {
        echo_sleep 0.3;
        echo -n hello bah;
    }
--- 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 1: ok
delete thread 2
delete thread 1
terminate 3: ok
delete thread 3

--- response_body
before thread 1 create
f: before capture
after thread 1 create
before thread 2 create
g: before capture
after thread 2 create
f: after capture: hello foo
capture: hello bar
g: after capture: hello bah
--- no_error_log
[error]



=== TEST 10: nested user threads
--- config
    location /lua {
        content_by_lua '
            local g
            local function f()
                ngx.say("before g")
                ngx.thread.spawn(g)
                ngx.say("after g")
            end

            function g()
                ngx.say("hello in g()")
            end

            ngx.say("before f")
            ngx.thread.spawn(f)
            ngx.say("after f")
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
create 3 in 2
spawn user thread 3 in 2
terminate 3: ok
terminate 1: ok
delete thread 1
terminate 2: ok
delete thread 3
delete thread 2

--- response_body
before f
before g
hello in g()
after f
after g
--- no_error_log
[error]



=== TEST 11: nested user threads (with I/O)
--- config
    location /lua {
        content_by_lua '
            local g
            local function f()
                ngx.say("before g")
                ngx.thread.spawn(g)
                ngx.say("after g")
            end

            function g()
                ngx.sleep(0.1)
                ngx.say("hello in g()")
            end

            ngx.say("before f")
            ngx.thread.spawn(f)
            ngx.say("after f")
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
create 3 in 2
spawn user thread 3 in 2
terminate 1: ok
delete thread 1
terminate 2: ok
delete thread 2
terminate 3: ok
delete thread 3

--- response_body
before f
before g
after f
after g
hello in g()
--- no_error_log
[error]



=== TEST 12: coroutine status of a running user thread
--- config
    location /lua {
        content_by_lua '
            local co
            local function f()
                co = coroutine.running()
                ngx.sleep(0.1)
            end

            ngx.thread.spawn(f)
            ngx.say("status: ", coroutine.status(co))
        ';
    }
--- 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: ok
delete thread 2

--- response_body
status: running
--- no_error_log
[error]



=== TEST 13: coroutine status of a dead user thread
--- config
    location /lua {
        content_by_lua '
            local co
            local function f()
                co = coroutine.running()
            end

            ngx.thread.spawn(f)
            ngx.say("status: ", coroutine.status(co))
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
terminate 2: ok
terminate 1: ok
delete thread 2
delete thread 1

--- response_body
status: zombie
--- no_error_log
[error]



=== TEST 14: coroutine status of a "normal" user thread
--- config
    location /lua {
        content_by_lua '
            local co
            local g
            local function f()
                co = coroutine.running()
                local co2 = coroutine.create(g)
                coroutine.resume(co2)
            end

            function g()
                ngx.sleep(0.1)
            end

            ngx.thread.spawn(f)
            ngx.say("status: ", coroutine.status(co))
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
create 3 in 2
terminate 1: ok
delete thread 1
terminate 3: ok
terminate 2: ok
delete thread 2

--- response_body
status: normal
--- no_error_log
[error]



=== TEST 15: creating user threads in a user coroutine
--- config
    location /lua {
        content_by_lua '
            local g
            local function f()
                ngx.say("before g")
                ngx.thread.spawn(g)
                ngx.say("after g")
            end

            function g()
                ngx.say("hello in g()")
            end

            ngx.say("before f")
            local co = coroutine.create(f)
            coroutine.resume(co)
            ngx.say("after f")
        ';
    }
--- 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
terminate 2: ok
delete thread 3
terminate 1: ok
delete thread 1

--- response_body
before f
before g
hello in g()
after g
after f
--- no_error_log
[error]



=== TEST 16: manual time slicing between a user thread and the entry thread
--- config
    location /lua {
        content_by_lua '
            local yield = coroutine.yield

            local function f()
                local self = coroutine.running()
                ngx.say("f 1")
                yield(self)
                ngx.say("f 2")
                yield(self)
                ngx.say("f 3")
            end

            local self = coroutine.running()
            ngx.say("0")
            yield(self)
            ngx.say("1")
            ngx.thread.spawn(f)
            ngx.say("2")
            yield(self)
            ngx.say("3")
            yield(self)
            ngx.say("4")
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
terminate 2: ok
terminate 1: ok
delete thread 2
delete thread 1

--- response_body
0
1
f 1
2
f 2
3
f 3
4
--- no_error_log
[error]



=== TEST 17: manual time slicing between two user threads
--- config
    location /lua {
        content_by_lua '
            local yield = coroutine.yield

            local function f()
                local self = coroutine.running()
                ngx.say("f 1")
                yield(self)
                ngx.say("f 2")
                yield(self)
                ngx.say("f 3")
            end

            local function g()
                local self = coroutine.running()
                ngx.say("g 1")
                yield(self)
                ngx.say("g 2")
                yield(self)
                ngx.say("g 3")
            end

            ngx.thread.spawn(f)
            ngx.thread.spawn(g)
            ngx.say("done")
        ';
    }
--- 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 1: ok
delete thread 1
terminate 2: ok
delete thread 2
terminate 3: ok
delete thread 3

--- response_body
f 1
g 1
f 2
done
g 2
f 3
g 3
--- no_error_log
[error]



=== TEST 18: entry thread and a user thread flushing at the same time
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("hello in thread")
                coroutine.yield(coroutine.running)
                ngx.flush(true)
            end

            ngx.say("before")
            ngx.thread.spawn(f)
            ngx.say("after")
            ngx.flush(true)
        ';
    }
--- 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: ok
delete thread 2

--- response_body
before
hello in thread
after
--- no_error_log
[error]



=== TEST 19: two user threads flushing at the same time
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.say("hello from f")
                ngx.flush(true)
            end

            local function g()
                ngx.say("hello from g")
                ngx.flush(true)
            end

            ngx.thread.spawn(f)
            ngx.thread.spawn(g)
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out_like
^(?:create 2 in 1
spawn user thread 2 in 1
create 3 in 1
spawn user thread 3 in 1
terminate 1: ok
delete thread 1
terminate 2: ok
delete thread 2
terminate 3: ok
delete thread 3|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
terminate 1: ok
delete thread 2
delete thread 3
delete thread 1)$

--- response_body
hello from f
hello from g
--- no_error_log
[error]



=== TEST 20: user threads + ngx.socket.tcp
--- config
    location /lua {
        content_by_lua '
            local function f()
                local sock = ngx.socket.tcp()
                local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
                if not ok then
                    ngx.say("failed to connect: ", err)
                    return
                end
                local bytes, err = sock:send("flush_all\\r\\n")
                if not bytes then
                    ngx.say("failed to send query: ", err)
                    return
                end

                local line, err = sock:receive()
                if not line then
                    ngx.say("failed to receive: ", err)
                    return
                end

                ngx.say("received: ", line)
            end

            ngx.say("before")
            ngx.thread.spawn(f)
            ngx.say("after")
        ';
    }
--- 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: ok
delete thread 2

--- response_body
before
after
received: OK
--- no_error_log
[error]



=== TEST 21: user threads + ngx.socket.udp
--- config
    location /lua {
        content_by_lua '
            local function f()
                local sock = ngx.socket.udp()
                local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_RAND_PORT_1)
                local bytes, err = sock:send("blah")
                if not bytes then
                    ngx.say("failed to send query: ", err)
                    return
                end

                local line, err = sock:receive()
                if not line then
                    ngx.say("failed to receive: ", err)
                    return
                end

                ngx.say("received: ", line)
            end

            ngx.say("before")
            ngx.thread.spawn(f)
            ngx.say("after")
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out_like chop
^(?:create 2 in 1
spawn user thread 2 in 1
terminate 1: ok
delete thread 1
terminate 2: ok
delete thread 2
|create 2 in 1
spawn user thread 2 in 1
terminate 2: ok
terminate 1: ok
delete thread 2
delete thread 1
)$

--- udp_listen: $TEST_NGINX_RAND_PORT_1
--- udp_query: blah
--- udp_reply: hello udp
--- response_body_like chop
^(?:before
after
received: hello udp
|before
received: hello udp
after)$

--- no_error_log
[error]



=== TEST 22: simple user thread with ngx.req.read_body()
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.req.read_body()
                local body = ngx.req.get_body_data()
                ngx.say("body: ", body)
            end

            ngx.say("before")
            ngx.thread.spawn(f)
            ngx.say("after")
        ';
    }
--- request
POST /lua
hello world
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out_like chop
^(?:create 2 in 1
spawn user thread 2 in 1
terminate 2: ok
terminate 1: ok
delete thread 2
delete thread 1|create 2 in 1
spawn user thread 2 in 1
terminate 1: ok
delete thread 1
terminate 2: ok
delete thread 2)$

--- response_body_like chop
^(?:before
body: hello world
after|before
after
body: hello world)$

--- no_error_log
[error]



=== TEST 23: simple user thread with ngx.req.socket()
ngx.req.socket() does not support in http3
--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3}
--- config
    location /lua {
        content_by_lua '
            local function f()
                local sock = ngx.req.socket()
                local body, err = sock:receive(11)
                if not body then
                    ngx.say("failed to read body: ", err)
                    return
                end

                ngx.say("body: ", body)
            end

            ngx.say("before")
            ngx.thread.spawn(f)
            ngx.say("after")
        ';
    }
--- request
POST /lua
hello world
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out_like chop
^(?:create 2 in 1
spawn user thread 2 in 1
terminate 2: ok
terminate 1: ok
delete thread 2
delete thread 1|create 2 in 1
spawn user thread 2 in 1
terminate 1: ok
delete thread 1
terminate 2: ok
delete thread 2)$

--- response_body_like chop
^(?:before
body: hello world
after|before
after
body: hello world)$

--- no_error_log
[error]



=== TEST 24: simple user thread with args
--- config
    location /lua {
        content_by_lua '
            local function f(a, b)
                ngx.say("hello ", a, " and ", b)
            end

            ngx.say("before")
            ngx.thread.spawn(f, "foo", 3.14)
            ngx.say("after")
        ';
    }
--- request
GET /lua
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out
create 2 in 1
spawn user thread 2 in 1
terminate 2: ok
terminate 1: ok
delete thread 2
delete thread 1

--- response_body
before
hello foo and 3.14
after
--- no_error_log
[error]



=== TEST 25: multiple user threads + subrequests returning 404 immediately
--- config
    location /t {
        content_by_lua '
            local capture = ngx.location.capture
            local insert = table.insert

            local function f(i)
                local res = capture("/proxy/" .. i)
                ngx.say("status: ", res.status)
            end

            local threads = {}
            for i = 1, 2 do
                local co = ngx.thread.spawn(f, i)
                insert(threads, co)
            end

            ngx.say("ok")
        ';
    }

    location ~ ^/proxy/(\d+) {
        return 404;
    }
--- request
    GET /t
--- stap2 eval: $::StapScript
--- stap eval
"$::GCScript"
.
'
F(ngx_http_finalize_request) {
    printf("finalize request %s: rc:%d c:%d a:%d\n", ngx_http_req_uri($r), $rc, $r->main->count, $r == $r->main);
    #if ($rc == -1) {
        #print_ubacktrace()
    #}
}

M(http-subrequest-done) {
    printf("subrequest %s done\n", ngx_http_req_uri($r))
}

F(ngx_http_lua_post_subrequest) {
    printf("post subreq: %s rc=%d, status=%d a=%d\n", ngx_http_req_uri($r), $rc,
         $r->headers_out->status, $r == $r->main)
    #print_ubacktrace()
}
'
--- stap_out_like chop
^create 2 in 1
spawn user thread 2 in 1
create 3 in 1
spawn user thread 3 in 1
terminate 1: ok
delete thread 1
finalize request /t: rc:-4 c:4 a:1
finalize request /proxy/1: rc:404 c:3 a:0
post subreq: /proxy/1 rc=404, status=0 a=0
subrequest /proxy/1 done
terminate 2: ok
delete thread 2
finalize request /proxy/2: rc:404 c:2 a:0
post subreq: /proxy/2 rc=404, status=0 a=0
subrequest /proxy/2 done
terminate 3: ok
delete thread 3
finalize request /t: rc:0 c:1 a:1
(?:finalize request /t: rc:0 c:1 a:1)?$

--- response_body
ok
status: 404
status: 404
--- no_error_log
[error]
--- timeout: 3



=== TEST 26: multiple user threads + subrequests returning 404 remotely (no wait)
--- config
    location /t {
        content_by_lua '
            local capture = ngx.location.capture
            local insert = table.insert

            local function f(i)
                local res = capture("/proxy/" .. i)
                ngx.say("status: ", res.status)
            end

            local threads = {}
            for i = 1, 5 do
                local co = ngx.thread.spawn(f, i)
                insert(threads, co)
            end

            ngx.say("ok")
        ';
    }

    location ~ ^/proxy/(\d+) {
        proxy_pass http://127.0.0.1:$server_port/d/$1;
    }

    location /d {
        return 404;
        #echo $uri;
    }
--- request
    GET /t
--- stap2 eval: $::StapScript
--- stap eval: $::GCScript
--- stap_out_like chop
^create 2 in 1
spawn user thread 2 in 1
create 3 in 1
spawn user thread 3 in 1
create 4 in 1
spawn user thread 4 in 1
create 5 in 1
spawn user thread 5 in 1
create 6 in 1
spawn user thread 6 in 1
terminate 1: ok
delete thread 1
(?:terminate 2: ok
delete thread 2
terminate 3: ok
delete thread 3
terminate 4: ok
delete thread 4
terminate 5: ok
delete thread 5
terminate 6: ok
delete thread 6
|terminate 6: ok
delete thread 6
terminate 5: ok
delete thread 5
terminate 4: ok
delete thread 4
terminate 3: ok
delete thread 3
terminate 2: ok
delete thread 2)$

--- response_body
ok
status: 404
status: 404
status: 404
status: 404
status: 404
--- no_error_log
[error]
--- timeout: 6



=== TEST 27: multiple user threads + subrequests returning 201 immediately
--- config
    location /t {
        content_by_lua '
            local capture = ngx.location.capture
            local insert = table.insert

            local function f(i)
                local res = capture("/proxy/" .. i)
                ngx.say("status: ", res.status)
            end

            local threads = {}
            for i = 1, 2 do
                local co = ngx.thread.spawn(f, i)
                insert(threads, co)
            end

            ngx.say("ok")
        ';
    }

    location ~ ^/proxy/(\d+) {
        content_by_lua 'ngx.exit(201)';
    }
--- request
    GET /t
--- stap2 eval: $::StapScript
--- stap eval
"$::GCScript"
.
'
F(ngx_http_finalize_request) {
    printf("finalize request %s: rc:%d c:%d a:%d\n", ngx_http_req_uri($r), $rc, $r->main->count, $r == $r->main);
    #if ($rc == -1) {
        #print_ubacktrace()
    #}
}

M(http-subrequest-done) {
    printf("subrequest %s done\n", ngx_http_req_uri($r))
}

F(ngx_http_lua_post_subrequest) {
    printf("post subreq: %s rc=%d, status=%d a=%d\n", ngx_http_req_uri($r), $rc,
         $r->headers_out->status, $r == $r->main)
    #print_ubacktrace()
}
'

--- stap_out_like chop
^create 2 in 1
spawn user thread 2 in 1
create 3 in 1
spawn user thread 3 in 1
terminate 1: ok
delete thread 1
finalize request /t: rc:-4 c:4 a:1
terminate 4: ok
delete thread 4
finalize request /proxy/1: rc:0 c:3 a:0
post subreq: /proxy/1 rc=0, status=201 a=0
subrequest /proxy/1 done
terminate 2: ok
delete thread 2
terminate 5: ok
delete thread 5
finalize request /proxy/2: rc:0 c:2 a:0
post subreq: /proxy/2 rc=0, status=201 a=0
subrequest /proxy/2 done
terminate 3: ok
delete thread 3
finalize request /t: rc:0 c:1 a:1
(?:finalize request /t: rc:0 c:1 a:1)?$

--- response_body
ok
status: 201
status: 201
--- no_error_log
[error]
--- timeout: 3



=== TEST 28: multiple user threads + subrequests returning 204 immediately
--- config
    location /t {
        content_by_lua '
            local capture = ngx.location.capture
            local insert = table.insert

            local function f(i)
                local res = capture("/proxy/" .. i)
                ngx.say("status: ", res.status)
            end

            local threads = {}
            for i = 1, 2 do
                local co = ngx.thread.spawn(f, i)
                insert(threads, co)
            end

            ngx.say("ok")
        ';
    }

    location ~ ^/proxy/(\d+) {
        content_by_lua 'ngx.exit(204)';
    }
--- request
    GET /t
--- stap2 eval: $::StapScript
--- stap eval
"$::GCScript"
.
'
F(ngx_http_finalize_request) {
    printf("finalize request %s: rc:%d c:%d a:%d\n", ngx_http_req_uri($r), $rc, $r->main->count, $r == $r->main);
    #if ($rc == -1) {
        #print_ubacktrace()
    #}
}

M(http-subrequest-done) {
    printf("subrequest %s done\n", ngx_http_req_uri($r))
}

F(ngx_http_lua_post_subrequest) {
    printf("post subreq: %s rc=%d, status=%d a=%d\n", ngx_http_req_uri($r), $rc,
         $r->headers_out->status, $r == $r->main)
    #print_ubacktrace()
}
'
--- stap_out_like chop
^create 2 in 1
spawn user thread 2 in 1
create 3 in 1
spawn user thread 3 in 1
terminate 1: ok
delete thread 1
finalize request /t: rc:-4 c:4 a:1
terminate 4: ok
delete thread 4
finalize request /proxy/1: rc:204 c:3 a:0
post subreq: /proxy/1 rc=204, status=204 a=0
subrequest /proxy/1 done
terminate 2: ok
delete thread 2
terminate 5: ok
delete thread 5
finalize request /proxy/2: rc:204 c:2 a:0
post subreq: /proxy/2 rc=204, status=204 a=0
subrequest /proxy/2 done
terminate 3: ok
delete thread 3
finalize request /t: rc:0 c:1 a:1
(?:finalize request /t: rc:0 c:1 a:1)?$

--- response_body
ok
status: 204
status: 204
--- no_error_log
[error]
--- timeout: 3



=== TEST 29: multiple user threads + subrequests returning 404 remotely (wait)
--- config
    location /t {
        content_by_lua '
            local n = 5
            local capture = ngx.location.capture
            local insert = table.insert

            local function f(i)
                local res = capture("/proxy/" .. i)
                return res.status
            end

            local threads = {}
            for i = 1, n do
                local co = ngx.thread.spawn(f, i)
                insert(threads, co)
            end

            for i = 1, n do
                local ok, res = ngx.thread.wait(threads[i])
                ngx.say(i, ": ", res)
            end

            ngx.say("ok")
        ';
    }

    location ~ ^/proxy/(\d+) {
        proxy_pass http://127.0.0.1:$server_port/d/$1;
    }

    location /d {
        return 404;
        #echo $uri;
    }
--- request
    GET /t
--- stap2 eval: $::StapScript
--- stap3 eval: $::GCScript
--- stap_out3
create 2 in 1
spawn user thread 2 in 1
create 3 in 1
spawn user thread 3 in 1
create 4 in 1
spawn user thread 4 in 1
create 5 in 1
spawn user thread 5 in 1
create 6 in 1
spawn user thread 6 in 1
terminate 2: ok
delete thread 2
terminate 3: ok
delete thread 3
terminate 4: ok
delete thread 4
terminate 5: ok
delete thread 5
terminate 6: ok
delete thread 6
terminate 1: ok
delete thread 1

--- response_body
1: 404
2: 404
3: 404
4: 404
5: 404
ok
--- no_error_log
[error]
--- timeout: 6



=== TEST 30: multiple user threads + subrequests remotely (wait)
--- config
    location /t {
        content_by_lua '
            local n = 20
            local capture = ngx.location.capture
            local insert = table.insert

            local function f(i)
                local res = capture("/proxy/" .. i)
                return res.status
            end

            local threads = {}
            for i = 1, n do
                local co = ngx.thread.spawn(f, i)
                insert(threads, co)
            end

            for i = 1, n do
                local ok, res = ngx.thread.wait(threads[i])
                ngx.say(i, ": ", res)
            end

            ngx.say("ok")
        ';
    }

    location ~ ^/proxy/(\d+) {
        proxy_pass http://127.0.0.1:$server_port/d/$1;
    }

    location /d {
        echo_sleep 0.001;
        echo $uri;
    }
--- request
    GET /t
--- stap2 eval: $::StapScript
--- stap3 eval: $::GCScript
--- stap_out3
create 2 in 1
spawn user thread 2 in 1
create 3 in 1
spawn user thread 3 in 1
create 4 in 1
spawn user thread 4 in 1
create 5 in 1
spawn user thread 5 in 1
create 6 in 1
spawn user thread 6 in 1
terminate 2: ok
delete thread 2
terminate 3: ok
delete thread 3
terminate 4: ok
delete thread 4
terminate 5: ok
delete thread 5
terminate 6: ok
delete thread 6
terminate 1: ok
delete thread 1

--- response_body
1: 200
2: 200
3: 200
4: 200
5: 200
6: 200
7: 200
8: 200
9: 200
10: 200
11: 200
12: 200
13: 200
14: 200
15: 200
16: 200
17: 200
18: 200
19: 200
20: 200
ok
--- no_error_log
[error]
[alert]
--- timeout: 10



=== TEST 31: simple user thread without I/O
--- config
    location /lua {
        content_by_lua '
            local function f()
                ngx.sleep(0.1)
                ngx.say("f")
            end

            ngx.thread.spawn(f)
            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: ok
delete thread 2

--- response_body
f
--- no_error_log
[error]
