github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/cleaner/cleaner.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cleaner
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/worker/v3"
    12  	"github.com/juju/worker/v3/catacomb"
    13  
    14  	"github.com/juju/juju/core/watcher"
    15  )
    16  
    17  // period is the amount of time to wait before running cleanups,
    18  // since the last time they were run. It is necessary to run
    19  // cleanups periodically because Cleanup will not return an
    20  // error if a specific cleanup fails, and the watcher will not
    21  // be triggered unless a new cleanup is added.
    22  const period = 30 * time.Second
    23  
    24  // logger is here to stop the desire of creating a package level logger.
    25  // Don't do this, instead pass one through as config to the worker.
    26  type logger interface{}
    27  
    28  var _ logger = struct{}{}
    29  
    30  type StateCleaner interface {
    31  	Cleanup() error
    32  	WatchCleanups() (watcher.NotifyWatcher, error)
    33  }
    34  
    35  // Cleaner is responsible for cleaning up the state.
    36  type Cleaner struct {
    37  	catacomb catacomb.Catacomb
    38  	st       StateCleaner
    39  	watcher  watcher.NotifyWatcher
    40  	clock    clock.Clock
    41  	logger   Logger
    42  }
    43  
    44  // NewCleaner returns a worker.Worker that runs state.Cleanup()
    45  // periodically, and whenever the CleanupWatcher signals documents
    46  // marked for deletion.
    47  func NewCleaner(st StateCleaner, clock clock.Clock, logger Logger) (worker.Worker, error) {
    48  	watcher, err := st.WatchCleanups()
    49  	if err != nil {
    50  		return nil, errors.Trace(err)
    51  	}
    52  	c := Cleaner{
    53  		st:      st,
    54  		watcher: watcher,
    55  		clock:   clock,
    56  		logger:  logger,
    57  	}
    58  	if err := catacomb.Invoke(catacomb.Plan{
    59  		Site: &c.catacomb,
    60  		Work: c.loop,
    61  		Init: []worker.Worker{watcher},
    62  	}); err != nil {
    63  		return nil, errors.Trace(err)
    64  	}
    65  	return &c, nil
    66  }
    67  
    68  func (c *Cleaner) loop() error {
    69  	timer := c.clock.NewTimer(period)
    70  	defer timer.Stop()
    71  	for {
    72  		select {
    73  		case <-c.catacomb.Dying():
    74  			return c.catacomb.ErrDying()
    75  		case _, ok := <-c.watcher.Changes():
    76  			if !ok {
    77  				return errors.New("change channel closed")
    78  			}
    79  		case <-timer.Chan():
    80  		}
    81  		err := c.st.Cleanup()
    82  		if err != nil {
    83  			// We don't exit if a cleanup fails, we just
    84  			// retry after when the timer fires. This
    85  			// enables us to retry cleanups that fail due
    86  			// to a transient failure, even when there
    87  			// are no new cleanups added.
    88  			c.logger.Errorf("cannot cleanup state: %v", err)
    89  		}
    90  		timer.Reset(period)
    91  	}
    92  }
    93  
    94  // Kill is part of the worker.Worker interface.
    95  func (c *Cleaner) Kill() {
    96  	c.catacomb.Kill(nil)
    97  }
    98  
    99  // Wait is part of the worker.Worker interface.
   100  func (c *Cleaner) Wait() error {
   101  	return c.catacomb.Wait()
   102  }