util/eventbus: rework to have a Client abstraction
The Client carries both publishers and subscribers for a single actor. This makes the APIs for publish and subscribe look more similar, and this structure is a better fit for upcoming debug facilities. Updates #15160 Signed-off-by: David Anderson <dave@tailscale.com>
This commit is contained in:
committed by
Dave Anderson
parent
f840aad49e
commit
3e18434595
+34
-31
@@ -3,56 +3,59 @@
|
||||
|
||||
// Package eventbus provides an in-process event bus.
|
||||
//
|
||||
// The event bus connects publishers of typed events with subscribers
|
||||
// interested in those events.
|
||||
// An event bus connects publishers of typed events with subscribers
|
||||
// interested in those events. Typically, there is one global event
|
||||
// bus per process.
|
||||
//
|
||||
// # Usage
|
||||
//
|
||||
// To publish events, use [PublisherOf] to get a typed publisher for
|
||||
// your event type, then call [Publisher.Publish] as needed. If your
|
||||
// event is expensive to construct, you can optionally use
|
||||
// [Publisher.ShouldPublish] to skip the work if nobody is listening
|
||||
// for the event.
|
||||
// To send or receive events, first use [Bus.Client] to register with
|
||||
// the bus. Clients should register with a human-readable name that
|
||||
// identifies the code using the client, to aid in debugging.
|
||||
//
|
||||
// To receive events, first use [Bus.Queue] to create an event
|
||||
// delivery queue, then use [Subscribe] to get a [Subscriber] for each
|
||||
// event type you're interested in. Receive the events themselves by
|
||||
// selecting over all your [Subscriber.Chan] channels, as well as
|
||||
// [Queue.Done] for shutdown notifications.
|
||||
// To publish events, use [Publish] on a Client to get a typed
|
||||
// publisher for your event type, then call [Publisher.Publish] as
|
||||
// needed. If your event is expensive to construct, you can optionally
|
||||
// use [Publisher.ShouldPublish] to skip the work if nobody is
|
||||
// listening for the event.
|
||||
//
|
||||
// To receive events, use [Subscribe] to get a typed subscriber for
|
||||
// each event type you're interested in. Receive the events themselves
|
||||
// by selecting over all your [Subscriber.Events] channels, as well as
|
||||
// [Subscriber.Done] for shutdown notifications.
|
||||
//
|
||||
// # Concurrency properties
|
||||
//
|
||||
// The bus serializes all published events, and preserves that
|
||||
// ordering when delivering to subscribers that are attached to the
|
||||
// same Queue. In more detail:
|
||||
// The bus serializes all published events across all publishers, and
|
||||
// preserves that ordering when delivering to subscribers that are
|
||||
// attached to the same Client. In more detail:
|
||||
//
|
||||
// - An event is published to the bus at some instant between the
|
||||
// start and end of the call to [Publisher.Publish].
|
||||
// - Events cannot be published at the same instant, and so are
|
||||
// - Two events cannot be published at the same instant, and so are
|
||||
// totally ordered by their publication time. Given two events E1
|
||||
// and E2, either E1 happens before E2, or E2 happens before E1.
|
||||
// - Queues dispatch events to their Subscribers in publication
|
||||
// order: if E1 happens before E2, the queue always delivers E1
|
||||
// - Clients dispatch events to their Subscribers in publication
|
||||
// order: if E1 happens before E2, the client always delivers E1
|
||||
// before E2.
|
||||
// - Queues do not synchronize with each other: given queues Q1 and
|
||||
// Q2, both subscribed to events E1 and E2, Q1 may deliver both E1
|
||||
// and E2 before Q2 delivers E1.
|
||||
// - Clients do not synchronize subscriptions with each other: given
|
||||
// clients C1 and C2, both subscribed to events E1 and E2, C1 may
|
||||
// deliver both E1 and E2 before C2 delivers E1.
|
||||
//
|
||||
// Less formally: there is one true timeline of all published events.
|
||||
// If you make a Queue and subscribe to events on it, you will receive
|
||||
// those events one at a time, in the same order as the one true
|
||||
// If you make a Client and subscribe to events, you will receive
|
||||
// events one at a time, in the same order as the one true
|
||||
// timeline. You will "skip over" events you didn't subscribe to, but
|
||||
// your view of the world always moves forward in time, never
|
||||
// backwards, and you will observe events in the same order as
|
||||
// everyone else.
|
||||
//
|
||||
// However, you cannot assume that what your subscribers on your queue
|
||||
// see as "now" is the same as what other subscribers on other
|
||||
// queues. Their queue may be further behind you in the timeline, or
|
||||
// running ahead of you. This means you should be careful about
|
||||
// reaching out to another component directly after receiving an
|
||||
// event, as its view of the world may not yet (or ever) be exactly
|
||||
// consistent with yours.
|
||||
// However, you cannot assume that what your client see as "now" is
|
||||
// the same as what other clients. They may be further behind you in
|
||||
// working through the timeline, or running ahead of you. This means
|
||||
// you should be careful about reaching out to another component
|
||||
// directly after receiving an event, as its view of the world may not
|
||||
// yet (or ever) be exactly consistent with yours.
|
||||
//
|
||||
// To make your code more testable and understandable, you should try
|
||||
// to structure it following the actor model: you have some local
|
||||
@@ -63,7 +66,7 @@
|
||||
// # Expected subscriber behavior
|
||||
//
|
||||
// Subscribers are expected to promptly receive their events on
|
||||
// [Subscriber.Chan]. The bus has a small, fixed amount of internal
|
||||
// [Subscriber.Events]. The bus has a small, fixed amount of internal
|
||||
// buffering, meaning that a slow subscriber will eventually cause
|
||||
// backpressure and block publication of all further events.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user