Skip to content

Commit 25fd4a6

Browse files
committed
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. Bind mount the parent directory of the notify socket, so that "start" can create the socket and it is still accessible from the container. Signed-off-by: Giuseppe Scrivano <[email protected]>
1 parent 0ff5352 commit 25fd4a6

File tree

4 files changed

+116
-45
lines changed

4 files changed

+116
-45
lines changed

notify_socket.go

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import (
77
"fmt"
88
"net"
99
"os"
10+
"path"
1011
"path/filepath"
12+
"strconv"
13+
"time"
1114

15+
"github.com/opencontainers/runc/libcontainer"
1216
"github.com/opencontainers/runtime-spec/specs-go"
13-
14-
"github.com/sirupsen/logrus"
1517
"github.com/urfave/cli"
1618
)
1719

@@ -27,12 +29,12 @@ func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *
2729
}
2830

2931
root := filepath.Join(context.GlobalString("root"), id)
30-
path := filepath.Join(root, "notify.sock")
32+
socketPath := filepath.Join(root, "notify", "notify.sock")
3133

3234
notifySocket := &notifySocket{
3335
socket: nil,
3436
host: notifySocketHost,
35-
socketPath: path,
37+
socketPath: socketPath,
3638
}
3739

3840
return notifySocket
@@ -44,13 +46,19 @@ func (s *notifySocket) Close() error {
4446

4547
// If systemd is supporting sd_notify protocol, this function will add support
4648
// for sd_notify protocol from within the container.
47-
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
48-
mount := specs.Mount{Destination: s.host, Source: s.socketPath, Options: []string{"bind"}}
49+
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) error {
50+
pathInContainer := filepath.Join("/run/notify", path.Base(s.socketPath))
51+
mount := specs.Mount{
52+
Destination: path.Dir(pathInContainer),
53+
Source: path.Dir(s.socketPath),
54+
Options: []string{"bind", "nosuid", "noexec", "nodev", "ro"},
55+
}
4956
spec.Mounts = append(spec.Mounts, mount)
50-
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
57+
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", pathInContainer))
58+
return nil
5159
}
5260

