From ecf53c23545092019602578583031c28fde4d2a1 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Fri, 25 May 2018 18:04:06 +0200 Subject: [PATCH] sd-notify: do not hang when NOTIFY_SOCKET is used with create if NOTIFY_SOCKET is used, do not block the main runc process waiting for events on the notify socket. Change the logic to create a new process that monitors exclusively the notify socket until an event is received. Signed-off-by: Giuseppe Scrivano --- init.go | 12 +++++++ notify_socket.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++--------- signals.go | 5 +-- 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/init.go b/init.go index c8f453192..6a3d9e91c 100644 --- a/init.go +++ b/init.go @@ -20,6 +20,18 @@ var initCommand = cli.Command{ Name: "init", Usage: `initialize the namespaces and launch the process (do not call it outside of runc)`, Action: func(context *cli.Context) error { + // If NOTIFY_SOCKET is used create a new process that stays around + // so to not block "runc start". It will automatically exits when the + // container notifies that it is ready, or when the container is deleted + if os.Getenv("_NOTIFY_SOCKET_FD") != "" { + fd := os.Getenv("_NOTIFY_SOCKET_FD") + pid := os.Getenv("_NOTIFY_SOCKET_PID") + hostNotifySocket := os.Getenv("_NOTIFY_SOCKET_HOST") + notifySocketPath := os.Getenv("_NOTIFY_SOCKET_PATH") + notifySocketInit(fd, pid, hostNotifySocket, notifySocketPath) + os.Exit(0) + } + factory, _ := libcontainer.New("") if err := factory.StartInitialization(); err != nil { // as the error is sent back to the parent there is no need to log diff --git a/notify_socket.go b/notify_socket.go index cd6c0a989..e04e9d660 100644 --- a/notify_socket.go +++ b/notify_socket.go @@ -6,10 +6,13 @@ import ( "bytes" "fmt" "net" + "os" + "os/exec" "path/filepath" + "strconv" + "time" "github.com/opencontainers/runtime-spec/specs-go" - "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -64,24 +67,94 @@ func (s *notifySocket) setupSocket() error { return nil } +func (notifySocket *notifySocket) notifyNewPid(pid int) { + notifySocketHostAddr := net.UnixAddr{Name: notifySocket.host, Net: "unixgram"} + client, err := net.DialUnix("unixgram", nil, ¬ifySocketHostAddr) + if err != nil { + return + } + newPid := fmt.Sprintf("MAINPID=%d\n", pid) + client.Write([]byte(newPid)) +} + // pid1 must be set only with -d, as it is used to set the new process as the main process // for the service in systemd func (notifySocket *notifySocket) run(pid1 int) { - buf := make([]byte, 512) - notifySocketHostAddr := net.UnixAddr{Name: notifySocket.host, Net: "unixgram"} - client, err := net.DialUnix("unixgram", nil, ¬ifySocketHostAddr) + file, err := notifySocket.socket.File() if err != nil { logrus.Error(err) return } - for { - r, err := notifySocket.socket.Read(buf) - if err != nil { - break + defer file.Close() + defer notifySocket.socket.Close() + + cmd := exec.Command("/proc/self/exe", "init") + cmd.ExtraFiles = []*os.File{file} + cmd.Env = append(cmd.Env, "_NOTIFY_SOCKET_FD=3", + fmt.Sprintf("_NOTIFY_SOCKET_PID=%d", pid1), + fmt.Sprintf("_NOTIFY_SOCKET_HOST=%s", notifySocket.host), + fmt.Sprintf("_NOTIFY_SOCKET_PATH=%s", notifySocket.socketPath)) + + if err := cmd.Start(); err != nil { + logrus.Fatal(err) + } + notifySocket.notifyNewPid(cmd.Process.Pid) + cmd.Process.Release() +} + +func notifySocketInit(envFd string, envPid string, notifySocketHost string, notifySocketPath string) { + intFd, err := strconv.Atoi(envFd) + if err != nil { + return + } + pid1, err := strconv.Atoi(envPid) + if err != nil { + return + } + + file := os.NewFile(uintptr(intFd), "unixgram") + defer file.Close() + + fileChan := make(chan []byte) + exitChan := make(chan bool) + + go func() { + for { + buf := make([]byte, 512) + r, err := file.Read(buf) + if err != nil { + return + } + fileChan <- buf[0:r] } - var out bytes.Buffer - for _, line := range bytes.Split(buf[0:r], []byte{'\n'}) { - if bytes.HasPrefix(line, []byte("READY=")) { + }() + go func() { + for { + if _, err := os.Stat(notifySocketPath); os.IsNotExist(err) { + exitChan <- true + return + } + time.Sleep(time.Second) + } + }() + + notifySocketHostAddr := net.UnixAddr{Name: notifySocketHost, Net: "unixgram"} + client, err := net.DialUnix("unixgram", nil, ¬ifySocketHostAddr) + if err != nil { + return + } + + for { + select { + case <-exitChan: + return + case b := <-fileChan: + for _, line := range bytes.Split(b, []byte{'\n'}) { + if !bytes.HasPrefix(line, []byte("READY=")) { + continue + } + + var out bytes.Buffer _, err = out.Write(line) if err != nil { return @@ -98,10 +171,8 @@ func (notifySocket *notifySocket) run(pid1 int) { } // now we can inform systemd to use pid1 as the pid to monitor - if pid1 > 0 { - newPid := fmt.Sprintf("MAINPID=%d\n", pid1) - client.Write([]byte(newPid)) - } + newPid := fmt.Sprintf("MAINPID=%d\n", pid1) + client.Write([]byte(newPid)) return } } diff --git a/signals.go b/signals.go index 1811de837..d0988cb39 100644 --- a/signals.go +++ b/signals.go @@ -70,7 +70,7 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach h.notifySocket.run(pid1) return 0, nil } else { - go h.notifySocket.run(0) + h.notifySocket.run(os.Getpid()) } } @@ -98,9 +98,6 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach // status because we must ensure that any of the go specific process // fun such as flushing pipes are complete before we return. process.Wait() - if h.notifySocket != nil { - h.notifySocket.Close() - } return e.status, nil } }