英文:
golang restarted parent process doesn't receive SIGINT
问题
我正在编写一个小程序来管理其他进程的重启。
基本上,当一个应用程序进程启动(称为A),它会生成一个新的进程(称为D),该进程具有一个简单的HTTP服务器。当D接收到一个HTTP请求时,它会终止A并重新启动它。
问题是,现在A不响应CTRL-C,我不确定为什么。可能是一些简单的问题,或者我可能没有真正理解进程、终端和信号之间的关系。但是它在同一个终端上以及相同的stdin/stdout/stderr中运行。下面是一个完整的程序,演示了这种行为。
package main
import (
"flag"
"log"
"net/http"
"os"
"os/exec"
"strconv"
"time"
)
/*
运行此程序会启动一个应用程序(重复打印'hi')并生成一个运行简单HTTP服务器的新进程
当服务器接收到请求时,它会终止另一个进程并重新启动它。
所有三个进程使用相同的stdin/stdout/stderr。
重新启动的进程不响应CTRL-C :(
*/
var serv = flag.Bool("serv", false, "run server")
// 运行应用程序或运行服务器
func main() {
flag.Parse()
if *serv {
runServer()
} else {
runApp()
}
}
// 处理服务器请求
// URL应包含要重新启动的进程的PID
func handler(w http.ResponseWriter, r *http.Request) {
pid, err := strconv.Atoi(r.URL.Path[1:])
if err != nil {
log.Println("send a number...")
}
// 查找进程
proc, err := os.FindProcess(pid)
if err != nil {
log.Println("can't find proc", pid)
return
}
// 终止进程
log.Println("Terminating the process...")
err = proc.Signal(os.Interrupt)
if err != nil {
log.Println("failed to signal interrupt")
return
}
// 重新启动进程
cmd := exec.Command("restarter")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Println("Failed to restart app")
return
}
log.Println("Process restarted")
}
// 运行服务器。
// 这只会在第一次运行时起作用,这没问题
func runServer() {
http.HandleFunc("/", handler)
if err := http.ListenAndServe(":9999", nil); err != nil {
log.Println(err)
}
}
// 应用程序循环打印'hi'
// 但首先它会生成一个运行服务器的子进程
func runApp() {
cmd := exec.Command("restarter", "-serv")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Println(err)
}
log.Println("This is my process. It goes like this")
log.Println("PID:", os.Getpid())
for {
time.Sleep(time.Second)
log.Println("hi again")
}
}
该程序需要被安装。为了方便起见,你可以使用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.
package main
import (
"flag"
"log"
"net/http"
"os"
"os/exec"
"strconv"
"time"
)
/*
Running this program starts an app (repeatdly prints 'hi') and spawns a new process running a simple HTTP server
When the server receives a request, it kills the other process and restarts it.
All three processes use the same stdin/stdout/stderr.
The restarted process does not respond to CTRL-C :(
*/
var serv = flag.Bool("serv", false, "run server")
// run the app or run the server
func main() {
flag.Parse()
if *serv {
runServer()
} else {
runApp()
}
}
// handle request to server
// url should contain pid of process to restart
func handler(w http.ResponseWriter, r *http.Request) {
pid, err := strconv.Atoi(r.URL.Path[1:])
if err != nil {
log.Println("send a number...")
}
// find the process
proc, err := os.FindProcess(pid)
if err != nil {
log.Println("can't find proc", pid)
return
}
// terminate the process
log.Println("Terminating the process...")
err = proc.Signal(os.Interrupt)
if err != nil {
log.Println("failed to signal interupt")
return
}
// restart the process
cmd := exec.Command("restarter")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Println("Failed to restart app")
return
}
log.Println("Process restarted")
}
// run the server.
// this will only work the first time and that's fine
func runServer() {
http.HandleFunc("/", handler)
if err := http.ListenAndServe(":9999", nil); err != nil {
log.Println(err)
}
}
// the app prints 'hi' in a loop
// but first it spawns a child process which runs the server
func runApp() {
cmd := exec.Command("restarter", "-serv")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Println(err)
}
log.Println("This is my process. It goes like this")
log.Println("PID:", os.Getpid())
for {
time.Sleep(time.Second)
log.Println("hi again")
}
}
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/<procid>
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
使用了一个通道:// 捕获信号: signals := make(chan os.Signal) signal.Notify(signals, os.Interrupt, os.Kill)
-
zenazn/goji
,其中graceful/signal.go
采用了类似的方法:var stdSignals = []os.Signal{syscall.SIGINT, syscall.SIGTERM} var sigchan = make(chan os.Signal, 1) func init() { go waitForSignal() }
英文:
You can check out the approach taken by two http server frameworks in order to listen and intercept signals (including SIGINT
, or even SIGTERM
)
-
kornel661/nserv
, where theZeroDowntime-example/server.go
uses a channel:// catch signals: signals := make(chan os.Signal) signal.Notify(signals, os.Interrupt, os.Kill)
-
zenazn/goji
, wheregraceful/signal.go
uses a similar approach:var stdSignals = []os.Signal{syscall.SIGINT, syscall.SIGTERM} var sigchan = make(chan os.Signal, 1) func init() { go waitForSignal() }
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论