簡體   English   中英

在 Windows 上正常終止進程

[英]Gracefully terminate a process on Windows

我正在用 Go 編寫服務器應用程序,並使用包裝器將其作為 Windows 服務運行。

需要優雅地關閉服務器(以正確關閉資源和連接),而在 UNIX 中,它將通過 SIGTERM 信號進行處理。 沒什么大不了。

盡管在 Windows 上,事情似乎非常不同。 我在本指南中看到信號實際上存在於 windows (?) 上,並且定義了 SIGTERM,盡管其他頁面表明它們沒有,或者使用其他機制,如 WM_CLOSE。

告訴無頭進程優雅終止的最佳方法是什么? 在 Go 中應該如何實現?

服務器設計為多平台的,因此最好采用最標准的方式。

信號在 Windows 上實現,但 Unix 信號不可用。 golang for Windows 的信號包中有一個例子,發送Ctrl-Break。 在此處被重構以供交互使用。

go的方式來啟動取消任務/服務,是使用context.Context包。

因此,如果您希望信號處理程序觸發關閉任務的context.Context

func registerSigHandler() context.Context {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

        rootCtx := context.Background()
        taskCtx, cancelFn := context.WithCancel(rootCtx)

        go func() {
                sig := <-sigCh
                log.Println("received signal:", sig)

                // let sub-task know to wrap up: cancel taskCtx
                cancelFn()
        }()

        return taskCtx
}

然后將返回的taskCtx傳遞給您的工作任務以供其監聽。

select {
    case <-taskCtx.Done():
        // context was canceled
    default: // poll - rather than block in this example
}

游樂場源代碼

輸出:

2019/03/10 19:18:51 Worker PID: 33186
2019/03/10 19:18:51 Will terminate on these signals: interrupt terminated
2019/03/10 19:18:51 2019-03-10 19:18:51.018962 -0400 EDT m=+0.001727305
2019/03/10 19:18:52 2019-03-10 19:18:52.022782 -0400 EDT m=+1.005517010
2019/03/10 19:18:53 2019-03-10 19:18:53.019925 -0400 EDT m=+2.002630457

$ kill -INT 33186

2019/03/10 19:18:53 received signal: interrupt
2019/03/10 19:18:53 task context terminated reason: context canceled
2019/03/10 19:18:53 wrapping up task...
2019/03/10 19:18:53 workerTask() exited cleanly
2019/03/10 19:18:53 main exited cleanly

編輯:

我在Windows 10上對此進行了測試,當從同一控制台發出 Ctrl-C 時觸發清理。 不確定如何在 Windows 上從外部發送信號 - 這可能是 OP 的原始問題。 使用 say killtask /F /PID 33186確實會在沒有觸發任何信號處理程序的情況下killtask /F /PID 33186進程。

所以,過了一會兒,我只想分享我是如何解決它的。 這很丑陋,但我找到的唯一真正的方法。 Windows golang 程序確實會偵聽 CTRL+C,這不是信號或類似信號,但它確實使用 UNIX 具有的相同信號在 go 程序中觸發了正常關閉。

這是相關的代碼:

// Create shutdown channel
exitChan := make(chan int)
interruptChan := make(chan os.Signal, 1)
signal.Notify(interruptChan, os.Interrupt)
select {
case exitCode := <-exitChan:
    os.Exit(exitCode)
case <-interruptChan:
    // This works in Windows if ctrl-c is invoked. It sucks though
    svr.Stop()
    os.Exit(-1)
}

為了在給定的過程中觸發 CTRL+C,我使用了一個名為 windows-kill.exe 的小程序

// Windows is "special" and we need to use a library for killing processes gracefully
// Unfortunately setting up the C++ library with go is a hassle, so we resort to a CLI tool
ex, _ := os.Executable()
cmdPath := filepath.Join(filepath.Dir(ex), "windows-kill.exe")
cmd := exec.CommandContext(context.Background(), cmdPath, "-SIGINT", strconv.Itoa(l.proc.Pid))

err := cmd.Start()
if err != nil {
    panic(err)
}

這是庫和工具的 repo: https : //github.com/alirdn/windows-kill

將 *nix 構造和函數用於 Windows 的想法通常很糟糕,並且容易出現可能有效也可能無效的奇怪技巧。

在關閉 GUI 應用程序時進行清理的正確方法是處理WM_QUERYENDSESSIONWM_ENDSESSION 此外,您可以靈活使用通用關閉機制,請在此處查看

如果您是服務,則獲得通知的正確方法是控制台應用程序的服務處理程序 (SERVICE_STOPPED)。 有關更多信息,請參閱編寫 ServiceMain

如果您正在運行某種形式的網絡服務,最好使用網絡服務本身來啟動關閉,因為跟蹤信號的 PIDS 可能會變得混亂。

要停止 http 網絡服務,只需添加如下路由:

http.HandleFunc("/shutdown",
        func(w http.ResponseWriter, r *http.Request) {
            // stops server - may want to add admin credentials check here!
            srv.Shutdown(context.Background())
        })

Playground源代碼示例。

2019/03/10 16:58:15 Listening on: :8080

$ curl 'localhost:8080/shutdown'

2019/03/10 17:04:17 Listening on: :8080
2019/03/10 17:04:19 exited cleanly

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM