signal no Python: encerre processos bem

Publicado em: 24/08/2026
Tempo de leitura: 7 minutos
View of multiple railway tracks with signals and buildings in an urban setting during daytime.

O módulo signal permite reagir a eventos assíncronos enviados ao processo, como interrupção por Ctrl+C, pedido de encerramento do sistema, timers e notificações relacionadas a processos filhos. Em aplicações de linha de comando, servidores e workers, sinais são essenciais para liberar recursos e encerrar sem corromper dados.

O comportamento do Python possui regras importantes: handlers Python são executados na thread principal do interpretador principal, não dentro do handler nativo de baixo nível. Além disso, somente a thread principal pode registrar novos handlers. Isso influencia como serviços multithread devem coordenar o shutdown.

Sinais mais usados

SIGINT normalmente vem de Ctrl+C e, por padrão, gera KeyboardInterrupt. SIGTERM é o pedido comum de encerramento usado por systemd, Docker, Kubernetes e comandos como kill. Em Unix, SIGHUP pode indicar fechamento do terminal ou pedido de reload, dependendo da aplicação.

SIGKILL e SIGSTOP não podem ser capturados, bloqueados ou ignorados. Por isso, um programa não consegue executar limpeza quando recebe SIGKILL.

Registrar um handler

import signal

encerrar = False

def ao_encerrar(signum, frame):
    global encerrar
    encerrar = True

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

O handler recebe o número do sinal e o frame atual. Para shutdown cooperativo, mantenha-o curto: altere uma flag, escreva em um descritor não bloqueante ou sinalize uma estrutura preparada para isso.

Não faça trabalho pesado no handler

Evite salvar arquivos, fechar centenas de conexões, adquirir locks ou executar chamadas lentas diretamente no handler. A documentação alerta que primitivas como threading.Lock podem causar deadlock nesse contexto.

O handler deve notificar o fluxo normal da aplicação. A limpeza ocorre depois, em código comum, onde exceções e ordem das etapas podem ser controladas.

Shutdown cooperativo em um loop

import signal
import time

parar = False

def pedir_parada(signum, frame):
    global parar
    parar = True

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

while not parar:
    executar_proxima_tarefa()
    time.sleep(0.2)

fechar_recursos()

O loop verifica a flag entre unidades de trabalho. Tarefas longas devem oferecer pontos de cancelamento. Se uma função C roda por muito tempo sem devolver controle ao interpretador, o handler Python pode atrasar até a função terminar.

Threads e sinais

Mesmo quando um sinal chega a outra thread, o handler Python executa na thread principal. Sinais não são um mecanismo apropriado de comunicação entre threads. Use threading.Event, filas ou outras primitivas.

import signal
import threading

parada = threading.Event()

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

signal.signal(signal.SIGTERM, handler)

Embora Event.set() seja frequentemente usado nesse padrão, mantenha handlers mínimos e teste a aplicação real. Não tente registrar signal.signal() dentro de um worker; isso gera ValueError.

SIGINT sem depender de KeyboardInterrupt

Aplicações complexas podem preferir um handler explícito em vez de capturar KeyboardInterrupt. Uma exceção gerada por sinal pode aparecer entre instruções e interromper código em um estado inesperado.

Um handler que apenas sinaliza o loop permite concluir a operação atual, impedir novas tarefas e executar o shutdown em ordem previsível.

Encerramento de servidores

Em servidores, a sequência pode ser: parar de aceitar conexões, aguardar requisições em andamento dentro de um prazo, cancelar tarefas restantes, fechar pools, flush de logs e sair.

O guia de socketserver no Python explica que o método shutdown() deve ser chamado por outra thread quando serve_forever() está ativo. O handler pode notificar essa thread, mas não deve bloquear esperando o servidor.

Containers e SIGTERM

Orquestradores normalmente enviam SIGTERM e aguardam um período antes de usar SIGKILL. O processo Python deve registrar SIGTERM, interromper novas entradas e concluir dentro do grace period.

Use o formato exec no comando do container para que o processo Python receba o sinal diretamente. Wrappers de shell mal configurados podem reter ou não encaminhar o sinal.

SIGPIPE e BrokenPipeError

Python ignora SIGPIPE por padrão para transformar escrita em pipe fechado em BrokenPipeError. Não restaure SIGPIPE para o comportamento padrão apenas para esconder a exceção, pois conexões de rede interrompidas poderiam encerrar o processo inesperadamente.

O artigo de errno no Python explica EPIPE e outros códigos do sistema.

Timers com alarm()

Em Unix, signal.alarm() agenda SIGALRM em segundos inteiros. Existe apenas um alarme por processo; um novo substitui o anterior.

import signal

class TempoEsgotado(TimeoutError):
    pass

def timeout(signum, frame):
    raise TempoEsgotado("Operação excedeu o prazo")

signal.signal(signal.SIGALRM, timeout)
signal.alarm(5)
try:
    operacao_bloqueante()
finally:
    signal.alarm(0)

Esse padrão é específico de Unix e só funciona na thread principal. Ele também usa exceção assíncrona, o que pode interromper código em pontos delicados. Prefira timeouts nativos da API quando existirem.

Timers de maior precisão

setitimer() aceita segundos fracionários e pode ser periódico. ITIMER_REAL gera SIGALRM; ITIMER_VIRTUAL mede tempo de CPU do processo; ITIMER_PROF combina tempo do processo e do kernel.

Timers por sinal são globais ao processo e podem conflitar com bibliotecas. Documente seu uso e restaure o estado anterior quando possível.

