-
Notifications
You must be signed in to change notification settings - Fork 51
/
standby.go
127 lines (113 loc) · 2.84 KB
/
standby.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
// Copyright (c) 2014-2020 Canonical Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package standby
import (
"time"
"github.com/canonical/pebble/internals/overlord/restart"
"github.com/canonical/pebble/internals/overlord/state"
)
var standbyWait = 5 * time.Second
var maxWait = 5 * time.Minute
type Opinionator interface {
CanStandby() bool
}
// StandbyOpinions tracks if pebble can go into socket activation mode
type StandbyOpinions struct {
state *state.State
startTime time.Time
opinions []Opinionator
stoppingCh chan struct{}
stoppedCh chan struct{}
}
// CanStandby returns true if the main ensure loop can go into
// "socket-activation" mode. This is only possible once seeding is done
// and there are no snaps on the system. This is to reduce the memory
// footprint on e.g. containers.
func (m *StandbyOpinions) CanStandby() bool {
st := m.state
st.Lock()
defer st.Unlock()
// check if enough time has passed
if m.startTime.Add(standbyWait).After(time.Now()) {
return false
}
// check if there are any changes in flight
for _, chg := range st.Changes() {
if !chg.Status().Ready() || !chg.IsClean() {
return false
}
}
// check the voice of the crowd
for _, ct := range m.opinions {
if !ct.CanStandby() {
return false
}
}
return true
}
func New(st *state.State) *StandbyOpinions {
return &StandbyOpinions{
state: st,
startTime: time.Now(),
stoppingCh: make(chan struct{}),
stoppedCh: make(chan struct{}),
}
}
func (m *StandbyOpinions) Start() {
go func() {
wait := standbyWait
timer := time.NewTimer(wait)
for {
if m.CanStandby() {
m.state.Lock()
restart.Request(m.state, restart.RestartSocket)
m.state.Unlock()
}
select {
case <-timer.C:
if wait < maxWait {
wait *= 2
}
case <-m.stoppingCh:
close(m.stoppedCh)
return
}
timer.Reset(wait)
}
}()
}
func (m *StandbyOpinions) Stop() {
select {
case <-m.stoppedCh:
// nothing left to do
return
case <-m.stoppingCh:
// nearly nothing to do
default:
close(m.stoppingCh)
}
<-m.stoppedCh
}
func (m *StandbyOpinions) AddOpinion(opi Opinionator) {
if opi != nil {
m.opinions = append(m.opinions, opi)
}
}
func FakeStandbyWait(d time.Duration) (restore func()) {
oldStandbyWait := standbyWait
standbyWait = d
return func() {
standbyWait = oldStandbyWait
}
}