Golang重新启动的父进程无法接收到SIGINT信号。

huangapple go评论110阅读模式
英文:

golang restarted parent process doesn't receive SIGINT

问题

我正在编写一个小程序来管理其他进程的重启。

基本上,当一个应用程序进程启动(称为A),它会生成一个新的进程(称为D),该进程具有一个简单的HTTP服务器。当D接收到一个HTTP请求时,它会终止A并重新启动它。

问题是,现在A不响应CTRL-C,我不确定为什么。可能是一些简单的问题,或者我可能没有真正理解进程、终端和信号之间的关系。但是它在同一个终端上以及相同的stdin/stdout/stderr中运行。下面是一个完整的程序,演示了这种行为。

  1. package main
  2. import (
  3. "flag"
  4. "log"
  5. "net/http"
  6. "os"
  7. "os/exec"
  8. "strconv"
  9. "time"
  10. )
  11. /*
  12. 运行此程序会启动一个应用程序(重复打印'hi')并生成一个运行简单HTTP服务器的新进程
  13. 当服务器接收到请求时,它会终止另一个进程并重新启动它。
  14. 所有三个进程使用相同的stdin/stdout/stderr。
  15. 重新启动的进程不响应CTRL-C :(
  16. */
  17. var serv = flag.Bool("serv", false, "run server")
  18. // 运行应用程序或运行服务器
  19. func main() {
  20. flag.Parse()
  21. if *serv {
  22. runServer()
  23. } else {
  24. runApp()
  25. }
  26. }
  27. // 处理服务器请求
  28. // URL应包含要重新启动的进程的PID
  29. func handler(w http.ResponseWriter, r *http.Request) {
  30. pid, err := strconv.Atoi(r.URL.Path[1:])
  31. if err != nil {
  32. log.Println("send a number...")
  33. }
  34. // 查找进程
  35. proc, err := os.FindProcess(pid)
  36. if err != nil {
  37. log.Println("can't find proc", pid)
  38. return
  39. }
  40. // 终止进程
  41. log.Println("Terminating the process...")
  42. err = proc.Signal(os.Interrupt)
  43. if err != nil {
  44. log.Println("failed to signal interrupt")
  45. return
  46. }
  47. // 重新启动进程
  48. cmd := exec.Command("restarter")
  49. cmd.Stdin = os.Stdin
  50. cmd.Stdout = os.Stdout
  51. cmd.Stderr = os.Stderr
  52. if err := cmd.Start(); err != nil {
  53. log.Println("Failed to restart app")
  54. return
  55. }
  56. log.Println("Process restarted")
  57. }
  58. // 运行服务器。
  59. // 这只会在第一次运行时起作用,这没问题
  60. func runServer() {
  61. http.HandleFunc("/", handler)
  62. if err := http.ListenAndServe(":9999", nil); err != nil {
  63. log.Println(err)
  64. }
  65. }
  66. // 应用程序循环打印'hi'
  67. // 但首先它会生成一个运行服务器的子进程
  68. func runApp() {
  69. cmd := exec.Command("restarter", "-serv")
  70. cmd.Stdin = os.Stdin
  71. cmd.Stdout = os.Stdout
  72. cmd.Stderr = os.Stderr
  73. if err := cmd.Start(); err != nil {
  74. log.Println(err)
  75. }
  76. log.Println("This is my process. It goes like this")
  77. log.Println("PID:", os.Getpid())
  78. for {
  79. time.Sleep(time.Second)
  80. log.Println("hi again")
  81. }
  82. }

该程序需要被安装。为了方便起见,你可以使用go get github.com/ebuchman/restarter来获取它。

使用restarter运行程序。它应该打印出它的进程ID。然后使用curl http://localhost:9999/<procid>来启动重启。新的进程现在将不会响应CTRL-C。为什么?我漏掉了什么?

英文:

I'm writing a little program to manage restarts to other processes.

Basically when an app process starts (call it A), it spawns a new process (call it D), which has a simple HTTP server. When D receives an http request, it kills A and restarts it.

