Many times developers need to send signals of some kind to an ongoing process or threads to inform them about the happening of some kings of event. The process or thread can then execute some callback/handler according to event. The execution can be immediate or even a little later. But the main purpose of signal was to make thread or process aware of an event.
The signal can be of any kind like kill process, terminate the process, interrupt the process, broken pipe, segmentation-fault, window resize, floating point exception, etc. These kinds of signals are generally handled by the underlying C code.
Python provides us with a module named signal which lets us perform an operation of our own when any signal is received.
The signal module lets us register a callback that will be executed when a signal of a particular type is received, raise a signal, raise signal repeatedly at a specified interval, wait for the signal, send a signal to the thread by id, send a signal to the process by id, etc.
The callback that we register using signal module will be executed by the main thread of the process. The underlying C code which generally handles this kind of system call will set a flag that will indicate a Python interpreter to execute the handler (callback) function later on.
As a part of this tutorial, we'll explain how to use Python module "signal" to send, receive and handle UNIX/Windows system signals with simple and easy-to-understand examples. We'll explain how we can register a callback with the signal of a particular type, raise a signal, send a signal to a thread, send signals to processes, etc. A tutorial is a detailed guide to Python module "signal" that covers the majority of its API.
Please make a NOTE that even though signal module lets us send signals to threads other than the main thread, the handler attached to the signal will be executed by the main thread of the process.
As a part of our first example, we are explaining how we can send an alarm signal using alarm() method and execute a callback when an alarm signal is received.
signal(signum, callback) - This method accepts a signal number and reference to the callback as input. It registers a given callback to be executed by the main thread of the process whenever a given signal is received by any thread of the process. The callback needs to function with two arguments. The first argument will be signal number and the second argument will be the current stack frame given to it when a signal is received.
alarm(time) - It accepts time in seconds and sets the alarm which will send SIGALRM signal to the process after that many seconds have passed.
strsignal(signum) - This method accepts a signal number as input and returns a string description of the signal on the system.
Our code for this example creates a function named handler() which will print a message saying that a signal was received at a particular time. Our main code starts by registering handler function with SIGALRM signal. It then sets the alarm to go off after 3 seconds. It then makes the main thread sleep for 6 seconds so that it has enough time for signal.
We are using logging module to print statements to standard output. It'll have information about process id, process name, thread id, and thread name which can be helpful when sending signals to different processes/threads.
If you are interested in learning about logging module then please feel free to check our simple tutorial on it.
When we execute the below script we can notice based on the time that the alarm is raised after 3 seconds and it calls the handler which prints the statement saying the signal received.
import signal
import time
import logging
from datetime import datetime
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGALRM, handler)
signal.alarm(3)
time.sleep(6)
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_1 : <module> : (Process Details : (19128, MainProcess), Thread Details : (139803896608576, MainThread))
Log Message : Start Time : 2021-02-25 16:57:20.729755
signal_example_1 : handler : (Process Details : (19128, MainProcess), Thread Details : (139803896608576, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 16:57:23.729976
signal_example_1 : <module> : (Process Details : (19128, MainProcess), Thread Details : (139803896608576, MainThread))
Log Message : End Time : 2021-02-25 16:57:26.732008
As a part of our second example, we'll explain how we can make a thread wait from proceeding further until some kind of signal is received using pause() method.
Our code for this example is almost the same as our previous example with the only change that we used pause() method to make the main thread wait until SIGALRM signal is received.
Our output for this example is almost the same as the previous example.
import signal
import time
import logging
from datetime import datetime
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGALRM, handler)
signal.alarm(3)
signal.pause()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_2 : <module> : (Process Details : (23666, MainProcess), Thread Details : (140699468449600, MainThread))
Log Message : Start Time : 2021-02-25 17:10:20.838100
signal_example_2 : handler : (Process Details : (23666, MainProcess), Thread Details : (140699468449600, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 17:10:23.838249
signal_example_2 : <module> : (Process Details : (23666, MainProcess), Thread Details : (140699468449600, MainThread))
Log Message : End Time : 2021-02-25 17:10:23.838353
Our code for this example is almost the same as our previous example with the only change that we have registered a callback with SIGINT signal and have removed the call to alarm() method. The SIGINT is a signal for keyboard interrupt.
When we run the below code, it'll just wait until some kind of signal is received. We have pressed Ctrl + C after a few seconds to raise the keyboard interrupt signal. We can notice from the output that it has received a keyboard interrupt signal.
import signal
import time
import logging
from datetime import datetime
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGINT, handler)
signal.pause()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_2_2 : <module> : (Process Details : (24940, MainProcess), Thread Details : (140328183949120, MainThread))
Log Message : Start Time : 2021-02-25 17:10:50.448587
^Csignal_example_2_2 : handler : (Process Details : (24940, MainProcess), Thread Details : (140328183949120, MainThread))
Log Message : Signal : 'Interrupt' Received. Handler Executed @ 2021-02-25 17:10:54.041721
signal_example_2_2 : <module> : (Process Details : (24940, MainProcess), Thread Details : (140328183949120, MainThread))
Log Message : End Time : 2021-02-25 17:10:54.042007
As a part of our third example, we are just printing a list of valid signals available on a system and their names using valid_signals() method.
Our code for this example simply calls valid_signals() method to retrieve a list of signals. It then prints signal numbers and signal descriptions.
import signal
import time
import logging
from datetime import datetime
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
print("Number of Valid Signals : {}".format(len(signal.valid_signals())))
print("First Few Signals : {}".format(list(signal.valid_signals())[:5]))
print("\n======= Signal - System Name ===================\n")
for signum in signal.valid_signals():
print("{} - {}".format(signum, signal.strsignal(signum)))
print()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_3 : <module> : (Process Details : (15401, MainProcess), Thread Details : (140262016907072, MainThread))
Log Message : Start Time : 2021-02-25 17:18:50.366693
Number of Valid Signals : 62
First Few Signals : [<Signals.SIGHUP: 1>, <Signals.SIGINT: 2>, <Signals.SIGQUIT: 3>, <Signals.SIGILL: 4>, <Signals.SIGTRAP: 5>]
======= Signal - System Name ===================
1 - Hangup
2 - Interrupt
3 - Quit
4 - Illegal instruction
5 - Trace/breakpoint trap
6 - Aborted
7 - Bus error
8 - Floating point exception
9 - Killed
10 - User defined signal 1
11 - Segmentation fault
12 - User defined signal 2
13 - Broken pipe
14 - Alarm clock
15 - Terminated
16 - Stack fault
17 - Child exited
18 - Continued
19 - Stopped (signal)
20 - Stopped
21 - Stopped (tty input)
22 - Stopped (tty output)
23 - Urgent I/O condition
24 - CPU time limit exceeded
25 - File size limit exceeded
26 - Virtual timer expired
27 - Profiling timer expired
28 - Window changed
29 - I/O possible
30 - Power failure
31 - Bad system call
34 - Real-time signal 0
35 - Real-time signal 1
36 - Real-time signal 2
37 - Real-time signal 3
38 - Real-time signal 4
39 - Real-time signal 5
40 - Real-time signal 6
41 - Real-time signal 7
42 - Real-time signal 8
43 - Real-time signal 9
44 - Real-time signal 10
45 - Real-time signal 11
46 - Real-time signal 12
47 - Real-time signal 13
48 - Real-time signal 14
49 - Real-time signal 15
50 - Real-time signal 16
51 - Real-time signal 17
52 - Real-time signal 18
53 - Real-time signal 19
54 - Real-time signal 20
55 - Real-time signal 21
56 - Real-time signal 22
57 - Real-time signal 23
58 - Real-time signal 24
59 - Real-time signal 25
60 - Real-time signal 26
61 - Real-time signal 27
62 - Real-time signal 28
63 - Real-time signal 29
64 - Real-time signal 30
signal_example_3 : <module> : (Process Details : (15401, MainProcess), Thread Details : (140262016907072, MainThread))
Log Message : End Time : 2021-02-25 17:18:50.367439
As a part of our fourth example, we are explaining how a signal is received and handled by a process other than the main process.
Our code for this example has two parts. The first part simply starts a child process and executes the second part of the code as an independent process. The second part of the code is a script that we have used in our second example.
When we execute the below script, we can notice from the log message how a signal is raised by different processes and handled. Please check the process IDs in log messages to verify them.
Our code for this example used subprocess module to create a child process. If you are interested in learning about it then please feel free to check our tutorial on the same.
import signal
import time
import logging
from datetime import datetime
import subprocess
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
res = subprocess.run(["python", "signal_example_4_helper.py"])
logging.info("End Time : {}".format(datetime.now()))
import signal
import time
import logging
from datetime import datetime
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGALRM, handler)
signal.alarm(3)
signal.pause()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_4 : <module> : (Process Details : (32421, MainProcess), Thread Details : (140353195149120, MainThread))
Log Message : Start Time : 2021-02-25 17:25:03.195087
signal_example_4_helper : <module> : (Process Details : (32422, MainProcess), Thread Details : (140605100934976, MainThread))
Log Message : Start Time : 2021-02-25 17:25:03.216288
signal_example_4_helper : handler : (Process Details : (32422, MainProcess), Thread Details : (140605100934976, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 17:25:06.216734
signal_example_4_helper : <module> : (Process Details : (32422, MainProcess), Thread Details : (140605100934976, MainThread))
Log Message : End Time : 2021-02-25 17:25:06.217018
signal_example_4 : <module> : (Process Details : (32421, MainProcess), Thread Details : (140353195149120, MainThread))
Log Message : End Time : 2021-02-25 17:25:06.223213
As a part of our fifth example, we are demonstrating how a signal can be received and handled by the main process again but this time using multiprocessing module.
Our code for this example has moved code that registers a callback for signal and raises alarm signal to a function named independent_process(). We then execute function independent_process() as independent process using multiprocessing module.
When we run the below script, it has almost the same output as our previous example. We can notice from the output log the difference between process ids.
If you are interested in learning about multiprocessing module then please feel free to check our simple tutorial on the same.
import signal
import time
import logging
from datetime import datetime
import multiprocessing
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
def independent_process():
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGALRM, handler)
signal.alarm(3)
signal.pause()
logging.info("End Time : {}".format(datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
ind_process = multiprocessing.Process(target=independent_process, name="Independent Process")
ind_process.start()
ind_process.join()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_5 : <module> : (Process Details : (9589, MainProcess), Thread Details : (140074799642432, MainThread))
Log Message : Start Time : 2021-02-25 17:28:07.531368
signal_example_5 : independent_process : (Process Details : (9629, Independent Process), Thread Details : (140074799642432, MainThread))
Log Message : Start Time : 2021-02-25 17:28:07.551086
signal_example_5 : handler : (Process Details : (9629, Independent Process), Thread Details : (140074799642432, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 17:28:10.552250
signal_example_5 : independent_process : (Process Details : (9629, Independent Process), Thread Details : (140074799642432, MainThread))
Log Message : End Time : 2021-02-25 17:28:10.552562
signal_example_5 : <module> : (Process Details : (9589, MainProcess), Thread Details : (140074799642432, MainThread))
Log Message : End Time : 2021-02-25 17:28:10.553815
As a part of our sixth example, we are demonstrating how we can raise a signal in a process using raise_signal() method.
Our code for this example has 2 handlers (handler1 and handler2). Both handlers simply print statements saying what kind of signal was received.
We have defined a function named independent_process() which will be executed when a process is created. The process registers a handler1 callback with signal SIGILL. It then tries to divide a number by zero and raise a signal inside of except block of code using raise_signal() method.
Our main code starts by creating a process where it'll execute a method independent_process() as an independent process. It then registers a handler2 callback to be called for SIGFPE signal. It then tries to divide a number by zero and raise a signal inside of except block of code using raise_signal() method.
When we run the below script, we can notice from the output how different handlers are executed by different processes when a signal is received.
import signal
import time
import logging
from datetime import datetime
import multiprocessing
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler1(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
def handler2(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
def independent_process():
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGILL, handler1)
try:
out = 10/0
except Exception as e:
signal.raise_signal(signal.SIGILL)
logging.info("End Time : {}".format(datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
ind_process = multiprocessing.Process(target=independent_process, name="Independent Process")
ind_process.start()
ind_process.join()
signal.signal(signal.SIGFPE, handler2)
try:
out = 10/0
except Exception as e:
signal.raise_signal(signal.SIGFPE)
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_6 : <module> : (Process Details : (17441, MainProcess), Thread Details : (139855215884096, MainThread))
Log Message : Start Time : 2021-02-25 17:42:42.867559
signal_example_6 : independent_process : (Process Details : (17442, Independent Process), Thread Details : (139855215884096, MainThread))
Log Message : Start Time : 2021-02-25 17:42:42.870487
signal_example_6 : handler1 : (Process Details : (17442, Independent Process), Thread Details : (139855215884096, MainThread))
Log Message : Signal : 'Illegal instruction' Received. Handler Executed @ 2021-02-25 17:42:42.870809
signal_example_6 : independent_process : (Process Details : (17442, Independent Process), Thread Details : (139855215884096, MainThread))
Log Message : End Time : 2021-02-25 17:42:42.870877
signal_example_6 : handler2 : (Process Details : (17441, MainProcess), Thread Details : (139855215884096, MainThread))
Log Message : Signal : 'Floating point exception' Received. Handler Executed @ 2021-02-25 17:42:42.871328
signal_example_6 : <module> : (Process Details : (17441, MainProcess), Thread Details : (139855215884096, MainThread))
Log Message : End Time : 2021-02-25 17:42:42.871479
As a part of our seventh example, we are demonstrating how we can send a signal to a particular process based on the process id using pidfd_send_signal().
Our code this example builds on example 5. It send signal SIGHUP to newly created independent process using pidfd_send_signal() method. The handler for the signal is registered inside the new process.
import signal
import time
import logging
from datetime import datetime
import multiprocessing
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
def independent_process():
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGHUP, handler)
time.sleep(10)
logging.info("End Time : {}".format(datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
ind_process = multiprocessing.Process(target=independent_process, name="Independent Process")
ind_process.start()
time.sleep(3)
print("Process ID : {}".format(ind_process.pid))
signal.pidfd_send_signal(ind_process.pid, signal.SIGHUP)
ind_process.join()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_7 : <module> : (Process Details : (19291, MainProcess), Thread Details : (140377436309312, MainThread))
Log Message : Start Time : 2021-02-25 17:43:21.531869
signal_example_7 : independent_process : (Process Details : (19292, Independent Process), Thread Details : (140377436309312, MainThread))
Log Message : Start Time : 2021-02-25 17:43:21.534435
Process ID : 19292
signal_example_7 : independent_process : (Process Details : (19292, Independent Process), Thread Details : (140377436309312, MainThread))
Signal : 'Hangup' Received. Handler Executed @ 2021-02-25 17:42:24.870809
signal_example_7 : independent_process : (Process Details : (19292, Independent Process), Thread Details : (140377436309312, MainThread))
Signal : End Time : 2021-02-25 17:43:34.543943
signal_example_7 : <module> : (Process Details : (19291, Independent Process), Thread Details : (140377436309312, MainThread))
Log Message : End Time : 2021-02-25 17:43:34.543943
As a part of our eighth example, we'll demonstrate how we can send a signal to a particular thread of the process using pthread_kill() method.
pthread_kill(thread_id, signum) - It sends a signal specified by sig_num to a thread specified by a thread_id.
sigwait(list_of_signals) - It suspends execution of the calling thread until one of the signals specified in the list is received by the thread.
getsignal(sig_num) - It returns a callback handler registered with the given signal.
Our code for this example has created a method named independent_thread() which will be executed as a thread. It then waits for the list of signals (SIGTERM and SIGALRM) using sigwait() method. Once the signal is received, it retrieves the handler of the signal and executes it.
Our main code starts by registering a handler with SIGTERM signal. It then creates a thread executing independent_thread() function. It then sleeps for 3 seconds. After that, it finds out the id of the newly created thread and sends SIGTERM signal to it using pthread_kill() method.
When we run a script, we can notice from the output that the handler is executed two times. Once by the main thread and once by a newly created thread.
If you are interested in learning about how to work with threads in Python then please feel free to check our extensive tutorial on the same.
import signal
import time
import logging
from datetime import datetime
import threading
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
def independent_thread():
logging.info("Start Time : {}".format(datetime.now()))
sig = signal.sigwait([signal.SIGTERM, signal.SIGALRM])
logging.info("Recived signal : '{}'. Time @ : {}".format(signal.strsignal(sig), datetime.now()))
handle = signal.getsignal(sig)
handle(sig, None)
time.sleep(2)
logging.info("End Time : {}".format(datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGTERM, handler)
ind_th = threading.Thread(target=independent_thread, name="Independent Thread")
ind_th.start()
time.sleep(3)
for thread in threading.enumerate():
if thread.name == "Independent Thread":
print("Independent Thread ID : {}\n".format(thread.ident))
signal.pthread_kill(thread.ident, signal.SIGTERM)
ind_th.join()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_8_1 : <module> : (Process Details : (17219, MainProcess), Thread Details : (139672598951744, MainThread))
Log Message : Start Time : 2021-02-25 18:05:39.425518
signal_example_8_1 : independent_thread : (Process Details : (17219, MainProcess), Thread Details : (139672575997696, Independent Thread))
Log Message : Start Time : 2021-02-25 18:05:39.425723
Independent Thread ID : 139672575997696
signal_example_8_1 : independent_thread : (Process Details : (17219, MainProcess), Thread Details : (139672575997696, Independent Thread))
Log Message : Recived signal : 'Terminated'. Time @ : 2021-02-25 18:05:42.429288
signal_example_8_1 : handler : (Process Details : (17219, MainProcess), Thread Details : (139672575997696, Independent Thread))
Log Message : Signal : 'Terminated' Received. Handler Executed @ 2021-02-25 18:05:42.429590
signal_example_8_1 : independent_thread : (Process Details : (17219, MainProcess), Thread Details : (139672575997696, Independent Thread))
Log Message : End Time : 2021-02-25 18:05:44.431822
signal_example_8_1 : <module> : (Process Details : (17219, MainProcess), Thread Details : (139672598951744, MainThread))
Log Message : End Time : 2021-02-25 18:05:44.432287
Our code for this example is exactly the same as our previous example with the only change that the newly created thread halts execution once it receives SIGTERM signal.
When we run the below script and compare its output with the output of the previous example, we can notice that statements about the new thread ending are not getting printed.
import signal
import time
import logging
from datetime import datetime
import threading
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
def independent_thread():
logging.info("Start Time : {}".format(datetime.now()))
sig = signal.sigwait([signal.SIGTERM, signal.SIGALRM])
logging.info("Recived signal : '{}'. Time @ : {}".format(signal.strsignal(sig), datetime.now()))
if signal.strsignal(sig) == "Terminated":
raise SystemExit("Terminating Thread : {}".format(threading.current_thread().name))
time.sleep(2) ### Line from this onwards won't be executed.
logging.info("End Time : {}".format(datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGTERM, handler)
ind_th = threading.Thread(target=independent_thread, name="Independent Thread")
ind_th.start()
time.sleep(3)
for thread in threading.enumerate():
if thread.name == "Independent Thread":
print("Independent Thread ID : {}\n".format(thread.ident))
signal.pthread_kill(thread.ident, signal.SIGTERM)
ind_th.join()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_8_2 : <module> : (Process Details : (26762, MainProcess), Thread Details : (140516357093184, MainThread))
Log Message : Start Time : 2021-02-25 18:09:14.302447
signal_example_8_2 : independent_thread : (Process Details : (26762, MainProcess), Thread Details : (140516334139136, Independent Thread))
Log Message : Start Time : 2021-02-25 18:09:14.302641
Independent Thread ID : 140516334139136
signal_example_8_2 : independent_thread : (Process Details : (26762, MainProcess), Thread Details : (140516334139136, Independent Thread))
Log Message : Recived signal : 'Terminated'. Time @ : 2021-02-25 18:09:17.304424
signal_example_8_2 : <module> : (Process Details : (26762, MainProcess), Thread Details : (140516357093184, MainThread))
Log Message : End Time : 2021-02-25 18:09:17.304845
Our code for this example explains how we can timeout after waiting for a specified amount of time for any signal from the list of signals to arrive.
Our code for this example is almost the same as our previous example with the only change that we are sigtimedwait() and catching its output. We are making the thread wait for 2 seconds to receive the signal. If it does then print signal details else message saying time out.
When we run the script, we can notice that after waiting for 2 seconds, it times out and returns.
import signal
import time
import logging
from datetime import datetime
import threading
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
def independent_thread():
logging.info("Start Time : {}".format(datetime.now()))
sig = signal.sigtimedwait([signal.SIGTERM, signal.SIGALRM], 2)
if sig:
logging.info("Recived signal : '{}'. Time @ : {}".format(signal.strsignal(sig), datetime.now()))
else:
print("Wait Signal Timed Out.\n")
logging.info("End Time : {}".format(datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGTERM, handler)
ind_th = threading.Thread(target=independent_thread, name="Independent Thread")
ind_th.start()
time.sleep(3)
for thread in threading.enumerate():
if thread.name == "Independent Thread":
print("Independent Thread ID : {}\n".format(thread.ident))
signal.pthread_kill(thread.ident, signal.SIGTERM)
ind_th.join()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_8_3 : <module> : (Process Details : (6426, MainProcess), Thread Details : (140134175528768, MainThread))
Log Message : Start Time : 2021-02-25 18:13:36.249474
signal_example_8_3 : independent_thread : (Process Details : (6426, MainProcess), Thread Details : (140134152574720, Independent Thread))
Log Message : Start Time : 2021-02-25 18:13:36.249663
Wait Signal Timed Out.
signal_example_8_3 : independent_thread : (Process Details : (6426, MainProcess), Thread Details : (140134152574720, Independent Thread))
Log Message : End Time : 2021-02-25 18:13:38.249856
signal_example_8_3 : <module> : (Process Details : (6426, MainProcess), Thread Details : (140134175528768, MainThread))
Log Message : End Time : 2021-02-25 18:13:39.252269
Our code for this example is almost the same as our previous examples (8.1, 8.2) with the only change that uses now uses sigwaitinfo() to wait for any signal to arrive from the list of signals.
import signal
import time
import logging
from datetime import datetime
import threading
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
def independent_thread():
logging.info("Start Time : {}".format(datetime.now()))
sig = signal.sigwaitinfo([signal.SIGTERM, signal.SIGALRM])
print("Signal Info : {}\n".format(sig))
logging.info("Recived signal : '{}'. Time @ : {}\n".format(signal.strsignal(sig.si_signo), datetime.now()))
logging.info("End Time : {}".format(datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGTERM, handler)
ind_th = threading.Thread(target=independent_thread, name="Independent Thread")
ind_th.start()
time.sleep(3)
for thread in threading.enumerate():
if thread.name == "Independent Thread":
print("Independent Thread ID : {}\n".format(thread.ident))
signal.pthread_kill(thread.ident, signal.SIGTERM)
ind_th.join()
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_8_4 : <module> : (Process Details : (17635, MainProcess), Thread Details : (139672984958784, MainThread))
Log Message : Start Time : 2021-02-25 18:17:44.382889
signal_example_8_4 : independent_thread : (Process Details : (17635, MainProcess), Thread Details : (139672962004736, Independent Thread))
Log Message : Start Time : 2021-02-25 18:17:44.383148
Independent Thread ID : 139672962004736
Signal Info : signal.struct_siginfo(si_signo=15, si_code=0, si_errno=0, si_pid=17635, si_uid=1001, si_status=0, si_band=4299262280931)
signal_example_8_4 : independent_thread : (Process Details : (17635, MainProcess), Thread Details : (139672962004736, Independent Thread))
Log Message : Recived signal : 'Terminated'. Time @ : 2021-02-25 18:17:47.384573
signal_example_8_4 : independent_thread : (Process Details : (17635, MainProcess), Thread Details : (139672962004736, Independent Thread))
Log Message : End Time : 2021-02-25 18:17:47.384684
signal_example_8_4 : <module> : (Process Details : (17635, MainProcess), Thread Details : (139672984958784, MainThread))
Log Message : End Time : 2021-02-25 18:17:47.384805
As a part of our ninth example, we'll demonstrate how we can send a signal after a specified amount of time (seconds) has passed and after that send a signal repeatedly at a specified interval (in seconds). We can do this using setitimer() method of signal module.
setitimer(sig_num,seconds,interval=0) - It accepts signal number and number seconds after which to send signal to the calling process. If interval is specified as greater than 0 then it'll keep sending signal repeatedly after that many seconds once an initial signal is sent after specified seconds. This method lets us send one of the below signals.
getitimer(sig_num) - This method accepts signal number and returns a tuple. The first value in the tuple is the value specifying the amount of time pending for sending signal and the second value is interval specified in setitimer() method.
Our code for this example is very simple to understand. The code first sets the handler function for the SIGALRM signal. It then sets the timer to be executed after 2 seconds using setitimer() method with ITIMER_REAL as a signal. This call will send SIGALRM signal to the calling main process after 2 seconds have passed. We have made the main thread wait for 5 seconds so that we can catch the signal before it completes.
When we run the script, we can notice from the output of handler function that the signal was received after 2 seconds.
import signal
import time
import logging
from datetime import datetime
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGALRM, handler)
signal.setitimer(signal.ITIMER_REAL, 2)
time.sleep(5)
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_9_1 : <module> : (Process Details : (28683, MainProcess), Thread Details : (139730350839616, MainThread))
Log Message : Start Time : 2021-02-25 18:21:46.684112
signal_example_9_1 : handler : (Process Details : (28683, MainProcess), Thread Details : (139730350839616, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:21:48.684294
signal_example_9_1 : <module> : (Process Details : (28683, MainProcess), Thread Details : (139730350839616, MainThread))
Log Message : End Time : 2021-02-25 18:21:51.687534
Our code for this example is almost the same as our previous example with only a change in the call to setitimer() method. We have introduced an interval parameter with 1 second of interval. This will make the method first send a signal after 2 seconds and after that signal will be sent every 1 second to the calling process until the process completes.
When we run the below script, we can notice from the output that the first time handler() was called after 2 seconds and then it's getting called every 1 second. We have put the main thread to sleep for 5 seconds. It completes soon after 5 seconds. We see handler() message 4 times because the first time it was called after 2 seconds and then 3 times after a 1-second interval, totaling 5 seconds for which man thread was sleeping.
import signal
import time
import logging
from datetime import datetime
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGALRM, handler)
signal.setitimer(signal.ITIMER_REAL, 2, 1)
time.sleep(5)
logging.info("End Time : {}".format(datetime.now()))
OUTPUT
signal_example_9_2 : <module> : (Process Details : (30529, MainProcess), Thread Details : (139938419754816, MainThread))
Log Message : Start Time : 2021-02-25 18:22:25.710157
signal_example_9_2 : handler : (Process Details : (30529, MainProcess), Thread Details : (139938419754816, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:22:27.710624
signal_example_9_2 : handler : (Process Details : (30529, MainProcess), Thread Details : (139938419754816, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:22:28.710407
signal_example_9_2 : handler : (Process Details : (30529, MainProcess), Thread Details : (139938419754816, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:22:29.710395
signal_example_9_2 : handler : (Process Details : (30529, MainProcess), Thread Details : (139938419754816, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:22:30.710647
signal_example_9_2 : <module> : (Process Details : (30529, MainProcess), Thread Details : (139938419754816, MainThread))
Log Message : End Time : 2021-02-25 18:22:30.710970
Our code for this example is almost the same as our previous example with the minor addition of code. We have added calls to getitimer() method to retrieve pending time to send a signal and printed it. We have also changed the arguments of setitimer() with 3 seconds for the first signal and a 1-second interval for repeatedly sending signals.
When we run the script, we can notice from the output that getitimer() returns pending time as 1.98 seconds because 1 second has passed when the main thread was sleeping. Our next call to getitimer() returns 0.98 seconds because, after the first 3 seconds, the signal gets sent every second.
import signal
import time
import logging
from datetime import datetime
logging.basicConfig(format="%(module)s : %(funcName)s : (Process Details : (%(process)d, %(processName)s), Thread Details : (%(thread)d, %(threadName)s))\nLog Message : %(message)s\n",
datefmt="%d-%B,%Y %I:%M:%S %p",
level=logging.INFO)
def handler(sig_num, curr_stack_frame):
logging.info("Signal : '{}' Received. Handler Executed @ {}".format(signal.strsignal(sig_num), datetime.now()))
if __name__ == "__main__":
logging.info("Start Time : {}".format(datetime.now()))
signal.signal(signal.SIGALRM, handler)
signal.setitimer(signal.ITIMER_REAL, 3, 1)
time.sleep(1)
timer_pending_time, interval = signal.getitimer(signal.ITIMER_REAL)
print("Current Pending Time of Timer : {}, Interval : {}\n".format(timer_pending_time, interval))
time.sleep(5)
timer_pending_time, interval = signal.getitimer(signal.ITIMER_REAL)
print("Current Pending Time of Timer : {}, Interval : {}\n".format(timer_pending_time, interval))
logging.info("End Time : {}".format(datetime.now()))
OUTPUT*
signal_example_9_3 : <module> : (Process Details : (32482, MainProcess), Thread Details : (139836996556608, MainThread))
Log Message : Start Time : 2021-02-25 18:23:06.856780
Current Pending Time of Timer : 1.998913, Interval : 1.0
signal_example_9_3 : handler : (Process Details : (32482, MainProcess), Thread Details : (139836996556608, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:23:09.857296
signal_example_9_3 : handler : (Process Details : (32482, MainProcess), Thread Details : (139836996556608, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:23:10.857260
signal_example_9_3 : handler : (Process Details : (32482, MainProcess), Thread Details : (139836996556608, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:23:11.857242
signal_example_9_3 : handler : (Process Details : (32482, MainProcess), Thread Details : (139836996556608, MainThread))
Log Message : Signal : 'Alarm clock' Received. Handler Executed @ 2021-02-25 18:23:12.857253
Current Pending Time of Timer : 0.98336, Interval : 1.0
signal_example_9_3 : <module> : (Process Details : (32482, MainProcess), Thread Details : (139836996556608, MainThread))
Log Message : End Time : 2021-02-25 18:23:12.873644
This ends our small tutorial explaining how we can send, receive and handle system signal in Python using "signal" module.
If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.
When going through coding examples, it's quite common to have doubts and errors.
If you have doubts about some code examples or are stuck somewhere when trying our code, send us an email at coderzcolumn07@gmail.com. We'll help you or point you in the direction where you can find a solution to your problem.
You can even send us a mail if you are trying something new and need guidance regarding coding. We'll try to respond as soon as possible.
If you want to