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 }