[RFC] Light threads in ngx_lua's "thread" branch

338 views
Skip to first unread message

agentzh

unread,
Oct 1, 2012, 6:10:26 PM10/1/12
to openresty, openresty-en
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

Matthieu Tourne

unread,
Oct 1, 2012, 6:38:04 PM10/1/12
to openre...@googlegroups.com, openresty


On Monday, October 1, 2012 3:10:27 PM UTC-7, agentzh wrote:
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.


This seems like a big step forward for writing powerful non-blocking web application servers.
The best part is that all the code still reads very sequentially, without a mess of callbacks!

Can't wait to try it out.

Great job!

Matthieu.
Reply all
Reply to author
Forward
0 new messages