forked from moby/moby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
restartmanager.go
128 lines (110 loc) · 2.75 KB
/
restartmanager.go
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
package restartmanager
import (
"errors"
"fmt"
"sync"
"time"
"github.com/docker/engine-api/types/container"
)
const (
backoffMultiplier = 2
defaultTimeout = 100 * time.Millisecond
)
// 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 active restartmanager")
}
// 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
}
if rm.timeout == 0 {
rm.timeout = defaultTimeout
} else {
rm.timeout *= backoffMultiplier
}
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() {
select {
case <-rm.cancel:
ch <- ErrRestartCanceled
close(ch)
case <-time.After(rm.timeout):
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
}