Skip to content

Commit 2d630aa

Browse files
fix: Windows build failed to compile — std.posix.kill on a non-POSIX pid
TTSEngine.stop() called std.posix.kill(pid, ...) unconditionally, but std.process.Child.Id is std.posix.pid_t (an integer) on POSIX and std.os.windows.HANDLE (*anyopaque) on Windows — confirmed directly against the actual installed Zig std lib's process/Child.zig. This was the first time the Windows build ever got far enough to actually compile this file (it always died earlier, first on a wrong MSYS2 library search path, then after that fix on gtk-4/sqlite3 linking — both already fixed separately). Windows now calls std.os.windows.ntdll.NtTerminateProcess directly rather than the higher-level Child.kill() — verified against Threaded.zig's own implementation that Child.kill() blocks until exit and reaps/finalizes the same Child struct, which would race with the spawning thread's own concurrent, already-blocking child.wait(e.io) call in playSequential/ playChapter below. Calling the same raw primitive Child.kill() uses internally, without its blocking wait-and-cleanup, preserves the exact "just signal, let the spawning thread's own wait() reap it" semantics the old POSIX-only code already relied on. Verified: `zig build` and `zig build test` (10/10 steps, 5/5 tests) still pass natively on macOS — this change is isolated to the Windows branch of a switch, the POSIX path is untouched. Could not verify the Windows compile itself locally (cross-compiling still fails at the gtk-4/sqlite3 linking stage on this Mac, same as before this fix, since there's no real MSYS2 install here — that failure occurs before per-file semantic analysis is reached, so it can't confirm or deny this specific fix either way). The real test is the next release-latest.yml Windows CI run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent fb2e136 commit 2d630aa

1 file changed

Lines changed: 18 additions & 1 deletion

File tree

src/services/tts_engine.zig

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,24 @@ pub const TTSEngine = struct {
6262
defer self.mutex.unlock(self.io);
6363
if (self.current_process) |p| {
6464
if (p.id) |pid| {
65-
if (pid != 0) std.posix.kill(pid, std.posix.SIG.TERM) catch {};
65+
// std.process.Child.Id is std.posix.pid_t (an integer) on
66+
// POSIX but std.os.windows.HANDLE (*anyopaque) on Windows —
67+
// std.posix.kill only exists/applies to the former. Using
68+
// the higher-level Child.kill() instead isn't safe here: it
69+
// blocks until the process exits and reaps/finalizes the
70+
// same Child struct that the spawning thread is
71+
// concurrently blocked inside child.wait(e.io) on (see
72+
// playSequential/playChapter below) — two threads racing to
73+
// finalize the same Child. So on Windows this calls the
74+
// same raw termination primitive Child.kill() itself uses
75+
// internally (std.os.windows.ntdll.NtTerminateProcess),
76+
// without its blocking wait-and-cleanup, matching the old
77+
// POSIX code's "just signal, let the spawning thread's own
78+
// wait() reap it" semantics exactly.
79+
switch (builtin.os.tag) {
80+
.windows => _ = std.os.windows.ntdll.NtTerminateProcess(pid, @enumFromInt(1)),
81+
else => if (pid != 0) std.posix.kill(pid, std.posix.SIG.TERM) catch {},
82+
}
6683
}
6784
}
6885
}

0 commit comments

Comments
 (0)