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