signal en Python: cierre correcto

Publicado el: 24/08/2026
Tempo de leitura: 7 minutos
Detailed view of programming code in a dark theme on a computer screen.

El módulo signal permite reaccionar a eventos asíncronos enviados al proceso, como Ctrl+C, solicitudes de terminación del sistema, timers y notificaciones de procesos hijos. Herramientas de terminal, servidores y workers usan señales para liberar recursos y detenerse sin corromper datos.

Python aplica reglas importantes: los handlers se ejecutan posteriormente en el thread principal del intérprete principal, no dentro del handler nativo de bajo nivel. Además, solo el thread principal puede instalar nuevos handlers. Estas reglas determinan cómo coordinar un shutdown seguro.

Señales frecuentes

SIGINT suele venir de Ctrl+C y por defecto genera KeyboardInterrupt. SIGTERM es la solicitud habitual de cierre enviada por systemd, Docker, Kubernetes y kill. En Unix, SIGHUP puede indicar cierre del terminal o una petición de reload definida por la aplicación.

SIGKILL y SIGSTOP no pueden capturarse, bloquearse ni ignorarse. No existe oportunidad de limpieza después de SIGKILL.

Instalar un handler mínimo

import signal

solicitar_cierre = False

def pedir_cierre(signum, frame):
    global solicitar_cierre
    solicitar_cierre = True

signal.signal(signal.SIGINT, pedir_cierre)
if hasattr(signal, "SIGTERM"):
    signal.signal(signal.SIGTERM, pedir_cierre)

El handler recibe el número y el frame actual. Mantén su trabajo mínimo: cambia una flag, escribe en un descriptor no bloqueante preparado o notifica un mecanismo de coordinación seguro.

No hagas limpieza pesada dentro del handler

Evita escribir archivos grandes, cerrar cientos de conexiones, adquirir locks o llamar servicios lentos directamente. La documentación advierte que primitivas como threading.Lock pueden producir deadlocks.

El handler debe notificar al flujo normal, que realizará la limpieza con orden y manejo de excepciones.

Loop con shutdown cooperativo

import signal
import time

cerrando = False

def pedir_cierre(signum, frame):
    global cerrando
    cerrando = True

signal.signal(signal.SIGINT, pedir_cierre)
if hasattr(signal, "SIGTERM"):
    signal.signal(signal.SIGTERM, pedir_cierre)

while not cerrando:
    procesar_siguiente_tarea()
    time.sleep(0.2)

cerrar_recursos()

El loop revisa la flag entre unidades de trabajo. Las tareas largas deberían tener puntos de cancelación. Una función C extensa puede retrasar el handler hasta devolver control al intérprete.

Threads y señales

Aunque una señal llegue a otro thread, el handler Python ejecuta en el principal. Las señales no son un mecanismo de comunicación entre threads. Usa threading.Event, colas u otras primitivas.

import signal
import threading

evento_cierre = threading.Event()

def handler(signum, frame):
    evento_cierre.set()

if hasattr(signal, "SIGTERM"):
    signal.signal(signal.SIGTERM, handler)

Instala el handler desde el thread principal. Hacerlo desde un worker produce ValueError.

No bases toda la arquitectura en KeyboardInterrupt

Una excepción generada por señal puede aparecer después de casi cualquier instrucción de bytecode. Una aplicación compleja puede quedar interrumpida mientras actualiza estado o después de adquirir un recurso.

Un handler explícito para SIGINT que marca el shutdown permite terminar o cancelar trabajo, rechazar nuevas tareas y cerrar componentes de forma predecible.

Secuencia de cierre en servidores

Un servidor puede dejar de aceptar conexiones, esperar solicitudes activas dentro de un plazo, cancelar tareas restantes, cerrar pools, vaciar logs y salir.

El artículo de socketserver en Python explica que shutdown() debe llamarse desde otro thread cuando serve_forever() está activo. El handler debería notificar ese thread sin bloquearse.

SIGTERM en containers

Los orquestadores suelen enviar SIGTERM, esperar un grace period y después enviar SIGKILL. El servicio debe manejar SIGTERM, detener nuevas entradas y terminar la limpieza antes del plazo.

Usa el formato exec del comando del container para que Python reciba la señal directamente. Un shell intermedio puede no reenviarla correctamente.

SIGPIPE y BrokenPipeError

Python ignora SIGPIPE por defecto para convertir escrituras en pipes o sockets cerrados en BrokenPipeError. No restaures la disposición predeterminada solo para ocultar la excepción: una conexión interrumpida podría cerrar todo el proceso.

errno en Python explica EPIPE y otros fallos del sistema.

Timeouts con alarm()

En Unix, signal.alarm() programa SIGALRM después de segundos enteros. Solo existe un alarm por proceso; una llamada nueva sustituye la anterior.

import signal

class TiempoAgotado(TimeoutError):
    pass

def timeout_handler(signum, frame):
    raise TiempoAgotado("La operación superó el plazo")

signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(5)
try:
    operacion_bloqueante()
finally:
    signal.alarm(0)

Es específico de Unix, del thread principal y utiliza una excepción asíncrona. Prefiere el timeout nativo de sockets, clientes HTTP, bases de datos o subprocesses cuando exista.

Timers de mayor precisión

setitimer() acepta segundos fraccionarios e intervalos repetidos. ITIMER_REAL entrega SIGALRM; ITIMER_VIRTUAL mide CPU del proceso; ITIMER_PROF combina tiempo de proceso y kernel.

Los timers por señal son globales y pueden entrar en conflicto con bibliotecas. Documenta su propiedad y restaura valores anteriores.

Descubrir señales disponibles

import signal

for numero in sorted(signal.valid_signals(), key=int):
    try:
        nombre = signal.Signals(numero).name
        descripcion = signal.strsignal(numero)
        print(numero, nombre, descripcion)
    except ValueError:
        pass

