.NET 11 RC 1 permite enviar SIGTERM para um processo filho sem P/Invoke
Signal, WaitForExitStatus e ProcessExitStatus chegam diretamente em System.Diagnostics.Process no .NET 11 RC 1, então encerrar um processo filho de forma limpa não exige mais um P/Invoke para kill nem um desvio por SafeProcessHandle.
O .NET 11 Release Candidate 1 saiu em 2026-09-08 com licença go-live. A seção de bibliotecas começa com algo que irrita qualquer pessoa que já tenha supervisionado um processo filho a partir do C#: System.Diagnostics.Process agora envia sinais POSIX e informa como o processo realmente morreu, sem descer até SafeProcessHandle (dotnet/runtime#131165).
Kill() sempre foi um SIGKILL
Process.Kill() no Unix é SIGKILL. O filho não tem chance de esvaziar um buffer, fechar um socket ou rodar um handler de encerramento. Isso serve para um processo descontrolado e é errado para quase todo o resto: um servidor de desenvolvimento, uma transcodificação com ffmpeg, um contêiner Postgres, um arcabouço de testes que você quer parar de forma limpa.
A alternativa era um P/Invoke:
[LibraryImport("libc", SetLastError = true)]
private static partial int kill(int pid, int sig);
// 15 on Linux and macOS, but you had to know that
kill(process.Id, 15);
São três linhas de suposições sobre a plataforma, um número de sinal fixo no código e nenhuma forma de saber se o sinal foi realmente entregue.
A superfície de API do RC 1
O RC 1 adiciona quatro membros a Process:
public bool Signal(PosixSignal signal);
public ProcessExitStatus WaitForExitStatus();
public bool TryWaitForExitStatus(TimeSpan timeout, out ProcessExitStatus? exitStatus);
public Task<ProcessExitStatus> WaitForExitStatusAsync(
CancellationToken cancellationToken = default);
Isso já basta para escrever o padrão de escalonamento do jeito certo:
using System.Diagnostics;
using System.Runtime.InteropServices;
using Process worker = Process.Start("./worker")!;
if (!worker.Signal(PosixSignal.SIGTERM))
{
// false means the process is already gone, not that the call failed
return;
}
if (!worker.TryWaitForExitStatus(TimeSpan.FromSeconds(10), out ProcessExitStatus? status))
{
worker.Signal(PosixSignal.SIGKILL);
status = worker.WaitForExitStatus();
}
Console.WriteLine($"exit={status.ExitCode} signal={status.Signal}");
ProcessExitStatus acaba com o chute no código de saída
ProcessExitStatus é uma classe pequena marcada como sealed com três propriedades: ExitCode, Canceled e um PosixSignal? Signal anulável. O sinal anulável é a parte útil. Até agora você inferia a morte por sinal a partir da convenção 128 + signal number embutida no código de saída, que colide silenciosamente com qualquer processo que saia legitimamente com 143. Agora status.Signal is PosixSignal.SIGTERM responde a pergunta direto, e Canceled informa se a espera terminou pelo seu tempo limite ou pelo seu CancellationToken em vez de pelo filho.
É o mesmo ProcessExitStatus retornado por Process.RunAndCaptureText e afins, que eu cobri quando as APIs de captura sem deadlock chegaram no Preview 4.
Onde isso não funciona
Três limites que vale conhecer antes de escrever código supervisor multiplataforma:
Signalé anotado com[UnsupportedOSPlatform("ios")]e[UnsupportedOSPlatform("tvos")]. Mac Catalyst é suportado.- No Windows apenas
PosixSignal.SIGKILLé aceito, e ele é mapeado paraTerminateProcess. Qualquer outro sinal lançaPlatformNotSupportedException, então um caminho primeiro limpo e depois forçado precisa de um desvio comOperatingSystem.IsWindows(). - No Unix,
WaitForExitStatussó funciona para um filho do processo atual. Esperar por umProcessao qual você se anexou pelo PID lança uma exceção.
A lista completa está nas notas de versão das bibliotecas do RC 1. Com a licença go-live junto, este é o primeiro RC que você pode colocar por trás de uma supervisão de processos em produção.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.