github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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 "os" 23 "time" 24 25 "github.com/snapcore/snapd/logger" 26 "github.com/snapcore/snapd/overlord/state" 27 ) 28 29 var standbyWait = 5 * time.Second 30 var maxWait = 5 * time.Minute 31 32 var stateRequestRestart = (*state.State).RequestRestart 33 34 type Opinionator interface { 35 CanStandby() bool 36 } 37 38 // StandbyOpinions tracks if snapd can go into socket activation mode 39 type StandbyOpinions struct { 40 state *state.State 41 standbyWait time.Duration 42 startTime time.Time 43 opinions []Opinionator 44 45 stoppingCh chan struct{} 46 stoppedCh chan struct{} 47 } 48 49 // CanStandby returns true if the main ensure loop can go into 50 // "socket-activation" mode. This is only possible once seeding is done 51 // and there are no snaps on the system. This is to reduce the memory 52 // footprint on e.g. containers. 53 func (m *StandbyOpinions) CanStandby() bool { 54 st := m.state 55 st.Lock() 56 defer st.Unlock() 57 58 // check if enough time has passed 59 if m.startTime.Add(m.standbyWait).After(time.Now()) { 60 return false 61 } 62 // check if there are any changes in flight 63 for _, chg := range st.Changes() { 64 if !chg.Status().Ready() || !chg.IsClean() { 65 return false 66 } 67 } 68 // check the voice of the crowd 69 for _, ct := range m.opinions { 70 if !ct.CanStandby() { 71 return false 72 } 73 } 74 return true 75 } 76 77 func New(st *state.State) *StandbyOpinions { 78 w := standbyWait 79 ovr := os.Getenv("SNAPD_STANDBY_WAIT") 80 if ovr != "" { 81 d, err := time.ParseDuration(ovr) 82 if err == nil { 83 w = d 84 } 85 } 86 return &StandbyOpinions{ 87 state: st, 88 standbyWait: w, 89 startTime: time.Now(), 90 stoppingCh: make(chan struct{}), 91 stoppedCh: make(chan struct{}), 92 } 93 } 94 95 func (m *StandbyOpinions) Start() { 96 logger.Debugf("will consider standby after: %v", m.standbyWait) 97 go func() { 98 wait := m.standbyWait 99 timer := time.NewTimer(wait) 100 for { 101 if m.CanStandby() { 102 stateRequestRestart(m.state, state.RestartSocket) 103 } 104 select { 105 case <-timer.C: 106 if wait < maxWait { 107 wait *= 2 108 } 109 case <-m.stoppingCh: 110 close(m.stoppedCh) 111 return 112 } 113 timer.Reset(wait) 114 } 115 }() 116 } 117 118 func (m *StandbyOpinions) Stop() { 119 select { 120 case <-m.stoppedCh: 121 // nothing left to do 122 return 123 case <-m.stoppingCh: 124 // nearly nothing to do 125 default: 126 close(m.stoppingCh) 127 } 128 <-m.stoppedCh 129 } 130 131 func (m *StandbyOpinions) AddOpinion(opi Opinionator) { 132 if opi != nil { 133 m.opinions = append(m.opinions, opi) 134 } 135 } 136 137 func MockStandbyWait(d time.Duration) (restore func()) { 138 oldStandbyWait := standbyWait 139 standbyWait = d 140 return func() { 141 standbyWait = oldStandbyWait 142 } 143 }