github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/event/event.go (about) 1 // Package event implements an event bus. 2 // for a great introduction to the event bus pattern in go, see: 3 // https://levelup.gitconnected.com/lets-write-a-simple-event-bus-in-go-79b9480d8997 4 package event 5 6 import ( 7 "context" 8 "fmt" 9 "sync" 10 "time" 11 12 golog "github.com/ipfs/go-log" 13 "github.com/qri-io/qri/profile" 14 ) 15 16 var ( 17 log = golog.Logger("event") 18 19 // ErrBusClosed indicates the event bus is no longer coordinating events 20 // because it's parent context has closed 21 ErrBusClosed = fmt.Errorf("event bus is closed") 22 // NowFunc is the function that generates timestamps (tests may override) 23 NowFunc = time.Now 24 ) 25 26 // Type is the set of all kinds of events emitted by the bus. Use "Type" to 27 // distinguish between different events. Event emitters should declare Types 28 // as constants and document the expected payload type. This term, although 29 // similar to the keyword in go, is used to match what react/redux use in 30 // their event system. 31 type Type string 32 33 // Event represents an event that subscribers will receive from the bus 34 type Event struct { 35 Type Type 36 Timestamp int64 37 ProfileID string 38 SessionID string 39 Payload interface{} 40 } 41 42 // Handler is a function that will be called by the event bus whenever a 43 // matching event is published. Handler calls are blocking, called in order 44 // of subscription. Any error returned by a handler is passed back to the 45 // event publisher. 46 // The handler context originates from the publisher, and in practice will often 47 // be scoped to a "request context" like an HTTP request or CLI command 48 // invocation. 49 // Generally, even handlers should aim to return quickly, and only delegate to 50 // goroutines when the publishing event is firing on a long-running process 51 type Handler func(ctx context.Context, e Event) error 52 53 // Publisher is an interface that can only publish an event 54 type Publisher interface { 55 Publish(ctx context.Context, typ Type, payload interface{}) error 56 PublishID(ctx context.Context, typ Type, sessionID string, payload interface{}) error 57 } 58 59 // Bus is a central coordination point for event publication and subscription. 60 // Zero or more subscribers register to be notified of events, optionally by type 61 // or id, then a publisher writes an event to the bus, which broadcasts to all 62 // matching subscribers 63 type Bus interface { 64 // Publish an event to the bus 65 Publish(ctx context.Context, typ Type, data interface{}) error 66 // PublishID publishes an event with an arbitrary session id 67 PublishID(ctx context.Context, typ Type, sessionID string, data interface{}) error 68 // Subscribe to one or more eventTypes with a handler function that will be called 69 // whenever the event type is published 70 SubscribeTypes(handler Handler, eventTypes ...Type) 71 // SubscribeID subscribes to only events that have a matching session id 72 SubscribeID(handler Handler, sessionID string) 73 // SubscribeAll subscribes to all events 74 SubscribeAll(handler Handler) 75 // NumSubscriptions returns the number of subscribers to the bus's events 76 NumSubscribers() int 77 } 78 79 // NilBus replaces a nil value. it implements the bus interface, but does 80 // nothing 81 var NilBus = nilBus{} 82 83 type nilBus struct{} 84 85 // assert at compile time that nilBus implements the Bus interface 86 var _ Bus = (*nilBus)(nil) 87 88 // Publish does nothing with the event 89 func (nilBus) Publish(_ context.Context, _ Type, _ interface{}) error { 90 return nil 91 } 92 93 // PublishID does nothing with the event 94 func (nilBus) PublishID(_ context.Context, _ Type, _ string, _ interface{}) error { 95 return nil 96 } 97 98 // SubscribeTypes does nothing 99 func (nilBus) SubscribeTypes(handler Handler, eventTypes ...Type) {} 100 101 func (nilBus) SubscribeID(handler Handler, id string) {} 102 103 func (nilBus) SubscribeAll(handler Handler) {} 104 105 func (nilBus) NumSubscribers() int { 106 return 0 107 } 108 109 type bus struct { 110 lk sync.RWMutex 111 closed bool 112 subs map[Type][]Handler 113 allSubs []Handler 114 idSubs map[string][]Handler 115 } 116 117 // assert at compile time that bus implements the Bus interface 118 var _ Bus = (*bus)(nil) 119 120 // NewBus creates a new event bus. Event busses should be instantiated as a 121 // singleton. If the passed in context is cancelled, the bus will stop emitting 122 // events and close all subscribed channels 123 // 124 // TODO (b5) - finish context-closing cleanup 125 func NewBus(ctx context.Context) Bus { 126 b := &bus{ 127 subs: map[Type][]Handler{}, 128 idSubs: map[string][]Handler{}, 129 allSubs: []Handler{}, 130 } 131 132 go func(b *bus) { 133 <-ctx.Done() 134 log.Debugf("close bus") 135 b.lk.Lock() 136 b.closed = true 137 b.lk.Unlock() 138 }(b) 139 140 return b 141 } 142 143 // Publish sends an event to the bus 144 func (b *bus) Publish(ctx context.Context, typ Type, payload interface{}) error { 145 return b.publish(ctx, typ, "", payload) 146 } 147 148 // PublishID sends an event with a given sessionID to the bus 149 func (b *bus) PublishID(ctx context.Context, typ Type, sessionID string, payload interface{}) error { 150 return b.publish(ctx, typ, sessionID, payload) 151 } 152 153 func (b *bus) publish(ctx context.Context, typ Type, sessionID string, payload interface{}) error { 154 log.Debugw("publish", "type", typ, "payload", payload) 155 b.lk.RLock() 156 defer b.lk.RUnlock() 157 158 if b.closed { 159 return ErrBusClosed 160 } 161 162 profileid := profile.IDFromCtx(ctx) 163 164 e := Event{ 165 Type: typ, 166 Timestamp: NowFunc().UnixNano(), 167 SessionID: sessionID, 168 ProfileID: profileid, 169 Payload: payload, 170 } 171 172 // TODO(dustmop): Add instrumentation, perhaps to ctx, to make logging / tracing 173 // a single event easier to do. 174 175 for _, handler := range b.subs[typ] { 176 if err := handler(ctx, e); err != nil { 177 return err 178 } 179 } 180 181 if sessionID != "" { 182 for _, handler := range b.idSubs[sessionID] { 183 if err := handler(ctx, e); err != nil { 184 return err 185 } 186 } 187 } 188 189 for _, handler := range b.allSubs { 190 if err := handler(ctx, e); err != nil { 191 return err 192 } 193 } 194 195 return nil 196 } 197 198 // Subscribe requests events from the given type, returning a channel of those events 199 func (b *bus) SubscribeTypes(handler Handler, eventTypes ...Type) { 200 b.lk.Lock() 201 defer b.lk.Unlock() 202 log.Debugf("Subscribe to types: %v", eventTypes) 203 204 for _, typ := range eventTypes { 205 b.subs[typ] = append(b.subs[typ], handler) 206 } 207 } 208 209 // SubscribeID requests events that match the given sessionID 210 func (b *bus) SubscribeID(handler Handler, sessionID string) { 211 b.lk.Lock() 212 defer b.lk.Unlock() 213 log.Debugf("Subscribe to ID: %v", sessionID) 214 b.idSubs[sessionID] = append(b.idSubs[sessionID], handler) 215 } 216 217 // SubscribeAll requests all events from the bus 218 func (b *bus) SubscribeAll(handler Handler) { 219 b.lk.Lock() 220 defer b.lk.Unlock() 221 log.Debugf("Subscribe All") 222 b.allSubs = append(b.allSubs, handler) 223 } 224 225 // NumSubscribers returns the number of subscribers to the bus's events 226 func (b *bus) NumSubscribers() int { 227 b.lk.RLock() 228 defer b.lk.RUnlock() 229 total := 0 230 for _, handlers := range b.subs { 231 total += len(handlers) 232 } 233 for _, handlers := range b.idSubs { 234 total += len(handlers) 235 } 236 total += len(b.allSubs) 237 return total 238 }