GoLang:致命错误:回调函数太多



我正在尝试编写一个go应用程序,该应用程序将监视我在windows中运行的服务器应用程序的状态。该应用程序将运行大约16个小时,然后抛出以下错误:(小片段(

fatal error: too many callback functions
goroutine 137 [running]:
runtime.throw(0xc4c0a1, 0x1b)
H:/Program Files/Go/src/runtime/panic.go:1117 +0x79 fp=0xc000639d30 sp=0xc000639d00 pc=0x899379
syscall.compileCallback(0xbd18a0, 0xc00041fce0, 0x1, 0x0)
H:/Program Files/Go/src/runtime/syscall_windows.go:201 +0x5e5 fp=0xc000639e28 sp=0xc000639d30 pc=0x8c90e5
syscall.NewCallback(...)
H:/Program Files/Go/src/syscall/syscall_windows.go:177
main.FindWindow(0xc47278, 0x13, 0xc000639f50, 0x2, 0x2)

我有两个档案。一个是调用大量Windows API内容的文件,另一个是每30秒执行一次的goroutine以获得更新。

我是一个新手,尤其是在windows相关的开发方面,所以我很难找到这个问题以及如何防止它

这是主文件(测试示例(。

func main() {
go updateServerStats()
select {}
}
func ServerStats() {
serverStatsTicker := time.NewTicker(30 * time.Second)
for range serverStatsTicker.C {
serverRunning, serverHung, err := ServerHangCheck()
if err != nil {
ErrorLogger.Println("Server Check Error: ", err)
}
if serverHung {
fmt.Println("Server is hung")
}
}
}

这是主回调/windows文件。这仍然是一项非常艰难、非常有进展的工作。我在操场上发现并修改的东西。

package main
import (
"fmt"
"syscall"
"unsafe"
)
var (
user32             = syscall.MustLoadDLL("user32.dll")
procEnumWindows    = user32.MustFindProc("EnumWindows")
procGetWindowTextW = user32.MustFindProc("GetWindowTextW")
procIsHungAppWindow = user32.MustFindProc("IsHungAppWindow")
)
//EnumWindows iterates over each window to be used for callbacks
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
//GetWindowText gets the description of the Window, which is the "Text" of the window.
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
len = int32(r0)
if len == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
//IsHungAppWindow uses the IsHungAppWindow to see if Windows has been getting responses.
func IsHungAppWindow(hwnd syscall.Handle) (ishung bool, err error) {
r2, _, err := syscall.Syscall(procIsHungAppWindow.Addr(), 2, uintptr(hwnd), 0, 0)
if r2 == 1{
return true, err
} 
return false, err
}
//FindWindow uses EnumWindows with a callback to GetWindowText, and if matches given Title, checks if its hung, and returns state.
func FindWindow(title string) (bool ,bool,  error) {
var hwnd syscall.Handle
var isHung bool = false
cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
b := make([]uint16, 200)
_, err := GetWindowText(h, &b[0], int32(len(b)))
if err != nil {
// ignore the error
return 1 // continue enumeration
}
if syscall.UTF16ToString(b) == title {
// note the window
isHung, _ = IsHungAppWindow(h)
hwnd = h
return 0 // stop enumeration
}
return 1 // continue enumeration
})
EnumWindows(cb, 0)
if hwnd == 0 {
return false, false, fmt.Errorf("DCS Not Found")
}

if isHung == true {
return true, isHung, fmt.Errorf("DCS Is Running But Hung")
}
return true, isHung, nil
}
//ServerHangCheck checks the server to see if the window is hung or process is running.
func ServerHangCheck() (bool, bool, error) {
const title = "server_application"
running, hung, err := FindWindow(title)
return running, hung, err
}

查看syscall.NewCallback,我们发现它实际上是通过runtime/syscall_windows.go作为函数compileCallback:实现的

func NewCallback(fn interface{}) uintptr {
return compileCallback(fn, true)
}

查看runtime/syscall_windows.go代码,我们发现它有一个固定大小的表,其中包含所有注册的go回调。这段代码在Go版本之间变化很大,所以在这里深入研究不会太有成效。然而,有一件事是清楚的:代码检查回调函数是否已经注册,如果是,将重新使用它。因此,一个回调函数占用一个表槽,但添加多个函数最终会占用所有表槽,并导致出现fatal error

你在评论中问道(评论在我写这篇文章时弹出(:

我需要重用它吗;关闭";原件Mallachar 7分钟前

您无法关闭原始文件。内存中的其他地方有一个实际的函数表;注册的回调会占用该表中的一个插槽,而且插槽永远不会释放。

根据托雷克的建议,我找到了一个解决方案。

我创建了一个全局变量

var callbacker uintptr 

然后我创建了一个由init 调用的函数

func init() {
callbacker = syscall.NewCallback(CallBackCreator)
}

func CallBackCreator(h syscall.Handle, p uintptr) uintptr {
hwnd = 0
b := make([]uint16, 200)
_, err := GetWindowText(h, &b[0], int32(len(b)))
if err != nil {
// ignore the error
return 1 // continue enumeration
}
if syscall.UTF16ToString(b) == title {
// note the window
isHung, _ = IsHungAppWindow(h)
hwnd = h
return 0 // stop enumeration
}
return 1 // continue enumeration
}

现在由函数调用

EnumWindows(callbacker, 0)

可能不是最";Go"适当的方式,但我现在正在取得更好的进步。

最新更新