github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/notifyworker.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package worker 5 6 import ( 7 "launchpad.net/tomb" 8 9 apiWatcher "github.com/juju/juju/api/watcher" 10 "github.com/juju/juju/state/watcher" 11 ) 12 13 // ensureErr is defined as a variable to allow the test suite 14 // to override it. 15 var ensureErr = watcher.EnsureErr 16 17 // notifyWorker is the internal implementation of the Worker 18 // interface, using a NotifyWatcher for handling changes. 19 type notifyWorker struct { 20 tomb tomb.Tomb 21 22 // handler is what will handle when events are triggered 23 handler NotifyWatchHandler 24 } 25 26 // NotifyWatchHandler implements the business logic that is triggered 27 // as part of watching a NotifyWatcher. 28 type NotifyWatchHandler interface { 29 // SetUp starts the handler, this should create the watcher we 30 // will be waiting on for more events. SetUp can return a Watcher 31 // even if there is an error, and the notify Worker will make sure 32 // to stop the watcher. 33 SetUp() (apiWatcher.NotifyWatcher, error) 34 35 // TearDown should cleanup any resources that are left around 36 TearDown() error 37 38 // Handle is called when the Watcher has indicated there are changes, do 39 // whatever work is necessary to process it. The done channel is closed if 40 // the worker is being interrupted to finish. Any worker should avoid any 41 // bare channel reads or writes, but instead use a select with the done 42 // channel. 43 Handle(done <-chan struct{}) error 44 } 45 46 // NewNotifyWorker starts a new worker running the business logic from 47 // the handler. The worker loop is started in another goroutine as a 48 // side effect of calling this. 49 func NewNotifyWorker(handler NotifyWatchHandler) Worker { 50 nw := ¬ifyWorker{ 51 handler: handler, 52 } 53 54 go func() { 55 defer nw.tomb.Done() 56 nw.tomb.Kill(nw.loop()) 57 }() 58 return nw 59 } 60 61 // Kill the loop with no-error 62 func (nw *notifyWorker) Kill() { 63 nw.tomb.Kill(nil) 64 } 65 66 // Wait for the looping to finish 67 func (nw *notifyWorker) Wait() error { 68 return nw.tomb.Wait() 69 } 70 71 type tearDowner interface { 72 TearDown() error 73 } 74 75 // propagateTearDown tears down the handler, but ensures any error is 76 // propagated through the tomb's Kill method. 77 func propagateTearDown(handler tearDowner, t *tomb.Tomb) { 78 if err := handler.TearDown(); err != nil { 79 t.Kill(err) 80 } 81 } 82 83 func (nw *notifyWorker) loop() error { 84 w, err := nw.handler.SetUp() 85 if err != nil { 86 if w != nil { 87 // We don't bother to propagate an error, because we 88 // already have an error 89 w.Stop() 90 } 91 return err 92 } 93 defer propagateTearDown(nw.handler, &nw.tomb) 94 defer watcher.Stop(w, &nw.tomb) 95 for { 96 select { 97 case <-nw.tomb.Dying(): 98 return tomb.ErrDying 99 case _, ok := <-w.Changes(): 100 if !ok { 101 return ensureErr(w) 102 } 103 if err := nw.handler.Handle(nw.tomb.Dying()); err != nil { 104 return err 105 } 106 } 107 } 108 }