fix(ext/process): treat Windows kill on exited process as NotFound (#34889)
## Problem
On Windows, `kill("SIGTERM")` randomly threw `PermissionError`,
depending on the timing/availability of resources. When this happened
via an `AbortSignal` it produced an uncaught error the user had no way
to handle (the Windows node-compat report would come back randomly empty
because of it).
## Root cause
On Windows, `kill()` opens the target with
`OpenProcess(PROCESS_TERMINATE, ...)` (`ext/process/lib.rs`). If the
child has already exited — or its pid was reused — `OpenProcess` can
fail with `ERROR_ACCESS_DENIED (5)`, which was mapped via
`Error::from_raw_os_error(5)` → `ErrorKind::PermissionDenied` → JS
`PermissionError`. That is the "randomly throws" race. Unix returns
`ESRCH` → `NotFound` for the same situation.
## Fix
- `ext/process/lib.rs`: when `OpenProcess(PROCESS_TERMINATE, ...)` fails
with `ERROR_ACCESS_DENIED` **and** the process is no longer alive, map
it to `NotFound` (mirroring Unix `ESRCH`) instead of `PermissionError`.
A process that is genuinely still alive but that the caller lacks rights
to terminate still yields `PermissionError`. Liveness is checked with a
small `process_is_alive()` helper
(`OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` + `GetExitCodeProcess`
== `STILL_ACTIVE`), which also de-duplicates the existing signal-0
health-check branch.
- `ext/node/polyfills/internal/child_process.ts`: the `kill()` "already
closed" handling now treats `Deno.errors.NotFound` as benign (alongside
`TypeError` and `PermissionDenied`), so a dead-process kill returns
`false` instead of throwing — on both platforms.
The abort-signal *symptom* was already masked by try/catch in the
`Deno.Command` abort handlers (#27112, #34069); this change addresses
the underlying root cause so an explicit `.kill()` during the exit
window no longer surfaces a misleading `PermissionError`.
## Tests
Added two `tests/unit_node/child_process_test.ts` cases:
- an `AbortSignal` aborts the child and emits an `AbortError` (Node
semantics) without an uncaught error;
- `kill()` on an already-exited process returns `false` and does not
throw.
Verified the Windows-only Rust compiles & is clippy-clean via `cargo
{check,clippy} -p deno_process --target x86_64-pc-windows-msvc`.
Closes #28882
Closes denoland/divybot#486
Co-authored-by: divybot <divybot@users.noreply.github.com>
Co-authored-by: Divy Srivastava <me@littledivy.com>