Fix telegram bot process checks on Windows
- commit
- 798174c22c9110b1247a4f971eae2548f8e8450d
- author
- patriceckhart · · 2026-06-13 15:39 UTC
- parent
- 7f954ce
- signature
- unsigned
- refs
- tag: v0.2.30
3 files · +101 -33
M packages/agent/modes/telegram/daemon.go +5 -33
| @@ -7,7 +7,6 @@ import ( | |||
| 7 | 7 | "path/filepath" | |
| 8 | 8 | "strconv" | |
| 9 | 9 | "strings" | |
| 10 | - | "syscall" | |
| 11 | 10 | "time" | |
| 12 | 11 | ) | |
| 13 | 12 | ||
| @@ -68,42 +67,15 @@ func IsRunning(zotHome string) (int, bool, error) { | |||
| 68 | 67 | if pid <= 0 { | |
| 69 | 68 | return 0, false, nil | |
| 70 | 69 | } | |
| 71 | - | proc, err := os.FindProcess(pid) | |
| 70 | + | alive, err := processAlive(pid) | |
| 72 | 71 | if err != nil { | |
| 73 | 72 | return pid, false, nil | |
| 74 | 73 | } | |
| 75 | - | // signal 0 is POSIX's "does the process exist?" probe. On Windows | |
| 76 | - | // os.Process is always usable and Signal(0) returns nil, so we'd | |
| 77 | - | // miss stale pids; acceptable for the macos/linux-first audience. | |
| 78 | - | if err := proc.Signal(syscall.Signal(0)); err != nil { | |
| 79 | - | if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) { | |
| 80 | - | return pid, false, nil | |
| 81 | - | } | |
| 82 | - | // Other errors (EPERM) mean the process exists but we can't | |
| 83 | - | // inspect it; treat as running. | |
| 84 | - | return pid, true, nil | |
| 85 | - | } | |
| 86 | - | return pid, true, nil | |
| 74 | + | return pid, alive, nil | |
| 87 | 75 | } | |
| 88 | 76 | ||
| 89 | - | // StopProcess sends SIGTERM to pid and waits up to graceful for it to | |
| 90 | - | // exit, then escalates to SIGKILL. Returns nil if the process is gone. | |
| 77 | + | // StopProcess asks pid to exit and waits up to graceful for it to stop, | |
| 78 | + | // then escalates to a forced kill. Returns nil if the process is gone. | |
| 91 | 79 | func StopProcess(pid int, graceful time.Duration) error { | |
| 92 | - | proc, err := os.FindProcess(pid) | |
| 93 | - | if err != nil { | |
| 94 | - | return err | |
| 95 | - | } | |
| 96 | - | _ = proc.Signal(syscall.SIGTERM) | |
| 97 | - | ||
| 98 | - | deadline := time.Now().Add(graceful) | |
| 99 | - | for time.Now().Before(deadline) { | |
| 100 | - | if err := proc.Signal(syscall.Signal(0)); err != nil { | |
| 101 | - | if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) { | |
| 102 | - | return nil | |
| 103 | - | } | |
| 104 | - | } | |
| 105 | - | time.Sleep(100 * time.Millisecond) | |
| 106 | - | } | |
| 107 | - | _ = proc.Kill() | |
| 108 | - | return nil | |
| 80 | + | return stopProcess(pid, graceful) | |
| 109 | 81 | } | |
| @@ -7,7 +7,6 @@ import ( | |||||
| 7 | "path/filepath" | 7 | "path/filepath" | ||
| 8 | "strconv" | 8 | "strconv" | ||
| 9 | "strings" | 9 | "strings" | ||
| 10 | - | "syscall" | |||
| 11 | "time" | 10 | "time" | ||
| 12 | ) | 11 | ) | ||
| 13 | 12 | ||||
| @@ -68,42 +67,15 @@ func IsRunning(zotHome string) (int, bool, error) { | |||||
| 68 | if pid <= 0 { | 67 | if pid <= 0 { | ||
| 69 | return 0, false, nil | 68 | return 0, false, nil | ||
| 70 | } | 69 | } | ||
| 71 | - | proc, err := os.FindProcess(pid) | 70 | + | alive, err := processAlive(pid) |
| 72 | if err != nil { | 71 | if err != nil { | ||
| 73 | return pid, false, nil | 72 | return pid, false, nil | ||
| 74 | } | 73 | } | ||
| 75 | - | // signal 0 is POSIX's "does the process exist?" probe. On Windows | 74 | + | return pid, alive, nil |
| 76 | - | // os.Process is always usable and Signal(0) returns nil, so we'd | |||
| 77 | - | // miss stale pids; acceptable for the macos/linux-first audience. | |||
| 78 | - | if err := proc.Signal(syscall.Signal(0)); err != nil { | |||
| 79 | - | if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) { | |||
| 80 | - | return pid, false, nil | |||
| 81 | - | } | |||
| 82 | - | // Other errors (EPERM) mean the process exists but we can't | |||
| 83 | - | // inspect it; treat as running. | |||
| 84 | - | return pid, true, nil | |||
| 85 | - | } | |||
| 86 | - | return pid, true, nil | |||
| 87 | } | 75 | } | ||
| 88 | 76 | ||||
| 89 | - | // StopProcess sends SIGTERM to pid and waits up to graceful for it to | 77 | + | // StopProcess asks pid to exit and waits up to graceful for it to stop, |
| 90 | - | // exit, then escalates to SIGKILL. Returns nil if the process is gone. | 78 | + | // then escalates to a forced kill. Returns nil if the process is gone. |
| 91 | func StopProcess(pid int, graceful time.Duration) error { | 79 | func StopProcess(pid int, graceful time.Duration) error { | ||
| 92 | - | proc, err := os.FindProcess(pid) | 80 | + | return stopProcess(pid, graceful) |
| 93 | - | if err != nil { | |||
| 94 | - | return err | |||
| 95 | - | } | |||
| 96 | - | _ = proc.Signal(syscall.SIGTERM) | |||
| 97 | - | ||||
| 98 | - | deadline := time.Now().Add(graceful) | |||
| 99 | - | for time.Now().Before(deadline) { | |||
| 100 | - | if err := proc.Signal(syscall.Signal(0)); err != nil { | |||
| 101 | - | if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) { | |||
| 102 | - | return nil | |||
| 103 | - | } | |||
| 104 | - | } | |||
| 105 | - | time.Sleep(100 * time.Millisecond) | |||
| 106 | - | } | |||
| 107 | - | _ = proc.Kill() | |||
| 108 | - | return nil | |||
| 109 | } | 81 | } | ||
A packages/agent/modes/telegram/process_unix.go +44 -0
| @@ -0,0 +1,44 @@ | |||
| 1 | + | //go:build !windows | |
| 2 | + | ||
| 3 | + | package telegram | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "errors" | |
| 7 | + | "os" | |
| 8 | + | "syscall" | |
| 9 | + | "time" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | func processAlive(pid int) (bool, error) { | |
| 13 | + | proc, err := os.FindProcess(pid) | |
| 14 | + | if err != nil { | |
| 15 | + | return false, err | |
| 16 | + | } | |
| 17 | + | if err := proc.Signal(syscall.Signal(0)); err != nil { | |
| 18 | + | if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) { | |
| 19 | + | return false, nil | |
| 20 | + | } | |
| 21 | + | // Other errors (EPERM) mean the process exists but we can't inspect it. | |
| 22 | + | return true, nil | |
| 23 | + | } | |
| 24 | + | return true, nil | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | func stopProcess(pid int, graceful time.Duration) error { | |
| 28 | + | proc, err := os.FindProcess(pid) | |
| 29 | + | if err != nil { | |
| 30 | + | return err | |
| 31 | + | } | |
| 32 | + | _ = proc.Signal(syscall.SIGTERM) | |
| 33 | + | ||
| 34 | + | deadline := time.Now().Add(graceful) | |
| 35 | + | for time.Now().Before(deadline) { | |
| 36 | + | alive, err := processAlive(pid) | |
| 37 | + | if err != nil || !alive { | |
| 38 | + | return nil | |
| 39 | + | } | |
| 40 | + | time.Sleep(100 * time.Millisecond) | |
| 41 | + | } | |
| 42 | + | _ = proc.Kill() | |
| 43 | + | return nil | |
| 44 | + | } | |
| @@ -0,0 +1,44 @@ | |||||
| 1 | + | //go:build !windows | |||
| 2 | + | ||||
| 3 | + | package telegram | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "errors" | |||
| 7 | + | "os" | |||
| 8 | + | "syscall" | |||
| 9 | + | "time" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | func processAlive(pid int) (bool, error) { | |||
| 13 | + | proc, err := os.FindProcess(pid) | |||
| 14 | + | if err != nil { | |||
| 15 | + | return false, err | |||
| 16 | + | } | |||
| 17 | + | if err := proc.Signal(syscall.Signal(0)); err != nil { | |||
| 18 | + | if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) { | |||
| 19 | + | return false, nil | |||
| 20 | + | } | |||
| 21 | + | // Other errors (EPERM) mean the process exists but we can't inspect it. | |||
| 22 | + | return true, nil | |||
| 23 | + | } | |||
| 24 | + | return true, nil | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | func stopProcess(pid int, graceful time.Duration) error { | |||
| 28 | + | proc, err := os.FindProcess(pid) | |||
| 29 | + | if err != nil { | |||
| 30 | + | return err | |||
| 31 | + | } | |||
| 32 | + | _ = proc.Signal(syscall.SIGTERM) | |||
| 33 | + | ||||
| 34 | + | deadline := time.Now().Add(graceful) | |||
| 35 | + | for time.Now().Before(deadline) { | |||
| 36 | + | alive, err := processAlive(pid) | |||
| 37 | + | if err != nil || !alive { | |||
| 38 | + | return nil | |||
| 39 | + | } | |||
| 40 | + | time.Sleep(100 * time.Millisecond) | |||
| 41 | + | } | |||
| 42 | + | _ = proc.Kill() | |||
| 43 | + | return nil | |||
| 44 | + | } | |||
A packages/agent/modes/telegram/process_windows.go +52 -0
| @@ -0,0 +1,52 @@ | |||
| 1 | + | //go:build windows | |
| 2 | + | ||
| 3 | + | package telegram | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "errors" | |
| 7 | + | "os" | |
| 8 | + | "time" | |
| 9 | + | ||
| 10 | + | "golang.org/x/sys/windows" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | const stillActive = 259 | |
| 14 | + | ||
| 15 | + | func processAlive(pid int) (bool, error) { | |
| 16 | + | h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid)) | |
| 17 | + | if err != nil { | |
| 18 | + | if errors.Is(err, windows.ERROR_INVALID_PARAMETER) { | |
| 19 | + | return false, nil | |
| 20 | + | } | |
| 21 | + | if errors.Is(err, windows.ERROR_ACCESS_DENIED) { | |
| 22 | + | return true, nil | |
| 23 | + | } | |
| 24 | + | return false, err | |
| 25 | + | } | |
| 26 | + | defer windows.CloseHandle(h) | |
| 27 | + | ||
| 28 | + | var code uint32 | |
| 29 | + | if err := windows.GetExitCodeProcess(h, &code); err != nil { | |
| 30 | + | return false, err | |
| 31 | + | } | |
| 32 | + | return code == stillActive, nil | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | func stopProcess(pid int, graceful time.Duration) error { | |
| 36 | + | proc, err := os.FindProcess(pid) | |
| 37 | + | if err != nil { | |
| 38 | + | return err | |
| 39 | + | } | |
| 40 | + | _ = proc.Signal(os.Interrupt) | |
| 41 | + | ||
| 42 | + | deadline := time.Now().Add(graceful) | |
| 43 | + | for time.Now().Before(deadline) { | |
| 44 | + | alive, err := processAlive(pid) | |
| 45 | + | if err != nil || !alive { | |
| 46 | + | return nil | |
| 47 | + | } | |
| 48 | + | time.Sleep(100 * time.Millisecond) | |
| 49 | + | } | |
| 50 | + | _ = proc.Kill() | |
| 51 | + | return nil | |
| 52 | + | } | |
| @@ -0,0 +1,52 @@ | |||||
| 1 | + | //go:build windows | |||
| 2 | + | ||||
| 3 | + | package telegram | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "errors" | |||
| 7 | + | "os" | |||
| 8 | + | "time" | |||
| 9 | + | ||||
| 10 | + | "golang.org/x/sys/windows" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | const stillActive = 259 | |||
| 14 | + | ||||
| 15 | + | func processAlive(pid int) (bool, error) { | |||
| 16 | + | h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid)) | |||
| 17 | + | if err != nil { | |||
| 18 | + | if errors.Is(err, windows.ERROR_INVALID_PARAMETER) { | |||
| 19 | + | return false, nil | |||
| 20 | + | } | |||
| 21 | + | if errors.Is(err, windows.ERROR_ACCESS_DENIED) { | |||
| 22 | + | return true, nil | |||
| 23 | + | } | |||
| 24 | + | return false, err | |||
| 25 | + | } | |||
| 26 | + | defer windows.CloseHandle(h) | |||
| 27 | + | ||||
| 28 | + | var code uint32 | |||
| 29 | + | if err := windows.GetExitCodeProcess(h, &code); err != nil { | |||
| 30 | + | return false, err | |||
| 31 | + | } | |||
| 32 | + | return code == stillActive, nil | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | func stopProcess(pid int, graceful time.Duration) error { | |||
| 36 | + | proc, err := os.FindProcess(pid) | |||
| 37 | + | if err != nil { | |||
| 38 | + | return err | |||
| 39 | + | } | |||
| 40 | + | _ = proc.Signal(os.Interrupt) | |||
| 41 | + | ||||
| 42 | + | deadline := time.Now().Add(graceful) | |||
| 43 | + | for time.Now().Before(deadline) { | |||
| 44 | + | alive, err := processAlive(pid) | |||
| 45 | + | if err != nil || !alive { | |||
| 46 | + | return nil | |||
| 47 | + | } | |||
| 48 | + | time.Sleep(100 * time.Millisecond) | |||
| 49 | + | } | |||
| 50 | + | _ = proc.Kill() | |||
| 51 | + | return nil | |||
| 52 | + | } | |||