The proposed example is not what one should really do with signals, it is just for the purpose of studying.
Well remember this point it will prove important later: http://docs.python.org/dev/library/signal.html#signal.getsignal
There is no way to “block” signals temporarily from critical sections (since this is not supported by all Unix flavors).
The idea transforming a process as a pseudo hardware component
Signals are like wires normally that carries a rising edge. On low level architecture you may use a wire to say validate when results are safe to propagate. and a wire to clear the results.
I do a simple component that just sets to 1 the bit at the nth position of a register according to the position of the wire/signal (could be used for multiplexing).
Here is the code:
#!/usr/bin/env python3.3 import signal as s from time import sleep from time import asctime as _asctime from random import randint import sys asctime= lambda : _asctime()[11:19] class Processor(object): def __init__(self,signal_map, slow=False, clear_sig=s.SIGHUP, validate_sig=s.SIGCONT): self.cmd=0 self.slow=slow self.signal_map = signal_map self.clear_sig = clear_sig self.validate_sig = validate_sig self.value = 0 self._help = [ "\nHow signal are wired"] self._signal_queue = [] self.current_signal=None if validate_sig in signal_map or clear_sig in signal_map: raise Exception("Dont wire twice a signal") def top_half(sig_no, frame): ## UNPROTECTED CRITICAL SECTION self._signal_queue.append(sig_no) ## END OF CRITICAL for offset,sig_no in enumerate(signal_map): s.signal(sig_no, top_half) self._help += [ "sig(%d) sets v[%d]=%d"%(sig_no, offset, 1) ] self._help += [ "attaching clearing to %d" % clear_sig] s.signal(clear_sig, top_half) self._help += [ "attaching validating to %d" % validate_sig ] s.signal(validate_sig,top_half) self._help = "\n".join( self._help) print(self._help) def bottom_half(self): sig_no = self._signal_queue.pop() now = asctime() seen = self.cmd self.cmd += 1 treated=False self.signal=None if sig_no in self.signal_map: offset=self.signal_map.index(sig_no) beauty = randint(3,10) if self.slow else 0 if self.slow: print("[%d]%s:RCV: sig%d => [%d]=1 in (%d)s" % ( seen,now,sig_no, offset, beauty )) sleep(beauty) self.value |= 1 << offset now=asctime() print("[%d]%s:ACK: sig%d => [%d]=1 (%d)" % ( seen,now,sig_no, offset, beauty )) treated=True if sig_no == self.clear_sig: print("[%d]%s:ACK clearing value" % (seen,now)) self.value=0 treated=True if sig_no == self.validate_sig: print("[%d]%s:ACK READING val is %d" % (seen,now,self.value)) treated=True if not treated: print("unhandled execption %d" % sig_no) exit(0) wired=Processor([ s.SIGUSR1, s.SIGUSR2, s.SIGBUS, s.SIGPWR ]) while True: s.pause() wired.bottom_half() sys.stdout.flush()Now, let's do some shell experiment:
$ ./signal_as_wire.py& [4] 9332 660 jul@faith:~/src/signal 19:04:03 $ How signal are wired sig(10) sets v[0]=1 sig(12) sets v[1]=1 sig(7) sets v[2]=1 sig(30) sets v[3]=1 attaching clearing to 1 attaching validating to 18 660 jul@faith:~/src/signal 19:04:04 $ for i in 1 12 10 7 18 1 7 30 18; do sleep 1 && kill -$i %4; done [0]19:04:31:ACK clearing value [1]19:04:32:ACK: sig12 => [1]=1 (0) [2]19:04:33:ACK: sig10 => [0]=1 (0) [3]19:04:34:ACK: sig7 => [2]=1 (0) [4]19:04:35:ACK READING val is 7 [5]19:04:36:ACK clearing value [6]19:04:37:ACK: sig7 => [2]=1 (0) [7]19:04:38:ACK: sig30 => [3]=1 (0) [8]19:04:39:ACK READING val is 12
Everything works as it should, no? :) I have brilliantly used signals to transmit data asynchronously to a process. With 1 signal per bit \o/
What about «not being able to block the signal»
$ for i in 1 12 10 7 18 1 7 30 18; do echo "kill -$i 9455; " ; done | sh [0]22:27:06:ACK clearing value [1]22:27:06:ACK clearing value [2]22:27:06:ACK: sig7 => [2]=1 (0) [3]22:27:06:ACK: sig30 => [3]=1 (0) [4]22:27:06:ACK: sig10 => [0]=1 (0) [5]22:27:06:ACK: sig12 => [1]=1 (0) [6]22:27:06:ACK READING val is 15
Oh a race condition, it already appears with the shell launching the kill instruction sequentially: the results are out of order. Plus you can clearly notice my critical section is not small enough to be atomic. And I lost signals :/
Is python worthless?
Not being able to block your code, is even making a top half/bottom half strategy risky. Okay, I should have used only atomic operations in the top half (which makes me wonder what operations are atomic in python) such has only setting one variable and doing the queuing in the while loop, but I fear it would have been worse.
Which means actually with python, you should not play with signals such as defined in stdlib since without blocking you have systematical race conditions or you risk loosing signals if you expect them to be reliable.
I am playing with signals as I would be playing with a m68K interrupt (I would still block signal before entering the critical section). To achieve the blocking and processing of pending signals I would need POSIX.1 sigaction, sisget, sigprocmask, sigpending.
Why python does not support them (in the stdlib)?
Well python is running on multiple operating systems, some do support POSIX.1 some don't. As signals are not standardized the same way except for POSIX compliant systems with the same POSIX versions, therefore it should not be in st(andar)dlib. And since it is *that* risky I would advocate not allowing to put a signal handler at first place (except for alarm maybe). But, take your own risk accordingly :)
If you feel it is a problem, then just remember binding C code to python is quite easy, and that on POSIX operating system we have everything we need. This solution given in stackoverflow is funky but less than having unprotected critical section: http://stackoverflow.com/a/3792294/1458574.