From 9fbfb55a3c7e7948d943ea249aeca45971b7ef51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Wed, 4 Feb 2026 16:18:19 +0300 Subject: [PATCH 1/3] add cross-platform interrupt signal utility --- process/interrupt_unix.go | 13 +++++++++++++ process/interrupt_windows.go | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 process/interrupt_unix.go create mode 100644 process/interrupt_windows.go 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_windows.go b/process/interrupt_windows.go new file mode 100644 index 0000000..2113a5f --- /dev/null +++ b/process/interrupt_windows.go @@ -0,0 +1,18 @@ +//go:build windows + +package process + +import "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() { + // CTRL_BREAK_EVENT = 1 + procGenerateConsoleCtrlEvent.Call(1, 0) +} From 2eee3afb1f861de445c12e583ec66a71c6f0b271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Wed, 4 Feb 2026 16:25:42 +0300 Subject: [PATCH 2/3] use process group pid on windows --- process/interrupt_windows.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/process/interrupt_windows.go b/process/interrupt_windows.go index 2113a5f..df7cfea 100644 --- a/process/interrupt_windows.go +++ b/process/interrupt_windows.go @@ -2,7 +2,10 @@ package process -import "syscall" +import ( + "os" + "syscall" +) var ( kernel32 = syscall.NewLazyDLL("kernel32.dll") @@ -13,6 +16,9 @@ var ( // On Windows, this uses GenerateConsoleCtrlEvent with CTRL_BREAK_EVENT // because Go's p.Signal(os.Interrupt) is not implemented on Windows. func SendInterrupt() { - // CTRL_BREAK_EVENT = 1 - procGenerateConsoleCtrlEvent.Call(1, 0) + // 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)) } From a3af13028ca5141fb4e6524fa9ecc931265db22c Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Thu, 5 Feb 2026 15:27:28 +0400 Subject: [PATCH 3/3] adding tests --- process/interrupt_unix_test.go | 27 +++++++++++++++++++++++++++ process/interrupt_windows_test.go | 20 ++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 process/interrupt_unix_test.go create mode 100644 process/interrupt_windows_test.go 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_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() +}