github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/thirdparty/notifier/notifier.go (about) 1 // Package notifier provides a simple notification dispatcher 2 // meant to be embedded in larger structres who wish to allow 3 // clients to sign up for event notifications. 4 package notifier 5 6 import ( 7 "sync" 8 9 process "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess" 10 ratelimit "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/ratelimit" 11 ) 12 13 // Notifiee is a generic interface. Clients implement 14 // their own Notifiee interfaces to ensure type-safety 15 // of notifications: 16 // 17 // type RocketNotifiee interface{ 18 // Countdown(r Rocket, countdown time.Duration) 19 // LiftedOff(Rocket) 20 // ReachedOrbit(Rocket) 21 // Detached(Rocket, Capsule) 22 // Landed(Rocket) 23 // } 24 // 25 type Notifiee interface{} 26 27 // Notifier is a notification dispatcher. It's meant 28 // to be composed, and its zero-value is ready to be used. 29 // 30 // type Rocket struct { 31 // notifier notifier.Notifier 32 // } 33 // 34 type Notifier struct { 35 mu sync.RWMutex // guards notifiees 36 nots map[Notifiee]struct{} 37 lim *ratelimit.RateLimiter 38 } 39 40 // RateLimited returns a rate limited Notifier. only limit goroutines 41 // will be spawned. If limit is zero, no rate limiting happens. This 42 // is the same as `Notifier{}`. 43 func RateLimited(limit int) Notifier { 44 n := Notifier{} 45 if limit > 0 { 46 n.lim = ratelimit.NewRateLimiter(process.Background(), limit) 47 } 48 return n 49 } 50 51 // Notify signs up Notifiee e for notifications. This function 52 // is meant to be called behind your own type-safe function(s): 53 // 54 // // generic function for pattern-following 55 // func (r *Rocket) Notify(n Notifiee) { 56 // r.notifier.Notify(n) 57 // } 58 // 59 // // or as part of other functions 60 // func (r *Rocket) Onboard(a Astronaut) { 61 // r.astronauts = append(r.austronauts, a) 62 // r.notifier.Notify(a) 63 // } 64 // 65 func (n *Notifier) Notify(e Notifiee) { 66 n.mu.Lock() 67 if n.nots == nil { // so that zero-value is ready to be used. 68 n.nots = make(map[Notifiee]struct{}) 69 } 70 n.nots[e] = struct{}{} 71 n.mu.Unlock() 72 } 73 74 // StopNotify stops notifying Notifiee e. This function 75 // is meant to be called behind your own type-safe function(s): 76 // 77 // // generic function for pattern-following 78 // func (r *Rocket) StopNotify(n Notifiee) { 79 // r.notifier.StopNotify(n) 80 // } 81 // 82 // // or as part of other functions 83 // func (r *Rocket) Detach(c Capsule) { 84 // r.notifier.StopNotify(c) 85 // r.capsule = nil 86 // } 87 // 88 func (n *Notifier) StopNotify(e Notifiee) { 89 n.mu.Lock() 90 if n.nots != nil { // so that zero-value is ready to be used. 91 delete(n.nots, e) 92 } 93 n.mu.Unlock() 94 } 95 96 // NotifyAll messages the notifier's notifiees with a given notification. 97 // This is done by calling the given function with each notifiee. It is 98 // meant to be called with your own type-safe notification functions: 99 // 100 // func (r *Rocket) Launch() { 101 // r.notifyAll(func(n Notifiee) { 102 // n.Launched(r) 103 // }) 104 // } 105 // 106 // // make it private so only you can use it. This function is necessary 107 // // to make sure you only up-cast in one place. You control who you added 108 // // to be a notifiee. If Go adds generics, maybe we can get rid of this 109 // // method but for now it is like wrapping a type-less container with 110 // // a type safe interface. 111 // func (r *Rocket) notifyAll(notify func(Notifiee)) { 112 // r.notifier.NotifyAll(func(n notifier.Notifiee) { 113 // notify(n.(Notifiee)) 114 // }) 115 // } 116 // 117 // Note well: each notification is launched in its own goroutine, so they 118 // can be processed concurrently, and so that whatever the notification does 119 // it _never_ blocks out the client. This is so that consumers _cannot_ add 120 // hooks into your object that block you accidentally. 121 func (n *Notifier) NotifyAll(notify func(Notifiee)) { 122 n.mu.Lock() 123 defer n.mu.Unlock() 124 125 if n.nots == nil { // so that zero-value is ready to be used. 126 return 127 } 128 129 // no rate limiting. 130 if n.lim == nil { 131 for notifiee := range n.nots { 132 go notify(notifiee) 133 } 134 return 135 } 136 137 // with rate limiting. 138 n.lim.Go(func(worker process.Process) { 139 for notifiee := range n.nots { 140 notifiee := notifiee // rebind for loop data races 141 n.lim.LimitedGo(func(worker process.Process) { 142 notify(notifiee) 143 }) 144 } 145 }) 146 }