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