My latest adventure into Lua [1] has been to embed the language into a C application (an outgrowth of extending in C [2]). I had this “proof-of-concept” network daemon I wrote [3] that was simple enough to gut completely and replace the entire server logic with Lua, leaving the C code to handle the sockets.
But that wasn't hard enough. No. I wanted each connection handled by a Lua thread [4], and the ability to write “normal looking code” such as:
-- A simple echo server in Lua function main(connection) while true do connection:write(connection:read("*a")) end end
I will say this, unlike Theo Schlossnagle, who doesn't care for Lua the language but likes embedding it [5], I came to the opposite conclusion: I like Lua the language but embedding it is … interesting. Perhaps because I dove headfirst into Lua coroutines to get this working.
The first oddness with embedding Lua is that you push parameters onto the Lua stack, but not all lua functions pop all parameters. Namely, anything using a table won't actually pop the table parameter off the stack, and the stack notation [6] used in the manual is not the notation I'm used to (having come to stack notations in Forth [7]). So I'm sure that a majority of the issues I had were due to improper stack manipulations on my part.
And about that stack. You can reference the stack in two ways, from the bottom using positive indices, or from the top using negative indices, with 0 as an invalid stack index.
Table: Lua execution stack of five elements Bottom of Stack Top of Stack 1 -5 2 -4 3 -3 4 -2 5 -1
As a longtime C (and assembly) programmer, it just looks weird to use 1- based indices in C.
Another horrible issue—via the C API (Application Program Interface) you can push integers onto the Lua stack, you can push strings onto the Lua stack, you can push functions (either written in Lua or C) onto the stack, heck, you can push everything but userdata structures onto the Lua stack. Userdata structures are allocated by Lua (via lua_newuserd ata() [8]), can contain anything you want, but there's no way to actually push such a structure onto the Lua stack, so you always have to keep it around on the Lua stack, or in a Lua variable or table, which is a bit of a pain. I suppose it has to be that way in order for Lua to keep track of it for garbage collection purposes, but still, a pain.
And then there's the coroutines [9] …
Easy enough to create a coroutine:
lua_State *new; Foo *userdata; int rc; /*---------------------------- ; create a new thread ; main(userdata) ;----------------------------*/ new = lua_newthread(g_L); lua_getglobal(new,"main"); userdata = lua_newuserdata(new,sizeof(Foo)); /* fill in our userdata */ rc = lua_resume(new,1);
But once the thread finishes, then what?
## lua_close
>
```
void lua_close (lua_State *L);
```
Destroys all objects in the given Lua state (calling the corresponding garbage-collection metamethods, if any) and frees all dynamic memory used by this state. On several platforms, you may not need to call this function, because all resources are naturally released when the host program ends. On the other hand, long-running programs, such as a daemon or a web server, might need to release states as soon as they are not needed, to avoid growing too large.
“Lua 5.1 Reference Manual [10]”
Okay, so I'm concerned about garbage collection—I don't want tons of garbage piling up and then … the … … big … … … pause … … … … while … … … … … garbage … … … … … … collection takes place. And seeing how lua_close() mentions daemons, I figure I can call lua_close when a thread terminates, since I'm writing a daemon. But when I do, every open connection suddenly closes and any new connection causes the daemon to crash.
Huh?
## lua_newthread
>
```
lua_State *lua_newthread (lua_State *L);
```
Creates a new thread, pushes it on the stack, and returns a pointer to a lua_State [11] that represents this new thread. The new state returned by this function shares with the original state all global objects (such as tables), but has an independent execution stack.
There is no explicit function to close or to destroy a thread. Threads are subject to garbage collection, like any Lua object.
“Lua 5.1 Reference Manual [12]”
Reading between the lines, you don't really get a new state, you get a copy of the existing state, but a new execution stack. Calling lua_close() on this “new” state is just like calling lua_close() on the already existing state.
Nope. What I wanted to do after each thread was to call lua_gc() [13], only now current connections are getting garbage-collected along with the finished ones, which meant I wasn't using luaL_ref() [14] and luaL_unref() [15] properly, probably because I wasn't managing the Lua stack properly …
Sigh.
It also took a bit of careful coding to get the Lua code to properly block (lua_yield() [16]) and resume (lua_resume() [17]) when you have input and output asynchronously appearing. It was also a bit tricky to have one Lua thread write to another Lua thread's socket without blowing things up. But eventually, not only did I get it such that the echo service above works (as written above) but the following simple chat script as well:
if members == nil then members = {} end local function login(socket) socket:write("Handle you go by: ") return socket:read() end local function wallaction(socket,who,everybody,me) for connection in pairs(members) do if connection ~= socket then connection:write(string.format("%s %s\n",who,everybody)) else if me ~= nil then connection:write(string.format("%s\n",me)) end end end end local function wall(socket,who,everybody,me) return wallaction(socket,who .. ":",everybody,me) end function main(socket) local name = login(socket) members[socket] = name wallaction(socket,name,"is in da room!","You are in the room.") io.stdout:write(string.format("%s has joined the party!\n",name)) while true do wall(socket,name,socket:read()) end end function fini(socket) io.stdout:write(string.format("%s has left the party!\n",members[socket])) wallaction(socket,members[socket],"has left the building!") members[socket] = nil end
Each Lua thread starts with main(). fini() is called when a connection is dropped. I also made the daemon such that sending it a SIGUSR1 will cause it to re-compile the script, so that changes to the service can be made without having to restart the program as a whole (which explains the odd way I define members—if we reload the script, I don't want to lose the current members).
As written, the code isn't multi-threaded—at the C level, it's just one process round-robinning a bunch of Lua threads, so I can get away with the chat process above without worry as only one Lua thread is executing at any one time.
But it's now quite easy to write network daemons, so easy that I'll leave you with one more—an RFC compliant Quote of the Day server [18]:
QUOTESFILE = "/home/spc/quotes/quotes.txt" quotes = {} do local eoln = "\r\n" local f = io.open(QUOTESFILE,"r") local s = "" for line in f:lines() do if line == "" then -- each quote is separated by a blank link if #s < 512 then table.insert(quotes,s) end s = "" else s = s .. line .. eoln end end f:close() end math.randomseek(os.time()) function main(socket) socket:write(quotes[math.random(#quotes)]) end
[4] http://www.lua.org/pil/9.html
[5] http://lethargy.org/~jesus/writes/extending-and-embedding
[6] http://www.lua.org/manual/5.1/manual.html#3.7
[7] http://www.forth.org/whatis.html
[8] http://www.lua.org/manual/5.1/manual.html#lua_newuserdata
[9] http://www.lua.org/manual/5.1/manual.html#2.11
[10] http://www.lua.org/manual/5.1/manual.html#lua_close
[11] http://www.lua.org/manual/5.1/manual.html#lua_State
[12] http://www.lua.org/manual/5.1/manual.html#2.11
[13] http://www.lua.org/manual/5.1/manual.html#lua_gc
[14] http://www.lua.org/manual/5.1/manual.html#luaL_ref
[15] http://www.lua.org/manual/5.1/manual.html#luaL_unref
[16] http://www.lua.org/manual/5.1/manual.html#lua_yield
[17] http://www.lua.org/manual/5.1/manual.html#lua_resume