# 45. Handling Signals in Python

Here are 10 Python code snippets demonstrating how to handle signals using the `signal` module. The `signal` module allows you to capture and respond to various system signals, such as termination signals, alarms, etc.

***

**1. Basic Signal Handling**

Handling the `SIGINT` (Ctrl+C) signal.

```python
import signal
import time

def signal_handler(sig, frame):
    print('SIGINT received, exiting...')
    exit(0)

# Register signal handler for SIGINT
signal.signal(signal.SIGINT, signal_handler)

print("Press Ctrl+C to exit")
while True:
    time.sleep(1)
```

This example captures `SIGINT` and gracefully exits the program when Ctrl+C is pressed.

***

**2. Handling Multiple Signals**

Handling both `SIGINT` and `SIGTERM` signals.

```python
import signal
import time

def signal_handler(sig, frame):
    if sig == signal.SIGINT:
        print('Received SIGINT')
    elif sig == signal.SIGTERM:
        print('Received SIGTERM')
    exit(0)

# Register signal handlers
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

print("Waiting for signals (Ctrl+C or kill -TERM pid)")
while True:
    time.sleep(1)
```

In this example, both `SIGINT` and `SIGTERM` are handled by the same handler.

***

**3. Setting a Timeout with SIGALRM**

Using `SIGALRM` to set a timeout for a task.

```python
import signal
import time

def timeout_handler(sig, frame):
    print('Timeout reached!')
    exit(1)

# Register signal handler for SIGALRM
signal.signal(signal.SIGALRM, timeout_handler)

# Set an alarm for 5 seconds
signal.alarm(5)

print("Waiting for timeout")
time.sleep(10)
```

This example sets an alarm that triggers `SIGALRM` after 5 seconds, calling `timeout_handler`.

***

**4. Ignoring a Signal**

Using `signal.SIG_IGN` to ignore a signal.

```python
import signal
import time

def signal_handler(sig, frame):
    print("This won't be printed because SIGINT is ignored.")

# Ignore SIGINT signal
signal.signal(signal.SIGINT, signal.SIG_IGN)

print("Press Ctrl+C. Nothing will happen.")
while True:
    time.sleep(1)
```

This code ignores the `SIGINT` signal (Ctrl+C) and does nothing when it is received.

***

**5. Handling Signals with a Custom Function**

Define a custom function for handling signals.

```python
import signal
import time

def custom_handler(signal, frame):
    print(f"Signal {signal} received!")
    exit(0)

# Handle SIGQUIT with a custom function
signal.signal(signal.SIGQUIT, custom_handler)

print("Press Ctrl+\\ to send SIGQUIT")
time.sleep(10)
```

This example defines a custom function `custom_handler` to respond to the `SIGQUIT` signal.

***

**6. Catching Signals in a Child Process**

Capture signals in a child process spawned by `os.fork()`.

```python
import os
import signal
import time

def child_handler(sig, frame):
    print("Child process received signal")
    exit(0)

if os.fork() == 0:
    # Child process
    signal.signal(signal.SIGUSR1, child_handler)
    print("Child waiting for signal")
    time.sleep(10)
else:
    # Parent process
    time.sleep(1)
    os.kill(os.getpid(), signal.SIGUSR1)
```

This snippet demonstrates how a child process can catch signals using the `signal` module.

***

**7. Using signal.sigtimedwait for Handling Signals**

Blocking until a signal is received.

```python
import signal
import time

def signal_handler(sig, frame):
    print(f"Signal {sig} received!")
    exit(0)

# Register signal handler
signal.signal(signal.SIGALRM, signal_handler)

# Set an alarm
signal.alarm(5)

# Wait for the signal
signal.sigtimedwait([signal.SIGALRM], timeout=10)
print("Waiting completed.")
```

In this example, the process waits for `SIGALRM` with a timeout using `sigtimedwait`.

***

**8. Handling Signals in Threads**

Handling signals inside a thread.

```python
import signal
import time
import threading

def signal_handler(sig, frame):
    print("Signal received in main thread!")
    exit(0)

def thread_function():
    time.sleep(5)
    print("Thread finished")

# Register signal handler
signal.signal(signal.SIGINT, signal_handler)

# Start a background thread
thread = threading.Thread(target=thread_function)
thread.start()

# Main thread waits for signal
print("Waiting for signal (Ctrl+C)...")
time.sleep(10)
```

This demonstrates how to handle signals in the main thread while running a background thread.

***

**9. Using signal.pause**

Pause the program until a signal is received.

```python
import signal

def signal_handler(sig, frame):
    print("Signal received, exiting.")
    exit(0)

# Register signal handler
signal.signal(signal.SIGINT, signal_handler)

# Wait for signals indefinitely
print("Waiting for signal...")
signal.pause()
```

The `signal.pause()` function causes the program to wait indefinitely for any signal to be received.

***

**10. Handling Signals in a Daemon**

Handling signals in a daemon process.

```python
import signal
import os
import time

def signal_handler(sig, frame):
    print(f"Daemon received signal {sig}")
    exit(0)

# Register signal handler
signal.signal(signal.SIGTERM, signal_handler)

# Create a daemon process
pid = os.fork()
if pid == 0:
    # Child (daemon) process
    print("Daemon running...")
    time.sleep(60)  # Simulate daemon work
else:
    # Parent process
    print("Parent sending SIGTERM")
    os.kill(pid, signal.SIGTERM)
    time.sleep(2)
```

In this example, the parent process sends a `SIGTERM` signal to the daemon (child process), which handles it with a signal handler.

***

These examples demonstrate how you can use Python's `signal` module to handle various types of signals in different scenarios, making your program more flexible and responsive to external events.
