github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/fortress/occupy.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package fortress 5 6 import ( 7 "github.com/juju/errors" 8 "gopkg.in/juju/worker.v1" 9 ) 10 11 // StartFunc starts a worker.Worker. 12 type StartFunc func() (worker.Worker, error) 13 14 // Occupy launches a Visit to fortress that creates a worker and holds the 15 // visit open until the worker completes. Like most funcs that return any 16 // Worker, the caller takes responsibility for its lifetime; be aware that 17 // the responsibility is especially heavy here, because failure to clean up 18 // the worker will block cleanup of the fortress. 19 // 20 // This may sound scary, but the alternative is to have multiple components 21 // "responsible for" a single worker's lifetime -- and Fortress itself would 22 // have to grow new concerns, of understanding and managing worker.Workers -- 23 // and that scenario ends up much worse. 24 func Occupy(fortress Guest, start StartFunc, abort Abort) (worker.Worker, error) { 25 26 // Create two channels to communicate success and failure of worker 27 // creation; and a worker-running func that sends on exactly one 28 // of them, and returns only when (1) a value has been sent and (2) 29 // no worker is running. Note especially that it always returns nil. 30 started := make(chan worker.Worker, 1) 31 failed := make(chan error, 1) 32 task := func() error { 33 worker, err := start() 34 if err != nil { 35 failed <- err 36 } else { 37 started <- worker 38 worker.Wait() // ignore error: worker is SEP now. 39 } 40 return nil 41 } 42 43 // Start a goroutine to run the task func inside the fortress. If 44 // this operation succeeds, we must inspect started and failed to 45 // determine what actually happened; but if it fails, we can be 46 // confident that the task (which never fails) did not run, and can 47 // therefore return the failure without waiting further. 48 finished := make(chan error, 1) 49 go func() { 50 finished <- fortress.Visit(task, abort) 51 }() 52 53 // Watch all these channels to figure out what happened and inform 54 // the client. A nil error from finished indicates that there will 55 // be some value waiting on one of the other channels. 56 for { 57 select { 58 case err := <-finished: 59 if err != nil { 60 return nil, errors.Trace(err) 61 } 62 case err := <-failed: 63 return nil, errors.Trace(err) 64 case worker := <-started: 65 return worker, nil 66 } 67 } 68 }