github.com/influxdata/influxdb/v2@v2.7.6/tsdb/epoch_tracker.go (about)

     1  package tsdb
     2  
     3  import (
     4  	"sync"
     5  )
     6  
     7  // TODO(jeff): using a mutex is easiest, but there may be a way to do
     8  // this with atomics only, and in a way such that writes are minimally
     9  // blocked.
    10  
    11  // epochTracker keeps track of epochs for write and delete operations
    12  // allowing a delete to block until all previous writes have completed.
    13  type epochTracker struct {
    14  	mu      sync.Mutex
    15  	epoch   uint64 // current epoch
    16  	largest uint64 // largest delete possible
    17  	writes  int64  // pending writes
    18  	// pending deletes waiting on writes
    19  	deletes map[uint64]*epochDeleteState
    20  }
    21  
    22  // newEpochTracker constructs an epochTracker.
    23  func newEpochTracker() *epochTracker {
    24  	return &epochTracker{
    25  		deletes: make(map[uint64]*epochDeleteState),
    26  	}
    27  }
    28  
    29  // epochDeleteState keeps track of the state for a pending delete.
    30  type epochDeleteState struct {
    31  	cond    *sync.Cond
    32  	guard   *guard
    33  	pending int64
    34  }
    35  
    36  // done signals that an earlier write has finished.
    37  func (e *epochDeleteState) done() {
    38  	e.cond.L.Lock()
    39  	e.pending--
    40  	if e.pending == 0 {
    41  		e.cond.Broadcast()
    42  	}
    43  	e.cond.L.Unlock()
    44  }
    45  
    46  // Wait blocks until all earlier writes have finished.
    47  func (e *epochDeleteState) Wait() {
    48  	e.cond.L.Lock()
    49  	for e.pending > 0 {
    50  		e.cond.Wait()
    51  	}
    52  	e.cond.L.Unlock()
    53  }
    54  
    55  // next bumps the epoch and returns it.
    56  func (e *epochTracker) next() uint64 {
    57  	e.epoch++
    58  	return e.epoch
    59  }
    60  
    61  // StartWrite should be called before a write is going to start, and after
    62  // it has checked for guards.
    63  func (e *epochTracker) StartWrite() ([]*guard, uint64) {
    64  	e.mu.Lock()
    65  	gen := e.next()
    66  	e.writes++
    67  
    68  	if len(e.deletes) == 0 {
    69  		e.mu.Unlock()
    70  		return nil, gen
    71  	}
    72  
    73  	guards := make([]*guard, 0, len(e.deletes))
    74  	for _, state := range e.deletes {
    75  		guards = append(guards, state.guard)
    76  	}
    77  
    78  	e.mu.Unlock()
    79  	return guards, gen
    80  }
    81  
    82  // EndWrite should be called when the write ends for any reason.
    83  func (e *epochTracker) EndWrite(gen uint64) {
    84  	e.mu.Lock()
    85  	if gen <= e.largest {
    86  		// TODO(jeff): at the cost of making waitDelete more
    87  		// complicated, we can keep a sorted slice which would
    88  		// allow this to exit early rather than go over the
    89  		// whole map.
    90  		for dgen, state := range e.deletes {
    91  			if gen > dgen {
    92  				continue
    93  			}
    94  			state.done()
    95  		}
    96  	}
    97  	e.writes--
    98  	e.mu.Unlock()
    99  }
   100  
   101  // epochWaiter is a type that can be waited on for prior writes to finish.
   102  type epochWaiter struct {
   103  	gen     uint64
   104  	guard   *guard
   105  	state   *epochDeleteState
   106  	tracker *epochTracker
   107  }
   108  
   109  // Wait blocks until all writes prior to the creation of the waiter finish.
   110  func (e epochWaiter) Wait() {
   111  	if e.state == nil || e.tracker == nil {
   112  		return
   113  	}
   114  	e.state.Wait()
   115  }
   116  
   117  // Done marks the delete as completed, removing its guard.
   118  func (e epochWaiter) Done() {
   119  	e.tracker.mu.Lock()
   120  	delete(e.tracker.deletes, e.gen)
   121  	e.tracker.mu.Unlock()
   122  	e.guard.Done()
   123  }
   124  
   125  // WaitDelete should be called after any delete guards have been installed.
   126  // The returned epochWaiter will not be affected by any future writes.
   127  func (e *epochTracker) WaitDelete(guard *guard) epochWaiter {
   128  	e.mu.Lock()
   129  	state := &epochDeleteState{
   130  		pending: e.writes,
   131  		cond:    sync.NewCond(new(sync.Mutex)),
   132  		guard:   guard,
   133  	}
   134  
   135  	// record our pending delete
   136  	gen := e.next()
   137  	e.largest = gen
   138  	e.deletes[gen] = state
   139  	e.mu.Unlock()
   140  
   141  	return epochWaiter{
   142  		gen:     gen,
   143  		guard:   guard,
   144  		state:   state,
   145  		tracker: e,
   146  	}
   147  }