github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/distribution/utils/progress.go (about) 1 package utils // import "github.com/docker/docker/distribution/utils" 2 3 import ( 4 "context" 5 "io" 6 "net" 7 "os" 8 "syscall" 9 10 "github.com/containerd/log" 11 "github.com/docker/docker/pkg/progress" 12 "github.com/docker/docker/pkg/streamformatter" 13 ) 14 15 // WriteDistributionProgress is a helper for writing progress from chan to JSON 16 // stream with an optional cancel function. 17 func WriteDistributionProgress(cancelFunc func(), outStream io.Writer, progressChan <-chan progress.Progress) { 18 progressOutput := streamformatter.NewJSONProgressOutput(outStream, false) 19 operationCancelled := false 20 21 for prog := range progressChan { 22 if err := progressOutput.WriteProgress(prog); err != nil && !operationCancelled { 23 // don't log broken pipe errors as this is the normal case when a client aborts 24 if isBrokenPipe(err) { 25 log.G(context.TODO()).Info("Pull session cancelled") 26 } else { 27 log.G(context.TODO()).Errorf("error writing progress to client: %v", err) 28 } 29 cancelFunc() 30 operationCancelled = true 31 // Don't return, because we need to continue draining 32 // progressChan until it's closed to avoid a deadlock. 33 } 34 } 35 } 36 37 func isBrokenPipe(e error) bool { 38 if netErr, ok := e.(*net.OpError); ok { 39 e = netErr.Err 40 if sysErr, ok := netErr.Err.(*os.SyscallError); ok { 41 e = sysErr.Err 42 } 43 } 44 return e == syscall.EPIPE 45 }