gopkg.in/docker/docker.v20@v20.10.27/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 interface { 24 Cancel() error 25 ShouldRestart(exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration) (bool, chan error, error) 26 } 27 28 type restartManager struct { 29 sync.Mutex 30 sync.Once 31 policy container.RestartPolicy 32 restartCount int 33 timeout time.Duration 34 active bool 35 cancel chan struct{} 36 canceled bool 37 } 38 39 // New returns a new restartManager based on a policy. 40 func New(policy container.RestartPolicy, restartCount int) RestartManager { 41 return &restartManager{policy: policy, restartCount: restartCount, cancel: make(chan struct{})} 42 } 43 44 func (rm *restartManager) SetPolicy(policy container.RestartPolicy) { 45 rm.Lock() 46 rm.policy = policy 47 rm.Unlock() 48 } 49 50 func (rm *restartManager) ShouldRestart(exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration) (bool, chan error, error) { 51 if rm.policy.IsNone() { 52 return false, nil, nil 53 } 54 rm.Lock() 55 unlockOnExit := true 56 defer func() { 57 if unlockOnExit { 58 rm.Unlock() 59 } 60 }() 61 62 if rm.canceled { 63 return false, nil, ErrRestartCanceled 64 } 65 66 if rm.active { 67 return false, nil, fmt.Errorf("invalid call on an active restart manager") 68 } 69 // if the container ran for more than 10s, regardless of status and policy reset the 70 // the timeout back to the default. 71 if executionDuration.Seconds() >= 10 { 72 rm.timeout = 0 73 } 74 switch { 75 case rm.timeout == 0: 76 rm.timeout = defaultTimeout 77 case rm.timeout < maxRestartTimeout: 78 rm.timeout *= backoffMultiplier 79 } 80 if rm.timeout > maxRestartTimeout { 81 rm.timeout = maxRestartTimeout 82 } 83 84 var restart bool 85 switch { 86 case rm.policy.IsAlways(): 87 restart = true 88 case rm.policy.IsUnlessStopped() && !hasBeenManuallyStopped: 89 restart = true 90 case rm.policy.IsOnFailure(): 91 // the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count 92 if max := rm.policy.MaximumRetryCount; max == 0 || rm.restartCount < max { 93 restart = exitCode != 0 94 } 95 } 96 97 if !restart { 98 rm.active = false 99 return false, nil, nil 100 } 101 102 rm.restartCount++ 103 104 unlockOnExit = false 105 rm.active = true 106 rm.Unlock() 107 108 ch := make(chan error) 109 go func() { 110 timeout := time.NewTimer(rm.timeout) 111 defer timeout.Stop() 112 113 select { 114 case <-rm.cancel: 115 ch <- ErrRestartCanceled 116 close(ch) 117 case <-timeout.C: 118 rm.Lock() 119 close(ch) 120 rm.active = false 121 rm.Unlock() 122 } 123 }() 124 125 return true, ch, nil 126 } 127 128 func (rm *restartManager) Cancel() error { 129 rm.Do(func() { 130 rm.Lock() 131 rm.canceled = true 132 close(rm.cancel) 133 rm.Unlock() 134 }) 135 return nil 136 }