Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions notify_socket.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,22 @@ func (s *notifySocket) run(pid1 int) error {
}
}

// forward reads systemd notifications from the container and forwards them
// to notifySocketHost.
func (s *notifySocket) forward(process *libcontainer.Process, detach bool) error {
if detach {
pid, err := process.Pid()
if err != nil {
return err
}
_ = s.run(pid)
} else {
_ = s.run(os.Getpid())
go func() { _ = s.run(0) }()
}
return nil
}

// notifyHost tells the host (usually systemd) that the container reported READY.
// Also sends MAINPID and BARRIER.
func notifyHost(client *net.UnixConn, ready []byte, pid1 int) error {
Expand Down
32 changes: 4 additions & 28 deletions signals.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os/signal"

"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/utils"

"github.com/sirupsen/logrus"
Expand All @@ -16,15 +15,7 @@ const signalBufferSize = 2048

// newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals
// while still forwarding all other signals to the process.
// If notifySocket is present, use it to read systemd notifications from the container and
// forward them to notifySocketHost.
func newSignalHandler(enableSubreaper bool, notifySocket *notifySocket) chan *signalHandler {
if enableSubreaper {
// set us as the subreaper before registering the signal handler for the container
if err := system.SetSubreaper(1); err != nil {
logrus.Warn(err)
}
}
func newSignalHandler() chan *signalHandler {
handler := make(chan *signalHandler)
// signal.Notify is actually quite expensive, as it has to configure the
// signal mask and add signal handlers for all signals (all ~65 of them).
Expand All @@ -37,8 +28,7 @@ func newSignalHandler(enableSubreaper bool, notifySocket *notifySocket) chan *si
// handle all signals for the process.
signal.Notify(s)
handler <- &signalHandler{
signals: s,
notifySocket: notifySocket,
signals: s,
}
}()
return handler
Expand All @@ -52,33 +42,19 @@ type exit struct {
}

type signalHandler struct {
signals chan os.Signal
notifySocket *notifySocket
signals chan os.Signal
}

// forward handles the main signal event loop forwarding, resizing, or reaping depending
// on the signal received.
func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach bool) (int, error) {
func (h *signalHandler) forward(process *libcontainer.Process, tty *tty) (int, error) {
// make sure we know the pid of our main process so that we can return
// after it dies.
if detach && h.notifySocket == nil {
return 0, nil
}

pid1, err := process.Pid()
if err != nil {
return -1, err
}

if h.notifySocket != nil {
if detach {
_ = h.notifySocket.run(pid1)
return 0, nil
}
_ = h.notifySocket.run(os.Getpid())
go func() { _ = h.notifySocket.run(0) }()
}

// Perform the initial tty resize. Always ignore errors resizing because
// stdout might have disappeared (due to races with when SIGHUP is sent).
_ = tty.resize()
Expand Down
46 changes: 30 additions & 16 deletions utils_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/specconv"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/system/kernelversion"
"github.com/opencontainers/runc/libcontainer/utils"
)
Expand Down Expand Up @@ -218,14 +219,16 @@ type runner struct {
subCgroupPaths map[string]string
}

func (r *runner) run(config *specs.Process) (int, error) {
var err error
func (r *runner) run(config *specs.Process) (_ int, retErr error) {
detach := r.detach || (r.action == CT_ACT_CREATE)
defer func() {
if err != nil {
// For a non-detached container, or we get an error, we
// should destroy the container.
if !detach || retErr != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we always want to do this for !detach containers? What if we fail before we start the process? Is this handled fine by r.destroy()? Or does this kill something else too, and we should do it here?

I don't know, maybe we want to just do it later in the other defer? I'd like to understand why one or the other, though. But that is what we were doing before, though, the destroy was not called for this part of the code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we always want to do this for !detach containers?

Yes, I just move this destroy from the last to defer.

What if we fail before we start the process? Is this handled fine by r.destroy()? Or does this kill something else too, and we should do it here?

I think without !detach || , it behaves the same as before — it would call destroy whenever an error occurred, regardless of whether it was a detach operation or not.

Please let me know if I missed anything.

r.destroy()
}
}()
if err = r.checkTerminal(config); err != nil {
if err := r.checkTerminal(config); err != nil {
return -1, err
}
process, err := newProcess(config)
Expand All @@ -250,11 +253,19 @@ func (r *runner) run(config *specs.Process) (int, error) {
}
process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i)))
}
detach := r.detach || (r.action == CT_ACT_CREATE)
// Setting up IO is a two stage process. We need to modify process to deal
// with detaching containers, and then we get a tty after the container has
// started.
handlerCh := newSignalHandler(r.enableSubreaper, r.notifySocket)
if r.enableSubreaper {
// set us as the subreaper before registering the signal handler for the container
if err := system.SetSubreaper(1); err != nil {
logrus.Warn(err)
}
}
var handlerCh chan *signalHandler
if !detach {
handlerCh = newSignalHandler()
}
tty, err := setupIO(process, r.container, config.Terminal, detach, r.consoleSocket)
if err != nil {
return -1, err
Expand Down Expand Up @@ -282,29 +293,32 @@ func (r *runner) run(config *specs.Process) (int, error) {
if err != nil {
return -1, err
}
defer func() {
// We should terminate the process once we got an error.
if retErr != nil {
r.terminate(process)
Comment on lines +296 to +299
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated now. The function deferred previously will also do the exact same thing. Is this on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Therefore, was this also unnecessary in the past? I suggest we keep it, as there may have been scenarios we haven't taken into account.

}
}()
if err = tty.waitConsole(); err != nil {
r.terminate(process)
return -1, err
}
tty.ClosePostStart()
if r.pidFile != "" {
if err = createPidFile(r.pidFile, process); err != nil {
r.terminate(process)
return -1, err
}
}
handler := <-handlerCh
status, err := handler.forward(process, tty, detach)
if err != nil {
r.terminate(process)
if r.notifySocket != nil {
if err = r.notifySocket.forward(process, detach); err != nil {
return -1, err
}
}
if detach {
return 0, nil
}
if err == nil {
r.destroy()
}
return status, err
// For non-detached container, we should forward signals to the container.
handler := <-handlerCh
return handler.forward(process, tty)
}

func (r *runner) destroy() {
Expand Down
Loading