Browse code

restartmanager: move to daemon/internal

The restartmanager is only used internally by the daemon, and has no external
users. Move it to the daemon/internal package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

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