Updated On : Aug-28,2022 Time Investment : ~20 mins

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

> Why Use System Signals (Unix/Windows)?

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.

> What Solution Python Offers for Working with System Signals?

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.

> What Can You Learn From This Article?

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.

NOTE

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.

Important Sections Of Tutorial

  1. Send Alarm Signal
  2. Make Thread Wait for Signal
    • 2.1: Wait till Alarm
    • 2.2: Wait till Keyboard Interrupt
  3. List of Available Signals on your System
  4. Sending Signal to a Child Process
  5. Sending Signal to a Process
  6. Raising a Signal
  7. Send a Signal to a Process using a Process ID
  8. Send a Signal to a Thread using the Thread ID
    • 8.1: Send Signal to a Thread and Wait for it using "sigwait()"
    • 8.2: Send Signal to a Thread and Wait for it using "sigwait()"
    • 8.3: Send Signal to a Thread and Wait for it using "sigtimedwait()"
    • 8.4: Send Signal to a Thread and Wait for it using "sigwaitinfo()"
  9. Send Signal Repeatedly After Specified Amount of Time
    • 9.1: Send Signal After Specified Amount of Time
    • 9.2: Send Signal Repeatedly
    • 9.3: Retrieve Timer Details

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.


Important Functions of Python Module "Signal"

  • 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

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.


Important Functions of Python Module "Signal"

  • pause() - It'll put 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.

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

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.


Important Functions of Python Module "Signal"

  • 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 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

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

signal_example_4_helper.py

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.

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.


Important Functions of Python Module "Signal"

  • 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 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

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


Important Functions of Python Module "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 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

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

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.


Important Functions of Python Module "Signal"

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

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.

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.


Important Functions of Python Module "Signal"

  • 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 has 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.

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 uses sigwaitinfo() to wait for any signal to arrive from the list of signals.


Important Functions of Python Module "Signal"

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

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.


Important Functions of Python Module "Signal"

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

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

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.

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.

References

Sunny Solanki  Sunny Solanki

YouTube Subscribe Comfortable Learning through Video Tutorials?

If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.

Need Help Stuck Somewhere? Need Help with Coding? Have Doubts About the Topic/Code?

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.

Share Views Want to Share Your Views? Have Any Suggestions?

If you want to

  • provide some suggestions on topic
  • share your views
  • include some details in tutorial
  • suggest some new topics on which we should create tutorials/blogs
Please feel free to contact us at coderzcolumn07@gmail.com. We appreciate and value your feedbacks. You can also support us with a small contribution by clicking DONATE.


Subscribe to Our YouTube Channel

YouTube SubScribe

Newsletter Subscription