github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/apicaller/worker.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apicaller
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/worker/v3"
     9  	"gopkg.in/tomb.v2"
    10  
    11  	"github.com/juju/juju/api"
    12  )
    13  
    14  // logger is here to stop the desire of creating a package level logger.
    15  // Don't do this, instead use the one passed as manifold config.
    16  type logger interface{}
    17  
    18  var _ logger = struct{}{}
    19  
    20  // newAPIConnWorker returns a worker that exists for as long as the associated
    21  // connection, and provides access to a base.APICaller via its manifold's Output
    22  // func. If the worker is killed, the connection will be closed; and if the
    23  // connection is broken, the worker will be killed.
    24  //
    25  // The lack of error return is considered and intentional; it signals the
    26  // transfer of responsibility for the connection from the caller to the
    27  // worker.
    28  func newAPIConnWorker(conn api.Connection) worker.Worker {
    29  	w := &apiConnWorker{conn: conn}
    30  	w.tomb.Go(w.loop)
    31  	return w
    32  }
    33  
    34  type apiConnWorker struct {
    35  	tomb tomb.Tomb
    36  	conn api.Connection
    37  }
    38  
    39  // Kill is part of the worker.Worker interface.
    40  func (w *apiConnWorker) Kill() {
    41  	w.tomb.Kill(nil)
    42  }
    43  
    44  // Wait is part of the worker.Worker interface.
    45  func (w *apiConnWorker) Wait() error {
    46  	return w.tomb.Wait()
    47  }
    48  
    49  // loop is somewhat out of the ordinary, because an api.Connection
    50  // *does* maintain an internal workeresque heartbeat goroutine, but it
    51  // doesn't implement Worker.
    52  func (w *apiConnWorker) loop() (err error) {
    53  	// TODO(fwereade): we should make this rational at some point.
    54  
    55  	defer func() {
    56  		// Since we can't tell for sure what error killed the connection, any
    57  		// error out of Close is more important and relevant than any error we
    58  		// might return in the loop.
    59  		if closeErr := w.conn.Close(); closeErr != nil {
    60  			err = closeErr
    61  		}
    62  	}()
    63  
    64  	// Note that we should never return a nil error from this loop. If we're
    65  	// shut down deliberately we should return ErrDying, to be overwritten by
    66  	// any non-nil Close error and otherwise not perturb the tomb; and if the
    67  	// connection closes on its own there's surely *something* wrong even if
    68  	// there's no error reported from Close. (sample problem: *someone else*
    69  	// closed the conn that we're meant to be in control of).
    70  	for {
    71  		select {
    72  		case <-w.tomb.Dying():
    73  			return tomb.ErrDying
    74  		case <-w.conn.Broken():
    75  			return errors.New("api connection broken unexpectedly")
    76  		}
    77  	}
    78  }