github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/distribution/xfer/upload.go (about)

     1  package xfer
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  
     7  	"github.com/Sirupsen/logrus"
     8  	"github.com/docker/distribution/digest"
     9  	"github.com/docker/docker/layer"
    10  	"github.com/docker/docker/pkg/progress"
    11  	"golang.org/x/net/context"
    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  }
    21  
    22  // NewLayerUploadManager returns a new LayerUploadManager.
    23  func NewLayerUploadManager(concurrencyLimit int) *LayerUploadManager {
    24  	return &LayerUploadManager{
    25  		tm: NewTransferManager(concurrencyLimit),
    26  	}
    27  }
    28  
    29  type uploadTransfer struct {
    30  	Transfer
    31  
    32  	diffID layer.DiffID
    33  	digest digest.Digest
    34  	err    error
    35  }
    36  
    37  // An UploadDescriptor references a layer that may need to be uploaded.
    38  type UploadDescriptor interface {
    39  	// Key returns the key used to deduplicate uploads.
    40  	Key() string
    41  	// ID returns the ID for display purposes.
    42  	ID() string
    43  	// DiffID should return the DiffID for this layer.
    44  	DiffID() layer.DiffID
    45  	// Upload is called to perform the Upload.
    46  	Upload(ctx context.Context, progressOutput progress.Output) (digest.Digest, error)
    47  }
    48  
    49  // Upload is a blocking function which ensures the listed layers are present on
    50  // the remote registry. It uses the string returned by the Key method to
    51  // deduplicate uploads.
    52  func (lum *LayerUploadManager) Upload(ctx context.Context, layers []UploadDescriptor, progressOutput progress.Output) (map[layer.DiffID]digest.Digest, error) {
    53  	var (
    54  		uploads          []*uploadTransfer
    55  		digests          = make(map[layer.DiffID]digest.Digest)
    56  		dedupDescriptors = make(map[string]struct{})
    57  	)
    58  
    59  	for _, descriptor := range layers {
    60  		progress.Update(progressOutput, descriptor.ID(), "Preparing")
    61  
    62  		key := descriptor.Key()
    63  		if _, present := dedupDescriptors[key]; present {
    64  			continue
    65  		}
    66  		dedupDescriptors[key] = struct{}{}
    67  
    68  		xferFunc := lum.makeUploadFunc(descriptor)
    69  		upload, watcher := lum.tm.Transfer(descriptor.Key(), xferFunc, progressOutput)
    70  		defer upload.Release(watcher)
    71  		uploads = append(uploads, upload.(*uploadTransfer))
    72  	}
    73  
    74  	for _, upload := range uploads {
    75  		select {
    76  		case <-ctx.Done():
    77  			return nil, ctx.Err()
    78  		case <-upload.Transfer.Done():
    79  			if upload.err != nil {
    80  				return nil, upload.err
    81  			}
    82  			digests[upload.diffID] = upload.digest
    83  		}
    84  	}
    85  
    86  	return digests, nil
    87  }
    88  
    89  func (lum *LayerUploadManager) makeUploadFunc(descriptor UploadDescriptor) DoFunc {
    90  	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
    91  		u := &uploadTransfer{
    92  			Transfer: NewTransfer(),
    93  			diffID:   descriptor.DiffID(),
    94  		}
    95  
    96  		go func() {
    97  			defer func() {
    98  				close(progressChan)
    99  			}()
   100  
   101  			progressOutput := progress.ChanOutput(progressChan)
   102  
   103  			select {
   104  			case <-start:
   105  			default:
   106  				progress.Update(progressOutput, descriptor.ID(), "Waiting")
   107  				<-start
   108  			}
   109  
   110  			retries := 0
   111  			for {
   112  				digest, err := descriptor.Upload(u.Transfer.Context(), progressOutput)
   113  				if err == nil {
   114  					u.digest = digest
   115  					break
   116  				}
   117  
   118  				// If an error was returned because the context
   119  				// was cancelled, we shouldn't retry.
   120  				select {
   121  				case <-u.Transfer.Context().Done():
   122  					u.err = err
   123  					return
   124  				default:
   125  				}
   126  
   127  				retries++
   128  				if _, isDNR := err.(DoNotRetry); isDNR || retries == maxUploadAttempts {
   129  					logrus.Errorf("Upload failed: %v", err)
   130  					u.err = err
   131  					return
   132  				}
   133  
   134  				logrus.Errorf("Upload failed, retrying: %v", err)
   135  				delay := retries * 5
   136  				ticker := time.NewTicker(time.Second)
   137  
   138  			selectLoop:
   139  				for {
   140  					progress.Updatef(progressOutput, descriptor.ID(), "Retrying in %d seconds", delay)
   141  					select {
   142  					case <-ticker.C:
   143  						delay--
   144  						if delay == 0 {
   145  							ticker.Stop()
   146  							break selectLoop
   147  						}
   148  					case <-u.Transfer.Context().Done():
   149  						ticker.Stop()
   150  						u.err = errors.New("upload cancelled during retry delay")
   151  						return
   152  					}
   153  				}
   154  			}
   155  		}()
   156  
   157  		return u
   158  	}
   159  }