Hi, guys!
I've been hacking on the "thread" branch of the ngx_lua module in the
last few weeks:
https://github.com/chaoslawful/lua-nginx-module/tree/thread
I've got a prototype of the "light threads" API implemented there.
Here's a brief description of this API and I really appreciate any
comments on either the design or the implementation (before I merge it
to "master").
co, err = ngx.thread.spawn(func, arg1, arg2, ...)
------------------------------------------------------------
Creates and spawn a "light thread" which is a Lua coroutine per se.
Returns a Lua coroutine object representing this "light thread".
"Light threads" are automatically scheduled by ngx_lua and the Nginx
event model. The user is never required to call coroutine.resume
herself.
The Lua coroutine for a "light thread" will be automatically resumed
by this spawn method for one and only one time before this method
returns. This method will resume the coroutine with the arguments
specified by "arg1", "arg2" and etc.
This API is available in rewrite_by_lua*, access_by_lua*, and
content_by_lua* only. The lifetime of any "light thread" cannot span
multiple Nginx requests. By default, rewrite_by_lua*, access_by_lua*,
and content_by_lua* will wait for all the running "light threads" to
complete, unless a "light thread" calls ngx.exit(), ngx.exec(),
ngx.req.set_uri(uri, true), or ngx.redirect() to quit immediately, in
which case, all the pending "light threads" will be aborted.
Due to a limitation in Nginx subrequests, "light threads" with pending
Nginx subrequests cannot be aborted by ngx.exit(), ngx.exec() and etc.
A Lua exception will be raised if the user tries to do that. It's
always required to call ngx.thread.wait to wait for those "light
threads" with pending Nginx subrequests before calling ngx.exit(),
ngx.exec(), and etc.
The status of the "light thread" coroutine can be "zombie" if
1. the "light thread" already terminates (either successfully or with an error),
2. its parent coroutine is still alive,
3. and its parent coroutine is not waiting on it with ngx.thread.wait.
You can call coroutine.status() and coroutine.yield() on the "light
thread" coroutines.
No automatic time-slicing is performed on "light threads". In other
words, the "light threads" is not pre-emptive. Calling
coroutine.yield() on the "light thread" coroutines is a way of doing
manual time-slicing. Here is an example:
location /lua {
content_by_lua '
local yield = coroutine.yield
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")
';
}
Then accessing /t yields the output
0
1
f 1
2
f 2
3
f 3
4
ok, res1, res2, ... = ngx.thread.wait(t1, t2, ...)
--------------------------------------------------------
Waits synchronously on the child "light threads" t1, t2, and etc.
Returns the result of the first terminated "light thread" immediately
without waiting for other pending "light threads". The return values
are exactly the same as the last (automatic) "coroutine.resume" call
behind the scene.
Only the Lua coroutine that directly spawns the "light thread" can
wait on it, otherwise a Lua exception will be raised.
Here's some examples:
Example 1
local capture = ngx.location.capture
local spawn = ngx.thread.spawn
local wait = ngx.thread.wait
local say = ngx.say
local function fetch(uri)
return capture(uri)
end
local threads = {
spawn(fetch, "/foo"),
spawn(fetch, "/bar"),
spawn(fetch, "/baz")
}
for i = 1, #threads do
local ok, res = wait(threads[i])
if not ok then
say(i, ": failed to run: ", res)
else
say(i, ": status: ", res.status)
say(i, ": body: ", res.body)
end
end
Example 2
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)
-- stop the "world", aborting other running threads
ngx.exit(ngx.OK)
--[[
Output:
f thread created: running
g thread created: running
g: hello
res: g done
]]
Example 3
-- query mysql, memcached, and a remote http service at the same time,
-- output the results in the order that they
-- actually return the results.
local mysql = require "resty.mysql"
local memcached = require "resty.memcached"
local function query_mysql()
local db = mysql:new()
db:connect{
host = "127.0.0.1",
port = 3306,
database = "test",
user = "monty",
password = "mypass"
}
local res, err, errno, sqlstate =
db:query("select * from cats order by id asc")
db:set_keepalive(0, 100)
ngx.say("mysql done")
end
local function query_memcached()
local memc = memcached:new()
memc:connect("127.0.0.1", 11211)
local res, err = memc:get("some_key")
ngx.say("memcached done")
end
local function query_http()
local res = ngx.location.capture("/my-http-proxy")
ngx.say("http done")
end
ngx.thread.spawn(query_mysql) -- create thread 1
ngx.thread.spawn(query_memcached) -- create thread 2
ngx.thread.spawn(query_http) -- create thread 3
Best regards,
-agentzh