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  }