diff --git a/process/interrupt_unix.go b/process/interrupt_unix.go new file mode 100644 index 0000000..69ad08b --- /dev/null +++ b/process/interrupt_unix.go @@ -0,0 +1,13 @@ +//go:build !windows + +package process + +import "os" + +// SendInterrupt sends an interrupt signal to the current process. +// On Unix systems, this sends os.Interrupt (SIGINT). +func SendInterrupt() { + if p, err := os.FindProcess(os.Getpid()); err == nil { + _ = p.Signal(os.Interrupt) + } +} diff --git a/process/interrupt_unix_test.go b/process/interrupt_unix_test.go new file mode 100644 index 0000000..19c9d2a --- /dev/null +++ b/process/interrupt_unix_test.go @@ -0,0 +1,27 @@ +//go:build !windows + +package process + +import ( + "os" + "os/signal" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestSendInterrupt(t *testing.T) { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt) + defer signal.Stop(sigChan) + + SendInterrupt() + + select { + case sig := <-sigChan: + require.Equal(t, os.Interrupt, sig) + case <-time.After(2 * time.Second): + t.Fatal("timeout waiting for interrupt signal") + } +} diff --git a/process/interrupt_windows.go b/process/interrupt_windows.go new file mode 100644 index 0000000..df7cfea --- /dev/null +++ b/process/interrupt_windows.go @@ -0,0 +1,24 @@ +//go:build windows + +package process + +import ( + "os" + "syscall" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGenerateConsoleCtrlEvent = kernel32.NewProc("GenerateConsoleCtrlEvent") +) + +// SendInterrupt sends an interrupt signal to the current process. +// On Windows, this uses GenerateConsoleCtrlEvent with CTRL_BREAK_EVENT +// because Go's p.Signal(os.Interrupt) is not implemented on Windows. +func SendInterrupt() { + // Send CTRL_BREAK_EVENT to current process's process group + // Using os.Getpid() targets only processes in our group (typically just us in production) + // This avoids sending to all console processes (group 0) which would kill parent processes + pid := os.Getpid() + procGenerateConsoleCtrlEvent.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid)) +} diff --git a/process/interrupt_windows_test.go b/process/interrupt_windows_test.go new file mode 100644 index 0000000..ad7d071 --- /dev/null +++ b/process/interrupt_windows_test.go @@ -0,0 +1,20 @@ +//go:build windows + +package process + +import "testing" + +func TestSendInterrupt(t *testing.T) { + // On Windows CI (GitHub Actions), GenerateConsoleCtrlEvent may not work + // as expected without a proper console attached. + // This test verifies the function doesn't panic and the syscall loads correctly. + defer func() { + if r := recover(); r != nil { + t.Fatalf("SendInterrupt panicked: %v", r) + } + }() + + // Just verify it doesn't crash - the actual signal delivery + // depends on console configuration which varies in CI + SendInterrupt() +}