github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/standby/standby.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 /* 3 * Copyright (C) 2018 Canonical Ltd 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 3 as 7 * published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 */ 18 19 package standby 20 21 import ( 22 "time" 23 24 "github.com/snapcore/snapd/overlord/state" 25 ) 26 27 var standbyWait = 5 * time.Second 28 var maxWait = 5 * time.Minute 29 30 var stateRequestRestart = (*state.State).RequestRestart 31 32 type Opinionator interface { 33 CanStandby() bool 34 } 35 36 // StandbyOpinions tracks if snapd can go into socket activation mode 37 type StandbyOpinions struct { 38 state *state.State 39 startTime time.Time 40 opinions []Opinionator 41 42 stoppingCh chan struct{} 43 stoppedCh chan struct{} 44 } 45 46 // CanStandby returns true if the main ensure loop can go into 47 // "socket-activation" mode. This is only possible once seeding is done 48 // and there are no snaps on the system. This is to reduce the memory 49 // footprint on e.g. containers. 50 func (m *StandbyOpinions) CanStandby() bool { 51 st := m.state 52 st.Lock() 53 defer st.Unlock() 54 55 // check if enough time has passed 56 if m.startTime.Add(standbyWait).After(time.Now()) { 57 return false 58 } 59 // check if there are any changes in flight 60 for _, chg := range st.Changes() { 61 if !chg.Status().Ready() || !chg.IsClean() { 62 return false 63 } 64 } 65 // check the voice of the crowd 66 for _, ct := range m.opinions { 67 if !ct.CanStandby() { 68 return false 69 } 70 } 71 return true 72 } 73 74 func New(st *state.State) *StandbyOpinions { 75 return &StandbyOpinions{ 76 state: st, 77 startTime: time.Now(), 78 stoppingCh: make(chan struct{}), 79 stoppedCh: make(chan struct{}), 80 } 81 } 82 83 func (m *StandbyOpinions) Start() { 84 go func() { 85 wait := standbyWait 86 timer := time.NewTimer(wait) 87 for { 88 if m.CanStandby() { 89 stateRequestRestart(m.state, state.RestartSocket) 90 } 91 select { 92 case <-timer.C: 93 if wait < maxWait { 94 wait *= 2 95 } 96 case <-m.stoppingCh: 97 close(m.stoppedCh) 98 return 99 } 100 timer.Reset(wait) 101 } 102 }() 103 } 104 105 func (m *StandbyOpinions) Stop() { 106 select { 107 case <-m.stoppedCh: 108 // nothing left to do 109 return 110 case <-m.stoppingCh: 111 // nearly nothing to do 112 default: 113 close(m.stoppingCh) 114 } 115 <-m.stoppedCh 116 } 117 118 func (m *StandbyOpinions) AddOpinion(opi Opinionator) { 119 if opi != nil { 120 m.opinions = append(m.opinions, opi) 121 } 122 } 123 124 func MockStandbyWait(d time.Duration) (restore func()) { 125 oldStandbyWait := standbyWait 126 standbyWait = d 127 return func() { 128 standbyWait = oldStandbyWait 129 } 130 }