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:
David Anderson
2025-03-04 11:22:30 -08:00
committed by Dave Anderson
parent f840aad49e
commit 3e18434595
6 changed files with 346 additions and 209 deletions
+34 -31
View File
@@ -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.
//