github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/utils/async/ring_buffer.go (about) 1 // Copyright 2021 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package async 16 17 import ( 18 "errors" 19 "io" 20 "os" 21 "sync" 22 ) 23 24 // RingBuffer is a dynamically sized ring buffer that is thread safe 25 type RingBuffer struct { 26 cond *sync.Cond 27 allocSize int 28 29 closed bool 30 epoch int 31 headPos int 32 tailPos int 33 headSlice int 34 tailSlice int 35 36 items [][]interface{} 37 } 38 39 // Returned from Push() when the supplied epoch does not match the 40 // buffer's current epoch. 41 var ErrWrongEpoch = errors.New("ring buffer: wrong epoch") 42 43 // NewRingBuffer creates a new RingBuffer instance 44 func NewRingBuffer(allocSize int) *RingBuffer { 45 itemBuffer := make([]interface{}, allocSize*2) 46 items := [][]interface{}{itemBuffer[:allocSize], itemBuffer[allocSize:]} 47 48 return &RingBuffer{ 49 cond: sync.NewCond(&sync.Mutex{}), 50 allocSize: allocSize, 51 items: items, 52 } 53 } 54 55 // Reset clears a ring buffer so that it can be reused 56 func (rb *RingBuffer) Reset() int { 57 rb.cond.L.Lock() 58 defer rb.cond.L.Unlock() 59 60 rb.closed = false 61 rb.headPos = 0 62 rb.tailPos = 0 63 rb.headSlice = 0 64 rb.tailSlice = 0 65 rb.epoch += 1 66 67 for i := 0; i < len(rb.items); i++ { 68 for j := 0; j < len(rb.items[i]); j++ { 69 rb.items[i][j] = nil 70 } 71 } 72 73 return rb.epoch 74 } 75 76 // Close closes a RingBuffer so that no new items can be pushed onto it. Items that are already in the buffer can still 77 // be read via Pop and TryPop. Close will broadcast to all go routines waiting inside Pop 78 func (rb *RingBuffer) Close() error { 79 rb.cond.L.Lock() 80 defer rb.cond.L.Unlock() 81 82 if rb.closed { 83 return os.ErrClosed 84 } 85 86 rb.closed = true 87 rb.cond.Broadcast() 88 89 return nil 90 } 91 92 // Push will add a new item to the RingBuffer and signal a go routine waiting inside Pop that new data is available 93 func (rb *RingBuffer) Push(item interface{}, epoch int) error { 94 rb.cond.L.Lock() 95 defer rb.cond.L.Unlock() 96 97 if rb.closed { 98 return os.ErrClosed 99 } 100 if epoch != rb.epoch { 101 return ErrWrongEpoch 102 } 103 104 rb.items[rb.headSlice][rb.headPos] = item 105 rb.headPos++ 106 107 if rb.headPos == rb.allocSize { 108 numSlices := len(rb.items) 109 nextSlice := (rb.headSlice + 1) % numSlices 110 111 if nextSlice == rb.tailSlice { 112 newItems := make([][]interface{}, numSlices+1) 113 114 j := 0 115 for i := rb.tailSlice; i < numSlices; i, j = i+1, j+1 { 116 newItems[j] = rb.items[i] 117 } 118 119 for i := 0; i < rb.tailSlice; i, j = i+1, j+1 { 120 newItems[j] = rb.items[i] 121 } 122 123 newItems[numSlices] = make([]interface{}, rb.allocSize) 124 125 rb.items = newItems 126 rb.tailSlice = 0 127 rb.headSlice = numSlices 128 } else { 129 rb.headSlice = nextSlice 130 } 131 132 rb.headPos = 0 133 } 134 135 rb.cond.Signal() 136 137 return nil 138 } 139 140 // noLockPop is used internally by methods that already hold a lock on the RingBuffer and should never be called directly 141 func (rb *RingBuffer) noLockPop() (interface{}, bool) { 142 if rb.tailPos == rb.headPos && rb.tailSlice == rb.headSlice { 143 return nil, false 144 } 145 146 item := rb.items[rb.tailSlice][rb.tailPos] 147 rb.tailPos++ 148 149 if rb.tailPos == rb.allocSize { 150 rb.tailPos = 0 151 rb.tailSlice = (rb.tailSlice + 1) % len(rb.items) 152 } 153 154 return item, true 155 } 156 157 // TryPop will return the next item in the RingBuffer. If there are no items available TryPop will return immediately 158 // with with `ok` set to false. 159 func (rb *RingBuffer) TryPop() (item interface{}, ok bool) { 160 rb.cond.L.Lock() 161 defer rb.cond.L.Unlock() 162 163 return rb.noLockPop() 164 } 165 166 // Pop will return the next item in the RingBuffer. If there are no items available, Pop will wait until a new item is 167 // pushed, or the RingBuffer is closed. 168 func (rb *RingBuffer) Pop() (item interface{}, err error) { 169 rb.cond.L.Lock() 170 defer rb.cond.L.Unlock() 171 172 for { 173 item, ok := rb.noLockPop() 174 175 if ok { 176 return item, nil 177 } 178 179 if rb.closed { 180 return nil, io.EOF 181 } 182 183 rb.cond.Wait() 184 } 185 }