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 }