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  }