github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/distribution/xfer/upload.go (about) 1 package xfer // import "github.com/docker/docker/distribution/xfer" 2 3 import ( 4 "context" 5 "errors" 6 "time" 7 8 "github.com/containerd/log" 9 "github.com/docker/distribution" 10 "github.com/docker/docker/layer" 11 "github.com/docker/docker/pkg/progress" 12 ) 13 14 const maxUploadAttempts = 5 15 16 // LayerUploadManager provides task management and progress reporting for 17 // uploads. 18 type LayerUploadManager struct { 19 tm *transferManager 20 waitDuration time.Duration 21 } 22 23 // SetConcurrency sets the max concurrent uploads for each push 24 func (lum *LayerUploadManager) SetConcurrency(concurrency int) { 25 lum.tm.setConcurrency(concurrency) 26 } 27 28 // NewLayerUploadManager returns a new LayerUploadManager. 29 func NewLayerUploadManager(concurrencyLimit int, options ...func(*LayerUploadManager)) *LayerUploadManager { 30 manager := LayerUploadManager{ 31 tm: newTransferManager(concurrencyLimit), 32 waitDuration: time.Second, 33 } 34 for _, option := range options { 35 option(&manager) 36 } 37 return &manager 38 } 39 40 type uploadTransfer struct { 41 transfer 42 43 remoteDescriptor distribution.Descriptor 44 err error 45 } 46 47 // An UploadDescriptor references a layer that may need to be uploaded. 48 type UploadDescriptor interface { 49 // Key returns the key used to deduplicate uploads. 50 Key() string 51 // ID returns the ID for display purposes. 52 ID() string 53 // DiffID should return the DiffID for this layer. 54 DiffID() layer.DiffID 55 // Upload is called to perform the Upload. 56 Upload(ctx context.Context, progressOutput progress.Output) (distribution.Descriptor, error) 57 // SetRemoteDescriptor provides the distribution.Descriptor that was 58 // returned by Upload. This descriptor is not to be confused with 59 // the UploadDescriptor interface, which is used for internally 60 // identifying layers that are being uploaded. 61 SetRemoteDescriptor(descriptor distribution.Descriptor) 62 } 63 64 // Upload is a blocking function which ensures the listed layers are present on 65 // the remote registry. It uses the string returned by the Key method to 66 // deduplicate uploads. 67 func (lum *LayerUploadManager) Upload(ctx context.Context, layers []UploadDescriptor, progressOutput progress.Output) error { 68 var ( 69 uploads []*uploadTransfer 70 dedupDescriptors = make(map[string]*uploadTransfer) 71 ) 72 73 for _, descriptor := range layers { 74 progress.Update(progressOutput, descriptor.ID(), "Preparing") 75 76 key := descriptor.Key() 77 if _, present := dedupDescriptors[key]; present { 78 continue 79 } 80 81 xferFunc := lum.makeUploadFunc(descriptor) 82 upload, watcher := lum.tm.transfer(descriptor.Key(), xferFunc, progressOutput) 83 defer upload.release(watcher) 84 uploads = append(uploads, upload.(*uploadTransfer)) 85 dedupDescriptors[key] = upload.(*uploadTransfer) 86 } 87 88 for _, upload := range uploads { 89 select { 90 case <-ctx.Done(): 91 return ctx.Err() 92 case <-upload.transfer.done(): 93 if upload.err != nil { 94 return upload.err 95 } 96 } 97 } 98 for _, l := range layers { 99 l.SetRemoteDescriptor(dedupDescriptors[l.Key()].remoteDescriptor) 100 } 101 102 return nil 103 } 104 105 func (lum *LayerUploadManager) makeUploadFunc(descriptor UploadDescriptor) doFunc { 106 return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) transfer { 107 u := &uploadTransfer{ 108 transfer: newTransfer(), 109 } 110 111 go func() { 112 defer func() { 113 close(progressChan) 114 }() 115 116 progressOutput := progress.ChanOutput(progressChan) 117 118 select { 119 case <-start: 120 default: 121 progress.Update(progressOutput, descriptor.ID(), "Waiting") 122 <-start 123 } 124 125 retries := 0 126 for { 127 remoteDescriptor, err := descriptor.Upload(u.transfer.context(), progressOutput) 128 if err == nil { 129 u.remoteDescriptor = remoteDescriptor 130 break 131 } 132 133 // If an error was returned because the context 134 // was cancelled, we shouldn't retry. 135 select { 136 case <-u.transfer.context().Done(): 137 u.err = err 138 return 139 default: 140 } 141 142 retries++ 143 if _, isDNR := err.(DoNotRetry); isDNR || retries == maxUploadAttempts { 144 log.G(context.TODO()).Errorf("Upload failed: %v", err) 145 u.err = err 146 return 147 } 148 149 log.G(context.TODO()).Errorf("Upload failed, retrying: %v", err) 150 delay := retries * 5 151 ticker := time.NewTicker(lum.waitDuration) 152 153 selectLoop: 154 for { 155 progress.Updatef(progressOutput, descriptor.ID(), "Retrying in %d second%s", delay, (map[bool]string{true: "s"})[delay != 1]) 156 select { 157 case <-ticker.C: 158 delay-- 159 if delay == 0 { 160 ticker.Stop() 161 break selectLoop 162 } 163 case <-u.transfer.context().Done(): 164 ticker.Stop() 165 u.err = errors.New("upload cancelled during retry delay") 166 return 167 } 168 } 169 } 170 }() 171 172 return u 173 } 174 }