Sinais & Controle de Processos
Lide com sinais do sistema operacional e gerencie processos filhos em serviços e CLIs Rust de longa execução.
Receita
use tokio::signal;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tokio::select! {
_ = signal::ctrl_c() => {
eprintln!("desligando graciosamente");
}
res = run_server() => res?,
}
Ok(())
}Quando usar isso: Servidores, workers e CLIs que precisam ser desligados de forma limpa no Ctrl+C ou SIGTERM.
Exemplo de Trabalho
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::signal;
use tokio::time::{sleep, Duration};
struct Supervisor {
shutdown: Arc<AtomicBool>,
child: Option<Child>,
}
impl Supervisor {
fn spawn_worker(&mut self) -> anyhow::Result<()> {
let child = Command::new("worker-bin")
.stdout(Stdio::piped())
.spawn()?;
self.child = Some(child);
Ok(())
}
fn stop_worker(&mut self) {
if let Some(mut child) = self.child.take() {
let _ = child.kill();
let _ = child.wait();
}
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let shutdown = Arc::new(AtomicBool::new(false));
let mut sup = Supervisor { shutdown: shutdown.clone(), child: None };
sup.spawn_worker()?;
tokio::select! {
_ = signal::ctrl_c() => {
shutdown.store(true, Ordering::SeqCst);
sup.stop_worker();
}
_ = async {
while !shutdown.load(Ordering::SeqCst) {
sleep(Duration::from_secs(1)).await;
}
} => {}
}
Ok(())
}O que isso demonstra:
ctrl_c()integra-se com o runtime assíncrono do TokioCommand::spawnlança processos filhoskill+waitprevine processos zumbis- Flag atômica coordena o desligamento entre tarefas
Mergulho Profundo
Sinais Unix
| Sinal | Uso Típico |
|---|---|
| SIGINT | Ctrl+C do terminal |
| SIGTERM | Parada do orquestrador de contêineres |
| SIGHUP | Recarregar configuração (daemons) |
No Unix, use tokio::signal::unix::signal para um controle mais refinado.
Grupos de Processos
use std::os::unix::process::CommandExt;
unsafe {
cmd.pre_exec(|| {
libc::setpgid(0, 0);
Ok(())
});
}Coloque os filhos em seu próprio grupo de processos para sinalizá-los como uma unidade.
Armadilhas
- Manipuladores de sinal assíncronos inseguros - Apenas operações assíncronas seguras em manipuladores brutos. Correção: Defina uma flag; manipule no thread principal.
- Filhos zumbis - Esquecer
waitvaza PIDs. Correção: Sempre colete os filhos após eles saírem. - Kill vs parada graciosa -
killpode corromper o estado do filho. Correção: Envie SIGTERM primeiro; escale para SIGKILL após o timeout. - Diferenças de sinal no Windows - Sem sinais POSIX. Correção: Use
tokio::signal::ctrl_cectrl_breakno Windows. - Desligamento duplo - Ctrl+C durante a limpeza causa pânico. Correção: Use um guard
AtomicBoole lógica de desligamento idempotente.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
crate signal-hook | Apps síncronos sem Tokio | Já está no runtime Tokio |
| systemd notify | Protocolo de prontidão de serviço | Ferramentas CLI simples |
| Supervisor de processos (systemd, k8s) | Gerenciamento de frota de produção | Scripts de desenvolvimento local |
FAQs
Padrão de desligamento gracioso?
Pare de aceitar trabalho, esvazie requisições em andamento, descarregue logs e então saia com 0.
Quanto tempo esperar antes de SIGKILL?
30 segundos é comum para servidores HTTP. Documente seu timeout nos runbooks de operações.
Recarregar configuração no SIGHUP?
Releia o arquivo de configuração e troque Arc<Config> atomicamente. Não reinicie ouvintes cegamente.
Streaming de stdout do filho?
Leia child.stdout em um thread dedicado ou tarefa assíncrona tokio::process.
Propagação do código de saída?
Mapeie o código de saída do filho para o pai: std::process::exit(status.code().unwrap_or(1)).
Processos órfãos?
Use grupos de processos ou prctl no Linux para matar filhos quando o pai morrer.
Testando o tratamento de sinais?
Envie sinais em testes de integração com nix::sys::signal::kill no filho do processo de teste.
Arquivos PID?
Escreva o PID em /var/run/app.pid para scripts de init. Remova no desligamento.
Daemonizando?
Prefira systemd ou entrypoints de contêiner em vez de daemons clássicos de double-fork.
Limites de recursos?
Defina ulimit via prlimit no Linux antes de iniciar filhos de worker pesados.
Relacionados
- Noções Básicas de Sistemas - Fundamentos de
Command - libc & Syscalls - APIs de sinal brutas
- Código de Sistemas Multiplataforma - Windows vs Unix
- Construindo Bindings Sólidos - Padrões de desligamento seguros
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.