├── .travis.yml ├── LICENSE ├── error.go ├── error_test.go ├── fifo_queue.go ├── fifo_queue_benchmark_test.go ├── fifo_queue_test.go ├── fixed_fifo_queue.go ├── fixed_fifo_queue_benchmark_test.go ├── fixed_fifo_queue_test.go ├── go.mod ├── go.sum ├── queue.go ├── readme.md └── web ├── FixedFIFO-vs-FIFO-dequeue.png ├── FixedFIFO-vs-FIFO-enqueue.png └── class-diagram.svg /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7.x 5 | - 1.8.x 6 | - 1.9.x 7 | - 1.10.x 8 | - 1.11.x 9 | - 1.12.x 10 | - 1.13.x 11 | - 1.14.x 12 | 13 | before_install: 14 | - go get -t -v ./... 15 | 16 | script: 17 | - go test -coverprofile=coverage.txt -covermode=atomic 18 | 19 | after_success: 20 | - bash <(curl -s https://codecov.io/bash) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Enrique Bris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | const ( 4 | QueueErrorCodeEmptyQueue = "empty-queue" 5 | QueueErrorCodeLockedQueue = "locked-queue" 6 | QueueErrorCodeIndexOutOfBounds = "index-out-of-bounds" 7 | QueueErrorCodeFullCapacity = "full-capacity" 8 | QueueErrorCodeInternalChannelClosed = "internal-channel-closed" 9 | ) 10 | 11 | type QueueError struct { 12 | code string 13 | message string 14 | } 15 | 16 | func NewQueueError(code string, message string) *QueueError { 17 | return &QueueError{ 18 | code: code, 19 | message: message, 20 | } 21 | } 22 | 23 | func (st *QueueError) Error() string { 24 | return st.message 25 | } 26 | 27 | func (st *QueueError) Code() string { 28 | return st.code 29 | } 30 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type QueueErrorTestSuite struct { 10 | suite.Suite 11 | queueError *QueueError 12 | } 13 | 14 | // *************************************************************************************** 15 | // ** Initialization 16 | // *************************************************************************************** 17 | 18 | // no elements at initialization 19 | func (suite *QueueErrorTestSuite) TestInitialization() { 20 | queueError := NewQueueError("code", "message") 21 | 22 | suite.Equal("code", queueError.Code()) 23 | suite.Equal("message", queueError.Error()) 24 | } 25 | 26 | // *************************************************************************************** 27 | // ** Run suite 28 | // *************************************************************************************** 29 | 30 | func TestQueueErrorTestSuite(t *testing.T) { 31 | suite.Run(t, new(QueueErrorTestSuite)) 32 | } 33 | -------------------------------------------------------------------------------- /fifo_queue.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | const ( 11 | WaitForNextElementChanCapacity = 1000 12 | dequeueOrWaitForNextElementInvokeGapTime = 10 13 | ) 14 | 15 | // FIFO (First In First Out) concurrent queue 16 | type FIFO struct { 17 | slice []interface{} 18 | rwmutex sync.RWMutex 19 | lockRWmutex sync.RWMutex 20 | isLocked bool 21 | // queue for watchers that will wait for next elements (if queue is empty at DequeueOrWaitForNextElement execution ) 22 | waitForNextElementChan chan chan interface{} 23 | // queue to unlock consumers that were locked when queue was empty (during DequeueOrWaitForNextElement execution) 24 | unlockDequeueOrWaitForNextElementChan chan struct{} 25 | } 26 | 27 | // NewFIFO returns a new FIFO concurrent queue 28 | func NewFIFO() *FIFO { 29 | ret := &FIFO{} 30 | ret.initialize() 31 | 32 | return ret 33 | } 34 | 35 | func (st *FIFO) initialize() { 36 | st.slice = make([]interface{}, 0) 37 | st.waitForNextElementChan = make(chan chan interface{}, WaitForNextElementChanCapacity) 38 | st.unlockDequeueOrWaitForNextElementChan = make(chan struct{}, WaitForNextElementChanCapacity) 39 | } 40 | 41 | // Enqueue enqueues an element. Returns error if queue is locked. 42 | func (st *FIFO) Enqueue(value interface{}) error { 43 | if st.isLocked { 44 | return NewQueueError(QueueErrorCodeLockedQueue, "The queue is locked") 45 | } 46 | 47 | // let consumers (DequeueOrWaitForNextElement) know there is a new element 48 | select { 49 | case st.unlockDequeueOrWaitForNextElementChan <- struct{}{}: 50 | default: 51 | // message could not be sent 52 | } 53 | 54 | // check if there is a listener waiting for the next element (this element) 55 | select { 56 | case listener := <-st.waitForNextElementChan: 57 | // send the element through the listener's channel instead of enqueue it 58 | select { 59 | case listener <- value: 60 | default: 61 | // enqueue if listener is not ready 62 | 63 | // lock the object to enqueue the element into the slice 64 | st.rwmutex.Lock() 65 | // enqueue the element 66 | st.slice = append(st.slice, value) 67 | defer st.rwmutex.Unlock() 68 | } 69 | 70 | default: 71 | // lock the object to enqueue the element into the slice 72 | st.rwmutex.Lock() 73 | // enqueue the element 74 | st.slice = append(st.slice, value) 75 | defer st.rwmutex.Unlock() 76 | } 77 | 78 | return nil 79 | } 80 | 81 | // Dequeue dequeues an element. Returns error if queue is locked or empty. 82 | func (st *FIFO) Dequeue() (interface{}, error) { 83 | if st.isLocked { 84 | return nil, NewQueueError(QueueErrorCodeLockedQueue, "The queue is locked") 85 | } 86 | 87 | st.rwmutex.Lock() 88 | defer st.rwmutex.Unlock() 89 | 90 | len := len(st.slice) 91 | if len == 0 { 92 | return nil, NewQueueError(QueueErrorCodeEmptyQueue, "empty queue") 93 | } 94 | 95 | elementToReturn := st.slice[0] 96 | st.slice = st.slice[1:] 97 | 98 | return elementToReturn, nil 99 | } 100 | 101 | // DequeueOrWaitForNextElement dequeues an element (if exist) or waits until the next element gets enqueued and returns it. 102 | // Multiple calls to DequeueOrWaitForNextElement() would enqueue multiple "listeners" for future enqueued elements. 103 | func (st *FIFO) DequeueOrWaitForNextElement() (interface{}, error) { 104 | return st.DequeueOrWaitForNextElementContext(context.Background()) 105 | } 106 | 107 | // DequeueOrWaitForNextElementContext dequeues an element (if exist) or waits until the next element gets enqueued and returns it. 108 | // Multiple calls to DequeueOrWaitForNextElementContext() would enqueue multiple "listeners" for future enqueued elements. 109 | // When the passed context expires this function exits and returns the context' error 110 | func (st *FIFO) DequeueOrWaitForNextElementContext(ctx context.Context) (interface{}, error) { 111 | for { 112 | if st.isLocked { 113 | return nil, NewQueueError(QueueErrorCodeLockedQueue, "The queue is locked") 114 | } 115 | 116 | // get the slice's len 117 | st.rwmutex.Lock() 118 | length := len(st.slice) 119 | st.rwmutex.Unlock() 120 | 121 | if length == 0 { 122 | // channel to wait for next enqueued element 123 | waitChan := make(chan interface{}) 124 | 125 | select { 126 | // enqueue a watcher into the watchForNextElementChannel to wait for the next element 127 | case st.waitForNextElementChan <- waitChan: 128 | 129 | // n 130 | for { 131 | // re-checks every i milliseconds (top: 10 times) ... the following verifies if an item was enqueued 132 | // around the same time DequeueOrWaitForNextElementContext was invoked, meaning the waitChan wasn't yet sent over 133 | // st.waitForNextElementChan 134 | for i := 0; i < dequeueOrWaitForNextElementInvokeGapTime; i++ { 135 | select { 136 | case <-ctx.Done(): 137 | return nil, ctx.Err() 138 | case dequeuedItem := <-waitChan: 139 | return dequeuedItem, nil 140 | case <-time.After(time.Millisecond * time.Duration(i)): 141 | if dequeuedItem, err := st.Dequeue(); err == nil { 142 | return dequeuedItem, nil 143 | } 144 | } 145 | } 146 | 147 | // return the next enqueued element, if any 148 | select { 149 | // new enqueued element, no need to keep waiting 150 | case <-st.unlockDequeueOrWaitForNextElementChan: 151 | // check if we got a new element just after we got <-st.unlockDequeueOrWaitForNextElementChan 152 | select { 153 | case item := <-waitChan: 154 | return item, nil 155 | default: 156 | } 157 | // go back to: for loop 158 | continue 159 | 160 | case item := <-waitChan: 161 | return item, nil 162 | case <-ctx.Done(): 163 | return nil, ctx.Err() 164 | } 165 | // n 166 | } 167 | default: 168 | // too many watchers (waitForNextElementChanCapacity) enqueued waiting for next elements 169 | return nil, NewQueueError(QueueErrorCodeEmptyQueue, "empty queue and can't wait for next element because there are too many DequeueOrWaitForNextElement() waiting") 170 | } 171 | } 172 | 173 | st.rwmutex.Lock() 174 | 175 | // verify that at least 1 item resides on the queue 176 | if len(st.slice) == 0 { 177 | st.rwmutex.Unlock() 178 | continue 179 | } 180 | elementToReturn := st.slice[0] 181 | st.slice = st.slice[1:] 182 | 183 | st.rwmutex.Unlock() 184 | return elementToReturn, nil 185 | } 186 | } 187 | 188 | // Get returns an element's value and keeps the element at the queue 189 | func (st *FIFO) Get(index int) (interface{}, error) { 190 | if st.isLocked { 191 | return nil, NewQueueError(QueueErrorCodeLockedQueue, "The queue is locked") 192 | } 193 | 194 | st.rwmutex.RLock() 195 | defer st.rwmutex.RUnlock() 196 | 197 | if len(st.slice) <= index { 198 | return nil, NewQueueError(QueueErrorCodeIndexOutOfBounds, fmt.Sprintf("index out of bounds: %v", index)) 199 | } 200 | 201 | return st.slice[index], nil 202 | } 203 | 204 | // Remove removes an element from the queue 205 | func (st *FIFO) Remove(index int) error { 206 | if st.isLocked { 207 | return NewQueueError(QueueErrorCodeLockedQueue, "The queue is locked") 208 | } 209 | 210 | st.rwmutex.Lock() 211 | defer st.rwmutex.Unlock() 212 | 213 | if len(st.slice) <= index { 214 | return NewQueueError(QueueErrorCodeIndexOutOfBounds, fmt.Sprintf("index out of bounds: %v", index)) 215 | } 216 | 217 | // remove the element 218 | st.slice = append(st.slice[:index], st.slice[index+1:]...) 219 | 220 | return nil 221 | } 222 | 223 | // GetLen returns the number of enqueued elements 224 | func (st *FIFO) GetLen() int { 225 | st.rwmutex.RLock() 226 | defer st.rwmutex.RUnlock() 227 | 228 | return len(st.slice) 229 | } 230 | 231 | // GetCap returns the queue's capacity 232 | func (st *FIFO) GetCap() int { 233 | st.rwmutex.RLock() 234 | defer st.rwmutex.RUnlock() 235 | 236 | return cap(st.slice) 237 | } 238 | 239 | // Lock // Locks the queue. No enqueue/dequeue operations will be allowed after this point. 240 | func (st *FIFO) Lock() { 241 | st.lockRWmutex.Lock() 242 | defer st.lockRWmutex.Unlock() 243 | 244 | st.isLocked = true 245 | } 246 | 247 | // Unlock unlocks the queue 248 | func (st *FIFO) Unlock() { 249 | st.lockRWmutex.Lock() 250 | defer st.lockRWmutex.Unlock() 251 | 252 | st.isLocked = false 253 | } 254 | 255 | // IsLocked returns true whether the queue is locked 256 | func (st *FIFO) IsLocked() bool { 257 | st.lockRWmutex.RLock() 258 | defer st.lockRWmutex.RUnlock() 259 | 260 | return st.isLocked 261 | } 262 | -------------------------------------------------------------------------------- /fifo_queue_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // *************************************************************************************** 8 | // ** Enqueue 9 | // *************************************************************************************** 10 | 11 | // single goroutine - enqueue 1 element 12 | func BenchmarkFIFOEnqueueSingleGR(b *testing.B) { 13 | fifo := NewFIFO() 14 | for i := 0; i < b.N; i++ { 15 | fifo.Enqueue(i) 16 | } 17 | } 18 | 19 | // single goroutine - enqueue 100 elements 20 | func BenchmarkFIFOEnqueue100SingleGR(b *testing.B) { 21 | fifo := NewFIFO() 22 | 23 | for i := 0; i < b.N; i++ { 24 | for c := 0; c < 100; c++ { 25 | fifo.Enqueue(c) 26 | } 27 | } 28 | } 29 | 30 | // multiple goroutines - enqueue 100 elements per gr 31 | func BenchmarkFIFOEnqueue100MultipleGRs(b *testing.B) { 32 | fifo := NewFIFO() 33 | 34 | b.RunParallel(func(pb *testing.PB) { 35 | for pb.Next() { 36 | for c := 0; c < 100; c++ { 37 | fifo.Enqueue(c) 38 | } 39 | } 40 | }) 41 | } 42 | 43 | // multiple goroutines - enqueue 1000 elements per gr 44 | func BenchmarkFIFOEnqueue1000MultipleGRs(b *testing.B) { 45 | fifo := NewFIFO() 46 | 47 | b.RunParallel(func(pb *testing.PB) { 48 | for pb.Next() { 49 | for c := 0; c < 1000; c++ { 50 | fifo.Enqueue(c) 51 | } 52 | } 53 | }) 54 | } 55 | 56 | // single goroutine - enqueue 1000 elements 57 | func BenchmarkFIFOEnqueue1000SingleGR(b *testing.B) { 58 | fifo := NewFIFO() 59 | 60 | for i := 0; i < b.N; i++ { 61 | for c := 0; c < 1000; c++ { 62 | fifo.Enqueue(c) 63 | } 64 | } 65 | } 66 | 67 | // *************************************************************************************** 68 | // ** Dequeue 69 | // *************************************************************************************** 70 | 71 | // single goroutine - dequeue 100 elements 72 | func BenchmarkFIFODequeue100SingleGR(b *testing.B) { 73 | // do not measure the queue initialization 74 | b.StopTimer() 75 | fifo := NewFIFO() 76 | 77 | for i := 0; i < b.N; i++ { 78 | // do not measure the enqueueing process 79 | b.StopTimer() 80 | for i := 0; i < 100; i++ { 81 | fifo.Enqueue(i) 82 | } 83 | 84 | // measure the dequeueing process 85 | b.StartTimer() 86 | for i := 0; i < 100; i++ { 87 | fifo.Dequeue() 88 | } 89 | } 90 | } 91 | 92 | // single goroutine 1000 - dequeue 1000 elements 93 | func BenchmarkFIFODequeue1000SingleGR(b *testing.B) { 94 | // do not measure the queue initialization 95 | b.StopTimer() 96 | fifo := NewFIFO() 97 | 98 | for i := 0; i < b.N; i++ { 99 | // do not measure the enqueueing process 100 | b.StopTimer() 101 | for i := 0; i < 1000; i++ { 102 | fifo.Enqueue(i) 103 | } 104 | 105 | // measure the dequeueing process 106 | b.StartTimer() 107 | for i := 0; i < 1000; i++ { 108 | fifo.Dequeue() 109 | } 110 | } 111 | } 112 | 113 | // multiple goroutines - dequeue 100 elements per gr 114 | func BenchmarkFIFODequeue100MultipleGRs(b *testing.B) { 115 | b.StopTimer() 116 | fifo := NewFIFO() 117 | 118 | b.RunParallel(func(pb *testing.PB) { 119 | for pb.Next() { 120 | b.StopTimer() 121 | for c := 0; c < 100; c++ { 122 | fifo.Enqueue(c) 123 | } 124 | 125 | b.StartTimer() 126 | for c := 0; c < 100; c++ { 127 | fifo.Dequeue() 128 | } 129 | } 130 | }) 131 | } 132 | 133 | // multiple goroutines - dequeue 1000 elements per gr 134 | func BenchmarkFIFODequeue1000MultipleGRs(b *testing.B) { 135 | b.StopTimer() 136 | fifo := NewFIFO() 137 | 138 | b.RunParallel(func(pb *testing.PB) { 139 | for pb.Next() { 140 | b.StopTimer() 141 | for c := 0; c < 1000; c++ { 142 | fifo.Enqueue(c) 143 | } 144 | 145 | b.StartTimer() 146 | for c := 0; c < 1000; c++ { 147 | fifo.Dequeue() 148 | } 149 | } 150 | }) 151 | } 152 | -------------------------------------------------------------------------------- /fifo_queue_test.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/suite" 11 | ) 12 | 13 | const ( 14 | testValue = "test value" 15 | ) 16 | 17 | type FIFOTestSuite struct { 18 | suite.Suite 19 | fifo *FIFO 20 | } 21 | 22 | func (suite *FIFOTestSuite) SetupTest() { 23 | suite.fifo = NewFIFO() 24 | } 25 | 26 | // *************************************************************************************** 27 | // ** Queue initialization 28 | // *************************************************************************************** 29 | 30 | // no elements at initialization 31 | func (suite *FIFOTestSuite) TestNoElementsAtInitialization() { 32 | len := suite.fifo.GetLen() 33 | suite.Equalf(0, len, "No elements expected at initialization, currently: %v", len) 34 | } 35 | 36 | // unlocked at initialization 37 | func (suite *FIFOTestSuite) TestNoLockedAtInitialization() { 38 | suite.True(suite.fifo.IsLocked() == false, "Queue must be unlocked at initialization") 39 | } 40 | 41 | // *************************************************************************************** 42 | // ** Enqueue && GetLen 43 | // *************************************************************************************** 44 | 45 | // single enqueue lock verification 46 | func (suite *FIFOTestSuite) TestEnqueueLockSingleGR() { 47 | suite.NoError(suite.fifo.Enqueue(1), "Unlocked queue allows to enqueue elements") 48 | 49 | suite.fifo.Lock() 50 | err := suite.fifo.Enqueue(1) 51 | suite.Error(err, "Locked queue does not allow to enqueue elements") 52 | 53 | // verify custom error: code: QueueErrorCodeLockedQueue 54 | customError, ok := err.(*QueueError) 55 | suite.True(ok, "Expected error type: QueueError") 56 | // verify custom error code 57 | suite.Equalf(QueueErrorCodeLockedQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeLockedQueue) 58 | } 59 | 60 | // single enqueue (1 element, 1 goroutine) 61 | func (suite *FIFOTestSuite) TestEnqueueLenSingleGR() { 62 | suite.fifo.Enqueue(testValue) 63 | len := suite.fifo.GetLen() 64 | suite.Equalf(1, len, "Expected number of elements in queue: 1, currently: %v", len) 65 | 66 | suite.fifo.Enqueue(5) 67 | len = suite.fifo.GetLen() 68 | suite.Equalf(2, len, "Expected number of elements in queue: 2, currently: %v", len) 69 | } 70 | 71 | // single enqueue and wait for next element 72 | func (suite *FIFOTestSuite) TestEnqueueWaitForNextElementSingleGR() { 73 | waitForNextElement := make(chan interface{}) 74 | // add the listener manually (ONLY for testings purposes) 75 | suite.fifo.waitForNextElementChan <- waitForNextElement 76 | 77 | value := 100 78 | // enqueue from a different GR to avoid blocking the listener channel 79 | go suite.fifo.Enqueue(value) 80 | // wait for the enqueued element 81 | result := <-waitForNextElement 82 | 83 | suite.Equal(value, result) 84 | } 85 | 86 | // TestEnqueueLenMultipleGR enqueues elements concurrently 87 | // 88 | // Detailed steps: 89 | // 1 - Enqueue totalGRs concurrently (from totalGRs different GRs) 90 | // 2 - Verifies the len, it should be equal to totalGRs 91 | // 3 - Verifies that all elements from 0 to totalGRs were enqueued 92 | func (suite *FIFOTestSuite) TestEnqueueLenMultipleGR() { 93 | var ( 94 | totalGRs = 500 95 | wg sync.WaitGroup 96 | ) 97 | 98 | // concurrent enqueueing 99 | // multiple GRs concurrently enqueueing consecutive integers from 0 to (totalGRs - 1) 100 | for i := 0; i < totalGRs; i++ { 101 | wg.Add(1) 102 | go func(value int) { 103 | defer wg.Done() 104 | suite.fifo.Enqueue(value) 105 | }(i) 106 | } 107 | wg.Wait() 108 | 109 | // check that there are totalGRs elements enqueued 110 | totalElements := suite.fifo.GetLen() 111 | suite.Equalf(totalGRs, totalElements, "Total enqueued elements should be %v, currently: %v", totalGRs, totalElements) 112 | 113 | // checking that the expected elements (1, 2, 3, ... totalGRs-1 ) were enqueued 114 | var ( 115 | tmpVal interface{} 116 | val int 117 | err error 118 | totalElementsVerified int 119 | ) 120 | // slice to check every element 121 | sl2check := make([]bool, totalGRs) 122 | 123 | for i := 0; i < totalElements; i++ { 124 | tmpVal, err = suite.fifo.Get(i) 125 | suite.NoError(err, "No error should be returned trying to get an existent element") 126 | 127 | val = tmpVal.(int) 128 | if !sl2check[val] { 129 | totalElementsVerified++ 130 | sl2check[val] = true 131 | } else { 132 | suite.Failf("Duplicated element", "Unexpected duplicated value: %v", val) 133 | } 134 | } 135 | suite.True(totalElementsVerified == totalGRs, "Enqueued elements are missing") 136 | } 137 | 138 | // call GetLen concurrently 139 | func (suite *FIFOTestSuite) TestGetLenMultipleGRs() { 140 | var ( 141 | totalGRs = 100 142 | totalElementsToEnqueue = 10 143 | wg sync.WaitGroup 144 | ) 145 | 146 | for i := 0; i < totalElementsToEnqueue; i++ { 147 | suite.fifo.Enqueue(i) 148 | } 149 | 150 | for i := 0; i < totalGRs; i++ { 151 | wg.Add(1) 152 | go func() { 153 | defer wg.Done() 154 | 155 | total := suite.fifo.GetLen() 156 | suite.Equalf(totalElementsToEnqueue, total, "Expected len: %v", totalElementsToEnqueue) 157 | }() 158 | } 159 | wg.Wait() 160 | } 161 | 162 | // *************************************************************************************** 163 | // ** GetCap 164 | // *************************************************************************************** 165 | 166 | // single GR getCapacity 167 | func (suite *FIFOTestSuite) TestGetCapSingleGR() { 168 | // initial capacity 169 | suite.Equal(cap(suite.fifo.slice), suite.fifo.GetCap(), "unexpected capacity") 170 | 171 | // checking after adding 2 items 172 | suite.fifo.Enqueue(1) 173 | suite.fifo.Enqueue(2) 174 | suite.Equal(cap(suite.fifo.slice), suite.fifo.GetCap(), "unexpected capacity") 175 | } 176 | 177 | // *************************************************************************************** 178 | // ** Get 179 | // *************************************************************************************** 180 | 181 | // get an element 182 | func (suite *FIFOTestSuite) TestGetLockSingleGR() { 183 | suite.fifo.Enqueue(1) 184 | _, err := suite.fifo.Get(0) 185 | suite.NoError(err, "Unlocked queue allows to get elements") 186 | 187 | suite.fifo.Lock() 188 | _, err = suite.fifo.Get(0) 189 | suite.Error(err, "Locked queue does not allow to get elements") 190 | 191 | // verify custom error: code: QueueErrorCodeLockedQueue 192 | customError, ok := err.(*QueueError) 193 | suite.True(ok, "Expected error type: QueueError") 194 | // verify custom error code 195 | suite.Equalf(QueueErrorCodeLockedQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeLockedQueue) 196 | } 197 | 198 | // get a valid element 199 | func (suite *FIFOTestSuite) TestGetSingleGR() { 200 | suite.fifo.Enqueue(testValue) 201 | val, err := suite.fifo.Get(0) 202 | 203 | // verify error (should be nil) 204 | suite.NoError(err, "No error should be enqueueing an element") 205 | 206 | // verify element's value 207 | suite.Equalf(testValue, val, "Different element returned: %v", val) 208 | } 209 | 210 | // get a invalid element 211 | func (suite *FIFOTestSuite) TestGetInvalidElementSingleGR() { 212 | suite.fifo.Enqueue(testValue) 213 | val, err := suite.fifo.Get(1) 214 | 215 | // verify error 216 | suite.Error(err, "An error should be returned after ask for a no existent element") 217 | // verify custom error: code: QueueErrorCodeLockedQueue 218 | customError, ok := err.(*QueueError) 219 | suite.True(ok, "Expected error type: QueueError") 220 | // verify custom error code 221 | suite.Equalf(QueueErrorCodeIndexOutOfBounds, customError.Code(), "Expected code: '%v'", QueueErrorCodeIndexOutOfBounds) 222 | 223 | // verify element's value 224 | suite.Equalf(val, nil, "Nil should be returned, currently returned: %v", val) 225 | } 226 | 227 | // call Get concurrently 228 | func (suite *FIFOTestSuite) TestGetMultipleGRs() { 229 | var ( 230 | totalGRs = 100 231 | totalElementsToEnqueue = 10 232 | wg sync.WaitGroup 233 | ) 234 | 235 | for i := 0; i < totalElementsToEnqueue; i++ { 236 | suite.fifo.Enqueue(i) 237 | } 238 | 239 | for i := 0; i < totalGRs; i++ { 240 | wg.Add(1) 241 | go func() { 242 | defer wg.Done() 243 | val, err := suite.fifo.Get(5) 244 | 245 | suite.NoError(err, "No error should be returned trying to get an existent element") 246 | suite.Equal(5, val.(int), "Expected element's value: 5") 247 | }() 248 | } 249 | wg.Wait() 250 | 251 | total := suite.fifo.GetLen() 252 | suite.Equalf(totalElementsToEnqueue, total, "Expected len: %v", totalElementsToEnqueue) 253 | } 254 | 255 | // *************************************************************************************** 256 | // ** Remove 257 | // *************************************************************************************** 258 | 259 | // single remove lock verification 260 | func (suite *FIFOTestSuite) TestRemoveLockSingleGR() { 261 | suite.fifo.Enqueue(1) 262 | suite.NoError(suite.fifo.Remove(0), "Unlocked queue allows to remove elements") 263 | 264 | suite.fifo.Enqueue(1) 265 | suite.fifo.Lock() 266 | err := suite.fifo.Remove(0) 267 | suite.Error(err, "Locked queue does not allow to remove elements") 268 | 269 | // verify custom error: code: QueueErrorCodeLockedQueue 270 | customError, ok := err.(*QueueError) 271 | suite.True(ok, "Expected error type: QueueError") 272 | // verify custom error code 273 | suite.Equalf(QueueErrorCodeLockedQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeLockedQueue) 274 | } 275 | 276 | // remove elements 277 | func (suite *FIFOTestSuite) TestRemoveSingleGR() { 278 | suite.fifo.Enqueue(testValue) 279 | suite.fifo.Enqueue(5) 280 | 281 | // removing first element 282 | err := suite.fifo.Remove(0) 283 | suite.NoError(err, "Unexpected error") 284 | 285 | // get element at index 0 286 | val, err2 := suite.fifo.Get(0) 287 | suite.NoError(err2, "Unexpected error") 288 | suite.Equal(5, val, "Queue returned the wrong element") 289 | 290 | // remove element having a wrong index 291 | err3 := suite.fifo.Remove(1) 292 | suite.Errorf(err3, "The index of the element to remove is out of bounds") 293 | // verify custom error: code: QueueErrorCodeLockedQueue 294 | customError, ok := err3.(*QueueError) 295 | suite.True(ok, "Expected error type: QueueError") 296 | // verify custom error code 297 | suite.Equalf(QueueErrorCodeIndexOutOfBounds, customError.Code(), "Expected code: '%v'", QueueErrorCodeIndexOutOfBounds) 298 | } 299 | 300 | // TestRemoveMultipleGRs removes elements concurrently. 301 | // 302 | // Detailed steps: 303 | // 1 - Enqueues totalElementsToEnqueue consecutive elements (0, 1, 2, 3, ... totalElementsToEnqueue - 1) 304 | // 2 - Hits fifo.Remove(1) concurrently from totalElementsToRemove different GRs 305 | // 3 - Verifies the final len == totalElementsToEnqueue - totalElementsToRemove 306 | // 4 - Verifies that final 2nd element == (1 + totalElementsToRemove) 307 | func (suite *FIFOTestSuite) TestRemoveMultipleGRs() { 308 | var ( 309 | wg sync.WaitGroup 310 | totalElementsToEnqueue = 100 311 | totalElementsToRemove = 90 312 | ) 313 | 314 | for i := 0; i < totalElementsToEnqueue; i++ { 315 | suite.fifo.Enqueue(i) 316 | } 317 | 318 | for i := 0; i < totalElementsToRemove; i++ { 319 | wg.Add(1) 320 | go func() { 321 | defer wg.Done() 322 | err := suite.fifo.Remove(1) 323 | suite.NoError(err, "Unexpected error during concurrent Remove(n)") 324 | }() 325 | } 326 | wg.Wait() 327 | 328 | // check len, should be == totalElementsToEnqueue - totalElementsToRemove 329 | totalElementsAfterRemove := suite.fifo.GetLen() 330 | suite.Equal(totalElementsToEnqueue-totalElementsToRemove, totalElementsAfterRemove, "Total elements on list does not match with expected number") 331 | 332 | // check current 2nd element (index 1) on the queue 333 | val, err := suite.fifo.Get(1) 334 | suite.NoError(err, "No error should be returned when getting an existent element") 335 | suite.Equalf(1+totalElementsToRemove, val, "The expected value at position 1 (2nd element) should be: %v", 1+totalElementsToRemove) 336 | } 337 | 338 | // *************************************************************************************** 339 | // ** Dequeue 340 | // *************************************************************************************** 341 | 342 | // single dequeue lock verification 343 | func (suite *FIFOTestSuite) TestDequeueLockSingleGR() { 344 | suite.fifo.Enqueue(1) 345 | _, err := suite.fifo.Dequeue() 346 | suite.NoError(err, "Unlocked queue allows to dequeue elements") 347 | 348 | suite.fifo.Enqueue(1) 349 | suite.fifo.Lock() 350 | _, err = suite.fifo.Dequeue() 351 | // error expected 352 | suite.Error(err, "Locked queue does not allow to dequeue elements") 353 | // verify custom error: code: QueueErrorCodeLockedQueue 354 | customError, ok := err.(*QueueError) 355 | suite.True(ok, "Expected error type: QueueError") 356 | // verify custom error code 357 | suite.Equalf(QueueErrorCodeLockedQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeLockedQueue) 358 | } 359 | 360 | // dequeue an empty queue 361 | func (suite *FIFOTestSuite) TestDequeueEmptyQueueSingleGR() { 362 | val, err := suite.fifo.Dequeue() 363 | 364 | // error expected 365 | suite.Errorf(err, "Can't dequeue an empty queue") 366 | 367 | // verify custom error type 368 | customError, ok := err.(*QueueError) 369 | suite.True(ok, "Expected error type: QueueError") 370 | // verify custom error code 371 | suite.Equalf(QueueErrorCodeEmptyQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeEmptyQueue) 372 | 373 | // no value expected 374 | suite.Equal(nil, val, "Can't get a value different than nil from an empty queue") 375 | } 376 | 377 | // dequeue all elements 378 | func (suite *FIFOTestSuite) TestDequeueSingleGR() { 379 | suite.fifo.Enqueue(testValue) 380 | suite.fifo.Enqueue(5) 381 | 382 | // get the first element 383 | val, err := suite.fifo.Dequeue() 384 | suite.NoError(err, "Unexpected error") 385 | suite.Equal(testValue, val, "Wrong element's value") 386 | len := suite.fifo.GetLen() 387 | suite.Equal(1, len, "Incorrect number of queue elements") 388 | 389 | // get the second element 390 | val, err = suite.fifo.Dequeue() 391 | suite.NoError(err, "Unexpected error") 392 | suite.Equal(5, val, "Wrong element's value") 393 | len = suite.fifo.GetLen() 394 | suite.Equal(0, len, "Incorrect number of queue elements") 395 | 396 | } 397 | 398 | // TestDequeueMultipleGRs dequeues elements concurrently 399 | // 400 | // Detailed steps: 401 | // 1 - Enqueues totalElementsToEnqueue consecutive integers 402 | // 2 - Dequeues totalElementsToDequeue concurrently from totalElementsToDequeue GRs 403 | // 3 - Verifies the final len, should be equal to totalElementsToEnqueue - totalElementsToDequeue 404 | // 4 - Verifies that the next dequeued element's value is equal to totalElementsToDequeue 405 | func (suite *FIFOTestSuite) TestDequeueMultipleGRs() { 406 | var ( 407 | wg sync.WaitGroup 408 | totalElementsToEnqueue = 100 409 | totalElementsToDequeue = 90 410 | ) 411 | 412 | for i := 0; i < totalElementsToEnqueue; i++ { 413 | suite.fifo.Enqueue(i) 414 | } 415 | 416 | for i := 0; i < totalElementsToDequeue; i++ { 417 | wg.Add(1) 418 | go func() { 419 | defer wg.Done() 420 | _, err := suite.fifo.Dequeue() 421 | suite.NoError(err, "Unexpected error during concurrent Dequeue()") 422 | }() 423 | } 424 | wg.Wait() 425 | 426 | // check len, should be == totalElementsToEnqueue - totalElementsToDequeue 427 | totalElementsAfterDequeue := suite.fifo.GetLen() 428 | suite.Equal(totalElementsToEnqueue-totalElementsToDequeue, totalElementsAfterDequeue, "Total elements on queue (after Dequeue) does not match with expected number") 429 | 430 | // check current first element 431 | val, err := suite.fifo.Dequeue() 432 | suite.NoError(err, "No error should be returned when dequeuing an existent element") 433 | suite.Equalf(totalElementsToDequeue, val, "The expected last element's value should be: %v", totalElementsToEnqueue-totalElementsToDequeue) 434 | } 435 | 436 | // *************************************************************************************** 437 | // ** DequeueOrWaitForNextElement 438 | // *************************************************************************************** 439 | 440 | // single GR Locked queue 441 | func (suite *FIFOTestSuite) TestDequeueOrWaitForNextElementLockSingleGR() { 442 | suite.fifo.Lock() 443 | result, err := suite.fifo.DequeueOrWaitForNextElement() 444 | suite.Nil(result, "No value expected if queue is locked") 445 | suite.Error(err, "Locked queue does not allow to enqueue elements") 446 | 447 | // verify custom error: code: QueueErrorCodeLockedQueue 448 | customError, ok := err.(*QueueError) 449 | suite.True(ok, "Expected error type: QueueError") 450 | // verify custom error code 451 | suite.Equalf(QueueErrorCodeLockedQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeLockedQueue) 452 | } 453 | 454 | // single GR DequeueOrWaitForNextElement with a previous enqueued element 455 | func (suite *FIFOTestSuite) TestDequeueOrWaitForNextElementWithEnqueuedElementSingleGR() { 456 | value := 100 457 | len := suite.fifo.GetLen() 458 | suite.fifo.Enqueue(value) 459 | 460 | result, err := suite.fifo.DequeueOrWaitForNextElement() 461 | 462 | suite.NoError(err) 463 | suite.Equal(value, result) 464 | // length must be exactly the same as it was before 465 | suite.Equal(len, suite.fifo.GetLen()) 466 | } 467 | 468 | // single GR DequeueOrWaitForNextElement 1 element 469 | func (suite *FIFOTestSuite) TestDequeueOrWaitForNextElementWithEmptyQueue() { 470 | var ( 471 | value = 100 472 | result interface{} 473 | err error 474 | done = make(chan struct{}) 475 | ) 476 | 477 | // waiting for next enqueued element 478 | go func() { 479 | result, err = suite.fifo.DequeueOrWaitForNextElement() 480 | done <- struct{}{} 481 | }() 482 | 483 | // enqueue an element 484 | go func() { 485 | suite.fifo.Enqueue(value) 486 | }() 487 | 488 | select { 489 | // wait for the dequeued element 490 | case <-done: 491 | suite.NoError(err) 492 | suite.Equal(value, result) 493 | 494 | // the following comes first if more time than expected happened while waiting for the dequeued element 495 | case <-time.After(2 * time.Second): 496 | suite.Fail("too much time waiting for the enqueued element") 497 | 498 | } 499 | } 500 | 501 | // single GR calling DequeueOrWaitForNextElement (WaitForNextElementChanCapacity + 1) times, last one should return error 502 | func (suite *FIFOTestSuite) TestDequeueOrWaitForNextElementWithFullWaitingChannel() { 503 | // enqueue WaitForNextElementChanCapacity listeners to future enqueued elements 504 | for i := 0; i < WaitForNextElementChanCapacity; i++ { 505 | suite.fifo.waitForNextElementChan <- make(chan interface{}) 506 | } 507 | 508 | result, err := suite.fifo.DequeueOrWaitForNextElement() 509 | suite.Nil(result) 510 | suite.Error(err) 511 | // verify custom error: code: QueueErrorCodeEmptyQueue 512 | customError, ok := err.(*QueueError) 513 | suite.True(ok, "Expected error type: QueueError") 514 | // verify custom error code 515 | suite.Equalf(QueueErrorCodeEmptyQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeEmptyQueue) 516 | } 517 | 518 | // multiple GRs, calling DequeueOrWaitForNextElement from different GRs and enqueuing the expected values later 519 | func (suite *FIFOTestSuite) TestDequeueOrWaitForNextElementMultiGR() { 520 | var ( 521 | wg sync.WaitGroup 522 | // channel to enqueue dequeued values 523 | dequeuedValues = make(chan int, WaitForNextElementChanCapacity) 524 | // map[dequeued_value] = times dequeued 525 | mp = make(map[int]int) 526 | ) 527 | 528 | for i := 0; i < WaitForNextElementChanCapacity; i++ { 529 | go func() { 530 | // wait for the next enqueued element 531 | result, err := suite.fifo.DequeueOrWaitForNextElement() 532 | // no error && no nil result 533 | suite.NoError(err) 534 | suite.NotNil(result) 535 | 536 | // send each dequeued element into the dequeuedValues channel 537 | resultInt, _ := result.(int) 538 | dequeuedValues <- resultInt 539 | 540 | // let the wg.Wait() know that this GR is done 541 | wg.Done() 542 | }() 543 | } 544 | 545 | // enqueue all needed elements 546 | for i := 0; i < WaitForNextElementChanCapacity; i++ { 547 | wg.Add(1) 548 | suite.fifo.Enqueue(i) 549 | // save the enqueued value as index 550 | mp[i] = 0 551 | } 552 | 553 | // wait until all GRs dequeue the elements 554 | wg.Wait() 555 | // close dequeuedValues channel in order to only read the previous enqueued values (from the channel) 556 | close(dequeuedValues) 557 | 558 | // verify that all enqueued values were dequeued 559 | for v := range dequeuedValues { 560 | val, ok := mp[v] 561 | suite.Truef(ok, "element dequeued but never enqueued: %v", val) 562 | // increment the m[p] value meaning the value p was dequeued 563 | mp[v] = val + 1 564 | } 565 | // verify that there are no duplicates 566 | for k, v := range mp { 567 | suite.Equalf(1, v, "%v was dequeued %v times", k, v) 568 | } 569 | } 570 | 571 | // multiple GRs, calling DequeueOrWaitForNextElement from different GRs and enqueuing the expected values later 572 | func (suite *FIFOTestSuite) TestDequeueOrWaitForNextElementMultiGR2() { 573 | var ( 574 | done = make(chan int, 10) 575 | total = 2000 576 | results = make(map[int]struct{}) 577 | totalOk = 0 578 | totalError = 0 579 | ) 580 | 581 | go func(fifo Queue, done chan int, total int) { 582 | for i := 0; i < total; i++ { 583 | go func(queue Queue, done chan int) { 584 | rawValue, err := fifo.DequeueOrWaitForNextElement() 585 | if err != nil { 586 | fmt.Println(err) 587 | // error 588 | done <- -1 589 | } else { 590 | val, _ := rawValue.(int) 591 | done <- val 592 | } 593 | }(fifo, done) 594 | 595 | go func(queue Queue, value int) { 596 | if err := fifo.Enqueue(value); err != nil { 597 | fmt.Println(err) 598 | } 599 | }(fifo, i) 600 | } 601 | }(suite.fifo, done, total) 602 | 603 | i := 0 604 | for { 605 | v := <-done 606 | if v != -1 { 607 | totalOk++ 608 | _, ok := results[v] 609 | suite.Falsef(ok, "duplicated value %v", v) 610 | 611 | results[v] = struct{}{} 612 | } else { 613 | totalError++ 614 | } 615 | 616 | i++ 617 | if i == total { 618 | break 619 | } 620 | } 621 | 622 | suite.Equal(total, totalError+totalOk) 623 | } 624 | 625 | // call DequeueOrWaitForNextElement(), wait some time and enqueue an item 626 | func (suite *FIFOTestSuite) TestDequeueOrWaitForNextElementGapSingleGR() { 627 | var ( 628 | expectedValue = 50 629 | done = make(chan struct{}, 3) 630 | ) 631 | 632 | // DequeueOrWaitForNextElement() 633 | go func(fifo *FIFO, done chan struct{}) { 634 | val, err := fifo.DequeueOrWaitForNextElement() 635 | suite.NoError(err) 636 | suite.Equal(expectedValue, val) 637 | done <- struct{}{} 638 | }(suite.fifo, done) 639 | 640 | // wait and Enqueue function 641 | go func(fifo *FIFO, done chan struct{}) { 642 | time.Sleep(time.Millisecond * dequeueOrWaitForNextElementInvokeGapTime * dequeueOrWaitForNextElementInvokeGapTime) 643 | suite.NoError(fifo.Enqueue(expectedValue)) 644 | done <- struct{}{} 645 | }(suite.fifo, done) 646 | 647 | for i := 0; i < 2; i++ { 648 | select { 649 | case <-done: 650 | case <-time.After(2 * time.Millisecond * dequeueOrWaitForNextElementInvokeGapTime * dequeueOrWaitForNextElementInvokeGapTime): 651 | suite.FailNow("Too much time waiting for the value") 652 | } 653 | } 654 | } 655 | 656 | // *************************************************************************************** 657 | // ** Lock / Unlock / IsLocked 658 | // *************************************************************************************** 659 | 660 | // single lock 661 | func (suite *FIFOTestSuite) TestLockSingleGR() { 662 | suite.fifo.Lock() 663 | suite.True(suite.fifo.isLocked == true, "fifo.isLocked has to be true after fifo.Lock()") 664 | } 665 | 666 | // single unlock 667 | func (suite *FIFOTestSuite) TestUnlockSingleGR() { 668 | suite.fifo.Lock() 669 | suite.fifo.Unlock() 670 | suite.True(suite.fifo.isLocked == false, "fifo.isLocked has to be false after fifo.Unlock()") 671 | } 672 | 673 | // single isLocked 674 | func (suite *FIFOTestSuite) TestIsLockedSingleGR() { 675 | suite.True(suite.fifo.isLocked == suite.fifo.IsLocked(), "fifo.IsLocked() has to be equal to fifo.isLocked") 676 | 677 | suite.fifo.Lock() 678 | suite.True(suite.fifo.isLocked == suite.fifo.IsLocked(), "fifo.IsLocked() has to be equal to fifo.isLocked") 679 | 680 | suite.fifo.Unlock() 681 | suite.True(suite.fifo.isLocked == suite.fifo.IsLocked(), "fifo.IsLocked() has to be equal to fifo.isLocked") 682 | } 683 | 684 | // *************************************************************************************** 685 | // ** Context 686 | // *************************************************************************************** 687 | 688 | // context canceled while waiting for element to be added 689 | func (suite *FIFOTestSuite) TestContextCanceledAfter1Second() { 690 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 691 | defer cancel() 692 | 693 | var ( 694 | result interface{} 695 | err error 696 | done = make(chan struct{}) 697 | ) 698 | 699 | go func() { 700 | result, err = suite.fifo.DequeueOrWaitForNextElementContext(ctx) 701 | done <- struct{}{} 702 | }() 703 | 704 | select { 705 | // wait for the dequeue to finish 706 | case <-done: 707 | suite.True(err == context.DeadlineExceeded, "Canceling the context passed to fifo.DequeueOrWaitForNextElementContext() must cancel the dequeue wait", err) 708 | suite.Nil(result) 709 | 710 | // the following comes first if more time than expected happened while waiting for the dequeued element 711 | case <-time.After(2 * time.Second): 712 | suite.Fail("DequeueOrWaitForNextElementContext did not return immediately after context was canceled") 713 | } 714 | } 715 | 716 | // passing a already-canceled context to DequeueOrWaitForNextElementContext 717 | func (suite *FIFOTestSuite) TestContextAlreadyCanceled() { 718 | ctx, cancel := context.WithCancel(context.Background()) 719 | cancel() 720 | 721 | var ( 722 | result interface{} 723 | err error 724 | done = make(chan struct{}) 725 | ) 726 | 727 | go func() { 728 | result, err = suite.fifo.DequeueOrWaitForNextElementContext(ctx) 729 | done <- struct{}{} 730 | }() 731 | 732 | select { 733 | // wait for the dequeue to finish 734 | case <-done: 735 | suite.True(err == context.Canceled, "Canceling the context passed to fifo.DequeueOrWaitForNextElementContext() must cancel the dequeue wait", err) 736 | suite.Nil(result) 737 | 738 | // the following comes first if more time than expected happened while waiting for the dequeued element 739 | case <-time.After(2 * time.Second): 740 | suite.Fail("DequeueOrWaitForNextElementContext did not return immediately after context was canceled") 741 | } 742 | } 743 | 744 | // *************************************************************************************** 745 | // ** Run suite 746 | // *************************************************************************************** 747 | 748 | func TestFIFOTestSuite(t *testing.T) { 749 | suite.Run(t, new(FIFOTestSuite)) 750 | } 751 | -------------------------------------------------------------------------------- /fixed_fifo_queue.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // Fixed capacity FIFO (First In First Out) concurrent queue 8 | type FixedFIFO struct { 9 | queue chan interface{} 10 | lockChan chan struct{} 11 | // queue for watchers that will wait for next elements (if queue is empty at DequeueOrWaitForNextElement execution ) 12 | waitForNextElementChan chan chan interface{} 13 | } 14 | 15 | func NewFixedFIFO(capacity int) *FixedFIFO { 16 | queue := &FixedFIFO{} 17 | queue.initialize(capacity) 18 | 19 | return queue 20 | } 21 | 22 | func (st *FixedFIFO) initialize(capacity int) { 23 | st.queue = make(chan interface{}, capacity) 24 | st.lockChan = make(chan struct{}, 1) 25 | st.waitForNextElementChan = make(chan chan interface{}, WaitForNextElementChanCapacity) 26 | } 27 | 28 | // Enqueue enqueues an element. Returns error if queue is locked or it is at full capacity. 29 | func (st *FixedFIFO) Enqueue(value interface{}) error { 30 | if st.IsLocked() { 31 | return NewQueueError(QueueErrorCodeLockedQueue, "The queue is locked") 32 | } 33 | 34 | // check if there is a listener waiting for the next element (this element) 35 | select { 36 | case listener := <-st.waitForNextElementChan: 37 | // verify whether it is possible to notify the listener (it could be the listener is no longer 38 | // available because the context expired: DequeueOrWaitForNextElementContext) 39 | select { 40 | // sends the element through the listener's channel instead of enqueueing it 41 | case listener <- value: 42 | default: 43 | // push the element into the queue instead of sending it through the listener's channel (which is not available at this moment) 44 | return st.enqueueIntoQueue(value) 45 | } 46 | 47 | default: 48 | // enqueue the element into the queue 49 | return st.enqueueIntoQueue(value) 50 | } 51 | 52 | return nil 53 | } 54 | 55 | // enqueueIntoQueue enqueues the given item directly into the regular queue 56 | func (st *FixedFIFO) enqueueIntoQueue(value interface{}) error { 57 | select { 58 | case st.queue <- value: 59 | default: 60 | return NewQueueError(QueueErrorCodeFullCapacity, "FixedFIFO queue is at full capacity") 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // Dequeue dequeues an element. Returns error if: queue is locked, queue is empty or internal channel is closed. 67 | func (st *FixedFIFO) Dequeue() (interface{}, error) { 68 | if st.IsLocked() { 69 | return nil, NewQueueError(QueueErrorCodeLockedQueue, "The queue is locked") 70 | } 71 | 72 | select { 73 | case value, ok := <-st.queue: 74 | if ok { 75 | return value, nil 76 | } 77 | return nil, NewQueueError(QueueErrorCodeInternalChannelClosed, "internal channel is closed") 78 | default: 79 | return nil, NewQueueError(QueueErrorCodeEmptyQueue, "empty queue") 80 | } 81 | } 82 | 83 | // DequeueOrWaitForNextElement dequeues an element (if exist) or waits until the next element gets enqueued and returns it. 84 | // Multiple calls to DequeueOrWaitForNextElement() would enqueue multiple "listeners" for future enqueued elements. 85 | func (st *FixedFIFO) DequeueOrWaitForNextElement() (interface{}, error) { 86 | return st.DequeueOrWaitForNextElementContext(context.Background()) 87 | } 88 | 89 | // DequeueOrWaitForNextElementContext dequeues an element (if exist) or waits until the next element gets enqueued and returns it. 90 | // Multiple calls to DequeueOrWaitForNextElementContext() would enqueue multiple "listeners" for future enqueued elements. 91 | // When the passed context expires this function exits and returns the context' error 92 | func (st *FixedFIFO) DequeueOrWaitForNextElementContext(ctx context.Context) (interface{}, error) { 93 | if st.IsLocked() { 94 | return nil, NewQueueError(QueueErrorCodeLockedQueue, "The queue is locked") 95 | } 96 | 97 | select { 98 | case value, ok := <-st.queue: 99 | if ok { 100 | return value, nil 101 | } 102 | return nil, NewQueueError(QueueErrorCodeInternalChannelClosed, "internal channel is closed") 103 | case <-ctx.Done(): 104 | return nil, ctx.Err() 105 | // queue is empty, add a listener to wait until next enqueued element is ready 106 | default: 107 | // channel to wait for next enqueued element 108 | waitChan := make(chan interface{}) 109 | 110 | select { 111 | // enqueue a watcher into the watchForNextElementChannel to wait for the next element 112 | case st.waitForNextElementChan <- waitChan: 113 | // return the next enqueued element, if any 114 | select { 115 | case item := <-waitChan: 116 | return item, nil 117 | case <-ctx.Done(): 118 | return nil, ctx.Err() 119 | // try again to get the element from the regular queue (in case waitChan doesn't provide any item) 120 | case value, ok := <-st.queue: 121 | if ok { 122 | return value, nil 123 | } 124 | return nil, NewQueueError(QueueErrorCodeInternalChannelClosed, "internal channel is closed") 125 | } 126 | default: 127 | // too many watchers (waitForNextElementChanCapacity) enqueued waiting for next elements 128 | return nil, NewQueueError(QueueErrorCodeEmptyQueue, "empty queue and can't wait for next element") 129 | } 130 | 131 | //return nil, NewQueueError(QueueErrorCodeEmptyQueue, "empty queue") 132 | } 133 | } 134 | 135 | // GetLen returns queue's length (total enqueued elements) 136 | func (st *FixedFIFO) GetLen() int { 137 | st.Lock() 138 | defer st.Unlock() 139 | 140 | return len(st.queue) 141 | } 142 | 143 | // GetCap returns the queue's capacity 144 | func (st *FixedFIFO) GetCap() int { 145 | st.Lock() 146 | defer st.Unlock() 147 | 148 | return cap(st.queue) 149 | } 150 | 151 | func (st *FixedFIFO) Lock() { 152 | // non-blocking fill the channel 153 | select { 154 | case st.lockChan <- struct{}{}: 155 | default: 156 | } 157 | } 158 | 159 | func (st *FixedFIFO) Unlock() { 160 | // non-blocking flush the channel 161 | select { 162 | case <-st.lockChan: 163 | default: 164 | } 165 | } 166 | 167 | func (st *FixedFIFO) IsLocked() bool { 168 | return len(st.lockChan) >= 1 169 | } 170 | -------------------------------------------------------------------------------- /fixed_fifo_queue_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // *************************************************************************************** 8 | // ** Enqueue 9 | // *************************************************************************************** 10 | 11 | // single goroutine - enqueue 1 element 12 | func BenchmarkFixedFIFOEnqueueSingleGR(b *testing.B) { 13 | fifo := NewFixedFIFO(5) 14 | for i := 0; i < b.N; i++ { 15 | fifo.Enqueue(i) 16 | } 17 | } 18 | 19 | // single goroutine - enqueue 100 elements 20 | func BenchmarkFixedFIFOEnqueue100SingleGR(b *testing.B) { 21 | fifo := NewFixedFIFO(100) 22 | 23 | for i := 0; i < b.N; i++ { 24 | for c := 0; c < 100; c++ { 25 | fifo.Enqueue(c) 26 | } 27 | } 28 | } 29 | 30 | // single goroutine - enqueue 1000 elements 31 | func BenchmarkFixedFIFOEnqueue1000SingleGR(b *testing.B) { 32 | fifo := NewFixedFIFO(1000) 33 | 34 | for i := 0; i < b.N; i++ { 35 | for c := 0; c < 1000; c++ { 36 | fifo.Enqueue(c) 37 | } 38 | } 39 | } 40 | 41 | // multiple goroutines - enqueue 100 elements per gr 42 | func BenchmarkFixedFIFOEnqueue100MultipleGRs(b *testing.B) { 43 | b.StopTimer() 44 | fifo := NewFixedFIFO(5000000) 45 | 46 | b.StartTimer() 47 | b.RunParallel(func(pb *testing.PB) { 48 | for pb.Next() { 49 | for c := 0; c < 100; c++ { 50 | fifo.Enqueue(c) 51 | } 52 | } 53 | }) 54 | } 55 | 56 | // multiple goroutines - enqueue 1000 elements per gr 57 | func BenchmarkFixedFIFOEnqueue1000MultipleGRs(b *testing.B) { 58 | b.StopTimer() 59 | fifo := NewFixedFIFO(5000000) 60 | 61 | b.StartTimer() 62 | b.RunParallel(func(pb *testing.PB) { 63 | for pb.Next() { 64 | for c := 0; c < 1000; c++ { 65 | fifo.Enqueue(c) 66 | } 67 | } 68 | }) 69 | } 70 | 71 | // *************************************************************************************** 72 | // ** Dequeue 73 | // *************************************************************************************** 74 | 75 | // _benchmarkFixedFIFODequeueNDingleGR is a helper for single GR dequeue 76 | func _benchmarkFixedFIFODequeueNDingleGR(total int, b *testing.B) { 77 | fifo := NewFixedFIFO(total) 78 | 79 | for i := 0; i < b.N; i++ { 80 | b.StopTimer() 81 | for i := 0; i < total; i++ { 82 | fifo.Enqueue(i) 83 | } 84 | 85 | b.StartTimer() 86 | for c := 0; c < total; c++ { 87 | fifo.Dequeue() 88 | } 89 | } 90 | } 91 | 92 | // single goroutine - dequeue 100 elements 93 | func BenchmarkFixedFIFODequeue100SingleGR(b *testing.B) { 94 | _benchmarkFixedFIFODequeueNDingleGR(100, b) 95 | } 96 | 97 | // single goroutine - dequeue 1000 elements 98 | func BenchmarkFixedFIFODequeue1000SingleGR(b *testing.B) { 99 | _benchmarkFixedFIFODequeueNDingleGR(1000, b) 100 | } 101 | 102 | // multiple goroutines - dequeue 100 elements per gr 103 | func BenchmarkFixedFIFODequeue100MultipleGRs(b *testing.B) { 104 | b.StopTimer() 105 | fifo := NewFixedFIFO(5000000) 106 | 107 | b.RunParallel(func(pb *testing.PB) { 108 | for pb.Next() { 109 | b.StopTimer() 110 | for c := 0; c < 100; c++ { 111 | fifo.Enqueue(c) 112 | } 113 | 114 | b.StartTimer() 115 | for c := 0; c < 100; c++ { 116 | fifo.Dequeue() 117 | } 118 | } 119 | }) 120 | } 121 | 122 | // multiple goroutines - dequeue 1000 elements per gr 123 | func BenchmarkFixedFIFODequeue1000MultipleGRs(b *testing.B) { 124 | b.StopTimer() 125 | fifo := NewFixedFIFO(5000000) 126 | 127 | b.RunParallel(func(pb *testing.PB) { 128 | for pb.Next() { 129 | b.StopTimer() 130 | for c := 0; c < 1000; c++ { 131 | fifo.Enqueue(c) 132 | } 133 | 134 | b.StartTimer() 135 | for c := 0; c < 1000; c++ { 136 | fifo.Dequeue() 137 | } 138 | } 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /fixed_fifo_queue_test.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/suite" 10 | ) 11 | 12 | const ( 13 | fixedFIFOQueueCapacity = 500 14 | ) 15 | 16 | type FixedFIFOTestSuite struct { 17 | suite.Suite 18 | fifo *FixedFIFO 19 | } 20 | 21 | func (suite *FixedFIFOTestSuite) SetupTest() { 22 | suite.fifo = NewFixedFIFO(fixedFIFOQueueCapacity) 23 | } 24 | 25 | // *************************************************************************************** 26 | // ** Run suite 27 | // *************************************************************************************** 28 | 29 | func TestFixedFIFOTestSuite(t *testing.T) { 30 | suite.Run(t, new(FixedFIFOTestSuite)) 31 | } 32 | 33 | // *************************************************************************************** 34 | // ** Enqueue && GetLen 35 | // *************************************************************************************** 36 | 37 | // single enqueue lock verification 38 | func (suite *FixedFIFOTestSuite) TestEnqueueLockSingleGR() { 39 | suite.NoError(suite.fifo.Enqueue(1), "Unlocked queue allows to enqueue elements") 40 | 41 | suite.fifo.Lock() 42 | err := suite.fifo.Enqueue(1) 43 | suite.Error(err, "Locked queue does not allow to enqueue elements") 44 | 45 | // verify custom error: code: QueueErrorCodeLockedQueue 46 | customError, ok := err.(*QueueError) 47 | suite.True(ok, "Expected error type: QueueError") 48 | // verify custom error code 49 | suite.Equalf(QueueErrorCodeLockedQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeLockedQueue) 50 | } 51 | 52 | // single enqueue (1 element, 1 goroutine) 53 | func (suite *FixedFIFOTestSuite) TestEnqueueLenSingleGR() { 54 | suite.fifo.Enqueue(testValue) 55 | len := suite.fifo.GetLen() 56 | suite.Equalf(1, len, "Expected number of elements in queue: 1, currently: %v", len) 57 | 58 | suite.fifo.Enqueue(5) 59 | len = suite.fifo.GetLen() 60 | suite.Equalf(2, len, "Expected number of elements in queue: 2, currently: %v", len) 61 | } 62 | 63 | // single enqueue at full capacity, 1 goroutine 64 | func (suite *FixedFIFOTestSuite) TestEnqueueFullCapacitySingleGR() { 65 | total := 5 66 | suite.fifo = NewFixedFIFO(total) 67 | 68 | for i := 0; i < total; i++ { 69 | suite.NoError(suite.fifo.Enqueue(i), "no error expected when queue is not full") 70 | } 71 | 72 | err := suite.fifo.Enqueue(0) 73 | suite.Error(err, "error expected when queue is full") 74 | // verify custom error: code: QueueErrorCodeLockedQueue 75 | customError, ok := err.(*QueueError) 76 | suite.True(ok, "Expected error type: QueueError") 77 | // verify custom error code 78 | suite.Equalf(QueueErrorCodeFullCapacity, customError.Code(), "Expected code: '%v'", QueueErrorCodeFullCapacity) 79 | } 80 | 81 | func (suite *FixedFIFOTestSuite) TestEnqueueListenerToExpireSingleGR() { 82 | var ( 83 | uselessChan = make(chan interface{}) 84 | value = "my-test-value" 85 | ) 86 | 87 | // let Enqueue knows there is a channel to send the next item instead of enqueueing it into the queue 88 | suite.fifo.waitForNextElementChan <- uselessChan 89 | 90 | // enqueues an item having an waiting channel but without a valid listener, so the item should be enqueued into the queue 91 | suite.fifo.Enqueue(value) 92 | // dequeues the item directly from the queue 93 | dequeuedValue, _ := suite.fifo.Dequeue() 94 | 95 | suite.Equal(value, dequeuedValue) 96 | } 97 | 98 | // TestEnqueueLenMultipleGR enqueues elements concurrently 99 | // 100 | // Detailed steps: 101 | // 102 | // 1 - Enqueue totalGRs concurrently (from totalGRs different GRs) 103 | // 2 - Verifies the len, it should be equal to totalGRs 104 | // 3 - Verifies that all elements from 0 to totalGRs were enqueued 105 | func (suite *FixedFIFOTestSuite) TestEnqueueLenMultipleGR() { 106 | var ( 107 | totalGRs = 500 108 | wg sync.WaitGroup 109 | ) 110 | 111 | // concurrent enqueueing 112 | // multiple GRs concurrently enqueueing consecutive integers from 0 to (totalGRs - 1) 113 | for i := 0; i < totalGRs; i++ { 114 | wg.Add(1) 115 | go func(value int) { 116 | defer wg.Done() 117 | suite.fifo.Enqueue(value) 118 | }(i) 119 | } 120 | wg.Wait() 121 | 122 | // check that there are totalGRs elements enqueued 123 | totalElements := suite.fifo.GetLen() 124 | suite.Equalf(totalGRs, totalElements, "Total enqueued elements should be %v, currently: %v", totalGRs, totalElements) 125 | 126 | // checking that the expected elements (1, 2, 3, ... totalGRs-1 ) were enqueued 127 | var ( 128 | tmpVal interface{} 129 | val int 130 | err error 131 | totalElementsVerified int 132 | ) 133 | 134 | // slice to check every element 135 | allElements := make([]bool, totalGRs) 136 | for i := 0; i < totalElements; i++ { 137 | tmpVal, err = suite.fifo.Dequeue() 138 | suite.NoError(err, "No error should be returned trying to get an existent element") 139 | 140 | val = tmpVal.(int) 141 | if allElements[val] { 142 | suite.Failf("Duplicated element", "Unexpected duplicated value: %v", val) 143 | } else { 144 | allElements[val] = true 145 | totalElementsVerified++ 146 | } 147 | } 148 | suite.True(totalElementsVerified == totalGRs, "Enqueued elements are missing") 149 | } 150 | 151 | // call GetLen concurrently 152 | func (suite *FixedFIFOTestSuite) TestGetLenMultipleGRs() { 153 | var ( 154 | totalGRs = 100 155 | totalElementsToEnqueue = 10 156 | wg sync.WaitGroup 157 | ) 158 | 159 | for i := 0; i < totalElementsToEnqueue; i++ { 160 | suite.fifo.Enqueue(i) 161 | } 162 | 163 | for i := 0; i < totalGRs; i++ { 164 | wg.Add(1) 165 | go func() { 166 | defer wg.Done() 167 | 168 | total := suite.fifo.GetLen() 169 | suite.Equalf(totalElementsToEnqueue, total, "Expected len: %v", totalElementsToEnqueue) 170 | }() 171 | } 172 | wg.Wait() 173 | } 174 | 175 | // *************************************************************************************** 176 | // ** GetCap 177 | // *************************************************************************************** 178 | 179 | // single GR getCapacity 180 | func (suite *FixedFIFOTestSuite) TestGetCapSingleGR() { 181 | // initial capacity 182 | suite.Equal(fixedFIFOQueueCapacity, suite.fifo.GetCap(), "unexpected capacity") 183 | 184 | // new fifo with capacity == 10 185 | suite.fifo = NewFixedFIFO(10) 186 | suite.Equal(10, suite.fifo.GetCap(), "unexpected capacity") 187 | } 188 | 189 | // *************************************************************************************** 190 | // ** Dequeue 191 | // *************************************************************************************** 192 | 193 | // single dequeue lock verification 194 | func (suite *FixedFIFOTestSuite) TestDequeueLockSingleGR() { 195 | suite.fifo.Enqueue(1) 196 | _, err := suite.fifo.Dequeue() 197 | suite.NoError(err, "Unlocked queue allows to dequeue elements") 198 | 199 | suite.fifo.Enqueue(1) 200 | suite.fifo.Lock() 201 | _, err = suite.fifo.Dequeue() 202 | suite.Error(err, "Locked queue does not allow to dequeue elements") 203 | 204 | // verify custom error: code: QueueErrorCodeLockedQueue 205 | customError, ok := err.(*QueueError) 206 | suite.True(ok, "Expected error type: QueueError") 207 | // verify custom error code 208 | suite.Equalf(QueueErrorCodeLockedQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeLockedQueue) 209 | } 210 | 211 | // dequeue an empty queue 212 | func (suite *FixedFIFOTestSuite) TestDequeueEmptyQueueSingleGR() { 213 | val, err := suite.fifo.Dequeue() 214 | 215 | // error expected 216 | suite.Errorf(err, "Can't dequeue an empty queue") 217 | 218 | // verify custom error type 219 | customError, ok := err.(*QueueError) 220 | suite.True(ok, "Expected error type: QueueError") 221 | // verify custom error code 222 | suite.Equalf(QueueErrorCodeEmptyQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeEmptyQueue) 223 | 224 | // no value expected 225 | suite.Equal(nil, val, "Can't get a value different than nil from an empty queue") 226 | } 227 | 228 | // dequeue all elements 229 | func (suite *FixedFIFOTestSuite) TestDequeueSingleGR() { 230 | suite.fifo.Enqueue(testValue) 231 | suite.fifo.Enqueue(5) 232 | 233 | // dequeue the first element 234 | val, err := suite.fifo.Dequeue() 235 | suite.NoError(err, "Unexpected error") 236 | suite.Equal(testValue, val, "Wrong element's value") 237 | len := suite.fifo.GetLen() 238 | suite.Equal(1, len, "Incorrect number of queue elements") 239 | 240 | // get the second element 241 | val, err = suite.fifo.Dequeue() 242 | suite.NoError(err, "Unexpected error") 243 | suite.Equal(5, val, "Wrong element's value") 244 | len = suite.fifo.GetLen() 245 | suite.Equal(0, len, "Incorrect number of queue elements") 246 | 247 | } 248 | 249 | // dequeue an item after closing the empty queue's channel 250 | func (suite *FixedFIFOTestSuite) TestDequeueClosedChannelSingleGR() { 251 | // enqueue a dummy item 252 | suite.fifo.Enqueue(1) 253 | // close the internal queue's channel 254 | close(suite.fifo.queue) 255 | // dequeue the dummy item 256 | suite.fifo.Dequeue() 257 | 258 | // dequeue after the queue's channel was closed 259 | val, err := suite.fifo.Dequeue() 260 | 261 | suite.Nil(val, "nil value expected after internal channel was closed") 262 | suite.Error(err, "error expected after internal queue channel was closed") 263 | // verify custom error: code: QueueErrorCodeLockedQueue 264 | customError, ok := err.(*QueueError) 265 | suite.True(ok, "Expected error type: QueueError") 266 | // verify custom error code 267 | suite.Equalf(QueueErrorCodeInternalChannelClosed, customError.Code(), "Expected code: '%v'", QueueErrorCodeInternalChannelClosed) 268 | } 269 | 270 | // TestDequeueMultipleGRs dequeues elements concurrently 271 | // 272 | // Detailed steps: 273 | // 274 | // 1 - Enqueues totalElementsToEnqueue consecutive integers 275 | // 2 - Dequeues totalElementsToDequeue concurrently from totalElementsToDequeue GRs 276 | // 3 - Verifies the final len, should be equal to totalElementsToEnqueue - totalElementsToDequeue 277 | // 4 - Verifies that the next dequeued element's value is equal to totalElementsToDequeue 278 | func (suite *FixedFIFOTestSuite) TestDequeueMultipleGRs() { 279 | var ( 280 | wg sync.WaitGroup 281 | totalElementsToEnqueue = 100 282 | totalElementsToDequeue = 90 283 | ) 284 | 285 | for i := 0; i < totalElementsToEnqueue; i++ { 286 | suite.fifo.Enqueue(i) 287 | } 288 | 289 | for i := 0; i < totalElementsToDequeue; i++ { 290 | wg.Add(1) 291 | go func() { 292 | defer wg.Done() 293 | _, err := suite.fifo.Dequeue() 294 | suite.NoError(err, "Unexpected error during concurrent Dequeue()") 295 | }() 296 | } 297 | wg.Wait() 298 | 299 | // check len, should be == totalElementsToEnqueue - totalElementsToDequeue 300 | totalElementsAfterDequeue := suite.fifo.GetLen() 301 | suite.Equal(totalElementsToEnqueue-totalElementsToDequeue, totalElementsAfterDequeue, "Total elements on queue (after Dequeue) does not match with expected number") 302 | 303 | // check current first element 304 | val, err := suite.fifo.Dequeue() 305 | suite.NoError(err, "No error should be returned when dequeuing an existent element") 306 | suite.Equalf(totalElementsToDequeue, val, "The expected last element's value should be: %v", totalElementsToEnqueue-totalElementsToDequeue) 307 | } 308 | 309 | // *************************************************************************************** 310 | // ** DequeueOrWaitForNextElement 311 | // *************************************************************************************** 312 | 313 | // single GR Locked queue 314 | func (suite *FixedFIFOTestSuite) TestDequeueOrWaitForNextElementLockSingleGR() { 315 | suite.fifo.Lock() 316 | result, err := suite.fifo.DequeueOrWaitForNextElement() 317 | suite.Nil(result, "No value expected if queue is locked") 318 | suite.Error(err, "Locked queue does not allow to enqueue elements") 319 | 320 | // verify custom error: code: QueueErrorCodeLockedQueue 321 | customError, ok := err.(*QueueError) 322 | suite.True(ok, "Expected error type: QueueError") 323 | // verify custom error code 324 | suite.Equalf(QueueErrorCodeLockedQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeLockedQueue) 325 | } 326 | 327 | // single GR DequeueOrWaitForNextElement with a previous enqueued element 328 | func (suite *FixedFIFOTestSuite) TestDequeueOrWaitForNextElementWithEnqueuedElementSingleGR() { 329 | value := 100 330 | len := suite.fifo.GetLen() 331 | suite.fifo.Enqueue(value) 332 | 333 | result, err := suite.fifo.DequeueOrWaitForNextElement() 334 | 335 | suite.NoError(err) 336 | suite.Equal(value, result) 337 | // length must be exactly the same as it was before 338 | suite.Equal(len, suite.fifo.GetLen()) 339 | } 340 | 341 | // single GR DequeueOrWaitForNextElement 1 element 342 | func (suite *FixedFIFOTestSuite) TestDequeueOrWaitForNextElementWithEmptyQueue() { 343 | var ( 344 | value = 100 345 | result interface{} 346 | err error 347 | done = make(chan struct{}) 348 | ) 349 | 350 | wait4gr := make(chan struct{}) 351 | // waiting for next enqueued element 352 | go func(ready chan struct{}) { 353 | // let flow know this goroutine is ready 354 | wait4gr <- struct{}{} 355 | 356 | result, err = suite.fifo.DequeueOrWaitForNextElement() 357 | done <- struct{}{} 358 | }(wait4gr) 359 | 360 | // wait until listener goroutine is ready to dequeue/wait 361 | <-wait4gr 362 | 363 | // enqueue an element 364 | go func() { 365 | suite.fifo.Enqueue(value) 366 | }() 367 | 368 | select { 369 | // wait for the dequeued element 370 | case <-done: 371 | suite.NoError(err) 372 | suite.Equal(value, result) 373 | 374 | // the following comes first if more time than expected passed while waiting for the dequeued element 375 | case <-time.After(2 * time.Second): 376 | suite.Fail("too much time waiting for the enqueued element") 377 | 378 | } 379 | } 380 | 381 | // calling DequeueOrWaitForNextElement with empty queue, then adding an item directly into queue's internal channel 382 | func (suite *FixedFIFOTestSuite) TestDequeueOrWaitForNextElementWithStuckWaitChan() { 383 | var ( 384 | dummyValue = "dummyValue" 385 | doneChan = make(chan struct{}) 386 | ) 387 | 388 | // consumer 389 | go func(queue *FixedFIFO, expectedValue interface{}, done chan struct{}) { 390 | item, err := queue.DequeueOrWaitForNextElement() 391 | suite.NoError(err) 392 | suite.Equal(expectedValue, item) 393 | 394 | done <- struct{}{} 395 | }(suite.fifo, dummyValue, doneChan) 396 | 397 | // a second should be enough for the consumer to start consuming ... 398 | time.Sleep(time.Second) 399 | 400 | // add an item (enqueue) directly into queue's internal channel 401 | suite.fifo.queue <- dummyValue 402 | 403 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 404 | defer cancel() 405 | 406 | select { 407 | case <-doneChan: 408 | 409 | case <-ctx.Done(): 410 | suite.Fail("too much time waiting ...") 411 | } 412 | } 413 | 414 | // single GR calling DequeueOrWaitForNextElement (WaitForNextElementChanCapacity + 1) times, last one should return error 415 | func (suite *FixedFIFOTestSuite) TestDequeueOrWaitForNextElementWithFullWaitingChannel() { 416 | // enqueue WaitForNextElementChanCapacity listeners to future enqueued elements 417 | for i := 0; i < WaitForNextElementChanCapacity; i++ { 418 | suite.fifo.waitForNextElementChan <- make(chan interface{}) 419 | } 420 | 421 | result, err := suite.fifo.DequeueOrWaitForNextElement() 422 | suite.Nil(result) 423 | suite.Error(err) 424 | // verify custom error: code: QueueErrorCodeEmptyQueue 425 | customError, ok := err.(*QueueError) 426 | suite.True(ok, "Expected error type: QueueError") 427 | // verify custom error code 428 | suite.Equalf(QueueErrorCodeEmptyQueue, customError.Code(), "Expected code: '%v'", QueueErrorCodeEmptyQueue) 429 | } 430 | 431 | // multiple GRs, calling DequeueOrWaitForNextElement from different GRs and enqueuing the expected values later 432 | func (suite *FixedFIFOTestSuite) TestDequeueOrWaitForNextElementMultiGR() { 433 | var ( 434 | wg, 435 | // waitgroup to wait until goroutines are running 436 | wg4grs sync.WaitGroup 437 | // channel to enqueue dequeued values 438 | dequeuedValues = make(chan int, WaitForNextElementChanCapacity) 439 | // map[dequeued_value] = times dequeued 440 | mp = make(map[int]int) 441 | ) 442 | 443 | for i := 0; i < WaitForNextElementChanCapacity; i++ { 444 | wg4grs.Add(1) 445 | go func() { 446 | wg4grs.Done() 447 | // wait for the next enqueued element 448 | result, err := suite.fifo.DequeueOrWaitForNextElement() 449 | // no error && no nil result 450 | suite.NoError(err) 451 | suite.NotNil(result) 452 | 453 | // send each dequeued element into the dequeuedValues channel 454 | resultInt, _ := result.(int) 455 | dequeuedValues <- resultInt 456 | 457 | // let the wg.Wait() know that this GR is done 458 | wg.Done() 459 | }() 460 | } 461 | 462 | // wait here until all goroutines (from above) are up and running 463 | wg4grs.Wait() 464 | 465 | // enqueue all needed elements 466 | for i := 0; i < WaitForNextElementChanCapacity; i++ { 467 | wg.Add(1) 468 | suite.fifo.Enqueue(i) 469 | // save the enqueued value as index 470 | mp[i] = 0 471 | } 472 | 473 | // wait until all GRs dequeue the elements 474 | wg.Wait() 475 | // close dequeuedValues channel in order to only read the previous enqueued values (from the channel) 476 | close(dequeuedValues) 477 | 478 | // verify that all enqueued values were dequeued 479 | for v := range dequeuedValues { 480 | val, ok := mp[v] 481 | suite.Truef(ok, "element dequeued but never enqueued: %v", val) 482 | // increment the m[p] value meaning the value p was dequeued 483 | mp[v] = val + 1 484 | } 485 | // verify there are no duplicates 486 | for k, v := range mp { 487 | suite.Equalf(1, v, "%v was dequeued %v times", k, v) 488 | } 489 | } 490 | 491 | // DequeueOrWaitForNextElement once queue's channel is closed 492 | func (suite *FixedFIFOTestSuite) TestDequeueOrWaitForNextElementClosedChannel() { 493 | close(suite.fifo.queue) 494 | 495 | result, err := suite.fifo.DequeueOrWaitForNextElement() 496 | suite.Nil(result, "no result expected if internal queue's channel is closed") 497 | suite.Error(err, "error expected if internal queue's channel is closed") 498 | 499 | // verify custom error: code: QueueErrorCodeEmptyQueue 500 | customError, ok := err.(*QueueError) 501 | suite.True(ok, "Expected error type: QueueError") 502 | // verify custom error code 503 | suite.Equalf(QueueErrorCodeInternalChannelClosed, customError.Code(), "Expected code: '%v'", QueueErrorCodeInternalChannelClosed) 504 | } 505 | 506 | // *************************************************************************************** 507 | // ** Lock / Unlock / IsLocked 508 | // *************************************************************************************** 509 | 510 | // single lock 511 | func (suite *FixedFIFOTestSuite) TestLockSingleGR() { 512 | suite.fifo.Lock() 513 | suite.True(suite.fifo.IsLocked(), "fifo.isLocked has to be true after fifo.Lock()") 514 | } 515 | 516 | func (suite *FixedFIFOTestSuite) TestMultipleLockSingleGR() { 517 | for i := 0; i < 5; i++ { 518 | suite.fifo.Lock() 519 | } 520 | 521 | suite.True(suite.fifo.IsLocked(), "queue must be locked after Lock() operations") 522 | } 523 | 524 | // single unlock 525 | func (suite *FixedFIFOTestSuite) TestUnlockSingleGR() { 526 | suite.fifo.Lock() 527 | suite.fifo.Unlock() 528 | suite.True(suite.fifo.IsLocked() == false, "fifo.isLocked has to be false after fifo.Unlock()") 529 | 530 | suite.fifo.Unlock() 531 | suite.True(suite.fifo.IsLocked() == false, "fifo.isLocked has to be false after fifo.Unlock()") 532 | } 533 | 534 | // *************************************************************************************** 535 | // ** Context 536 | // *************************************************************************************** 537 | 538 | // context canceled while waiting for element to be added 539 | func (suite *FixedFIFOTestSuite) TestContextCanceledAfter1Second() { 540 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 541 | defer cancel() 542 | 543 | var ( 544 | result interface{} 545 | err error 546 | done = make(chan struct{}) 547 | ) 548 | 549 | go func() { 550 | result, err = suite.fifo.DequeueOrWaitForNextElementContext(ctx) 551 | done <- struct{}{} 552 | }() 553 | 554 | select { 555 | // wait for the dequeue to finish 556 | case <-done: 557 | suite.True(err == context.DeadlineExceeded, "Canceling the context passed to fifo.DequeueOrWaitForNextElementContext() must cancel the dequeue wait", err) 558 | suite.Nil(result) 559 | 560 | // the following comes first if more time than expected happened while waiting for the dequeued element 561 | case <-time.After(2 * time.Second): 562 | suite.Fail("DequeueOrWaitForNextElementContext did not return immediately after context was canceled") 563 | } 564 | } 565 | 566 | // passing a already-canceled context to DequeueOrWaitForNextElementContext 567 | func (suite *FixedFIFOTestSuite) TestContextAlreadyCanceled() { 568 | ctx, cancel := context.WithCancel(context.Background()) 569 | cancel() 570 | 571 | var ( 572 | result interface{} 573 | err error 574 | done = make(chan struct{}) 575 | ) 576 | 577 | go func() { 578 | result, err = suite.fifo.DequeueOrWaitForNextElementContext(ctx) 579 | done <- struct{}{} 580 | }() 581 | 582 | select { 583 | // wait for the dequeue to finish 584 | case <-done: 585 | suite.True(err == context.Canceled, "Canceling the context passed to fifo.DequeueOrWaitForNextElementContext() must cancel the dequeue wait", err) 586 | suite.Nil(result) 587 | 588 | // the following comes first if more time than expected happened while waiting for the dequeued element 589 | case <-time.After(2 * time.Second): 590 | suite.Fail("DequeueOrWaitForNextElementContext did not return immediately after context was canceled") 591 | } 592 | } 593 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/enriquebris/goconcurrentqueue 2 | 3 | go 1.17 4 | 5 | require github.com/stretchr/testify v1.7.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.0 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | package goconcurrentqueue 2 | 3 | import "context" 4 | 5 | // Queue interface with basic && common queue functions 6 | type Queue interface { 7 | // Enqueue element 8 | Enqueue(interface{}) error 9 | // Dequeue element 10 | Dequeue() (interface{}, error) 11 | // DequeueOrWaitForNextElement dequeues an element (if exist) or waits until the next element gets enqueued and returns it. 12 | // Multiple calls to DequeueOrWaitForNextElement() would enqueue multiple "listeners" for future enqueued elements. 13 | DequeueOrWaitForNextElement() (interface{}, error) 14 | // DequeueOrWaitForNextElementContext dequeues an element (if exist) or waits until the next element gets enqueued and returns it. 15 | // Multiple calls to DequeueOrWaitForNextElementContext() would enqueue multiple "listeners" for future enqueued elements. 16 | // When the passed context expires this function exits and returns the context' error 17 | DequeueOrWaitForNextElementContext(context.Context) (interface{}, error) 18 | // Get number of enqueued elements 19 | GetLen() int 20 | // Get queue's capacity 21 | GetCap() int 22 | 23 | // Lock the queue. No enqueue/dequeue/remove/get operations will be allowed after this point. 24 | Lock() 25 | // Unlock the queue. 26 | Unlock() 27 | // Return true whether the queue is locked 28 | IsLocked() bool 29 | } 30 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/mod/github.com/enriquebris/goconcurrentqueue) [![godoc reference](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/enriquebris/goconcurrentqueue) ![version](https://img.shields.io/badge/version-v0.7.0-yellowgreen.svg?style=flat "goconcurrentqueue v0.7.0") [![Build Status](https://api.travis-ci.org/enriquebris/goconcurrentqueue.svg?branch=master)](https://travis-ci.org/enriquebris/goconcurrentqueue) [![Go Report Card](https://goreportcard.com/badge/github.com/enriquebris/goconcurrentqueue)](https://goreportcard.com/report/github.com/enriquebris/goconcurrentqueue) [![codecov](https://codecov.io/gh/enriquebris/goconcurrentqueue/branch/master/graph/badge.svg)](https://codecov.io/gh/enriquebris/goconcurrentqueue) [![CodeFactor](https://www.codefactor.io/repository/github/enriquebris/goconcurrentqueue/badge)](https://www.codefactor.io/repository/github/enriquebris/goconcurrentqueue) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 2 | 3 | # goconcurrentqueue - Concurrent safe queues 4 | The package goconcurrentqueue offers a public interface Queue with methods for a [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)). 5 | It comes with multiple Queue's concurrent-safe implementations, meaning they could be used concurrently by multiple goroutines without adding race conditions. 6 | 7 | ## Topics 8 | - [Installation](#installation) 9 | - [Documentation](#documentation) 10 | - [Classes diagram](#classes-diagram) 11 | - [Queues](#queues) 12 | - [FIFO](#fifo) 13 | - [FixedFIFO](#fixedfifo) 14 | - [Benchmarks](#benchmarks-fixedfifo-vs-fifo) 15 | - [Get started](#get-started) 16 | - [History](#history) 17 | 18 | ## Installation 19 | 20 | Execute 21 | ```bash 22 | go get github.com/enriquebris/goconcurrentqueue 23 | ``` 24 | 25 | This package is compatible with all golang versions >= 1.7.x 26 | 27 | ## Documentation 28 | Visit [goconcurrentqueue at go.dev](https://pkg.go.dev/mod/github.com/enriquebris/goconcurrentqueue) 29 | 30 | ## Classes diagram 31 | ![goconcurrentqueue class diagram](web/class-diagram.svg "goconcurrentqueue class diagram") 32 | 33 | ## Queues 34 | 35 | - First In First Out (FIFO) 36 | - [FIFO](#fifo) 37 | - [FixedFIFO](#fixedfifo) 38 | - [Benchmarks FixedFIFO vs FIFO](#benchmarks-fixedfifo-vs-fifo) 39 | 40 | ### FIFO 41 | 42 | **FIFO**: concurrent-safe auto expandable queue. 43 | 44 | #### pros 45 | - It is possible to enqueue as many items as needed. 46 | - Extra methods to get and remove enqueued items: 47 | - [Get](https://godoc.org/github.com/enriquebris/goconcurrentqueue#FIFO.Get): returns an element's value and keeps the element at the queue 48 | - [Remove](https://godoc.org/github.com/enriquebris/goconcurrentqueue#FIFO.Get): removes an element (using a given position) from the queue 49 | 50 | #### cons 51 | - It is slightly slower than FixedFIFO. 52 | 53 | ### FixedFIFO 54 | 55 | **FixedFIFO**: concurrent-safe fixed capacity queue. 56 | 57 | #### pros 58 | - FixedFIFO is, at least, 2x faster than [FIFO](#fifo) in concurrent scenarios (multiple GR accessing the queue simultaneously). 59 | 60 | #### cons 61 | - It has a fixed capacity meaning that no more items than this capacity could coexist at the same time. 62 | 63 | ## Benchmarks FixedFIFO vs FIFO 64 | 65 | The numbers for the following charts were obtained by running the benchmarks in a 2012 MacBook Pro (2.3 GHz Intel Core i7 - 16 GB 1600 MHz DDR3) with golang v1.12 66 | 67 | ### Enqueue 68 | 69 | ![concurrent-safe FixedFIFO vs FIFO . operation: enqueue](web/FixedFIFO-vs-FIFO-enqueue.png "concurrent-safe FixedFIFO vs FIFO . operation: enqueue") 70 | 71 | ### Dequeue 72 | 73 | ![concurrent-safe FixedFIFO vs FIFO . operation: dequeue](web/FixedFIFO-vs-FIFO-dequeue.png "concurrent-safe FixedFIFO vs FIFO . operation: dequeue") 74 | 75 | ## Get started 76 | 77 | ### FIFO queue simple usage 78 | [Live code - playground](https://play.golang.org/p/CRhg7kX0ikH) 79 | 80 | ```go 81 | package main 82 | 83 | import ( 84 | "fmt" 85 | 86 | "github.com/enriquebris/goconcurrentqueue" 87 | ) 88 | 89 | type AnyStruct struct { 90 | Field1 string 91 | Field2 int 92 | } 93 | 94 | func main() { 95 | queue := goconcurrentqueue.NewFIFO() 96 | 97 | queue.Enqueue("any string value") 98 | queue.Enqueue(5) 99 | queue.Enqueue(AnyStruct{Field1: "hello world", Field2: 15}) 100 | 101 | // will output: 3 102 | fmt.Printf("queue's length: %v\n", queue.GetLen()) 103 | 104 | item, err := queue.Dequeue() 105 | if err != nil { 106 | fmt.Println(err) 107 | return 108 | } 109 | 110 | // will output "any string value" 111 | fmt.Printf("dequeued item: %v\n", item) 112 | 113 | // will output: 2 114 | fmt.Printf("queue's length: %v\n", queue.GetLen()) 115 | 116 | } 117 | ``` 118 | 119 | ### Wait until an element gets enqueued 120 | [Live code - playground](https://play.golang.org/p/S7oSg3iUNhs) 121 | 122 | ```go 123 | package main 124 | 125 | import ( 126 | "fmt" 127 | "time" 128 | 129 | "github.com/enriquebris/goconcurrentqueue" 130 | ) 131 | 132 | func main() { 133 | var ( 134 | fifo = goconcurrentqueue.NewFIFO() 135 | done = make(chan struct{}) 136 | ) 137 | 138 | go func() { 139 | fmt.Println("1 - Waiting for next enqueued element") 140 | value, _ := fifo.DequeueOrWaitForNextElement() 141 | fmt.Printf("2 - Dequeued element: %v\n", value) 142 | 143 | done <- struct{}{} 144 | }() 145 | 146 | fmt.Println("3 - Go to sleep for 3 seconds") 147 | time.Sleep(3 * time.Second) 148 | 149 | fmt.Println("4 - Enqueue element") 150 | fifo.Enqueue(100) 151 | 152 | <-done 153 | } 154 | 155 | ``` 156 | 157 | ### Wait until an element gets enqueued with timeout 158 | [Live code - playground](https://play.golang.org/p/E3xdHcW5nJy) 159 | 160 | ```go 161 | package main 162 | 163 | import ( 164 | "context" 165 | "fmt" 166 | "time" 167 | 168 | "github.com/enriquebris/goconcurrentqueue" 169 | ) 170 | 171 | func main() { 172 | var ( 173 | fifo = goconcurrentqueue.NewFIFO() 174 | ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) 175 | ) 176 | defer cancel() 177 | 178 | fmt.Println("1 - Waiting for next enqueued element") 179 | _, err := fifo.DequeueOrWaitForNextElementContext(ctx) 180 | 181 | if err != nil { 182 | fmt.Printf("2 - Failed waiting for new element: %v\n", err) 183 | return 184 | } 185 | } 186 | 187 | ``` 188 | 189 | ### Dependency Inversion Principle using concurrent-safe queues 190 | 191 | *High level modules should not depend on low level modules. Both should depend on abstractions.* Robert C. Martin 192 | 193 | [Live code - playground](https://play.golang.org/p/3GAbyR7wrX7) 194 | 195 | ```go 196 | package main 197 | 198 | import ( 199 | "fmt" 200 | 201 | "github.com/enriquebris/goconcurrentqueue" 202 | ) 203 | 204 | func main() { 205 | var ( 206 | queue goconcurrentqueue.Queue 207 | dummyCondition = true 208 | ) 209 | 210 | // decides which Queue's implementation is the best option for this scenario 211 | if dummyCondition { 212 | queue = goconcurrentqueue.NewFIFO() 213 | } else { 214 | queue = goconcurrentqueue.NewFixedFIFO(10) 215 | } 216 | 217 | fmt.Printf("queue's length: %v\n", queue.GetLen()) 218 | workWithQueue(queue) 219 | fmt.Printf("queue's length: %v\n", queue.GetLen()) 220 | } 221 | 222 | // workWithQueue uses a goconcurrentqueue.Queue to perform the work 223 | func workWithQueue(queue goconcurrentqueue.Queue) error { 224 | // do some work 225 | 226 | // enqueue an item 227 | if err := queue.Enqueue("test value"); err != nil { 228 | return err 229 | } 230 | 231 | return nil 232 | } 233 | ``` 234 | 235 | ## History 236 | 237 | ### v0.7.0 238 | 239 | - Prevents FIFO.DequeueOrWaitForNextElement to keep waiting for a waitChan while internal queues contain items 240 | 241 | ### v0.6.3 242 | 243 | - Prevents FIFO.DequeueOrWaitForNextElement to add useless wait channels 244 | 245 | ### v0.6.2 246 | 247 | - Prevents FIFO.DequeueOrWaitForNextElement to gets blocked when waiting for an enqueued element 248 | 249 | ### v0.6.1 250 | 251 | - FixedFifo.Enqueue prevents to get blocked trying to send the item over an invalid waitForNextElementChan channel 252 | 253 | ### v0.6.0 254 | 255 | - Added DequeueOrWaitForNextElementContext() 256 | 257 | ### v0.5.1 258 | 259 | - FIFO.DequeueOrWaitForNextElement() was modified to avoid deadlock when DequeueOrWaitForNextElement && Enqueue are invoked around the same time. 260 | - Added multiple goroutine unit testings for FIFO.DequeueOrWaitForNextElement() 261 | 262 | ### v0.5.0 263 | 264 | - Added DequeueOrWaitForNextElement() 265 | 266 | ### v0.4.0 267 | 268 | - Added QueueError (custom error) 269 | 270 | ### v0.3.0 271 | 272 | - Added FixedFIFO queue's implementation (at least 2x faster than FIFO for multiple GRs) 273 | - Added benchmarks for both FIFO / FixedFIFO 274 | - Added GetCap() to Queue interface 275 | - Removed Get() and Remove() methods from Queue interface 276 | 277 | ### v0.2.0 278 | 279 | - Added Lock/Unlock/IsLocked methods to control operations locking 280 | 281 | ### v0.1.0 282 | 283 | - First In First Out (FIFO) queue added 284 | -------------------------------------------------------------------------------- /web/FixedFIFO-vs-FIFO-dequeue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enriquebris/goconcurrentqueue/c09fe977b95956c2b174a5e10f77f322ba42cae9/web/FixedFIFO-vs-FIFO-dequeue.png -------------------------------------------------------------------------------- /web/FixedFIFO-vs-FIFO-enqueue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enriquebris/goconcurrentqueue/c09fe977b95956c2b174a5e10f77f322ba42cae9/web/FixedFIFO-vs-FIFO-enqueue.png -------------------------------------------------------------------------------- /web/class-diagram.svg: -------------------------------------------------------------------------------- 1 | goconcurrentqueueFIFOslice []interface{}rwmutex sync.RWMutexlockRWmutex sync.RWMutexisLocked boolwaitForNextElementChanchanchaninterface{}initialize()Enqueue(valueinterface{})Dequeue() (interface{}, error)DequeueOrWaitForNextElement() (interface{}, error)DequeueOrWaitForNextElementContext(ctx context.Context) (interface{}, error)Get(index int) (interface{}, error)Remove(index int)GetLen()GetCap()Lock()Unlock()IsLocked()QueueEnqueue(interface{})Dequeue() (interface{}, error)DequeueOrWaitForNextElement() (interface{}, error)DequeueOrWaitForNextElementContext( context.Context) (interface{}, error)GetLen()GetCap()Lock()Unlock()IsLocked()FixedFIFOqueuechaninterface{}lockChanchanstruct{}waitForNextElementChanchanchaninterface{}initialize(capacity int)Enqueue(valueinterface{})Dequeue() (interface{}, error)DequeueOrWaitForNextElement() (interface{}, error)DequeueOrWaitForNextElementContext(ctx context.Context) (interface{}, error)GetLen()GetCap()Lock()Unlock()IsLocked()QueueErrorcode stringmessage stringError()Code() 88 | --------------------------------------------------------------------------------