Some Lua trickery

In my previous post [1], I presented this bit of Lua [2] code:

>
```
process = require("org.conman.process")
-- --------------------------------------------------------------------
-- process limits added because an earlier version of the code actually
-- crashed the server it was running on, due to resource exhaustion.
-- --------------------------------------------------------------------
process.limits.hard.cpu = "10m" -- 10 minutes
process.limits.hard.core = 0 -- no core file
process.limits.hard.data = "20m" -- 20 MB
```

It looks like a simple assignment to set process limits, yet under Unix, you need to call setrlimit(). What's happening under the hood (so to speak) is that it's easy to intercept assignments to tables (Lua “go-to” data structure) and that's exactly what's going on here. During the process of registering the module org.conman.process (more on the name later) we create some fake structures for the hard limits (and soft limits, but since it's similar, I'll skip that part) and attach a metatable, which contains code to intercept both reads and writes so we can do a bit of magic:

>
```
#define SYS_LIMIT_HARD "rlimit_hard"
#define SYS_LIMIT_SOFT "rlimit_soft"
static const struct luaL_reg mhlimit_reg[] =
{
{ "__index" , mhlimitlua___index } ,
{ "__newindex" , mhlimitlua___newindex } ,
{ NULL , NULL }
};
static const struct luaL_reg mslimit_reg[] =
{
{ "__index" , mslimitlua___index } ,
{ "__newindex" , mslimitlua___newindex } ,
{ NULL , NULL }
};
int luaopen_org_conman_process(lua_State *const L)
{
void *udata;
assert(L != NULL);
luaL_newmetatable(L,SYS_LIMIT_HARD);
luaL_register(L,NULL,mhlimit_reg);
luaL_newmetatable(L,SYS_LIMIT_SOFT);
luaL_register(L,NULL,mslimit_reg);
luaL_register(L,"org.conman.process",mprocess_reg);
lua_createtable(L,0,2);
udata = lua_newuserdata(L,sizeof(int));
luaL_getmetatable(L,SYS_LIMIT_HARD);
lua_setmetatable(L,-2);
lua_setfield(L,-2,"hard");
udata = lua_newuserdata(L,sizeof(int));
luaL_getmetatable(L,SYS_LIMIT_SOFT);
lua_setmetatable(L,-2);
lua_setfield(L,-2,"soft");
lua_setfield(L,-2,"limits");
return 1;
}
```

When Lua sees an assignment to the process.limits.hard table, it calls mhlimit_lua___newindex(), where the magic happens:

>
```
static int mhlimitlua___newindex(lua_State *const L)
{
struct rlimit limit;
void *ud;
const char *tkey;
int key;
lua_Integer ival;
assert(L != NULL);
ud = luaL_checkudata(L,1,SYS_LIMIT_HARD);
tkey = luaL_checkstring(L,2);
if (!mlimit_trans(&key,tkey))
return luaL_error(L,"Illegal limit resource: %s",tkey);
if (lua_isnumber(L,3))
ival = lua_tointeger(L,3);
else if (lua_isstring(L,3))
{
const char *tval;
const char *unit;
tval = lua_tostring(L,3);
ival = strtoul(tval,(char **)&unit,10);
if (!mlimit_valid_suffix(&ival,key,unit))
return luaL_error(L,"Illegal suffix: %c",*unit);
}
else
return luaL_error(L,"Non-supported type");
limit.rlim_cur = ival;
limit.rlim_max = ival;
setrlimit(key,&limit);
return 0;
}
```

We basically take the key we're given, say, “cpu”, and translate it to the appropriate value (which happens in mlimit_trans()—nothing terribly interesting, it just maps the string to the appropriate constant value, in this example, RLIMIT_CPU) and the same for the value; if it's a number, we'll use that and if it's a string, we'll convert it to a value and use any suffix to modify the value. For our example, “cpu”, it's a meaure of time, so the suffix “m” means “minutes.” mlimit_valid_suffix() handles this and again, it's pretty straightforward code.

I think it's a pretty cool trick, but I can see why some might not like the idea of masking what amounts to a system call with what looks like a simple assignment, since it does have side effects outside of the simple assignment, but I like the way it looks, and it's a more “natural” or even “Luaish” way of specifying the intent of the code.

Now, on to the name of the module, org.conman.process. When I first started playing around with Lua I wrote a few modules that did similar operations as existing modules, with the same names. One example is syslog. There's an existing Lua syslog module [3], but I don't like how it works, so I wrote my own.

The problem now becomes, what if I want to use a module that uses the existing Lua syslog module, but the rest of my code uses mine? If they both have the same name, some code is going to get a nasty surprise. To work around that, I decided to put all my modules under a “namespace” I control and is not likely to cause any conflicts with any existing (or even future) modules. Thus, the org.conman namespace.

[1] /boston/2011/11/28.1

[2] http://www.lua.org/

[3] http://lsyslog.luaforge.net/

Gemini Mention this post

Contact the author