fix: Windows build support with platform-specific process/signal handling

Separate platform-dependent code into build-tagged files:
- process_unix.go / process_windows.go: isProcessRunning() implementation
- signals_unix.go / signals_windows.go: daemon signal handling (Windows lacks SIGUSR1)

Windows implementation uses windows.OpenProcess with PROCESS_QUERY_LIMITED_INFORMATION
and checks exit code against STILL_ACTIVE (259).

Original-PR: #447
Co-Authored-By: Johann Dirry <johann.dirry@microsea.at>
This commit is contained in:
Johann Dirry
2026-01-13 20:56:16 -08:00
committed by Steve Yegge
parent 60da5de104
commit 5d96243414
6 changed files with 90 additions and 21 deletions

View File

@@ -6,7 +6,6 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/gofrs/flock"
@@ -451,19 +450,3 @@ func verifyShutdown(t *tmux.Tmux, townRoot string) []string {
return respawned
}
// isProcessRunning checks if a process with the given PID exists.
func isProcessRunning(pid int) bool {
if pid <= 0 {
return false // Invalid PID
}
err := syscall.Kill(pid, 0)
if err == nil {
return true
}
// EPERM means process exists but we don't have permission to signal it
if err == syscall.EPERM {
return true
}
return false
}

View File

@@ -0,0 +1,20 @@
//go:build !windows
package cmd
import "syscall"
// isProcessRunning checks if a process with the given PID exists.
func isProcessRunning(pid int) bool {
if pid <= 0 {
return false
}
err := syscall.Kill(pid, 0)
if err == nil {
return true
}
// EPERM means process exists but we don't have permission to signal it.
return err == syscall.EPERM
}

View File

@@ -0,0 +1,27 @@
//go:build windows
package cmd
import "golang.org/x/sys/windows"
const processStillActive = 259
// isProcessRunning checks if a process with the given PID exists.
func isProcessRunning(pid int) bool {
if pid <= 0 {
return false
}
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
return false
}
defer windows.CloseHandle(handle)
var exitCode uint32
if err := windows.GetExitCodeProcess(handle, &exitCode); err != nil {
return false
}
return exitCode == processStillActive
}

View File

@@ -127,7 +127,7 @@ func (d *Daemon) Run() error {
// Handle signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
signal.Notify(sigChan, daemonSignals()...)
// Fixed recovery-focused heartbeat (no activity-based backoff)
// Normal wake is handled by feed subscription (bd activity --follow)
@@ -162,9 +162,9 @@ func (d *Daemon) Run() error {
return d.shutdown(state)
case sig := <-sigChan:
if sig == syscall.SIGUSR1 {
// SIGUSR1: immediate lifecycle processing (from gt handoff)
d.logger.Println("Received SIGUSR1, processing lifecycle requests immediately")
if isLifecycleSignal(sig) {
// Lifecycle signal: immediate lifecycle processing (from gt handoff)
d.logger.Println("Received lifecycle signal, processing lifecycle requests immediately")
d.processLifecycleRequests()
} else {
d.logger.Printf("Received signal %v, shutting down", sig)

View File

@@ -0,0 +1,20 @@
//go:build !windows
package daemon
import (
"os"
"syscall"
)
func daemonSignals() []os.Signal {
return []os.Signal{
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGUSR1,
}
}
func isLifecycleSignal(sig os.Signal) bool {
return sig == syscall.SIGUSR1
}

View File

@@ -0,0 +1,19 @@
//go:build windows
package daemon
import (
"os"
"syscall"
)
func daemonSignals() []os.Signal {
return []os.Signal{
syscall.SIGINT,
syscall.SIGTERM,
}
}
func isLifecycleSignal(sig os.Signal) bool {
return false
}