package pubsub import ( "context" "sync" "github.com/google/uuid" ) // genericListener is either a Listener or ListenerWithErr type genericListener struct { l Listener le ListenerWithErr } func (g genericListener) send(ctx context.Context, message []byte) { if g.l != nil { g.l(ctx, message) } if g.le != nil { g.le(ctx, message, nil) } } // MemoryPubsub is an in-memory Pubsub implementation. It's an exported type so that our test code can do type // checks. type MemoryPubsub struct { mut sync.RWMutex listeners map[string]map[uuid.UUID]genericListener } func (m *MemoryPubsub) Subscribe(event string, listener Listener) (cancel func(), err error) { return m.subscribeGeneric(event, genericListener{l: listener}) } func (m *MemoryPubsub) SubscribeWithErr(event string, listener ListenerWithErr) (cancel func(), err error) { return m.subscribeGeneric(event, genericListener{le: listener}) } func (m *MemoryPubsub) subscribeGeneric(event string, listener genericListener) (cancel func(), err error) { m.mut.Lock() defer m.mut.Unlock() var listeners map[uuid.UUID]genericListener var ok bool if listeners, ok = m.listeners[event]; !ok { listeners = map[uuid.UUID]genericListener{} m.listeners[event] = listeners } var id uuid.UUID for { id = uuid.New() if _, ok = listeners[id]; !ok { break } } listeners[id] = listener return func() { m.mut.Lock() defer m.mut.Unlock() listeners := m.listeners[event] delete(listeners, id) }, nil } func (m *MemoryPubsub) Publish(event string, message []byte) error { m.mut.RLock() defer m.mut.RUnlock() listeners, ok := m.listeners[event] if !ok { return nil } var wg sync.WaitGroup for _, listener := range listeners { wg.Add(1) listener := listener go func() { defer wg.Done() listener.send(context.Background(), message) }() } wg.Wait() return nil } func (*MemoryPubsub) Close() error { return nil } func NewInMemory() Pubsub { return &MemoryPubsub{ listeners: make(map[string]map[uuid.UUID]genericListener), } }