Start Debugging

.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:

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.

< Voltar