El conjunto cambia por plataforma. Usa hasattr() y valid_signals() en vez de asumir que todas las señales Unix existen en Windows o WebAssembly.

Restaurar handlers anteriores

signal.signal() devuelve el handler previo. Una biblioteca que lo cambia temporalmente debería restaurarlo.

anterior = signal.signal(signal.SIGINT, handler)
try:
    ejecutar_operacion()
finally:
    signal.signal(signal.SIGINT, anterior)

Las bibliotecas reutilizables no deben sobrescribir permanentemente la política de la aplicación principal.

Despertar loops con set_wakeup_fd()

Un loop bloqueado en select o poll puede necesitar un descriptor legible al llegar una señal. set_wakeup_fd() escribe el número como un byte en un descriptor no bloqueante para señales con handler registrado.

import os
import signal

lectura, escritura = os.pipe()
os.set_blocking(lectura, False)
os.set_blocking(escritura, False)

signal.set_wakeup_fd(escritura)
if hasattr(signal, "SIGTERM"):
    signal.signal(signal.SIGTERM, lambda signum, frame: None)

El loop debe drenar el descriptor. El buffer es finito, por lo que warn_on_full_buffer debe configurarse según si importan los bytes individuales. El siguiente artículo sobre select aplicará este patrón.

Bloquear y esperar señales en Unix

pthread_sigmask() cambia la máscara del thread. sigwait(), sigwaitinfo() y sigtimedwait() permiten que un thread dedicado acepte señales bloqueadas de forma síncrona.

La arquitectura puede simplificar servicios complejos: bloquea señales antes de iniciar workers y dedica un thread a convertirlas en eventos. Prueba la plataforma y bibliotecas reales.

No intentes recuperarte de SIGSEGV

Fallos síncronos como SIGSEGV, SIGBUS o SIGFPE causados por código nativo no pueden repararse con un handler Python normal. Al volver, probablemente se repite la instrucción inválida.

Usa faulthandler para diagnóstico. El artículo de ctypes en Python recomienda aislar bibliotecas inestables en subprocesses.

Subprocesos y grupos

Decide si la terminación alcanza solo al hijo directo o a todo el grupo. terminate() y kill() cambian entre Windows y Unix. Evita nietos huérfanos.

En Linux, pidfds reducen riesgos de reutilización de PID. No señales un PID reutilizado sin verificar identidad.

Manejo idempotente

El handler puede ejecutarse más de una vez. La primera señal inicia el cierre; las siguientes pueden reducir el plazo o ignorarse.

cerrando = False

def handler(signum, frame):
    global cerrando
    if cerrando:
        return
    cerrando = True

La limpieza también debe tolerar componentes ya cerrados.

Logs dentro del handler

El logging complejo puede adquirir locks. Registra el motivo desde el loop normal después de despertar. Mantén cualquier diagnóstico del handler al mínimo y pruébalo por plataforma.

Convertir señales en eventos normales

Una arquitectura robusta transforma la señal asíncrona en una transición ordinaria: el handler escribe al wakeup FD o cambia una flag, el selector despierta y el código normal ejecuta la máquina de estados de shutdown.

Para syscalls interrumpidas, consulta errno en Python. Para crashes y callbacks nativos, revisa ctypes en Python.

Pruebas recomendadas

Prueba SIGINT y SIGTERM durante espera, procesamiento, escritura y cierre; señales repetidas; una función C larga; registro fuera del thread principal; ausencia de señales Unix en Windows; wakeup buffer lleno; cancelación de alarm; grupos de procesos y grace period del container.

Ejecuta tests de señales en procesos hijos para no interrumpir el runner. Verifica código de salida, marcadores de limpieza y duración.

Errores comunes

Los fallos frecuentes son hacer limpieza pesada, adquirir locks, intentar recuperarse de SIGSEGV, depender solo de KeyboardInterrupt, olvidar SIGTERM en containers, asumir señales Unix en todas partes, no restaurar handlers y dejar el loop bloqueado sin wakeup FD.

Conclusión

signal convierte eventos del sistema en shutdown cooperativo, timers y notificaciones controladas. Mantén el handler mínimo y mueve el trabajo al flujo normal.

Respeta el thread principal, soporta SIGTERM y SIGINT, haz la limpieza idempotente y prueba en subprocesses. Consulta la documentación oficial de signal y el manual signal(7) de Linux.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Creative concept showing the word 'error' with cut out letters on a table with scissors and paper.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    errno en Python: errores del sistema

    Aprende errno en Python para interpretar códigos del sistema, tratar OSError, archivos, red, retries y llamadas nativas de forma portable.

    Ler mais

    Tempo de leitura: 4 minutos
    24/08/2026
    African American man using a laptop in a well-organized library setting with colorful bookshelves.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ctypes en Python: usa bibliotecas C

    Aprende ctypes en Python para cargar bibliotecas C, definir tipos y punteros, gestionar memoria, callbacks, ABI y errores nativos.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    expat en Python: parser XML de bajo nivel

    Aprende expat en Python para parsing XML de bajo nivel, handlers, namespaces, diagnósticos y protección contra amplificación y DoS.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ElementInclude en Python: XInclude seguro

    Aprende ElementInclude en Python para usar XInclude con loaders seguros, base URL, profundidad máxima y bloqueo de rutas externas.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    xmlreader en Python: controla parsers SAX

    Aprende xmlreader en Python para configurar parsers SAX, InputSource, parsing incremental, atributos, locators y seguridad XML.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    saxutils en Python: utilidades para XML

    Aprende saxutils en Python para escapar XML, preparar atributos, generar documentos, crear filtros SAX y evitar errores de contexto.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026