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

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026