golang重新启动的父进程未收到SIGINT

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?

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

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.