* * * * *

                              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] gopher://gopher.conman.org/0Phlog:2011/11/28.1
[2] http://www.lua.org/
[3] http://lsyslog.luaforge.net/

Email author at [email protected]