So I have this Lua module to handle signals [1] I wrote …
Originally, I just set a flag that had to be manually checked, as that was the safest thing to do (make that “practically the only thing you can do” if you want to be pedantic about it).
But after a while, I thought it would be nice to write a handler in Lua and not have to manually check a flag. Unfortunately, signal handlers have to be thought of as asynchronous threads that run at the worst possible time [2], which means calling into Lua from a signal handler is … not a good idea. The way to handle this is to hook into the Lua VM (Virtual Machine) in the signal handler. lua_sethook() [3] is the only safe Lua function to call from an asynchronous thread (or signal handler) as it just sets a flag in the Lua VM. Then when the Lua VM is at a safe spot, the hook is call which can then run the Lua function.
So we write our Lua function:
>
```
function Lua_signal_handler()
print("Hi! I'm handing a signal!")
end
```
and here I'm making it a global function just for illustrative purposes. At some point, we catch the signal to install the signal handler (which has to be in C):
>
```
/*------------------------------------------------------------------------
; In order to hook the Lua VM, we need to statically store the Lua state.
; That's what this variable is here for ...
;-----------------------------------------------------------------------*/
static lua_State *gL;
/* ... code code blah blah ... */
/*----------------------------------------------------------------
; we're in some function and we have access to a Lua state. Store
; it so the signal handler can reference it.
;----------------------------------------------------------------*/
gL = L;
/*---------------------------------------------------------------
; sigaction() should be used, but that's quite a bit of overhead
; just to make a point. Anyway, we are installing our signal
; handler.
;--------------------------------------------------------------*/
signal(SIGINT,C_signal_handler);
```
The signal handler installs a hook:
>
```
static void C_signal_handler(int sig)
{
lua_sethook(gL,luasignalhook,LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT,1);
}
```
and when it's safe for the VM to call the hook, it does, which then calls our signal handler written in Lua:
>
```
static void luasignalhook(lua_State *L,lua_Debug *ar)
{
/*------------------------------------------------------------------
; remove the hook as we don't want to be called over and over again
;------------------------------------------------------------------*/
lua_sethook(L,NULL,0,0);
/*--------------------------------------------------------------------------
; get our function (which is a global, just for illustrative purposes, and
; call it.
;--------------------------------------------------------------------------*/
lua_getglobal(L,"Lua_signal_handler");
lua_call(L,1,0);
}
```
Yes, it's a rather round-about way to handle a signal, but that's what is required to run Lua code as a signal handler. And it works except for two cases (that I have so far identified—there might be more).
The first case—coroutines. Lua coroutines [4] can be thought of as threads, but unlike system threads, they have to be scheduled manually. And like system threads, signals and coroutines don't mix. Each coroutine creates a new Lua state, which means that if a signal happens, the Lua state that is hooked may not be the one that is currently running and thus, the Lua-written signal handler may never be called!
The second issue involves a feature of POSIX signals—the ability to restart system calls. Normally, a signal will interrupt a system call and its up to the program to restart it. There is an option to restart a system call automatically when a signal happens so the program doesn't have to deal with it. The funny thing is—under the right conditions, the Lua signal handler is never called! Say the program is making a long system call, such as waiting for a network packet to arrive. A signal is raised, our signal handler is called, which hooks the Lua VM. Then the system call is resumed. Until a packet arrives (and thus we return from the system call) the Lua VM never gets control, and thus, our function to handle the signal is never called (or called way past when it should have been called).
Fortunately, I found these issues out in testing, not in production code. But it has me thinking that I should probably work to avoid using signals if at all possible.
[1] https://github.com/spc476/lua-conmanorg/blob/master/src/signal.c
[3] http://www.lua.org/manual/5.3/manual.html#lua_sethook