|
|
|
@ -6,7 +6,7 @@ import (
|
|
|
|
|
"sync/atomic"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// EventBuffer is a single-writer, multiple-reader, unlimited length concurrent
|
|
|
|
|
// eventBuffer is a single-writer, multiple-reader, unlimited length concurrent
|
|
|
|
|
// buffer of events that have been published on a topic. The buffer is
|
|
|
|
|
// effectively just the head of an atomically updated single-linked list. Atomic
|
|
|
|
|
// accesses are usually to be suspected as premature optimization but this
|
|
|
|
@ -27,7 +27,7 @@ import (
|
|
|
|
|
//
|
|
|
|
|
// The buffer is used to deliver all messages broadcast toa topic for active
|
|
|
|
|
// subscribers to consume, but it is also an effective way to both deliver and
|
|
|
|
|
// optionally cache snapshots per topic and key. byt using an EventBuffer,
|
|
|
|
|
// optionally cache snapshots per topic and key. byt using an eventBuffer,
|
|
|
|
|
// snapshot functions don't have to read the whole snapshot into memory before
|
|
|
|
|
// delivery - they can stream from memdb. However simply by storing a pointer to
|
|
|
|
|
// the first event in the buffer, we can cache the buffered events for future
|
|
|
|
@ -46,26 +46,26 @@ import (
|
|
|
|
|
// automatically keep the events we need to make that work for exactly the
|
|
|
|
|
// optimal amount of time and no longer.
|
|
|
|
|
//
|
|
|
|
|
// A new buffer is constructed with a sentinel "empty" BufferItem that has a nil
|
|
|
|
|
// A new buffer is constructed with a sentinel "empty" bufferItem that has a nil
|
|
|
|
|
// Events array. This enables subscribers to start watching for the next update
|
|
|
|
|
// immediately.
|
|
|
|
|
//
|
|
|
|
|
// The zero value EventBuffer is _not_ a usable type since it has not been
|
|
|
|
|
// The zero value eventBuffer is _not_ a usable type since it has not been
|
|
|
|
|
// initialized with an empty bufferItem so can't be used to wait for the first
|
|
|
|
|
// published event. Call NewEventBuffer to construct a new buffer.
|
|
|
|
|
// published event. Call newEventBuffer to construct a new buffer.
|
|
|
|
|
//
|
|
|
|
|
// Calls to Append or AppendBuffer that mutate the head must be externally
|
|
|
|
|
// synchronized. This allows systems that already serialize writes to append
|
|
|
|
|
// without lock overhead (e.g. a snapshot goroutine appending thousands of
|
|
|
|
|
// events).
|
|
|
|
|
type EventBuffer struct {
|
|
|
|
|
type eventBuffer struct {
|
|
|
|
|
head atomic.Value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewEventBuffer creates an EventBuffer ready for use.
|
|
|
|
|
func NewEventBuffer() *EventBuffer {
|
|
|
|
|
b := &EventBuffer{}
|
|
|
|
|
b.head.Store(NewBufferItem())
|
|
|
|
|
// newEventBuffer creates an eventBuffer ready for use.
|
|
|
|
|
func newEventBuffer() *eventBuffer {
|
|
|
|
|
b := &eventBuffer{}
|
|
|
|
|
b.head.Store(newBufferItem())
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -76,9 +76,9 @@ func NewEventBuffer() *EventBuffer {
|
|
|
|
|
// mutations to the events as they may have been exposed to subscribers in other
|
|
|
|
|
// goroutines. Append only supports a single concurrent caller and must be
|
|
|
|
|
// externally synchronized with other Append, AppendBuffer or AppendErr calls.
|
|
|
|
|
func (b *EventBuffer) Append(events []Event) {
|
|
|
|
|
func (b *eventBuffer) Append(events []Event) {
|
|
|
|
|
// Push events to the head
|
|
|
|
|
it := NewBufferItem()
|
|
|
|
|
it := newBufferItem()
|
|
|
|
|
it.Events = events
|
|
|
|
|
b.AppendBuffer(it)
|
|
|
|
|
}
|
|
|
|
@ -92,7 +92,7 @@ func (b *EventBuffer) Append(events []Event) {
|
|
|
|
|
//
|
|
|
|
|
// AppendBuffer only supports a single concurrent caller and must be externally
|
|
|
|
|
// synchronized with other Append, AppendBuffer or AppendErr calls.
|
|
|
|
|
func (b *EventBuffer) AppendBuffer(item *BufferItem) {
|
|
|
|
|
func (b *eventBuffer) AppendBuffer(item *bufferItem) {
|
|
|
|
|
// First store it as the next node for the old head this ensures once it's
|
|
|
|
|
// visible to new searchers the linked list is already valid. Not sure it
|
|
|
|
|
// matters but this seems nicer.
|
|
|
|
@ -110,20 +110,20 @@ func (b *EventBuffer) AppendBuffer(item *BufferItem) {
|
|
|
|
|
// streaming subscription and return the error. AppendErr only supports a
|
|
|
|
|
// single concurrent caller and must be externally synchronized with other
|
|
|
|
|
// Append, AppendBuffer or AppendErr calls.
|
|
|
|
|
func (b *EventBuffer) AppendErr(err error) {
|
|
|
|
|
b.AppendBuffer(&BufferItem{Err: err})
|
|
|
|
|
func (b *eventBuffer) AppendErr(err error) {
|
|
|
|
|
b.AppendBuffer(&bufferItem{Err: err})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Head returns the current head of the buffer. It will always exist but it may
|
|
|
|
|
// be a "sentinel" empty item with a nil Events slice to allow consumers to
|
|
|
|
|
// watch for the next update. Consumers should always check for empty Events and
|
|
|
|
|
// treat them as no-ops. Will panic if EventBuffer was not initialized correctly
|
|
|
|
|
// with EventBuffer.
|
|
|
|
|
func (b *EventBuffer) Head() *BufferItem {
|
|
|
|
|
return b.head.Load().(*BufferItem)
|
|
|
|
|
// treat them as no-ops. Will panic if eventBuffer was not initialized correctly
|
|
|
|
|
// with eventBuffer.
|
|
|
|
|
func (b *eventBuffer) Head() *bufferItem {
|
|
|
|
|
return b.head.Load().(*bufferItem)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BufferItem represents a set of events published by a single raft operation.
|
|
|
|
|
// bufferItem represents a set of events published by a single raft operation.
|
|
|
|
|
// The first item returned by a newly constructed buffer will have nil Events.
|
|
|
|
|
// It is a sentinel value which is used to wait on the next events via Next.
|
|
|
|
|
//
|
|
|
|
@ -135,9 +135,9 @@ func (b *EventBuffer) Head() *BufferItem {
|
|
|
|
|
// they have been delivered except where it's intentional to maintain a cache or
|
|
|
|
|
// trailing store of events for performance reasons.
|
|
|
|
|
//
|
|
|
|
|
// Subscribers must not mutate the BufferItem or the Events or Encoded payloads
|
|
|
|
|
// Subscribers must not mutate the bufferItem or the Events or Encoded payloads
|
|
|
|
|
// inside as these are shared between all readers.
|
|
|
|
|
type BufferItem struct {
|
|
|
|
|
type bufferItem struct {
|
|
|
|
|
// Events is the set of events published at one raft index. This may be nil as
|
|
|
|
|
// a sentinel value to allow watching for the first event in a buffer. Callers
|
|
|
|
|
// should check and skip nil Events at any point in the buffer. It will also
|
|
|
|
@ -170,10 +170,10 @@ type bufferLink struct {
|
|
|
|
|
ch chan struct{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewBufferItem returns a blank buffer item with a link and chan ready to have
|
|
|
|
|
// newBufferItem returns a blank buffer item with a link and chan ready to have
|
|
|
|
|
// the fields set and be appended to a buffer.
|
|
|
|
|
func NewBufferItem() *BufferItem {
|
|
|
|
|
return &BufferItem{
|
|
|
|
|
func newBufferItem() *bufferItem {
|
|
|
|
|
return &bufferItem{
|
|
|
|
|
link: &bufferLink{
|
|
|
|
|
ch: make(chan struct{}),
|
|
|
|
|
},
|
|
|
|
@ -182,7 +182,7 @@ func NewBufferItem() *BufferItem {
|
|
|
|
|
|
|
|
|
|
// Next return the next buffer item in the buffer. It may block until ctx is
|
|
|
|
|
// cancelled or until the next item is published.
|
|
|
|
|
func (i *BufferItem) Next(ctx context.Context) (*BufferItem, error) {
|
|
|
|
|
func (i *bufferItem) Next(ctx context.Context) (*bufferItem, error) {
|
|
|
|
|
// See if there is already a next value, block if so. Note we don't rely on
|
|
|
|
|
// state change (chan nil) as that's not threadsafe but detecting close is.
|
|
|
|
|
select {
|
|
|
|
@ -197,7 +197,7 @@ func (i *BufferItem) Next(ctx context.Context) (*BufferItem, error) {
|
|
|
|
|
// shouldn't be possible
|
|
|
|
|
return nil, errors.New("invalid next item")
|
|
|
|
|
}
|
|
|
|
|
next := nextRaw.(*BufferItem)
|
|
|
|
|
next := nextRaw.(*bufferItem)
|
|
|
|
|
if next.Err != nil {
|
|
|
|
|
return nil, next.Err
|
|
|
|
|
}
|
|
|
|
@ -210,12 +210,12 @@ func (i *BufferItem) Next(ctx context.Context) (*BufferItem, error) {
|
|
|
|
|
|
|
|
|
|
// NextNoBlock returns the next item in the buffer without blocking. If it
|
|
|
|
|
// reaches the most recent item it will return nil and no error.
|
|
|
|
|
func (i *BufferItem) NextNoBlock() (*BufferItem, error) {
|
|
|
|
|
func (i *bufferItem) NextNoBlock() (*bufferItem, error) {
|
|
|
|
|
nextRaw := i.link.next.Load()
|
|
|
|
|
if nextRaw == nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
next := nextRaw.(*BufferItem)
|
|
|
|
|
next := nextRaw.(*bufferItem)
|
|
|
|
|
if next.Err != nil {
|
|
|
|
|
return nil, next.Err
|
|
|
|
|
}
|
|
|
|
@ -230,14 +230,14 @@ func (i *BufferItem) NextNoBlock() (*BufferItem, error) {
|
|
|
|
|
// one, or if not it returns an empty item (that will be ignored by subscribers)
|
|
|
|
|
// that has the same link as the current buffer so that it will be notified of
|
|
|
|
|
// future updates in the buffer without including the current item.
|
|
|
|
|
func (i *BufferItem) FollowAfter() (*BufferItem, error) {
|
|
|
|
|
func (i *bufferItem) FollowAfter() (*bufferItem, error) {
|
|
|
|
|
next, err := i.NextNoBlock()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if next == nil {
|
|
|
|
|
// Return an empty item that can be followed to the next item published.
|
|
|
|
|
item := &BufferItem{}
|
|
|
|
|
item := &bufferItem{}
|
|
|
|
|
item.link = i.link
|
|
|
|
|
return item, nil
|
|
|
|
|
}
|
|
|
|
|