Cam.Davidson.Pilon
Cam.Davidson.Pilon

Reputation: 1716

More than one SIGTERM handler in a Python script - who should call sys.exit()?

Let's say I have the script below:

import signal
import sys

class Klass:
   def __init__(self, name):
     self.name = name

     def disconnect_gracefully(*args):
         print(self.name)
         sys.exit()

     signal.signal(signal.SIGINT, disconnect_gracefully)


a = Klass("A")
b = Klass("B")

while True:
    pass

Note that both classes have a graceful exit after a SIGINT. When I run this, and crtl-c, only "B" is printed.

Generally, in a case like this, who or what should call sys.exit() - should both instances, neither?

Upvotes: 0

Views: 1001

Answers (2)

Vishal Dhawan
Vishal Dhawan

Reputation: 351

Have created this gist (but this will only work if register_signal_handler is used everywhere or atleast in the end)

https://gist.github.com/SuperThinking/ba07424e86308faaf2ab533a75d2bbc6

Upvotes: 0

Mark Plotnick
Mark Plotnick

Reputation: 10261

The Python documentation for signal only has this to say about what happens when you set a handler for the same signal multiple times:

A handler for a particular signal, once set, remains installed until it is explicitly reset

and

[when calling signal.signal] The previous signal handler will be returned

which seems to imply that, like POSIX signals, a process can have just a single handler for a given signal at a time.

We can add some statements to your program to show what signal.signal returns (in CPython):

import signal
import sys
class K:
  def __init__(self, name):
    self.name=name
    def disconnect(*args):
      print(self.name)
      sys.exit()
    self.disconnectfuncid=id(disconnect)
    self.oldsig=signal.signal(signal.SIGINT, disconnect)


>>> a=K("a")
>>> hex(a.disconnectfuncid)
'0x7fc5c1057f28'
>>> a.oldsig
<built-in function default_int_handler>
>>> signal.getsignal(signal.SIGINT)
<function K.__init__.<locals>.disconnect at 0x7fc5c1057f28>


>>> b=K("b")
>>> hex(b.disconnectfuncid)
'0x7fc5c0ed0950'
>>> b.oldsig
<function K.__init__.<locals>.disconnect at 0x7fc5c1057f28>
>>> signal.getsignal(signal.SIGINT)
<function K.__init__.<locals>.disconnect at 0x7fc5c0ed0950>


>>> while True:
...   pass
...^C b

So the handler starts out as <built-in function default_int_handler>, then gets set to the disconnect function in the a instance, then gets set to the disconnect function in the b instance, and the latter is what gets called when the signal is delivered.

If you want to exit when receiving SIGINT, and run multiple functions just before exit, one way to do that is to call the following (which will suppress printing the traceback and KeyboardInterrupt messages):

 signal.signal(signal.SIGINT, lambda *args: sys.exit())

and then register each function using atexit.register.

Upvotes: 2

Related Questions