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 ❌ ❌ ❌ ❌ βœ