Updated On : Feb-27,2021 Tags system-signals
signal - Simple Guide to Send, Receive and Handle System Signals in Python

signal - Simple Guide to Send, Receive and Handle System Signals in Python

Many times developers need to send signals of some kind to an ongoing process or threads to inform it about some situation or perform some operations. 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 let 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 calls 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 try to explain various methods of signal module 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, etc.

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.

Example 1: Send Alarm Signal

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 signal 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 prints 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.

In [ ]:
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

Example 2: Make Thread Wait for Signal

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.


  • pause() - It'll make the calling process to sleep until a signal of some kind is received.

2.1: Wait till Alarm

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.

In [ ]:
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

2.2: Wait till Keyboard Interrupt

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 few seconds to raise the keyboard interrupt signal. We can notice from the output that it has received a keyboard interrupt signal.

In [ ]:
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

Example 3: List of Valid Signals

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.


  • valid_signals() - It returns list of valid signals available on the system.

Our code for this example simply calls valid_signals() method to retrieve a list of signals. It then prints signal numbers and signals descriptions.

In [ ]:
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

Example 4: Sending Signal to a Child Process

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 it.

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.

In [ ]:
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()))

signal_example_4_helper.py

In [ ]:
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

Example 5: Sending Signal to a Process

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.

In [ ]:
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

Example 6: Raising a Signal

As a part of our sixth example, we are demonstrating how we can raise a signal in a process using raise_signal() method.


  • raise_signal(sig_num) - It'll send the given signal to the calling process.

Our code for this example has 2 handlers (handler1 and handler2). Both handlers simply print statements saying what kind of signal 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.

In [ ]:
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

Example 7: Send a Signal to a Process using a Process ID

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().


  • pidfd_send_signal(pidfd,sig_num) - It sends the signal specified by sig_num to a process with id pidfd.

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 of the new process.

In [ ]:
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

Example 8: Send a Signal to a Thread using the Thread ID

As a part of our eight 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.


8.1: Send Signal to a Thread and Wait for it using sigwait()

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.

In [ ]:
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

8.2: Send Signal to a Thread and Wait for it using sigwait()

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.

In [ ]:
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

8.3: Send Signal to a Thread and Wait for it using sigtimedwait()

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.


  • sigtimedwait(list_of_signals, timeout) - It waits for a specified number of seconds per timeout period until one of the signals specified in the list of signals have arrived. It'll continue with the next statement if it has not arrived any signal until then.

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.

In [ ]:
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

8.4: Send Signal to a Thread and Wait for it using sigwaitinfo()

Our code for this example is almost the same as our previous examples (8.1, 8.2) with the only change that uses now it uses sigwaitinfo() to wait for any signal to arrive from the list of signals.


  • sigwaitinfo(list_of_signals) - It suspends execution of the calling thread until one of the signals specified in the list of signals arrives. It returns struct_siginfo instance which has information about the captured signal.

In [ ]:
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

Example 9: Send Signal Repeatedly After Specified Amount of Time

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.

    • ITIMER_REAL - It decrements the timer and sends SIGALRM signal to the caller.
    • ITIMER_VIRTUAL - It decrements time when the process is executing (not when blocked or sleeping) and sends SIGVTALRM signal to the calling process.
    • ITIMER_PROF - It decrements the timer when the process is executing and the system is executing on behalf of the process and sends SIGPROF signal to the calling process.
  • 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.


9.1: Send Signal After Specified Amount of TIme

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.

In [ ]:
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

9.2: Send Signal Repeatedly

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 1-second interval, totaling 5 seconds for which man thread was sleeping.

In [ ]:
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

9.3: Retrieve Timer Details

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.

In [ ]:
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. Please feel free to let us know your views in the comments section.

References



Sunny Solanki  Sunny Solanki