github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/restartmanager/restartmanager.go (about)

     1  package restartmanager // import "github.com/docker/docker/restartmanager"
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/docker/docker/api/types/container"
    10  )
    11  
    12  const (
    13  	backoffMultiplier = 2
    14  	defaultTimeout    = 100 * time.Millisecond
    15  	maxRestartTimeout = 1 * time.Minute
    16  )
    17  
    18  // ErrRestartCanceled is returned when the restart manager has been
    19  // canceled and will no longer restart the container.
    20  var ErrRestartCanceled = errors.New("restart canceled")
    21  
    22  // RestartManager defines object that controls container restarting rules.
    23  type RestartManager struct {
    24  	sync.Mutex
    25  	sync.Once
    26  	policy       container.RestartPolicy
    27  	restartCount int
    28  	timeout      time.Duration
    29  	active       bool
    30  	cancel       chan struct{}
    31  	canceled     bool
    32  }
    33  
    34  // New returns a new RestartManager based on a policy.
    35  func New(policy container.RestartPolicy, restartCount int) *RestartManager {
    36  	return &RestartManager{policy: policy, restartCount: restartCount, cancel: make(chan struct{})}
    37  }
    38  
    39  // SetPolicy sets the restart-policy for the RestartManager.
    40  func (rm *RestartManager) SetPolicy(policy container.RestartPolicy) {
    41  	rm.Lock()
    42  	rm.policy = policy
    43  	rm.Unlock()
    44  }
    45  
    46  // ShouldRestart returns whether the container should be restarted.
    47  func (rm *RestartManager) ShouldRestart(exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration) (bool, chan error, error) {
    48  	if rm.policy.IsNone() {
    49  		return false, nil, nil
    50  	}
    51  	rm.Lock()
    52  	unlockOnExit := true
    53  	defer func() {
    54  		if unlockOnExit {
    55  			rm.Unlock()
    56  		}
    57  	}()
    58  
    59  	if rm.canceled {
    60  		return false, nil, ErrRestartCanceled
    61  	}
    62  
    63  	if rm.active {
    64  		return false, nil, fmt.Errorf("invalid call on an active restart manager")
    65  	}
    66  	// if the container ran for more than 10s, regardless of status and policy reset the
    67  	// the timeout back to the default.
    68  	if executionDuration.Seconds() >= 10 {
    69  		rm.timeout = 0
    70  	}
    71  	switch {
    72  	case rm.timeout == 0:
    73  		rm.timeout = defaultTimeout
    74  	case rm.timeout < maxRestartTimeout:
    75  		rm.timeout *= backoffMultiplier
    76  	}
    77  	if rm.timeout > maxRestartTimeout {
    78  		rm.timeout = maxRestartTimeout
    79  	}
    80  
    81  	var restart bool
    82  	switch {
    83  	case rm.policy.IsAlways():
    84  		restart = true
    85  	case rm.policy.IsUnlessStopped() && !hasBeenManuallyStopped:
    86  		restart = true
    87  	case rm.policy.IsOnFailure():
    88  		// the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count
    89  		if maxRetryCount := rm.policy.MaximumRetryCount; maxRetryCount == 0 || rm.restartCount < maxRetryCount {
    90  			restart = exitCode != 0
    91  		}
    92  	}
    93  
    94  	if !restart {
    95  		rm.active = false
    96  		return false, nil, nil
    97  	}
    98  
    99  	rm.restartCount++
   100  
   101  	unlockOnExit = false
   102  	rm.active = true
   103  	rm.Unlock()
   104  
   105  	ch := make(chan error)
   106  	go func() {
   107  		timeout := time.NewTimer(rm.timeout)
   108  		defer timeout.Stop()
   109  
   110  		select {
   111  		case <-rm.cancel:
   112  			ch <- ErrRestartCanceled
   113  			close(ch)
   114  		case <-timeout.C:
   115  			rm.Lock()
   116  			close(ch)
   117  			rm.active = false
   118  			rm.Unlock()
   119  		}
   120  	}()
   121  
   122  	return true, ch, nil
   123  }
   124  
   125  // Cancel tells the RestartManager to no longer restart the container.
   126  func (rm *RestartManager) Cancel() {
   127  	rm.Do(func() {
   128  		rm.Lock()
   129  		rm.canceled = true
   130  		close(rm.cancel)
   131  		rm.Unlock()
   132  	})
   133  }