-3

I have a multi-threaded program and want to catch SIGINT. I tried using the signal interface but it appears to stop doing anything when the program uses threading. What is the correct signal API for multi-threaded python programs?

import signal
import sys
import threading
import os

# register my Ctrl-C handler        
def signal_handler(signal, frame):
    print('Signal.')
    os._exit(0)
signal.signal(signal.SIGINT, signal_handler)

# stop the main thread
lock = threading.Lock()
with lock:
    threading.Condition(lock).wait()

I am using python 2.7.6 and my pip freeze says:

Flask==0.10.1
Jinja2==2.9.6
Mako==1.0.7
MarkupSafe==1.0
PAM==0.4.2
Pillow==2.3.0
PyYAML==3.12
Saltscaffold==3.0.5
Twisted-Core==13.2.0
Twisted-Web==13.2.0
Werkzeug==0.9.4
adium-theme-ubuntu==0.3.4
apt-xapian-index==0.45
argparse==1.2.1
arrow==0.10.0
astroid==1.0.1
binaryornot==0.4.4
blinker==1.3
chardet==3.0.4
click==6.7
colorama==0.2.5
command-not-found==0.3
cookiecutter==1.5.1
debtagshw==0.1
defer==1.0.6
dirspec==13.10
duplicity==0.6.23
future==0.16.0
gevent==1.0
greenlet==0.4.2
gunicorn==17.5
html5lib==0.999
httplib2==0.8
itsdangerous==0.22
jinja2-time==0.2.0
lockfile==0.8
logilab-common==0.61.0
lxml==3.3.3
nose==1.3.7
oauthlib==0.6.1
oneconf==0.3.7.14.04.1
pexpect==3.1
piston-mini-client==0.7.5
poyo==0.4.1
pyOpenSSL==0.13
pycrypto==2.6.1
pycups==1.9.66
pygobject==3.12.0
pyinotify==0.9.4
pylint==1.1.0
pyserial==2.6
pysmbc==1.0.14.1
python-apt==0.9.3.5ubuntu2
python-dateutil==2.6.1
python-debian==0.1.21-nmu2ubuntu2
pyxdg==0.25
reportlab==3.0
requests==2.2.1
sessioninstaller==0.0.0
simplejson==3.3.1
six==1.10.0
software-center-aptd-plugins==0.0.0
ssh-import-id==3.21
system-service==0.1.6
unity-lens-photos==1.0
urllib3==1.7.1
vboxapi==1.0
wheel==0.24.0
whichcraft==0.4.1
wsgiref==0.1.2
xdiagnose==3.6.3build2
zope.interface==4.0.5

I also tried doing signal.pause() in my main thread, but it still doesn't work:

import threading, sys, signal, os

stderr_lock = threading.Lock()

def Log(module, msg):
    with stderr_lock:
        sys.stderr.write("%s: %s\n" % (module, msg))

class My_Thread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        Log("Init", "Initing.")
        self.start()
    def run(self):
        while True:
            Log("Run", "Running.")

for i in range(100):
    My_Thread()

# trap ctrl-C in main thread
def handler(signal, frame):
    print "GOT SIGINT!"
    os._exit(0)
signal.signal(signal.SIGINT, handler)
signal.pause()

Curiously, it usually works if you decrease the number of threads from 100 to 87.

Tom
  • 16,842
  • 17
  • 45
  • 54
personal_cloud
  • 3,943
  • 3
  • 28
  • 38
  • It looks like this issue has been encountered before: https://stackoverflow.com/questions/25676835/signal-handling-in-multi-threaded-python. There is a proposed solution that defines an InterruptableThread. – personal_cloud Sep 11 '17 at 20:19
  • As you comment in https://stackoverflow.com/questions/46161884/python-2-7-how-to-catch-keyboard-interrupt-in-program-with-more-than-25-threads/ this is exactly the same issue as you see there. The issue is that you are not include the thread creation in your `try` block. See my answer to the other question for a more elaborate explanation. – JohanL Sep 13 '17 at 18:30
  • @JohanL. In the context of catching signals... The observation here is that we can't rely on the `signal` interface alone if we have threads. We *must* catch exceptions too. See my comments on Tobe's answer. – personal_cloud Oct 05 '17 at 23:41

1 Answers1

1

Only the main thread is listening to SIGINT. Make sure all threads are listening to the SIGINT value.

  • Tried that, but got: `ValueError: signal only works in main thread`. So you have to call `signal.signal()` in your main thread only. – personal_cloud Sep 11 '17 at 16:40
  • Also, the official 2.7 documentation for `signal.signal()` says "When threads are enabled, this function can only be called from the main thread; attempting to call it from other threads will cause a ValueError exception to be raised." – personal_cloud Sep 11 '17 at 16:44
  • I appreciate your contributions. Why did you reject my edits? Things seem to work if you have the signal.pause() in the main thread and the try..except in all the other threads. Though I did notice that in some cases the signal is lost if there is an exception in the early parsing phase. Perhaps the solution is to have __init__.py import everything within a try block. Though it would be much better if Python just had a "don't break ctrl-C please" flag. – personal_cloud Oct 05 '17 at 18:15