File-New-Project/EarTrumpet · error · ZombieProcessException

Process is a zombie: {processId}

Error message

Process is a zombie: {processId}

What it means

ZombieProcessException is both the exception type and the detector. ThrowIfZombie opens a process handle and calls Kernel32.WaitForSingleObject(handle, 0): a non-WAIT_TIMEOUT return means the handle is already signaled, i.e. the process has terminated. It then throws. Both DesktopAppInfo and ModernAppInfo call this right after a successful OpenProcess to catch the race where the process died between session creation and info construction.

Source

Thrown at EarTrumpet/DataModel/AppInformation/Internal/ZombieProcessException.cs:14

using EarTrumpet.Interop;
using System;

namespace EarTrumpet.DataModel.AppInformation.Internal
{
    public class ZombieProcessException : Exception
    {
        public ZombieProcessException(int processId) : base($"Process is a zombie: {processId}") { }

        public static void ThrowIfZombie(int processId, IntPtr handle)
        {
            if (Kernel32.WaitForSingleObject(handle, 0) != Kernel32.WAIT_TIMEOUT)
            {
                throw new ZombieProcessException(processId);
            }
        }
    }
}

View on GitHub (pinned to aa894e51c2)

Solutions

  1. Catch ZombieProcessException wherever you construct DesktopAppInfo / ModernAppInfo and treat the session as expired.
  2. If you call ThrowIfZombie yourself, ensure the caller can tolerate the throw and clean up the handle (it is in a try/finally that closes the handle).
  3. Do not widen the WaitForSingleObject timeout to mask the issue; instead discard the dead session.
  4. Log the PID for diagnostics, then suppress the session from the UI.

Example fix

// before
using (var h = Kernel32.OpenProcess(flags, false, pid))
{
    ZombieProcessException.ThrowIfZombie(pid, h);
    ReadInfo(h);
}

// after
try { using (var h = Kernel32.OpenProcess(flags, false, pid)) { ZombieProcessException.ThrowIfZombie(pid, h); ReadInfo(h); } }
catch (ZombieProcessException) { /* process died mid-read */ }
Defensive patterns

Strategy: try-catch

Validate before calling

// if you call ThrowIfZombie yourself, check first to avoid the throw
bool IsSignaled(IntPtr handle) => Kernel32.WaitForSingleObject(handle, 0) != Kernel32.WAIT_TIMEOUT;

Try / catch

try { ZombieProcessException.ThrowIfZombie(pid, handle); /* read info */ } catch (ZombieProcessException) { /* process already terminated; abort info read */ }

Prevention

When it happens

Trigger: A process handle was opened successfully, but by the time ThrowIfZombie runs the process has exited, so WaitForSingleObject returns WAIT_OBJECT_0 (signaled) instead of WAIT_TIMEOUT, and the exception is thrown.

Common situations: Process exits during the window between OpenProcess and the first info query; teardown race when a device/app is closing; rapid create/destroy of audio processes (notifications, system sounds).

Related errors


AI-assisted analysis of File-New-Project/EarTrumpet@aa894e51c2 (2026-08-13). Data as JSON: /api/errors/22d503d8d54648d8. Report an issue: GitHub.