Consultar sinais disponíveis

import signal

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

Nem todos os sinais existem em todas as plataformas. Use hasattr(signal, "SIGTERM") e valid_signals() em código portátil.

Restaurar handlers

signal.signal() retorna o handler anterior. Bibliotecas devem restaurá-lo quando terminarem sua responsabilidade.

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

Não sobrescreva silenciosamente um handler instalado pela aplicação principal.

set_wakeup_fd()

Loops baseados em select ou poll podem ficar bloqueados enquanto uma flag de shutdown foi definida. set_wakeup_fd() escreve um byte em um descritor não bloqueante quando um sinal com handler registrado chega, acordando o loop.

import os
import signal

leitura, escrita = os.pipe()
os.set_blocking(leitura, False)
os.set_blocking(escrita, False)

signal.set_wakeup_fd(escrita)
signal.signal(signal.SIGTERM, lambda signum, frame: None)

O loop deve drenar os bytes. O descritor possui buffer limitado; escolha corretamente warn_on_full_buffer. O próximo conjunto sobre select mostrará como integrar esse padrão.

Bloquear e esperar sinais em Unix

pthread_sigmask() pode bloquear sinais em uma thread. sigwait(), sigwaitinfo() e sigtimedwait() permitem que uma thread espere sinais de um conjunto bloqueado de forma síncrona.

Essa arquitetura pode simplificar aplicações Unix complexas: bloqueie determinados sinais antes de iniciar workers e dedique uma thread a esperá-los. Ainda assim, confirme o comportamento de bibliotecas e da plataforma.

Não capture SIGSEGV para recuperar

Erros síncronos como SIGSEGV, SIGFPE ou SIGBUS causados por código C não podem ser corrigidos com um handler Python comum. Ao retornar, a instrução inválida provavelmente executará novamente.

Use faulthandler para obter diagnóstico de crashes nativos. O conjunto sobre ctypes no Python recomenda isolar bibliotecas instáveis em subprocessos.

Subprocessos e grupos

Ao controlar subprocessos, decida se o sinal deve atingir apenas o processo filho ou todo o grupo. terminate() e kill() possuem diferenças entre Windows e Unix. Evite deixar netos órfãos.

Não envie sinais usando PID reutilizado sem confirmar a identidade do processo. Em Linux moderno, pidfds podem reduzir esse risco.

Tratamento idempotente

O handler pode ser chamado mais de uma vez. A primeira chamada inicia o shutdown; uma segunda pode apenas registrar urgência ou reduzir o prazo.

encerrando = False

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

A limpeza também deve tolerar etapas já concluídas.

Logs dentro do handler

Evite logging complexo dentro do handler, pois ele pode adquirir locks. Registre a decisão no loop principal. Quando um diagnóstico mínimo é indispensável, use mecanismos desenhados para esse contexto e teste em cada plataforma.

Integração com eventos

Uma arquitetura robusta converte o sinal em um evento normal do sistema. O handler escreve no wakeup FD ou define uma flag, o seletor acorda, e o loop executa a máquina de estados de shutdown.

Para entender falhas de syscalls interrompidas, consulte novamente errno no Python. Para configuração de fontes e loops assíncronos orientados a descritores, o conjunto seguinte abordará select.

Testes recomendados

Teste SIGINT e SIGTERM durante espera, processamento, escrita e shutdown; dois sinais em sequência; tarefa C longa; handler registrado fora da thread principal; ausência do sinal em Windows; wakeup FD cheio; timer cancelado; subprocessos e grace period do container.

Execute testes de sinais em subprocesso para não interferir no runner principal. Verifique código de saída, recursos fechados e tempo de encerramento.

Erros comuns

Os erros mais frequentes são fazer limpeza pesada no handler, usar locks, capturar SIGSEGV como recuperação, depender apenas de KeyboardInterrupt, esquecer SIGTERM em containers, presumir sinais Unix no Windows, não restaurar handlers e deixar o loop bloqueado sem wakeup FD.

Conclusão

signal permite transformar eventos do sistema em shutdown cooperativo, timers e notificações controladas. O handler deve ser mínimo e transferir a decisão para o fluxo normal da aplicação.

Respeite a thread principal, trate SIGTERM e SIGINT, mantenha a limpeza idempotente e teste o comportamento real em subprocessos. Consulte a documentação oficial de signal e o manual signal(7) do Linux.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Detailed view of programming code in a dark theme on a computer screen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    errno no Python: entenda erros do sistema

    Aprenda errno no Python para interpretar códigos do sistema, tratar OSError, rede, arquivos, retries e chamadas nativas de forma portátil.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ctypes no Python: use bibliotecas C

    Aprenda ctypes no Python para carregar bibliotecas C, definir tipos e ponteiros, gerenciar memória, callbacks, ABI e erros com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    expat no Python: parser XML de baixo nível

    Aprenda expat no Python para parsing XML de baixo nível, handlers, namespaces, erros e proteções contra amplificação e DoS.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ElementInclude no Python: use XInclude

    Aprenda ElementInclude no Python para usar XInclude com loaders seguros, base URL, profundidade máxima e bloqueio de caminhos externos.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    xmlreader no Python: controle parsers SAX

    Aprenda xmlreader no Python para configurar parsers SAX, InputSource, parsing incremental, atributos, locators e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    saxutils no Python: utilitários para XML

    Aprenda saxutils no Python para escapar XML, preparar atributos, gerar documentos, criar filtros SAX e evitar erros de contexto.

    Ler mais

    Tempo de leitura: 6 minutos
    23/08/2026