|
|
- package eventlog
-
- import (
- "time"
- )
-
- // checkPrune checks whether the log has exceeded its boundaries of size or
- // age, and if so prunes the log and updates the head.
- func (lg *Log) checkPrune(head *logEntry, size int, age time.Duration) error {
- // To avoid potentially re-pruning for every event, don't trigger an age
- // prune until we're at least this far beyond the designated size.
- const windowSlop = 30 * time.Second
-
- if age < (lg.windowSize+windowSlop) && (lg.maxItems <= 0 || size <= lg.maxItems) {
- lg.numItemsGauge.Set(float64(lg.numItems))
- return nil // no pruning is needed
- }
-
- var newState logState
- var err error
-
- switch {
- case lg.maxItems > 0 && size > lg.maxItems:
- // We exceeded the size cap. In this case, age does not matter: count off
- // the newest items and drop the unconsumed tail. Note that we prune by a
- // fraction rather than an absolute amount so that we only have to prune
- // for size occasionally.
-
- // TODO(creachadair): We may want to spill dropped events to secondary
- // storage rather than dropping them. The size cap is meant as a safety
- // valve against unexpected extremes, but if a network has "expected"
- // spikes that nevertheless exceed any safe buffer size (e.g., Osmosis
- // epochs), we may want to have a fallback so that we don't lose events
- // that would otherwise fall within the window.
- newSize := 3 * size / 4
- newState, err = lg.pruneSize(head, newSize)
-
- default:
- // We did not exceed the size cap, but some items are too old.
- newState = lg.pruneAge(head)
- }
-
- // Note that when we update the head after pruning, we do not need to signal
- // any waiters; pruning never adds new material to the log so anyone waiting
- // should continue doing so until a subsequent Add occurs.
- lg.mu.Lock()
- defer lg.mu.Unlock()
- lg.numItems = newState.size
- lg.numItemsGauge.Set(float64(newState.size))
- lg.oldestCursor = newState.oldest
- lg.head = newState.head
- return err
- }
-
- // pruneSize returns a new log state by pruning head to newSize.
- // Precondition: newSize ≤ len(head).
- func (lg *Log) pruneSize(head *logEntry, newSize int) (logState, error) {
- // Special case for size 0 to simplify the logic below.
- if newSize == 0 {
- return logState{}, ErrLogPruned // drop everything
- }
-
- // Initialize: New head has the same item as the old head.
- first := &logEntry{item: head.item} // new head
- last := first // new tail (last copied cons)
-
- cur := head.next
- for i := 1; i < newSize; i++ {
- cp := &logEntry{item: cur.item}
- last.next = cp
- last = cp
-
- cur = cur.next
- }
- var err error
- if head.item.Cursor.Diff(last.item.Cursor) <= lg.windowSize {
- err = ErrLogPruned
- }
-
- return logState{
- oldest: last.item.Cursor,
- newest: first.item.Cursor,
- size: newSize,
- head: first,
- }, err
- }
-
- // pruneAge returns a new log state by pruning items older than the window
- // prior to the head element.
- func (lg *Log) pruneAge(head *logEntry) logState {
- first := &logEntry{item: head.item}
- last := first
-
- size := 1
- for cur := head.next; cur != nil; cur = cur.next {
- diff := head.item.Cursor.Diff(cur.item.Cursor)
- if diff > lg.windowSize {
- break // all remaining items are older than the window
- }
- cp := &logEntry{item: cur.item}
- last.next = cp
- last = cp
- size++
- }
- return logState{
- oldest: last.item.Cursor,
- newest: first.item.Cursor,
- size: size,
- head: first,
- }
- }
|