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  }