os.Process.Wait and signals before process dies

1,478 views
Skip to first unread message

ig...@mir2.org

unread,
Jul 13, 2015, 9:58:18 AM7/13/15
to golan...@googlegroups.com
Hello,

I would like to send SIGTERM to a child process while a goroutine waits for its temination in os.Process.Wait. However, I do not know how can I ensure that signal is *only* sent before the process terminates. For example, consider a fragment:

    p, err := os.StartProcess(path, argv, &attr)
    if err != nil {
        log.Fatal(err)
    }
    go func() {
        ...
        err := p.Signal(syscall.SIGTERM)
        ...
    }()
    status, err := p.Wait()

The trouble with this fragment is that p.Signal(syscall.SIGTERM) can be executed after the process p terminats and p.Wait() returns. This is not harmless as on a busy system after p.Wait() the operation system can reuse the pid for another process and SIGTERM will be sent to a wrong process. How can I avoid that?
   

Brad Fitzpatrick

unread,
Jul 13, 2015, 10:10:13 AM7/13/15
to ig...@mir2.org, golang-nuts
I'm not even aware of a general solution to this even in C.

Operating systems (at least Linux) seem to take care not to reuse pids very quickly. I think the best you can do is minimize your window where you don't know whether it's already been reaped by wait. If you were very concerned (and much was in your goroutine's ...), you could add a channel you close after Wait and do:

    select {
    case <-didWait:
    default:
           p.Signal(syscall.SIGTERM)
    }


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ian Lance Taylor

unread,
Jul 13, 2015, 10:42:26 AM7/13/15
to ig...@mir2.org, golang-nuts
You can poll the wait status in a loop with a sleep by calling
syscall.Wait4 passing syscall.WNOHANG. You can use a lock to ensure
that you never call Wait4 and Kill simultaneously. Then don't Kill
after the Wait4 succeeds. This is ugly because it requires a polling
loop.

Or, this is probably impossible in Go today, but if you are running on
GNU/Linux you could use CLONE_NEWPID to start the process. Then open
one of the files in /proc/PID/ns. That will keep the PID namespace
alive until the descriptor is closed. That means that the PID will
not be reused.

Ian

Matt Harden

unread,
Jul 13, 2015, 10:58:53 AM7/13/15
to Ian Lance Taylor, ig...@mir2.org, golang-nuts
Bear in mind also that on UNIX / Linux, you normally can't send a signal to a process you don't own unless you are running as root. One of many reasons not to run things as root. The process will also hang around in "orphaned" status until the parent runs Wait. Use a mutex to protect all your calls to p.Wait and p.Signal. You can avoid blocking on p.Wait by only running it after receiving a SIGCHLD.

Igor Bukanov

unread,
Jul 13, 2015, 4:48:51 PM7/13/15
to Ian Lance Taylor, golang-nuts
On 13 July 2015 at 16:42, Ian Lance Taylor <ia...@golang.org> wrote:
> You can poll the wait status in a loop with a sleep by calling
> syscall.Wait4 passing syscall.WNOHANG. You can use a lock to ensure
> that you never call Wait4 and Kill simultaneously. Then don't Kill
> after the Wait4 succeeds. This is ugly because it requires a polling
> loop.

Thanks for the tip about directly calling Wait4! Together with waiting
for syscall.SIGCHLD the code avoided the busy wait and eliminated an
extra goroutine. I used a select to wait both for the child signal and
a notification to terminate the child:

p, err := os.StartProcess(path, argv, &attr)
...
signalChild := make(chan os.Signal, 1)
defer close(signalChild)
signal.Notify(signalChild, syscall.SIGCHLD)
defer signal.Stop(signalChild)

var ws syscall.WaitStatus
childWait: for {
select {
case _ = (<-terminateNotification):
err := p.Signal(syscall.SIGTERM)
...
case _ = (<-signalChild):
pid, err := syscall.Wait4(p.Pid, &ws, syscall.WNOHANG, nil)
...
if pid != 0 {
break childWait
}
}
}
p.Release()
if ws.Exited() && ws.ExitStatus() == 0 {
...
}

>
> Or, this is probably impossible in Go today, but if you are running on
> GNU/Linux you could use CLONE_NEWPID to start the process. Then open
> one of the files in /proc/PID/ns. That will keep the PID namespace
> alive until the descriptor is closed. That means that the PID will
> not be reused.

CLONE_NEWPID requires CAP_SYS_ADMIN on Linux, so it does not work for
non-privileged processes even if
https://golang.org/pkg/syscall/#StartProcess and syscall.CLONE_NEWPID
indicate that it should be possible to code that in Go. Still I keep
that in mind for more complex cases.
Reply all
Reply to author
Forward
0 new messages