返回 DeepSeek-Reasonix
hang_watchdog_windows.go
根目录 / desktop / hang_watchdog_windows.go
1 //go:build windows
2
3 package main
4
5 import (
6 "os"
7 "sync"
8 "syscall"
9 "time"
10 "unsafe"
11
12 "golang.org/x/sys/windows"
13 )
14
15 const (
16 wmNull = 0x0000
17 smtoBlock = 0x0001
18 smtoAbortIfHung = 0x0002
19 )
20
21 var (
22 user32DLL = windows.NewLazySystemDLL("user32.dll")
23 enumWindowsProc = user32DLL.NewProc("EnumWindows")
24 getClassNameProc = user32DLL.NewProc("GetClassNameW")
25 getWindowThreadProcessProc = user32DLL.NewProc("GetWindowThreadProcessId")
26 sendMessageTimeoutProc = user32DLL.NewProc("SendMessageTimeoutW")
27 windowsHeartbeatMu sync.Mutex
28 windowsHeartbeatStop chan struct{}
29 enumWindowsMu sync.Mutex
30 enumWindowsPID uint32
31 enumWindowsFound uintptr
32 enumWindowsCallback = syscall.NewCallback(enumCurrentProcessTopLevelWindow)
33 )
34
35 func mainThreadWatchdogSupported() bool { return true }
36
37 func startNativeMainThreadHeartbeat(intervalMS uint64) {
38 windowsHeartbeatMu.Lock()
39 if windowsHeartbeatStop != nil {
40 windowsHeartbeatMu.Unlock()
41 return
42 }
43 stop := make(chan struct{})
44 windowsHeartbeatStop = stop
45 windowsHeartbeatMu.Unlock()
46
47 go func() {
48 interval := time.Duration(intervalMS) * time.Millisecond
49 if interval <= 0 {
50 interval = time.Second
51 }
52 ticker := time.NewTicker(interval)
53 defer ticker.Stop()
54 for {
55 select {
56 case <-stop:
57 return
58 case now := <-ticker.C:
59 hwnd := currentProcessTopLevelWindow()
60 // Window creation can lag OnStartup. Treat absence as
61 // inconclusive instead of manufacturing a startup hang.
62 if hwnd == 0 || windowMessageLoopResponsive(hwnd, interval) {
63 recordMainThreadHeartbeat(now)
64 }
65 }
66 }
67 }()
68 }
69
70 func stopNativeMainThreadHeartbeat() {
71 windowsHeartbeatMu.Lock()
72 stop := windowsHeartbeatStop
73 windowsHeartbeatStop = nil
74 windowsHeartbeatMu.Unlock()
75 if stop != nil {
76 close(stop)
77 }
78 }
79
80 func currentProcessTopLevelWindow() uintptr {
81 // Go's Windows callback table is process-lifetime state with a finite
82 // capacity. Reuse one callback instead of consuming an entry on every
83 // one-second heartbeat.
84 enumWindowsMu.Lock()
85 defer enumWindowsMu.Unlock()
86 enumWindowsPID = uint32(os.Getpid())
87 enumWindowsFound = 0
88 enumWindowsProc.Call(enumWindowsCallback, 0)
89 return enumWindowsFound
90 }
91
92 func enumCurrentProcessTopLevelWindow(hwnd uintptr, _ uintptr) uintptr {
93 var windowPID uint32
94 getWindowThreadProcessProc.Call(hwnd, uintptr(unsafe.Pointer(&windowPID)))
95 if windowPID == enumWindowsPID && windowClassName(hwnd) == "wailsWindow" {
96 enumWindowsFound = hwnd
97 return 0
98 }
99 return 1
100 }
101
102 func windowClassName(hwnd uintptr) string {
103 var name [256]uint16
104 n, _, _ := getClassNameProc.Call(
105 hwnd,
106 uintptr(unsafe.Pointer(&name[0])),
107 uintptr(len(name)),
108 )
109 if n == 0 {
110 return ""
111 }
112 return windows.UTF16ToString(name[:n])
113 }
114
115 func windowMessageLoopResponsive(hwnd uintptr, timeout time.Duration) bool {
116 timeoutMS := timeout.Milliseconds()
117 if timeoutMS < 250 {
118 timeoutMS = 250
119 }
120 var result uintptr
121 ok, _, _ := sendMessageTimeoutProc.Call(
122 hwnd,
123 wmNull,
124 0,
125 0,
126 smtoBlock|smtoAbortIfHung,
127 uintptr(timeoutMS),
128 uintptr(unsafe.Pointer(&result)),
129 )
130 return ok != 0
131 }
132
132 lines GO