github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/stream/event_buffer.go (about)

     1  package stream
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  )
    12  
    13  // eventBuffer is a single-writer, multiple-reader, fixed length concurrent
    14  // buffer of events that have been published. The buffer is
    15  // the head and tail of an atomically updated single-linked list. Atomic
    16  // accesses are usually to be suspected as premature optimization but this
    17  // specific design has several important features that significantly simplify a
    18  // lot of our PubSub machinery.
    19  //
    20  // eventBuffer is an adaptation of conuls agent/stream/event eventBuffer but
    21  // has been updated to be a max length buffer to work for Nomad's usecase.
    22  //
    23  // The eventBuffer only tracks the most recent set of published events,
    24  // up to the max configured size, older events are dropped from the buffer
    25  // but will only be garbage collected once the slowest reader drops the item.
    26  // Consumers are notified of new events by closing a channel on the previous head
    27  // allowing efficient broadcast to many watchers without having to run multiple
    28  // goroutines or deliver to O(N) separate channels.
    29  //
    30  // Because eventBuffer is a linked list with atomically updated pointers, readers don't
    31  // have to take a lock and can consume at their own pace. Slow readers will eventually
    32  // be forced to reconnect to the lastest head by being notified via a bufferItem's droppedCh.
    33  //
    34  // A new buffer is constructed with a sentinel "empty" bufferItem that has a nil
    35  // Events array. This enables subscribers to start watching for the next update
    36  // immediately.
    37  //
    38  // The zero value eventBuffer is _not_ usable, as it has not been
    39  // initialized with an empty bufferItem so can not be used to wait for the first
    40  // published event. Call newEventBuffer to construct a new buffer.
    41  //
    42  // Calls to Append or purne that mutate the head must be externally
    43  // synchronized. This allows systems that already serialize writes to append
    44  // without lock overhead.
    45  type eventBuffer struct {
    46  	size *int64
    47  
    48  	head atomic.Value
    49  	tail atomic.Value
    50  
    51  	maxSize int64
    52  }
    53  
    54  // newEventBuffer creates an eventBuffer ready for use.
    55  func newEventBuffer(size int64) *eventBuffer {
    56  	zero := int64(0)
    57  	b := &eventBuffer{
    58  		maxSize: size,
    59  		size:    &zero,
    60  	}
    61  
    62  	item := newBufferItem(&structs.Events{Index: 0, Events: nil})
    63  
    64  	b.head.Store(item)
    65  	b.tail.Store(item)
    66  
    67  	return b
    68  }
    69  
    70  // Append a set of events from one raft operation to the buffer and notify
    71  // watchers. After calling append, the caller must not make any further
    72  // mutations to the events as they may have been exposed to subscribers in other
    73  // goroutines. Append only supports a single concurrent caller and must be
    74  // externally synchronized with other Append calls.
    75  func (b *eventBuffer) Append(events *structs.Events) {
    76  	b.appendItem(newBufferItem(events))
    77  }
    78  
    79  func (b *eventBuffer) appendItem(item *bufferItem) {
    80  	// Store the next item to the old tail
    81  	oldTail := b.Tail()
    82  	oldTail.link.next.Store(item)
    83  
    84  	// Update the tail to the new item
    85  	b.tail.Store(item)
    86  
    87  	// Increment the buffer size
    88  	atomic.AddInt64(b.size, 1)
    89  
    90  	// Advance Head until we are under allowable size
    91  	for atomic.LoadInt64(b.size) > b.maxSize {
    92  		b.advanceHead()
    93  	}
    94  
    95  	// notify waiters next event is available
    96  	close(oldTail.link.nextCh)
    97  }
    98  
    99  func newSentinelItem() *bufferItem {
   100  	return newBufferItem(&structs.Events{})
   101  }
   102  
   103  // advanceHead drops the current Head buffer item and notifies readers
   104  // that the item should be discarded by closing droppedCh.
   105  // Slow readers will prevent the old head from being GC'd until they
   106  // discard it.
   107  func (b *eventBuffer) advanceHead() {
   108  	old := b.Head()
   109  
   110  	next := old.link.next.Load()
   111  	// if the next item is nil replace it with a sentinel value
   112  	if next == nil {
   113  		next = newSentinelItem()
   114  	}
   115  
   116  	// notify readers that old is being dropped
   117  	close(old.link.droppedCh)
   118  
   119  	// store the next value to head
   120  	b.head.Store(next)
   121  
   122  	// If the old head is equal to the tail
   123  	// update the tail value as well
   124  	if old == b.Tail() {
   125  		b.tail.Store(next)
   126  	}
   127  
   128  	// In the case of there being a sentinel item or advanceHead being called
   129  	// on a sentinel item, only decrement if there are more than sentinel
   130  	// values
   131  	if atomic.LoadInt64(b.size) > 0 {
   132  		// update the amount of events we have in the buffer
   133  		atomic.AddInt64(b.size, -1)
   134  	}
   135  }
   136  
   137  // Head returns the current head of the buffer. It will always exist but it may
   138  // be a "sentinel" empty item with a nil Events slice to allow consumers to
   139  // watch for the next update. Consumers should always check for empty Events and
   140  // treat them as no-ops. Will panic if eventBuffer was not initialized correctly
   141  // with NewEventBuffer
   142  func (b *eventBuffer) Head() *bufferItem {
   143  	return b.head.Load().(*bufferItem)
   144  }
   145  
   146  // Tail returns the current tail of the buffer. It will always exist but it may
   147  // be a "sentinel" empty item with a Nil Events slice to allow consumers to
   148  // watch for the next update. Consumers should always check for empty Events and
   149  // treat them as no-ops. Will panic if eventBuffer was not initialized correctly
   150  // with NewEventBuffer
   151  func (b *eventBuffer) Tail() *bufferItem {
   152  	return b.tail.Load().(*bufferItem)
   153  }
   154  
   155  // StarStartAtClosest returns the closest bufferItem to a requested starting
   156  // index as well as the offset between the requested index and returned one.
   157  func (b *eventBuffer) StartAtClosest(index uint64) (*bufferItem, int) {
   158  	item := b.Head()
   159  	if index < item.Events.Index {
   160  		return item, int(item.Events.Index) - int(index)
   161  	}
   162  	if item.Events.Index == index {
   163  		return item, 0
   164  	}
   165  
   166  	for {
   167  		prev := item
   168  		item = item.NextNoBlock()
   169  		if item == nil {
   170  			return prev, int(index) - int(prev.Events.Index)
   171  		}
   172  		if index < item.Events.Index {
   173  			return item, int(item.Events.Index) - int(index)
   174  		}
   175  		if index == item.Events.Index {
   176  			return item, 0
   177  		}
   178  	}
   179  }
   180  
   181  // Len returns the current length of the buffer
   182  func (b *eventBuffer) Len() int {
   183  	return int(atomic.LoadInt64(b.size))
   184  }
   185  
   186  // bufferItem represents a set of events published by a single raft operation.
   187  // The first item returned by a newly constructed buffer will have nil Events.
   188  // It is a sentinel value which is used to wait on the next events via Next.
   189  //
   190  // To iterate to the next event, a Next method may be called which may block if
   191  // there is no next element yet.
   192  //
   193  // Holding a pointer to the item keeps all the events published since in memory
   194  // so it's important that subscribers don't hold pointers to buffer items after
   195  // they have been delivered except where it's intentional to maintain a cache or
   196  // trailing store of events for performance reasons.
   197  //
   198  // Subscribers must not mutate the bufferItem or the Events or Encoded payloads
   199  // inside as these are shared between all readers.
   200  type bufferItem struct {
   201  	// Events is the set of events published at one raft index. This may be nil as
   202  	// a sentinel value to allow watching for the first event in a buffer. Callers
   203  	// should check and skip nil Events at any point in the buffer. It will also
   204  	// be nil if the producer appends an Error event because they can't complete
   205  	// the request to populate the buffer. Err will be non-nil in this case.
   206  	Events *structs.Events
   207  
   208  	// Err is non-nil if the producer can't complete their task and terminates the
   209  	// buffer. Subscribers should return the error to clients and cease attempting
   210  	// to read from the buffer.
   211  	Err error
   212  
   213  	// link holds the next pointer and channel. This extra bit of indirection
   214  	// allows us to splice buffers together at arbitrary points without including
   215  	// events in one buffer just for the side-effect of watching for the next set.
   216  	// The link may not be mutated once the event is appended to a buffer.
   217  	link *bufferLink
   218  
   219  	createdAt time.Time
   220  }
   221  
   222  type bufferLink struct {
   223  	// next is an atomically updated pointer to the next event in the buffer. It
   224  	// is written exactly once by the single published and will always be set if
   225  	// ch is closed.
   226  	next atomic.Value
   227  
   228  	// nextCh is closed when the next event is published. It should never be mutated
   229  	// (e.g. set to nil) as that is racey, but is closed once when the next event
   230  	// is published. the next pointer will have been set by the time this is
   231  	// closed.
   232  	nextCh chan struct{}
   233  
   234  	// droppedCh is closed when the event is dropped from the buffer due to
   235  	// sizing constraints.
   236  	droppedCh chan struct{}
   237  }
   238  
   239  // newBufferItem returns a blank buffer item with a link and chan ready to have
   240  // the fields set and be appended to a buffer.
   241  func newBufferItem(events *structs.Events) *bufferItem {
   242  	return &bufferItem{
   243  		link: &bufferLink{
   244  			nextCh:    make(chan struct{}),
   245  			droppedCh: make(chan struct{}),
   246  		},
   247  		Events:    events,
   248  		createdAt: time.Now(),
   249  	}
   250  }
   251  
   252  // Next return the next buffer item in the buffer. It may block until ctx is
   253  // cancelled or until the next item is published.
   254  func (i *bufferItem) Next(ctx context.Context, forceClose <-chan struct{}) (*bufferItem, error) {
   255  	// See if there is already a next value, block if so. Note we don't rely on
   256  	// state change (chan nil) as that's not threadsafe but detecting close is.
   257  	select {
   258  	case <-ctx.Done():
   259  		return nil, ctx.Err()
   260  	case <-forceClose:
   261  		return nil, fmt.Errorf("subscription closed")
   262  	case <-i.link.nextCh:
   263  	}
   264  
   265  	// Check if the reader is too slow and the event buffer as discarded the event
   266  	// This must happen after the above select to prevent a random selection
   267  	// between linkCh and droppedCh
   268  	select {
   269  	case <-i.link.droppedCh:
   270  		return nil, fmt.Errorf("event dropped from buffer")
   271  	default:
   272  	}
   273  
   274  	// If channel closed, there must be a next item to read
   275  	nextRaw := i.link.next.Load()
   276  	if nextRaw == nil {
   277  		// shouldn't be possible
   278  		return nil, errors.New("invalid next item")
   279  	}
   280  	next := nextRaw.(*bufferItem)
   281  	if next.Err != nil {
   282  		return nil, next.Err
   283  	}
   284  	return next, nil
   285  }
   286  
   287  // NextNoBlock returns the next item in the buffer without blocking. If it
   288  // reaches the most recent item it will return nil.
   289  func (i *bufferItem) NextNoBlock() *bufferItem {
   290  	nextRaw := i.link.next.Load()
   291  	if nextRaw == nil {
   292  		return nil
   293  	}
   294  	return nextRaw.(*bufferItem)
   295  }