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 }