Documentation
¶
Overview ¶
Example ¶
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/johnknl/replicators"
)
type MyMsg int
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
start := make(chan struct{})
subbed := make(chan struct{})
dropped := make(chan struct{})
hub := replicators.NewHub(
ctx,
replicators.WithDevLogger[MyMsg](),
replicators.WithCounterHandler[MyMsg](),
replicators.WithEventHandler(replicators.EventHandlerFunc[MyMsg](func(_ context.Context, e replicators.Event[MyMsg]) {
switch e.(type) {
case replicators.EvtSubscribed[MyMsg]:
close(subbed)
case replicators.EvtSubDropped[MyMsg]:
close(dropped)
}
})),
)
wg := sync.WaitGroup{}
wg.Go(func() {
// 1. The first message is never received
// 2. One message is read
// 3. Next delivery will be dropped by the hub (but tolerated)
// 4. Final delivery will be dropped by the hub, and the subscription will be dropped
for i := range 4 {
_ = hub.Broadcast(ctx, MyMsg(i))
if i == 0 {
close(start)
<-subbed
}
}
})
wg.Go(func() {
// Don't start the next consumer until the first send is dropped
<-start
subscription, err := hub.Subscribe(ctx, replicators.WithMaxDeliveryTimeouts[MyMsg](1))
if err != nil {
panic(err)
}
// We'll read one message, then block until the subscription is
// dropped by the hub.
<-subscription.Data()
<-dropped
fmt.Println("error: " + subscription.Err().Error()) // nolint:forbidigo // example code
})
wg.Wait()
fmt.Printf("%#v\n", hub.Stats(ctx).Counts) // nolint:forbidigo // example code
}
Output: error: subscription dropped &replicators.Counts{Subscriptions:1, Cancellations:0, Sent:4, Timeouts:2, Dropped:1, Delivered:1, Undeliverable:1}
Index ¶
- Constants
- Variables
- func WithAttachBuffer[T any](size int) func(*Hub[T])
- func WithCancelBuffer[T any](size int) func(*Hub[T])
- func WithCounterHandler[T any]() func(*Hub[T])
- func WithDeliveryTimeout[T any](timeout time.Duration) func(*Hub[T])
- func WithDevLogger[T any]() func(*Hub[T])
- func WithEchoBuffer[T any](n int) func(*Hub[T])
- func WithEventHandler[T any](handler EventHandler[T]) func(*Hub[T])
- func WithEventHandlerFunc[T any](fn func(context.Context, Event[T])) func(*Hub[T])
- func WithEventHandlers[T any](handlers ...EventHandler[T]) func(*Hub[T])
- func WithMaxDeliveryTimeouts[T any](tolerance int) func(*Subscription[T])
- func WithReceiveBuffer[T any](buffSize int) func(*Subscription[T])
- func WithSendBuffer[T any](size int) func(*Hub[T])
- func WithShutdownTimeout[T any](timeout time.Duration) func(*Hub[T])
- func WithSlogger[T any](ctx context.Context, logger *slog.Logger) func(*Hub[T])
- type CounterHandler
- type Counts
- type Event
- type EventHandler
- type EventHandlerFunc
- type EventName
- type EvtCancelFailed
- type EvtCancelled
- type EvtDelivered
- type EvtDeliveryTimeout
- type EvtDrainCancelled
- type EvtDraining
- type EvtNoSubscribers
- type EvtSendContextCancelled
- type EvtSent
- type EvtShutdown
- type EvtSubDropped
- type EvtSubscribed
- type Gauges
- type Hub
- type HubEventHandlerHub
- type Ring
- type SnapshottingGuages
- type Stats
- type Subscription
Examples ¶
Constants ¶
const ( // DefaultSendBuffer is the default size of the send buffer. DefaultSendBuffer = 10 // DefaultAttachBuffer is the default size of the attach buffer. DefaultAttachBuffer = 1 // DefaultCancelBuffer is the default size of the cancel buffer. DefaultCancelBuffer = 1 // DefaultDeliveryTimeout is the default timeout for delivering messages to subscribers. DefaultDeliveryTimeout = 100 * time.Millisecond // DefaultShutdownTimeout is the default timeout for shutting down the hub. DefaultShutdownTimeout = 10 * time.Second )
Variables ¶
var ( // ErrSubscriptionDropped is returned when a subscription is dropped. // This happens when the subscriber is too slow to process messages and the tolerance is exceeded. ErrSubscriptionDropped = errors.New("subscription dropped") // ErrSubscriptionCancelled is returned when a subscription is cancelled. // This happens when the subscriber cancels the subscription. ErrSubscriptionCancelled = errors.New("subscription cancelled") )
Functions ¶
func WithAttachBuffer ¶
WithAttachBuffer sets the buffer size for the attach channel. The attach channel is used to queue new subscribers. Note that on shutdown, the attach buffer is discarded.
func WithCancelBuffer ¶
WithCancelBuffer sets the buffer size for the cancel channel. The cancel channel is used to cancel subscriptions. Note that on shutdown, the cancel buffer is discarded.
func WithCounterHandler ¶
WithCounterHandler adds a CounterHandler to the hub.
func WithDeliveryTimeout ¶
WithDeliveryTimeout sets the maximum time to wait for a subscriber to process a message.
func WithDevLogger ¶
WithDevLogger is a convenience function that sets up a development logger using the standard library's slog package.
func WithEchoBuffer ¶ added in v0.2.0
WithEchoBuffer enables the echo feature. When enabled, the hub will echo the last `n` messages sent, to new subscribers.
Example ¶
package main
import (
"context"
"fmt"
"github.com/johnknl/replicators"
)
type MyMsg int
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dropped := make(chan struct{})
hub := replicators.NewHub(ctx,
replicators.WithDevLogger[MyMsg](),
replicators.WithEchoBuffer[MyMsg](1),
replicators.WithEventHandlerFunc[MyMsg](func(_ context.Context, e replicators.Event[MyMsg]) {
if _, ok := e.(replicators.EvtSubDropped[MyMsg]); ok {
close(dropped)
}
}),
replicators.WithCounterHandler[MyMsg](),
)
// Broadcast a message, it'll be undeliverable because there are no subscribers,
// but the hub will remember it and send it to the next subscriber.
// This would be better accomplished with a send buffer but this is just
// part of the example.
if err := hub.Broadcast(ctx, MyMsg(42)); err != nil {
panic(err)
}
// Subscribe to the hub, the subscriber will receive the last message
// that was broadcasted.
subscription, err := hub.Subscribe(ctx)
if err != nil {
panic(err)
}
msg := <-subscription.Data()
fmt.Println(msg) // nolint:forbidigo // example code
// Now broadcast another message, the subscriber will receive it.
if err = hub.Broadcast(ctx, MyMsg(43)); err != nil {
panic(err)
}
msg = <-subscription.Data()
fmt.Println(msg) // nolint:forbidigo // example code
// Now subscribe to the hub again, the subscriber will receive the last message
// that was broadcasted.
subscription2, err := hub.Subscribe(ctx)
if err != nil {
panic(err)
}
msg = <-subscription2.Data()
fmt.Println(msg) // nolint:forbidigo // example code
// Now subscribe again, but don't read the message: subscriber is
// immediately subject to max delivery timeout (which defaults to 0 timeouts of 100ms).
_, err = hub.Subscribe(ctx)
if err != nil {
panic(err)
}
// Wait for the subscription to be dropped by the hub.
<-dropped
fmt.Printf("%#v\n", hub.Stats(ctx).Counts) // nolint:forbidigo // example code
}
Output: 42 43 43 &replicators.Counts{Subscriptions:3, Cancellations:0, Sent:2, Timeouts:1, Dropped:1, Delivered:3, Undeliverable:1}
func WithEventHandler ¶
func WithEventHandler[T any](handler EventHandler[T]) func(*Hub[T])
WithEventHandler adds an EventHandler for the hub. It will wrap any existing EventHandler.To set all event handlers, use WithEventHandlers instead.
func WithEventHandlerFunc ¶
WithEventHandlerFunc adds an EventHandler for the hub.
func WithEventHandlers ¶
func WithEventHandlers[T any](handlers ...EventHandler[T]) func(*Hub[T])
WithEventHandlers sets the EventHandler(s) for the hub.
func WithMaxDeliveryTimeouts ¶
func WithMaxDeliveryTimeouts[T any](tolerance int) func(*Subscription[T])
WithMaxDeliveryTimeouts sets the maximum number of timeouts before a subscription is dropped.
func WithReceiveBuffer ¶
func WithReceiveBuffer[T any](buffSize int) func(*Subscription[T])
WithReceiveBuffer sets the receive buffer size for a subscription.
func WithSendBuffer ¶
WithSendBuffer sets the buffer size for the send channel.
func WithShutdownTimeout ¶
WithShutdownTimeout sets the maximum total time to wait for the hub to shutdown gracefully.
func WithSlogger ¶
WithSlogger is a convenience function that sets up a slog.Logger as an event handler for the hub.
Example ¶
package main
import (
"context"
"log/slog"
"os"
"github.com/johnknl/replicators"
)
type MyMsg int
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
replicators.NewHub(ctx, replicators.WithSlogger[MyMsg](ctx, logger))
}
Output:
Types ¶
type CounterHandler ¶
type CounterHandler[T any] struct { // contains filtered or unexported fields }
CounterHandler is an EventHandler that updates the counters based on the events received.
func NewCounterHandler ¶
func NewCounterHandler[T any]() *CounterHandler[T]
NewCounterHandler creates a new CounterHandler.
func (*CounterHandler[T]) HandleEvent ¶
func (s *CounterHandler[T]) HandleEvent(_ context.Context, e Event[T])
HandleEvent updates the stats based on the event type.
type Counts ¶
type Counts struct {
// Subscriptions is the total number of subscriptions created.
Subscriptions int64
// Cancellations is the total number of subscriptions cancelled.
Cancellations int64
// Sent is the total number of *messages* as counted when pushed on the queue.
Sent int64
// Timeouts is the total number of *deliveries* abandoned due to timeout.
Timeouts int64
// Dropped is the total number of *subscriptions* dropped due to slow consumers.
Dropped int64
// Delivered is the total number of copies delivered.
Delivered int64
// Undeliverable is the total number of messages that could not be delivered to any subscriber.
Undeliverable int64
}
Counts contains the current stats for the hub.
type EventHandler ¶
EventHandler is a simple interface for handling events.
func DevLoggerHandler ¶
func DevLoggerHandler[T any]() EventHandler[T]
DevLoggerHandler returns an EventHandler that logs events using the standard library's slog package.
func SlogEventHandler ¶
SlogEventHandler returns an EventHandler that logs events using the provided slog.Logger.
Note that the supported levels are determined at creation time and will not be reflected if the logger's level is changed later. The logger will use the context created with the EventHandler, not the context passed when the event is handled.
Disabling warning logging will result in nothing being logged.
type EventHandlerFunc ¶
EventHandlerFunc is a function type that implements the EventHandler interface.
func (EventHandlerFunc[T]) HandleEvent ¶
func (f EventHandlerFunc[T]) HandleEvent(ctx context.Context, e Event[T])
HandleEvent calls the function with the event.
type EvtCancelFailed ¶
type EvtCancelFailed[T any] struct { Sub *Subscription[T] Err error }
EvtCancelFailed is emitted when a subscription cancellation fails.
func (EvtCancelFailed[T]) Name ¶
func (e EvtCancelFailed[T]) Name() EventName
Name returns the name of the event.
func (EvtCancelFailed[T]) Subscription ¶
func (e EvtCancelFailed[T]) Subscription() *Subscription[T]
Subscription returns the subscription that failed to be cancelled.
type EvtCancelled ¶
type EvtCancelled[T any] struct{ Sub *Subscription[T] }
EvtCancelled is emitted when a subscription is explicitly cancelled.
func (EvtCancelled[T]) Name ¶
func (e EvtCancelled[T]) Name() EventName
Name returns the name of the event.
func (EvtCancelled[T]) Subscription ¶
func (e EvtCancelled[T]) Subscription() *Subscription[T]
Subscription returns the subscription that was cancelled.
type EvtDelivered ¶
type EvtDelivered[T any] struct{ Msg T }
EvtDelivered is emitted when a message is delivered to a subscriber.
func (EvtDelivered[T]) Message ¶
func (e EvtDelivered[T]) Message() T
Message returns the message that was delivered to a subscriber.
func (EvtDelivered[T]) Name ¶
func (e EvtDelivered[T]) Name() EventName
Name returns the name of the event.
type EvtDeliveryTimeout ¶
EvtDeliveryTimeout is emitted when a message delivery to a subscriber times out.
func (EvtDeliveryTimeout[T]) Message ¶
func (e EvtDeliveryTimeout[T]) Message() T
Message returns the message that timed out.
func (EvtDeliveryTimeout[T]) Name ¶
func (e EvtDeliveryTimeout[T]) Name() EventName
Name returns the name of the event.
type EvtDrainCancelled ¶
type EvtDrainCancelled struct{ Remaining int }
EvtDrainCancelled is emitted when draining the send buffer on shutdown is cancelled due to the context being cancelled, and contains the number of messages left in the queue.
func (EvtDrainCancelled) Name ¶
func (e EvtDrainCancelled) Name() EventName
Name returns the name of the event.
type EvtDraining ¶
type EvtDraining struct{ Count int }
EvtDraining is emitted when the hub is draining, and contains the number of messages left in the queue.
type EvtNoSubscribers ¶
type EvtNoSubscribers[T any] struct{ Msg T }
EvtNoSubscribers is emitted when a message is sent but there are no subscribers to receive it.
func (EvtNoSubscribers[T]) Message ¶
func (e EvtNoSubscribers[T]) Message() T
Message returns the message that was sent but not received by any subscribers.
func (EvtNoSubscribers[T]) Name ¶
func (e EvtNoSubscribers[T]) Name() EventName
Name returns the name of the event.
type EvtSendContextCancelled ¶
type EvtSendContextCancelled[T any] struct{ Msg T }
EvtSendContextCancelled is emitted when a message send is cancelled due to the context being cancelled.
func (EvtSendContextCancelled[T]) Message ¶
func (e EvtSendContextCancelled[T]) Message() T
Message returns the message that was attempted to be sent when the context was cancelled.
func (EvtSendContextCancelled[T]) Name ¶
func (e EvtSendContextCancelled[T]) Name() EventName
Name returns the name of the event.
type EvtSent ¶
type EvtSent[T any] struct{ Msg T }
EvtSent is emitted when a message is sent to the hub.
type EvtSubDropped ¶
type EvtSubDropped[T any] struct{ Sub *Subscription[T] }
EvtSubDropped is emitted when a subscription is dropped due its consumer being slower than allowed.
func (EvtSubDropped[T]) Name ¶
func (e EvtSubDropped[T]) Name() EventName
Name returns the name of the event.
type EvtSubscribed ¶
type EvtSubscribed[T any] struct{ Sub *Subscription[T] }
EvtSubscribed is emitted when a subscription is successfully created.
func (EvtSubscribed[T]) Name ¶
func (e EvtSubscribed[T]) Name() EventName
Name returns the name of the event.
func (EvtSubscribed[T]) Subscription ¶
func (e EvtSubscribed[T]) Subscription() *Subscription[T]
Subscription returns the subscription that was created.
type Gauges ¶
type Gauges struct {
// Queued is the current size of the message queue.
Queued int
// Subscriptions is the current number of subscribers
// attached to the hub.
Subscriptions int
// Waiting is the current number of subscribers waiting
// to be attached to the hub.
Waiting int
// Cancelling is the current number of subscribers waiting
// to be detached from the hub.
Cancelling int
// Buffered is the current total number of messages buffered
// in the delivery buffers. This is the sum of all messages
// buffered for all subscribers.
Buffered int
}
Gauges contains the current gauges for the hub.
type Hub ¶
type Hub[T any] struct { // contains filtered or unexported fields }
Hub provides the frontend for interacting with the loop. It provides an API for sending messages, subscribing to messages, and cancelling subscriptions.
func (*Hub[T]) Broadcast ¶
Broadcast a message to subscribers. If the context is canceled, this is effectively a no-op.
func (*Hub[T]) Cancel ¶
func (s *Hub[T]) Cancel(ctx context.Context, sub *Subscription[T]) error
Cancel a subscription. If the context is canceled, cancellation will fail.
func (*Hub[T]) Stats ¶
Stats returns a snapshot of hub statistics. When no CounterHandler is attached, the counts will be zero. Note that calling this method will block the main dispatch loop briefly while gathering metrics.
The SnapshottingGuages event is emitted at the start of the stats gathering process.
Calling this method after shutdown may result in a deadlock if the passed context is not canceled.
func (*Hub[T]) Subscribe ¶
func (s *Hub[T]) Subscribe(ctx context.Context, opts ...func(*Subscription[T])) (*Subscription[T], error)
Subscribe to messages.
When dealing with a subscriber that has very variable message processing time, a larger buffer will help smooth things out. Otherwise, a buffer of 1 is usually sufficient. The tolerance parameter is the number of messages that can be dropped before the subscription is cancelled. If tolerance is equal or lower than 0, the subscription will be cancelled immediately when a message is dropped.
The passed context is used to cancel the subscription. If the context is canceled, the subscription will be cancelled and an error will be returned. Note that the context is not the only way a subscription may be detached.
A subscription can also be cancelled by invoking Cancel() on the hub, and it will be dropped if the subscriber is too slow to process messages and the tolerance is exceeded. When the context is canceled, the subscription's error will be set to context.Context.Err().
Example (WithReceiveBuffer) ¶
package main
import (
"context"
"fmt"
"time"
"github.com/johnknl/replicators"
)
type MyMsg int
func main() {
buffSize := 10
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
subbed := make(chan struct{})
hub := replicators.NewHub(
ctx,
replicators.WithEventHandlerFunc[MyMsg](func(_ context.Context, e replicators.Event[MyMsg]) {
switch e.(type) {
case replicators.EvtSubscribed[MyMsg]:
// Because the default settings use a non-zero attach buffer, some of the data would be
// missed unless we sync. It would be more straightforward to use a zero attach buffer,
// but this is just an intentionally belabored example.
close(subbed)
}
}),
)
subscription, err := hub.Subscribe(ctx, replicators.WithReceiveBuffer[MyMsg](buffSize))
if err != nil {
panic(err)
}
// Wait for the subscription to be fully attached before broadcasting messages.
<-subbed
// fill up the buffer
for i := range buffSize {
_ = hub.Broadcast(ctx, MyMsg(i))
}
// read the messages from the receive buffer
for msg := range subscription.Data() {
fmt.Println(msg) // nolint:forbidigo // example code
}
}
Output: 0 1 2 3 4 5 6 7 8 9
type HubEventHandlerHub ¶ added in v0.3.0
type HubEventHandlerHub[T any] struct { // contains filtered or unexported fields }
HubEventHandlerHub is an EventHandler that dispatches events to multiple EventHandlers using a buffered channel.
func NewHubEventHandlerHub ¶ added in v0.3.0
func NewHubEventHandlerHub[T any](ctx context.Context, buffer int, handlers ...EventHandler[T]) *HubEventHandlerHub[T]
NewHubEventHandlerHub creates a new HubEventHandlerHub with the given buffer size and EventHandlers.
func (*HubEventHandlerHub[T]) HandleEvent ¶ added in v0.3.0
func (h *HubEventHandlerHub[T]) HandleEvent(ctx context.Context, e Event[T])
HandleEvent dispatches the event to all registered EventHandlers.
type Ring ¶ added in v0.2.0
type Ring[T any] struct { // contains filtered or unexported fields }
Ring is a generic ring buffer that holds a fixed number of elements of type T.
func (*Ring[T]) Add ¶ added in v0.2.0
func (r *Ring[T]) Add(v T)
Add adds a new element to the ring buffer. If the buffer is full, it overwrites the oldest element.
type SnapshottingGuages ¶
type SnapshottingGuages[T any] struct{ Msg T }
SnapshottingGuages is emitted when the hub is snapshotting its gauges.
func (SnapshottingGuages[T]) Name ¶
func (e SnapshottingGuages[T]) Name() EventName
Name returns the name of the event.
type Stats ¶
type Stats struct {
// Gauges contains the current gauges for the hub.
Gauges *Gauges
// Counts contains the current counts for the hub.
Counts *Counts
}
Stats contains the current stats for the hub.
type Subscription ¶
type Subscription[T any] struct { // contains filtered or unexported fields }
Subscription represents a subscription to a Hub.
func (*Subscription[T]) Cancel ¶
func (s *Subscription[T]) Cancel(ctx context.Context) error
Cancel cancels the subscription via the associated Hub.
func (*Subscription[T]) Data ¶
func (s *Subscription[T]) Data() <-chan T
Data returns a read-only channel that receives data from the subscription.
func (*Subscription[T]) Err ¶
func (s *Subscription[T]) Err() error
Err returns the error associated with the subscription, if any.