diff --git a/global/signal.go b/global/signal.go new file mode 100644 index 0000000..45d99b9 --- /dev/null +++ b/global/signal.go @@ -0,0 +1,52 @@ +package global + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + "time" + + log "github.com/sirupsen/logrus" +) + +var ( + mainStopCh chan struct{} + mainOnce sync.Once + + dumpMutex sync.Mutex +) + +func dumpStack() { + dumpMutex.Lock() + defer dumpMutex.Unlock() + + log.Info("开始 dump 当前 goroutine stack 信息") + + buf := make([]byte, 1024) + for { + n := runtime.Stack(buf, true) + if n < len(buf) { + buf = buf[:n] + break + } + buf = make([]byte, 2*len(buf)) + } + + fileName := fmt.Sprintf("%s.%d.stacks.%d.log", filepath.Base(os.Args[0]), os.Getpid(), time.Now().Unix()) + fd, err := os.Create(fileName) + if err != nil { + log.Errorf("保存 stackdump 到文件时出现错误: %v", err) + log.Warnf("无法保存 stackdump. 将直接打印\n %s", buf) + return + } + defer fd.Close() + _, err = fd.Write(buf) + if err != nil { + log.Errorf("写入 stackdump 失败: %v", err) + log.Warnf("无法保存 stackdump. 将直接打印\n %s", buf) + return + } + log.Infof("stackdump 已保存至 %s", fileName) +} diff --git a/global/signal_unix.go b/global/signal_unix.go new file mode 100644 index 0000000..dd19e33 --- /dev/null +++ b/global/signal_unix.go @@ -0,0 +1,34 @@ +//+build !windows + +package global + +import ( + "os" + "os/signal" + "sync" + "syscall" +) + +func SetupMainSignalHandler() <-chan struct{} { + mainOnce.Do(func() { + mc := make(chan os.Signal, 2) + closeOnce := sync.Once{} + signal.Notify(mc, os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1) + go func() { + for { + s := <-mc + switch s { + case os.Interrupt, syscall.SIGTERM: + closeOnce.Do(func() { + close(mc) + }) + case syscall.SIGUSR1: + dumpStack() + } + } + }() + + mainStopCh = make(chan struct{}) + }) + return mainStopCh +} diff --git a/global/signal_windows.go b/global/signal_windows.go new file mode 100644 index 0000000..f1ae658 --- /dev/null +++ b/global/signal_windows.go @@ -0,0 +1,86 @@ +//+build windows + +package global + +import ( + "fmt" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/Microsoft/go-winio" + log "github.com/sirupsen/logrus" +) + +var ( + validTasks = []string{ + "dumpstack", + } +) + +func SetupMainSignalHandler() <-chan struct{} { + mainOnce.Do(func() { + // for stack trace collecting on windows + pipeName := fmt.Sprintf(`\\.\pipe\go-cqhttp-%d`, os.Getpid()) + pipe, err := winio.ListenPipe(pipeName, &winio.PipeConfig{}) + if err != nil { + log.Error("创建 named pipe 失败. 将无法使用 dumpstack 功能") + } else { + maxTaskLen := 0 + for i := range validTasks { + if l := len(validTasks[i]); l > maxTaskLen { + maxTaskLen = l + } + } + go func() { + for { + c, err := pipe.Accept() + if err != nil { + log.Errorf("accept named pipe 失败: %v", err) + continue + } + go func() { + defer c.Close() + _ = c.SetReadDeadline(time.Now().Add(5 * time.Second)) + buf := make([]byte, maxTaskLen) + n, err := c.Read(buf) + if err != nil { + log.Errorf("读取 named pipe 失败: %v", err) + return + } + cmd := string(buf[:n]) + switch cmd { + case "dumpstack": + dumpStack() + default: + log.Warnf("named pipe 读取到未知指令: %q", cmd) + } + }() + } + }() + } + + mc := make(chan os.Signal, 2) + closeOnce := sync.Once{} + signal.Notify(mc, os.Interrupt, syscall.SIGTERM) + go func() { + for { + s := <-mc + switch s { + case os.Interrupt, syscall.SIGTERM: + closeOnce.Do(func() { + close(mc) + if pipe != nil { + _ = pipe.Close() + } + }) + } + } + }() + + mainStopCh = make(chan struct{}) + }) + return mainStopCh +} diff --git a/go.mod b/go.mod index 8be54ad..8d0680d 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f + github.com/Microsoft/go-winio v0.5.0 // indirect github.com/Mrs4s/MiraiGo v0.0.0-20210611054116-d61d3d491ec7 github.com/dustin/go-humanize v1.0.0 github.com/gin-gonic/gin v1.7.1 // indirect diff --git a/go.sum b/go.sum index 4d2bc3d..b558622 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII= github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Mrs4s/MiraiGo v0.0.0-20210611054116-d61d3d491ec7 h1:aW4SXcVqktrcyfYlGiAZ3oDPk0m3yG3y5KQIfQFyyG8= github.com/Mrs4s/MiraiGo v0.0.0-20210611054116-d61d3d491ec7/go.mod h1:emmgsvc5Mt7yIvR7XOL59pFrKS0Cegrp09s/tZTtzj0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -101,6 +103,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= @@ -164,6 +167,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818 h1:f1CIuDlJhwANEC2MM87MBEVMr3jl5bifgsfj90XAF9c= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/main.go b/main.go index 6bd7a7f..4eea4d2 100644 --- a/main.go +++ b/main.go @@ -11,12 +11,10 @@ import ( "io/ioutil" "os" "os/exec" - "os/signal" "path" "runtime" "strings" "sync" - "syscall" "time" "github.com/Mrs4s/go-cqhttp/coolq" @@ -434,10 +432,10 @@ func main() { } log.Info("资源初始化完成, 开始处理信息.") log.Info("アトリは、高性能ですから!") - c := make(chan os.Signal, 1) + go checkUpdate() - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - <-c + + <-global.SetupMainSignalHandler() } // PasswordHashEncrypt 使用key加密给定passwordHash