53-
func (s *notifySocket) setupSocket() error {
61+
func (s *notifySocket) bindSocket() error {
5462
addr := net.UnixAddr{
5563
Name: s.socketPath,
5664
Net: "unixgram",
@@ -71,46 +79,92 @@ func (s *notifySocket) setupSocket() error {
7179
return nil
7280
}
7381

74-
// pid1 must be set only with -d, as it is used to set the new process as the main process
75-
// for the service in systemd
76-
func (s *notifySocket) run(pid1 int) {
77-
buf := make([]byte, 512)
78-
notifySocketHostAddr := net.UnixAddr{Name: s.host, Net: "unixgram"}
82+
func (s *notifySocket) setupSocketDirectory() error {
83+
return os.Mkdir(path.Dir(s.socketPath), 0755)
84+
}
85+
86+
func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*notifySocket, error) {
87+
notifySocket := newNotifySocket(context, notifySocketHost, id)
88+
if notifySocket == nil {
89+
return nil, nil
90+
}
91+
92+
if err := notifySocket.bindSocket(); err != nil {
93+
return nil, err
94+
}
95+
return notifySocket, nil
96+
}
97+
98+
func (n *notifySocket) waitForContainer(container libcontainer.Container) error {
99+
s, err := container.State()
100+
if err != nil {
101+
return err
102+
}
103+
return n.run(s.InitProcessPid)
104+
}
105+
106+
func (n *notifySocket) run(pid1 int) error {
107+
if n.socket == nil {
108+
return nil
109+
}
110+
notifySocketHostAddr := net.UnixAddr{Name: n.host, Net: "unixgram"}
79111
client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
80112
if err != nil {
81-
logrus.Error(err)
82-
return
113+
return err
83114
}
84-
for {
85-
r, err := s.socket.Read(buf)
86-
if err != nil {
87-
break
88-
}
89-
var out bytes.Buffer
90-
for _, line := range bytes.Split(buf[0:r], []byte{'\n'}) {
91-
if bytes.HasPrefix(line, []byte("READY=")) {
92-
_, err = out.Write(line)
93-
if err != nil {
94-
return
95-
}
96115

97-
_, err = out.Write([]byte{'\n'})
98-
if err != nil {
99-
return
100-
}
116+
ticker := time.NewTicker(time.Millisecond * 100)
117+
defer ticker.Stop()
101118

102-
_, err = client.Write(out.Bytes())
103-
if err != nil {
119+
fileChan := make(chan []byte)
120+
go func() {
121+
for {
122+
buf := make([]byte, 4096)
123+
r, err := n.socket.Read(buf)
124+
if err != nil {
125+
return
126+
}
127+
got := buf[0:r]
128+
// systemd-ready sends a single datagram with the state string as payload,
129+
// so we don't need to worry about partial messages.
130+
for _, line := range bytes.Split(got, []byte{'\n'}) {
131+
if bytes.HasPrefix(got, []byte("READY=")) {
132+
fileChan <- line
104133
return
105134
}
135+
}
106136

107-
// now we can inform systemd to use pid1 as the pid to monitor
108-
if pid1 > 0 {
109-
newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
110-
client.Write([]byte(newPid))
111-
}
112-
return
137+
}
138+
}()
139+
140+
for {
141+
select {
142+
case <-ticker.C:
143+
_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid1)))
144+
if err != nil {
145+
return nil
113146
}
147+
case b := <-fileChan:
148+
var out bytes.Buffer
149+
_, err = out.Write(b)
150+
if err != nil {
151+
return err
152+
}
153+
154+
_, err = out.Write([]byte{'\n'})
155+
if err != nil {
156+
return err
157+
}
158+
159+
_, err = client.Write(out.Bytes())
160+
if err != nil {
161+
return err
162+
}
163+
164+
// now we can inform systemd to use pid1 as the pid to monitor
165+
newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
166+
client.Write([]byte(newPid))
167+
return nil
114168
}
115169
}
116170
}

signals.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
7070
h.notifySocket.run(pid1)
7171
return 0, nil
7272
}
73+
h.notifySocket.run(os.Getpid())
7374
go h.notifySocket.run(0)
7475
}
7576

@@ -97,9 +98,6 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
9798
// status because we must ensure that any of the go specific process
9899
// fun such as flushing pipes are complete before we return.
99100
process.Wait()
100-
if h.notifySocket != nil {
101-
h.notifySocket.Close()
102-
}
103101
return e.status, nil
104102
}
105103
}

start.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"errors"
55
"fmt"
6+
"os"
67

78
"github.com/opencontainers/runc/libcontainer"
89
"github.com/urfave/cli"
@@ -31,7 +32,17 @@ your host.`,
3132
}
3233
switch status {
3334
case libcontainer.Created:
34-
return container.Exec()
35+
notifySocket, err := notifySocketStart(context, os.Getenv("NOTIFY_SOCKET"), container.ID())
36+
if err != nil {
37+
return err
38+
}
39+
if err := container.Exec(); err != nil {
40+
return err
41+
}
42+
if notifySocket != nil {
43+
return notifySocket.waitForContainer(container)
44+
}
45+
return nil
3546
case libcontainer.Stopped:
3647
return errors.New("cannot start a container that has stopped")
3748
case libcontainer.Running:

utils_linux.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,9 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
408408

409409
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
410410
if notifySocket != nil {
411-
notifySocket.setupSpec(context, spec)
411+
if err := notifySocket.setupSpec(context, spec); err != nil {
412+
return -1, err
413+
}
412414
}
413415

414416
container, err := createContainer(context, id, spec)
@@ -417,10 +419,16 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
417419
}
418420

419421
if notifySocket != nil {
420-
err := notifySocket.setupSocket()
422+
err := notifySocket.setupSocketDirectory()
421423
if err != nil {
422424
return -1, err
423425
}
426+
if action == CT_ACT_RUN {
427+
err := notifySocket.bindSocket()
428+
if err != nil {
429+
return -1, err
430+
}
431+
}
424432
}
425433

426434
// Support on-demand socket activation by passing file descriptors into the container init process.

0 commit comments

Comments
 (0)