Problem is, A now doesn't respond to CTRL-C, and I'm not sure why. It may be something simple or maybe I don't really understand the relationship between processes, the terminal, and signals. But it's running in the same terminal with the same stdin/stdout/stderr. Below is a full program demonstrating this behaviour.

  1. package main
  2. import (
  3. &quot;flag&quot;
  4. &quot;log&quot;
  5. &quot;net/http&quot;
  6. &quot;os&quot;
  7. &quot;os/exec&quot;
  8. &quot;strconv&quot;
  9. &quot;time&quot;
  10. )
  11. /*
  12. Running this program starts an app (repeatdly prints &#39;hi&#39;) and spawns a new process running a simple HTTP server
  13. When the server receives a request, it kills the other process and restarts it.
  14. All three processes use the same stdin/stdout/stderr.
  15. The restarted process does not respond to CTRL-C :(
  16. */
  17. var serv = flag.Bool(&quot;serv&quot;, false, &quot;run server&quot;)
  18. // run the app or run the server
  19. func main() {
  20. flag.Parse()
  21. if *serv {
  22. runServer()
  23. } else {
  24. runApp()
  25. }
  26. }
  27. // handle request to server
  28. // url should contain pid of process to restart
  29. func handler(w http.ResponseWriter, r *http.Request) {
  30. pid, err := strconv.Atoi(r.URL.Path[1:])
  31. if err != nil {
  32. log.Println(&quot;send a number...&quot;)
  33. }
  34. // find the process
  35. proc, err := os.FindProcess(pid)
  36. if err != nil {
  37. log.Println(&quot;can&#39;t find proc&quot;, pid)
  38. return
  39. }
  40. // terminate the process
  41. log.Println(&quot;Terminating the process...&quot;)
  42. err = proc.Signal(os.Interrupt)
  43. if err != nil {
  44. log.Println(&quot;failed to signal interupt&quot;)
  45. return
  46. }
  47. // restart the process
  48. cmd := exec.Command(&quot;restarter&quot;)
  49. cmd.Stdin = os.Stdin
  50. cmd.Stdout = os.Stdout
  51. cmd.Stderr = os.Stderr
  52. if err := cmd.Start(); err != nil {
  53. log.Println(&quot;Failed to restart app&quot;)
  54. return
  55. }
  56. log.Println(&quot;Process restarted&quot;)
  57. }
  58. // run the server.
  59. // this will only work the first time and that&#39;s fine
  60. func runServer() {
  61. http.HandleFunc(&quot;/&quot;, handler)
  62. if err := http.ListenAndServe(&quot;:9999&quot;, nil); err != nil {
  63. log.Println(err)
  64. }
  65. }
  66. // the app prints &#39;hi&#39; in a loop
  67. // but first it spawns a child process which runs the server
  68. func runApp() {
  69. cmd := exec.Command(&quot;restarter&quot;, &quot;-serv&quot;)
  70. cmd.Stdin = os.Stdin
  71. cmd.Stdout = os.Stdout
  72. cmd.Stderr = os.Stderr
  73. if err := cmd.Start(); err != nil {
  74. log.Println(err)
  75. }
  76. log.Println(&quot;This is my process. It goes like this&quot;)
  77. log.Println(&quot;PID:&quot;, os.Getpid())
  78. for {
  79. time.Sleep(time.Second)
  80. log.Println(&quot;hi again&quot;)
  81. }
  82. }

The program expects to be installed. For convenience you can fetch it with go get github.com/ebuchman/restarter.

Run the program with restarter. It should print its process id. Then curl http://localhost:9999/&lt;procid&gt; to initiate the restart. The new process will now not respond to CTRL-C. Why? What am I missing?

答案1

得分: 1

这与Go语言没有太大关系。你从终端启动进程A。进程A启动进程D(不确定B发生了什么,但不要紧)。进程D终止了进程A。现在你的shell发现它启动的进程已经退出,所以准备监听另一个命令。进程D启动了另一个进程A的副本,但是shell对此一无所知。当你输入^C时,shell会处理它。如果你运行另一个程序,shell会安排^C传递给那个程序。shell对你的进程A副本一无所知,所以它永远不会将^C传递给那个进程。

英文:

This doesn't really have anything to do with Go. You start process A from your terminal shell. Process A starts process D (not sure what happened to B, but never mind). Process D kills process A. Now your shell sees that the process it started has exited, so the shell prepares to listen to another command. Process D starts another copy of process A, but the shell doesn't know anything about it. When you type ^C, the shell will handle it. If you run another program, the shell will arrange so that ^C goes to that program. The shell knows nothing about your copy of process A, so it's never going to direct a ^C to that process.

答案2

得分: 0

你可以查看两个HTTP服务器框架采用的方法来监听和拦截信号(包括SIGINT,甚至SIGTERM)。

  • kornel661/nserv,其中ZeroDowntime-example/server.go使用了一个通道:

    1. // 捕获信号:
    2. signals := make(chan os.Signal)
    3. signal.Notify(signals, os.Interrupt, os.Kill)
  • zenazn/goji,其中graceful/signal.go采用了类似的方法:

    1. var stdSignals = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
    2. var sigchan = make(chan os.Signal, 1)
    3. func init() {
    4. go waitForSignal()
    5. }
英文:

You can check out the approach taken by two http server frameworks in order to listen and intercept signals (including SIGINT, or even SIGTERM)

huangapple
  • 本文由 发表于 2015年2月8日 07:30:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/28388758.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定