lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


It was thus said that the Great Soni L. once stated:
> 
> 
> On 30/09/15 05:05 PM, Sean Conner wrote:
> >It was thus said that the Great Soni L. once stated:
> >>The same way you'd do it in Lua: Your module table. Or the metatable.
> >   I went through my existing modules to see if I could forgo the use of 
> >   the
> >registry table.  So maybe you can help me.  One problematic module:
> >
> >	local signal = require "org.conman.signal" -- [1]
> >
> >	signal.catch('interrupt',function()
> >	  print "You can't interrupt me! Muahahahaha!"
> >	end)
> >
> >   Seems simple to use, but we need to save a reference to the passed in
> >function because, as in this example, it can be an anonymous function not
> >otherwise anchored into memory.  Second, there are some 30+ signals we can
> >capture (keep that in mind).  Third, our signal handler:
> Use a table as an upvalue.
> >
> >	static void signal_handler(int sig)
> >	{
> >	 ...
> >
> >   Okay.  No context other than the signal that was just generated.  This
> >means we need to cache the lua_State in a variable visible to our signal
> >handler.  That can be done during the luaopen() call, so now we have:
> >
> >	static lua_State *m_L;
> >	static void signal_handler(int sig)
> >	{
> >	 ...
> >
> >   We're running in a signal handler.  There is not much you can do 
> >   *safely*
> >from a signal handler (because you could be in the middle of allocating
> >memory, in the middle of a Lua call, halfway through manipulating a linked
> >list, any number of half-finished states exist), and the *only* thing you
> >can safely call in Lua is lua_sethook().  Even trying to write the signal
> >number to a table is unsafe.  So we have:
> >
> >	static lua_State *m_L;
> >	static volatile sig_atomic_t m_sig;
> >	static void signal_handler(int sig)
> >	{
> >	  m_sig = sig;
> >	  lua_sethook(m_L,hook_handler,LUA_MASKCALL | LUA_MASKRET | 
> >	  LUA_MASKCOUNT,1);
> >	}
> >
> >   We now resume from the signal, and when it's safe for Lua to do so, our
> >hook_handler() gets called:
> >
> >	static void hook_handler(lua_State *L,lua_Debug *ar)
> >	{
> >	 ...
> >	}
> >
> >   Okay, where's our function to handle the signal?  What meta table?
> >Where's the module table?  I could try looking for
> >"package.loaded['org.conman.syslog']" but I can't rely upon that if the
> >global environment has been sandboxed (the global environment might have
> >'signal' already loaded, but not 'package' or 'require').  I don't want to
> >pollute the global environment.  But I have no context to save or retrieve
> >which function to run.
> >
> >   Do you have any alternatives that don't rely upon package.loaded[]?
> >Because I have no ideas ...
> >
> >   -spc
> >
> >[1]	https://github.com/spc476/lua-conmanorg/blob/master/src/signal.c
> >
> This is VERY unsafe! You're potentially breaking a sandbox by doing a 
> sethook. And you don't reset it, either. 

  If you mean "I don't call lua_sethook(L,NULL,0,0)" then you have misread
the code---I do call that.  But if you mean "I don't restore the previous
hooks" then you're right, I don't.  When I first started this implementation
of the signal module, I was concerned about that.  And I tried.  And it was
very fragile.  I then decided to check how other signal modules handled this
and guess what?  NONE OF THEM RESTORE THE HOOKS!  Not even the standalone
Lua interpeter bothers with restoring any hooks.

> And you're doing it from a separate thread.

  Potentially a different thread.  If you don't create coroutines, then
everything works just fine (for various values for fine [2]).  Yes,
attempting to handle signals with Lua coroutines is ... challenging.  One
solution I tried was keeping track of every lua_State that went through
signal.catch(), and then setting a hook in every one and that didn't go very
well because it doesn't work when a new coroutine created in the meantime is
started and ... 

  It's messy.  It's nasty.  But then again, signals are inherently messy and
nasty.  

> Use a (light?) userdata and do the polling in Lua. Or just set the hook
> and have it called nonstop.

  I used to do signal polling in Lua.  Using my previous version of my
signal module (which my updated version is compatible with) you would do:

	signal.catch('interrupt')

	if signal.caught('interrupt') then
		...
	elseif signal.caught('terminate') then
		...
	elseif signal.caught('windowchange') then
		...
	end

  The problem here is you either need to have a main program loop and check
the signals (at some point in the loop) or sprinkle the code with periodic
checks.  In fact, at that time, my signal module was the odd-man out because
you couldn't run a Lua function when a signal was delivered.

  Besides, there are some cases where polling for a signal just isn't the
right thing to do.  For example, I have a script I want to run but if it
takes longer than five minutes, log an error and exit.  That's pretty easy
if you don't have to poll:

	clock  = require "org.conman.clock"
	signal = require "org.conman.signal"
	syslog = require "org.conman.syslog"

	-- NOTE:  we do NOT set the restart flag for this signal

	signal.catch('alarm',function()
	  syslog('error',"script took too long to finish---aborting")
	  os.exit(1)
	end)

	clock.itimer("5m")	-- alarm in five minutes

	-- now we can begin our script, sure that we'll exit in
	-- at most five minutes ... 

  So, with that requirement, how would you write a signal module?  Or deal
with ensuring your script doesn't run more then 5 minutes (wall time, not
CPU time)?

> (Also how do you deal with multiple states? As far as I know, you can't, 
> unless you use a lightuserdata!)

  I don't.  None of the modules dealing with signals do.  But please, I
would love to see your signal handling module that doesn't use the registry
and does things "correctly."

  -spc 
  
[1]	https://github.com/spc476/lua-conmanorg/blob/master/src/signal.c

[2]	If you are using the POSIX version of the module, you can do:

		signal.catch('windowchange',handler,{ restart = true })

	This causes the OS to restart a system call if a signal is delivered
	during a system call.  This avoids a lot of mess with interrupted
	system calls and having to check for EINTR and manually restarting
	them.

	Wonderful!

	But miserable in Lua.  If I'm waiting in a long term system call
	like recvfrom() (receive data from a socket), and a signal is
	delivered, the handler is called, which sets a Lua hook, and then
	the system call is resumed.  And until it either times out or I
	receive a packet, the Lua function to handle the signal isn't run.

	Not very good when you want to run something periodically (via
	SIGALRM) or the system is trying to tell you to shutdown (SIGTERM).