diff --git a/daemon/build.go b/daemon/build.go index 596ed01ab702bbbedef7b1fe0928e6e59bb4f7bd..b66a9058affb2ca7bba48e02e45301284ee2c8ef 100644 --- a/daemon/build.go +++ b/daemon/build.go @@ -17,7 +17,8 @@ import ( "bufio" "context" "io" - "io/ioutil" + "os" + "syscall" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -64,14 +65,16 @@ func (b *Backend) Build(req *pb.BuildRequest, stream pb.Control_BuildServer) err var berr error imageID, berr = builder.Build() - // in case there is error during Build stage, the backend will always waiting for content write into - // the pipeFile, which will cause frontend hangs forever. - // so if the output type is archive(pipeFile is not empty string) and any error occurred, we write the error - // message into the pipe to make the goroutine move on instead of hangs. if berr != nil && pipeWrapper != nil { - pipeWrapper.Close() - if perr := ioutil.WriteFile(pipeWrapper.PipeFile, []byte(berr.Error()), constant.DefaultRootFileMode); perr != nil { - logrus.WithField(util.LogKeySessionID, req.BuildID).Warnf("Write error [%v] in to pipe file failed: %v", berr, perr) + // in case there is error during Build stage, the backend will always waiting for content write into + // the pipeFile, which will cause frontend hangs forever. + // so if the output type is archive(pipeWrapper is not nil) and any error occurred, we try to open and close + // the pipe in O_NONBLOCK flag to make the goroutine move on instead of hangs. + f, perr := os.OpenFile(pipeWrapper.PipeFile, os.O_WRONLY|syscall.O_NONBLOCK, os.ModeNamedPipe) + if perr == nil && f != nil { + if cerr := f.Close(); cerr != nil { + logrus.WithField(util.LogKeySessionID, req.BuildID).Warnf("Close pipe file failed: %v", cerr) + } } } @@ -96,7 +99,7 @@ func (b *Backend) Build(req *pb.BuildRequest, stream pb.Control_BuildServer) err buf := make([]byte, constant.BufferSize, constant.BufferSize) for { length, rerr := reader.Read(buf) - if rerr == io.EOF || pipeWrapper.Done { + if rerr == io.EOF { break } if rerr != nil { diff --git a/daemon/save.go b/daemon/save.go index 9899414d464a4f4a17b60b3b3cd980abf3149a75..25f1729426ce4891850f1e27f109abb42d5e7b07 100644 --- a/daemon/save.go +++ b/daemon/save.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" "strings" + "syscall" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -173,7 +174,16 @@ func exportHandler(ctx context.Context, opts *saveOptions, cliLogger *logger.Log } if err := exporter.Export(opts.imageID, opts.output, exOpts, opts.store); err != nil { - opts.pipeWrapper.Close() + // in case there is error during export, the backend will always waiting for content write into + // the pipeFile, which will cause frontend hangs forever. + // so if any error occurred, we try to open and close the pipe in O_NONBLOCK flag to make the + // goroutine move on instead of hangs. + f, perr := os.OpenFile(opts.pipeWrapper.PipeFile, os.O_WRONLY|syscall.O_NONBLOCK, os.ModeNamedPipe) + if perr == nil && f != nil { + if cerr := f.Close(); cerr != nil { + logrus.WithField(util.LogKeySessionID, opts.saveID).Warnf("Close pipe file failed: %v", cerr) + } + } logrus.Errorf("Save image %s failed: %v", opts.imageInfo, err) return err } @@ -198,7 +208,7 @@ func dataHandler(req *pb.SaveRequest, stream pb.Control_SaveServer, opts *saveOp buf := make([]byte, constant.BufferSize, constant.BufferSize) for { length, err := reader.Read(buf) - if err == io.EOF || opts.pipeWrapper.Done { + if err == io.EOF { break } if err != nil {