Handling SIGINT (Ctrl+C) Gracefully
π·οΈ Operating System and System Operations / Working with Processes and Signals
When you run a Python script in the terminal and press Ctrl+C, your operating system sends a SIGINT (Signal Interrupt) to the running process. By default, this immediately terminates your program. However, in many real-world scenariosβlike cleaning up temporary files, closing database connections, or saving progressβyou want your script to shut down gracefully instead of stopping abruptly. Handling SIGINT allows you to intercept this signal and run custom cleanup logic before exiting.
βοΈ What is SIGINT?
- SIGINT stands for Signal Interrupt. It is a standard Unix/Linux signal sent when a user presses Ctrl+C in the terminal.
- The default behavior of Python is to raise a KeyboardInterrupt exception when SIGINT is received.
- If unhandled, this exception causes your script to exit immediately with a traceback error message.
- By catching this signal, you can control what happens before your program stops.
π οΈ How to Catch SIGINT Using the signal Module
Python's built-in signal module allows you to register a custom handler function that runs when a specific signal is received.
- Import the signal module and the sys module for clean exit.
- Define a handler function that accepts two arguments: signum (the signal number) and frame (the current stack frame).
- Use signal.signal(signal.SIGINT, your_handler) to register your function.
- Inside the handler, print a message, perform cleanup, and call sys.exit(0) to exit gracefully.
Example flow:
- Your script starts and registers the handler.
- While running, the user presses Ctrl+C.
- Instead of crashing, your handler runs: it prints "Shutting down gracefully...", closes any open resources, and exits with status code 0.
π΅οΈ Using a Graceful Shutdown Flag
A common pattern for long-running scripts (like servers or data processors) is to use a global flag that signals the main loop to stop.
- Create a variable like running = True.
- In your SIGINT handler, set running = False instead of calling sys.exit immediately.
- Your main loop checks this flag at the start of each iteration.
- When the flag becomes False, the loop exits naturally, and any cleanup code after the loop runs.
This approach is cleaner because it lets your program finish its current task before stopping.
π Comparison: Default vs Graceful Handling
| Aspect | Default Behavior | Graceful Handling |
|---|---|---|
| What happens on Ctrl+C | Script crashes with KeyboardInterrupt traceback | Custom handler runs, then clean exit |
| Cleanup | No cleanup performed | Resources like files, sockets, and DB connections are closed |
| Exit code | Non-zero (error) | Zero (success) |
| User experience | Ugly error message | Friendly message like "Shutting down..." |
| Control | None | You decide what to do before exit |
π§ͺ Practical Example Scenario
Imagine you have a script that writes data to a file every second.
- Without handling SIGINT: Pressing Ctrl+C stops the script mid-write, potentially corrupting the file.
- With graceful handling: The script catches the signal, finishes writing the current data, closes the file properly, and prints "Data saved. Exiting." before stopping.
β Best Practices for Engineers
- Always register your SIGINT handler at the very beginning of your script, before any critical operations.
- Keep your handler function short and fastβavoid complex logic inside it.
- Use a shutdown flag for loops instead of exiting immediately from the handler.
- If your script uses multiple threads, remember that signals are only received by the main thread.
- Test your graceful shutdown by running your script and pressing Ctrl+C to verify the behavior.
π§ Summary
Handling SIGINT gracefully is a simple but powerful technique that makes your Python scripts more robust and professional. Instead of letting your program crash with an ugly traceback, you take control of the shutdown processβcleaning up resources, saving state, and providing a clean exit. This is especially important for scripts that run for long periods, interact with external systems, or handle important data. By using the signal module and a shutdown flag pattern, you ensure your programs behave reliably even when interrupted.
This topic shows how to catch the SIGINT signal (sent when a user presses Ctrl+C) and perform cleanup actions before your Python program exits.
π Example 1: Basic SIGINT handler that prints a message
This example creates a simple handler that prints a message when Ctrl+C is pressed, instead of immediately terminating.
import signal
import time
def handle_sigint(signal_number, frame):
print("You pressed Ctrl+C. Exiting now.")
signal.signal(signal.SIGINT, handle_sigint)
print("Press Ctrl+C to trigger the handler.")
while True:
time.sleep(1)
π€ Output: Press Ctrl+C to trigger the handler. (then after Ctrl+C) You pressed Ctrl+C. Exiting now.
π Example 2: Graceful shutdown with a flag
This example uses a running flag to stop a loop cleanly when Ctrl+C is pressed.
import signal
import time
running = True
def handle_sigint(signal_number, frame):
global running
print("Shutting down gracefully...")
running = False
signal.signal(signal.SIGINT, handle_sigint)
print("Program is running. Press Ctrl+C to stop.")
while running:
print("Working...")
time.sleep(1)
print("Cleanup complete. Goodbye.")
π€ Output: Program is running. Press Ctrl+C to stop. (then after Ctrl+C) Shutting down gracefully... Cleanup complete. Goodbye.
π Example 3: Performing cleanup before exit
This example shows how to close a file or release a resource when the user interrupts the program.
import signal
import time
file_handle = None
def handle_sigint(signal_number, frame):
print("Interrupt received. Closing file...")
if file_handle is not None:
file_handle.close()
print("File closed. Exiting.")
exit(0)
signal.signal(signal.SIGINT, handle_sigint)
file_handle = open("temp_log.txt", "w")
file_handle.write("Starting work...\n")
print("Writing to file. Press Ctrl+C to stop.")
while True:
file_handle.write("Working...\n")
time.sleep(1)
π€ Output: Writing to file. Press Ctrl+C to stop. (then after Ctrl+C) Interrupt received. Closing file... File closed. Exiting.
π Example 4: Restoring the default SIGINT behavior
This example shows how to temporarily override Ctrl+C behavior and then restore the default handler.
import signal
import time
def custom_handler(signal_number, frame):
print("Custom handler: Ctrl+C ignored this time.")
print("Setting custom handler for 5 seconds...")
signal.signal(signal.SIGINT, custom_handler)
for i in range(5):
print(f"Second {i+1} - try Ctrl+C now")
time.sleep(1)
print("Restoring default SIGINT behavior.")
signal.signal(signal.SIGINT, signal.SIG_DFL)
print("Now Ctrl+C will exit normally.")
while True:
time.sleep(1)
π€ Output: Setting custom handler for 5 seconds... (then messages for 5 seconds) Restoring default SIGINT behavior. Now Ctrl+C will exit normally.
π Example 5: Handling SIGINT in a multi-threaded program
This example demonstrates how to stop all worker threads gracefully when Ctrl+C is pressed.
import signal
import time
import threading
running = True
def worker_thread(thread_id):
while running:
print(f"Thread {thread_id} is working...")
time.sleep(1)
print(f"Thread {thread_id} stopped.")
def handle_sigint(signal_number, frame):
global running
print("Stopping all threads...")
running = False
signal.signal(signal.SIGINT, handle_sigint)
thread1 = threading.Thread(target=worker_thread, args=(1,))
thread2 = threading.Thread(target=worker_thread, args=(2,))
thread1.start()
thread2.start()
print("Main program running. Press Ctrl+C to stop all threads.")
thread1.join()
thread2.join()
print("All threads stopped. Program exiting.")
π€ Output: Main program running. Press Ctrl+C to stop all threads. (then threads print messages) (after Ctrl+C) Stopping all threads... Thread 1 stopped. Thread 2 stopped. All threads stopped. Program exiting.
Comparison Table
| Feature | Basic Handler | Graceful Shutdown | Cleanup Actions | Restore Default | Multi-threaded |
|---|---|---|---|---|---|
| Prints message on Ctrl+C | β | β | β | β | β |
| Stops loop cleanly | β | β | β | β | β |
| Releases resources | β | β | β | β | β |
| Restores default behavior | β | β | β | β | β |
| Handles multiple threads | β | β | β | β | β |
When you run a Python script in the terminal and press Ctrl+C, your operating system sends a SIGINT (Signal Interrupt) to the running process. By default, this immediately terminates your program. However, in many real-world scenariosβlike cleaning up temporary files, closing database connections, or saving progressβyou want your script to shut down gracefully instead of stopping abruptly. Handling SIGINT allows you to intercept this signal and run custom cleanup logic before exiting.
βοΈ What is SIGINT?
- SIGINT stands for Signal Interrupt. It is a standard Unix/Linux signal sent when a user presses Ctrl+C in the terminal.
- The default behavior of Python is to raise a KeyboardInterrupt exception when SIGINT is received.
- If unhandled, this exception causes your script to exit immediately with a traceback error message.
- By catching this signal, you can control what happens before your program stops.
π οΈ How to Catch SIGINT Using the signal Module
Python's built-in signal module allows you to register a custom handler function that runs when a specific signal is received.
- Import the signal module and the sys module for clean exit.
- Define a handler function that accepts two arguments: signum (the signal number) and frame (the current stack frame).
- Use signal.signal(signal.SIGINT, your_handler) to register your function.
- Inside the handler, print a message, perform cleanup, and call sys.exit(0) to exit gracefully.
Example flow:
- Your script starts and registers the handler.
- While running, the user presses Ctrl+C.
- Instead of crashing, your handler runs: it prints "Shutting down gracefully...", closes any open resources, and exits with status code 0.
π΅οΈ Using a Graceful Shutdown Flag
A common pattern for long-running scripts (like servers or data processors) is to use a global flag that signals the main loop to stop.
- Create a variable like running = True.
- In your SIGINT handler, set running = False instead of calling sys.exit immediately.
- Your main loop checks this flag at the start of each iteration.
- When the flag becomes False, the loop exits naturally, and any cleanup code after the loop runs.
This approach is cleaner because it lets your program finish its current task before stopping.
π Comparison: Default vs Graceful Handling
| Aspect | Default Behavior | Graceful Handling |
|---|---|---|
| What happens on Ctrl+C | Script crashes with KeyboardInterrupt traceback | Custom handler runs, then clean exit |
| Cleanup | No cleanup performed | Resources like files, sockets, and DB connections are closed |
| Exit code | Non-zero (error) | Zero (success) |
| User experience | Ugly error message | Friendly message like "Shutting down..." |
| Control | None | You decide what to do before exit |
π§ͺ Practical Example Scenario
Imagine you have a script that writes data to a file every second.
- Without handling SIGINT: Pressing Ctrl+C stops the script mid-write, potentially corrupting the file.
- With graceful handling: The script catches the signal, finishes writing the current data, closes the file properly, and prints "Data saved. Exiting." before stopping.
β Best Practices for Engineers
- Always register your SIGINT handler at the very beginning of your script, before any critical operations.
- Keep your handler function short and fastβavoid complex logic inside it.
- Use a shutdown flag for loops instead of exiting immediately from the handler.
- If your script uses multiple threads, remember that signals are only received by the main thread.
- Test your graceful shutdown by running your script and pressing Ctrl+C to verify the behavior.
π§ Summary
Handling SIGINT gracefully is a simple but powerful technique that makes your Python scripts more robust and professional. Instead of letting your program crash with an ugly traceback, you take control of the shutdown processβcleaning up resources, saving state, and providing a clean exit. This is especially important for scripts that run for long periods, interact with external systems, or handle important data. By using the signal module and a shutdown flag pattern, you ensure your programs behave reliably even when interrupted.
Interactive Views
You are currently in π All-in-One mode. Use the tabs at the top to switch to π Theory Only or π» Code Only views.
This topic shows how to catch the SIGINT signal (sent when a user presses Ctrl+C) and perform cleanup actions before your Python program exits.
π Example 1: Basic SIGINT handler that prints a message
This example creates a simple handler that prints a message when Ctrl+C is pressed, instead of immediately terminating.
import signal
import time
def handle_sigint(signal_number, frame):
print("You pressed Ctrl+C. Exiting now.")
signal.signal(signal.SIGINT, handle_sigint)
print("Press Ctrl+C to trigger the handler.")
while True:
time.sleep(1)
π€ Output: Press Ctrl+C to trigger the handler. (then after Ctrl+C) You pressed Ctrl+C. Exiting now.
π Example 2: Graceful shutdown with a flag
This example uses a running flag to stop a loop cleanly when Ctrl+C is pressed.
import signal
import time
running = True
def handle_sigint(signal_number, frame):
global running
print("Shutting down gracefully...")
running = False
signal.signal(signal.SIGINT, handle_sigint)
print("Program is running. Press Ctrl+C to stop.")
while running:
print("Working...")
time.sleep(1)
print("Cleanup complete. Goodbye.")
π€ Output: Program is running. Press Ctrl+C to stop. (then after Ctrl+C) Shutting down gracefully... Cleanup complete. Goodbye.
π Example 3: Performing cleanup before exit
This example shows how to close a file or release a resource when the user interrupts the program.
import signal
import time
file_handle = None
def handle_sigint(signal_number, frame):
print("Interrupt received. Closing file...")
if file_handle is not None:
file_handle.close()
print("File closed. Exiting.")
exit(0)
signal.signal(signal.SIGINT, handle_sigint)
file_handle = open("temp_log.txt", "w")
file_handle.write("Starting work...\n")
print("Writing to file. Press Ctrl+C to stop.")
while True:
file_handle.write("Working...\n")
time.sleep(1)
π€ Output: Writing to file. Press Ctrl+C to stop. (then after Ctrl+C) Interrupt received. Closing file... File closed. Exiting.
π Example 4: Restoring the default SIGINT behavior
This example shows how to temporarily override Ctrl+C behavior and then restore the default handler.
import signal
import time
def custom_handler(signal_number, frame):
print("Custom handler: Ctrl+C ignored this time.")
print("Setting custom handler for 5 seconds...")
signal.signal(signal.SIGINT, custom_handler)
for i in range(5):
print(f"Second {i+1} - try Ctrl+C now")
time.sleep(1)
print("Restoring default SIGINT behavior.")
signal.signal(signal.SIGINT, signal.SIG_DFL)
print("Now Ctrl+C will exit normally.")
while True:
time.sleep(1)
π€ Output: Setting custom handler for 5 seconds... (then messages for 5 seconds) Restoring default SIGINT behavior. Now Ctrl+C will exit normally.
π Example 5: Handling SIGINT in a multi-threaded program
This example demonstrates how to stop all worker threads gracefully when Ctrl+C is pressed.
import signal
import time
import threading
running = True
def worker_thread(thread_id):
while running:
print(f"Thread {thread_id} is working...")
time.sleep(1)
print(f"Thread {thread_id} stopped.")
def handle_sigint(signal_number, frame):
global running
print("Stopping all threads...")
running = False
signal.signal(signal.SIGINT, handle_sigint)
thread1 = threading.Thread(target=worker_thread, args=(1,))
thread2 = threading.Thread(target=worker_thread, args=(2,))
thread1.start()
thread2.start()
print("Main program running. Press Ctrl+C to stop all threads.")
thread1.join()
thread2.join()
print("All threads stopped. Program exiting.")
π€ Output: Main program running. Press Ctrl+C to stop all threads. (then threads print messages) (after Ctrl+C) Stopping all threads... Thread 1 stopped. Thread 2 stopped. All threads stopped. Program exiting.
Comparison Table
| Feature | Basic Handler | Graceful Shutdown | Cleanup Actions | Restore Default | Multi-threaded |
|---|---|---|---|---|---|
| Prints message on Ctrl+C | β | β | β | β | β |
| Stops loop cleanly | β | β | β | β | β |
| Releases resources | β | β | β | β | β |
| Restores default behavior | β | β | β | β | β |
| Handles multiple threads | β | β | β | β | β |