in sources/host-ctr/cmd/host-ctr/main.go [819:878]
func pullImage(ctx context.Context, source string, client *containerd.Client, registryConfigPath string) (containerd.Image, error) {
// Handle registry config
var registryConfig *RegistryConfig
if registryConfigPath != "" {
var err error
registryConfig, err = NewRegistryConfig(registryConfigPath)
if err != nil {
log.G(ctx).
WithError(err).
WithField("registry-config", registryConfigPath).
Error("failed to read registry config")
return nil, err
}
}
// Pull the image
// Retry with exponential backoff when failures occur, maximum retry duration will not exceed 31 seconds
const maxRetryAttempts = 5
const intervalMultiplier = 2
const maxRetryInterval = 30 * time.Second
const jitterPeakAmplitude = 4000
const jitterLowerBound = 2000
var retryInterval = 1 * time.Second
var retryAttempts = 0
var img containerd.Image
for {
var err error
img, err = client.Pull(ctx, source,
withDynamicResolver(ctx, source, registryConfig),
containerd.WithSchema1Conversion)
if err == nil {
log.G(ctx).WithField("img", img.Name()).Info("pulled image successfully")
break
}
if retryAttempts >= maxRetryAttempts {
return nil, errors.Wrap(err, "retries exhausted")
}
// Add a random jitter between 2 - 6 seconds to the retry interval
retryIntervalWithJitter := retryInterval + time.Duration(rand.Int31n(jitterPeakAmplitude))*time.Millisecond + jitterLowerBound*time.Millisecond
log.G(ctx).WithError(err).Warnf("failed to pull image. waiting %s before retrying...", retryIntervalWithJitter)
timer := time.NewTimer(retryIntervalWithJitter)
select {
case <-timer.C:
retryInterval *= intervalMultiplier
if retryInterval > maxRetryInterval {
retryInterval = maxRetryInterval
}
retryAttempts++
case <-ctx.Done():
return nil, errors.Wrap(err, "context ended while retrying")
}
}
log.G(ctx).WithField("img", img.Name()).Info("unpacking image...")
if err := img.Unpack(ctx, containerd.DefaultSnapshotter); err != nil {
return nil, errors.Wrap(err, "failed to unpack image")
}
return img, nil
}