Skip to content

Commit

Permalink
Report child error better (and later)
Browse files Browse the repository at this point in the history
We use a unix domain socketpair instead of a pipe for the sync pipe,
which allows us to use two-way shutdown. After sending the
context we shut down the write side which lets the child know
it finished reading.

We then block on a read in the parent for the child closing the file
(ensuring we close our version of it too) to sync for when the child
is finished initializing. If the read is non-empty we assume this
is an error report and fail with an error. Otherwise we continue as
before.

This also means we're now calling back the start callback later,
meaning at that point its more likely to have succeeded, as well as
having consumed all the container resources (like volume mounts,
making it safe to e.g. unmount them when the start callback is
called).

Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)
  • Loading branch information
alexlarsson authored and Michael Crosby committed Jun 26, 2014
1 parent f975ff9 commit ca95445
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 5 deletions.
8 changes: 7 additions & 1 deletion namespaces/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
if err != nil {
return -1, err
}
defer syncPipe.Close()

if container.Tty {
master, console, err = system.CreateMasterAndConsole()
Expand All @@ -52,6 +53,9 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
return -1, err
}

// Now we passed the pipe to the child, close our side
syncPipe.CloseChild()

started, err := system.GetProcessStartTime(command.Process.Pid)
if err != nil {
return -1, err
Expand Down Expand Up @@ -90,7 +94,9 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
defer libcontainer.DeleteState(dataPath)

// Sync with child
syncPipe.Close()
if err := syncPipe.BlockOnChild(); err != nil {
return -1, err
}

if startCallback != nil {
startCallback()
Expand Down
10 changes: 7 additions & 3 deletions namespaces/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ import (
// Move this to libcontainer package.
// Init is the init process that first runs inside a new namespace to setup mounts, users, networking,
// and other options required for the new container.
func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error {
func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) (err error) {
defer func() {
if err != nil {
syncPipe.ReportError(err)
}
}()

rootfs, err := utils.ResolveRootfs(uncleanRootfs)
if err != nil {
return err
Expand All @@ -42,10 +48,8 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn
// We always read this as it is a way to sync with the parent as well
networkState, err := syncPipe.ReadFromParent()
if err != nil {
syncPipe.Close()
return err
}
syncPipe.Close()

if consolePath != "" {
if err := console.OpenAndDup(consolePath); err != nil {
Expand Down
31 changes: 30 additions & 1 deletion namespaces/sync_pipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io/ioutil"
"os"
"syscall"

"github.com/docker/libcontainer/network"
)
Expand All @@ -18,10 +19,14 @@ type SyncPipe struct {

func NewSyncPipe() (s *SyncPipe, err error) {
s = &SyncPipe{}
s.child, s.parent, err = os.Pipe()

fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
if err != nil {
return nil, err
}
s.child = os.NewFile(uintptr(fds[0]), "child syncpipe")
s.parent = os.NewFile(uintptr(fds[1]), "parent syncpipe")

return s, nil
}

Expand Down Expand Up @@ -51,6 +56,18 @@ func (s *SyncPipe) SendToChild(networkState *network.NetworkState) error {
return err
}
s.parent.Write(data)
syscall.Shutdown(int(s.parent.Fd()), syscall.SHUT_WR)
return nil
}

func (s *SyncPipe) BlockOnChild() error {
data, err := ioutil.ReadAll(s.parent)
if err != nil {
return nil
}
if len(data) > 0 {
return fmt.Errorf("Child error: %s", string(data))
}
return nil
}

Expand All @@ -69,6 +86,11 @@ func (s *SyncPipe) ReadFromParent() (*network.NetworkState, error) {

}

func (s *SyncPipe) ReportError(err error) {
s.child.Write([]byte(err.Error()))
s.CloseChild()
}

func (s *SyncPipe) Close() error {
if s.parent != nil {
s.parent.Close()
Expand All @@ -78,3 +100,10 @@ func (s *SyncPipe) Close() error {
}
return nil
}

func (s *SyncPipe) CloseChild() {
if s.child != nil {
s.child.Close()
s.child = nil
}
}

0 comments on commit ca95445

Please sign in to comment.