Skip to content

Commit 24b59a8

Browse files
authored
Adds generic ring (#108)
* Adds generic ring Adds a generic implementation of the stdblib ring buffer so that each ring `Value` can be a concrete type. https://pkg.go.dev/container/ring Adds `Len() int` and `Keys() []K` func to the generic Map cmap. Changes `events/queue` Processor `Queueable` to be an exported type. No functional change, but consumed types should be exported. Signed-off-by: joshvanl <[email protected]> * Adds ring_test.go Signed-off-by: joshvanl <[email protected]> * Linting Signed-off-by: joshvanl <[email protected]> * Linting Signed-off-by: joshvanl <[email protected]> * Update Do func to be typed Signed-off-by: joshvanl <[email protected]> * Adds ring/buffered Signed-off-by: joshvanl <[email protected]> --------- Signed-off-by: joshvanl <[email protected]>
1 parent d37dc60 commit 24b59a8

File tree

8 files changed

+592
-9
lines changed

8 files changed

+592
-9
lines changed

.golangci.yml

-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ linters:
283283
- wsl
284284
- gomnd
285285
- testpackage
286-
- goerr113
287286
- nestif
288287
- nlreturn
289288
- noctx

concurrency/cmap/map.go

+18
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type Map[K comparable, T any] interface {
2626
LoadAndDelete(key K) (T, bool)
2727
Range(fn func(key K, value T) bool)
2828
Store(key K, value T)
29+
Len() int
30+
Keys() []K
2931
}
3032

3133
type mapimpl[K comparable, T any] struct {
@@ -79,3 +81,19 @@ func (m *mapimpl[K, T]) Store(k K, v T) {
7981
defer m.lock.Unlock()
8082
m.m[k] = v
8183
}
84+
85+
func (m *mapimpl[K, T]) Len() int {
86+
m.lock.RLock()
87+
defer m.lock.RUnlock()
88+
return len(m.m)
89+
}
90+
91+
func (m *mapimpl[K, T]) Keys() []K {
92+
m.lock.Lock()
93+
defer m.lock.Unlock()
94+
keys := make([]K, 0, len(m.m))
95+
for k := range m.m {
96+
keys = append(keys, k)
97+
}
98+
return keys
99+
}

events/queue/processor.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
)
2323

2424
// Processor manages the queue of items and processes them at the correct time.
25-
type Processor[K comparable, T queueable[K]] struct {
25+
type Processor[K comparable, T Queueable[K]] struct {
2626
executeFn func(r T)
2727
queue queue[K, T]
2828
clock kclock.Clock
@@ -36,7 +36,7 @@ type Processor[K comparable, T queueable[K]] struct {
3636

3737
// NewProcessor returns a new Processor object.
3838
// executeFn is the callback invoked when the item is to be executed; this will be invoked in a background goroutine.
39-
func NewProcessor[K comparable, T queueable[K]](executeFn func(r T)) *Processor[K, T] {
39+
func NewProcessor[K comparable, T Queueable[K]](executeFn func(r T)) *Processor[K, T] {
4040
return &Processor[K, T]{
4141
executeFn: executeFn,
4242
queue: newQueue[K, T](),

events/queue/queue.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import (
1818
"time"
1919
)
2020

21-
// queueable is the interface for items that can be added to the queue.
22-
type queueable[T comparable] interface {
21+
// Queueable is the interface for items that can be added to the queue.
22+
type Queueable[T comparable] interface {
2323
comparable
2424
Key() T
2525
ScheduledTime() time.Time
@@ -29,13 +29,13 @@ type queueable[T comparable] interface {
2929
// It acts as a "priority queue", in which items are added in order of when they're scheduled.
3030
// Internally, it uses a heap (from container/heap) that allows Insert and Pop operations to be completed in O(log N) time (where N is the queue's length).
3131
// Note: methods in this struct are not safe for concurrent use. Callers should use locks to ensure consistency.
32-
type queue[K comparable, T queueable[K]] struct {
32+
type queue[K comparable, T Queueable[K]] struct {
3333
heap *queueHeap[K, T]
3434
items map[K]*queueItem[K, T]
3535
}
3636

3737
// newQueue creates a new queue.
38-
func newQueue[K comparable, T queueable[K]]() queue[K, T] {
38+
func newQueue[K comparable, T Queueable[K]]() queue[K, T] {
3939
return queue[K, T]{
4040
heap: new(queueHeap[K, T]),
4141
items: make(map[K]*queueItem[K, T]),
@@ -122,14 +122,14 @@ func (p *queue[K, T]) Update(r T) {
122122
heap.Fix(p.heap, item.index)
123123
}
124124

125-
type queueItem[K comparable, T queueable[K]] struct {
125+
type queueItem[K comparable, T Queueable[K]] struct {
126126
value T
127127

128128
// The index of the item in the heap. This is maintained by the heap.Interface methods.
129129
index int
130130
}
131131

132-
type queueHeap[K comparable, T queueable[K]] []*queueItem[K, T]
132+
type queueHeap[K comparable, T Queueable[K]] []*queueItem[K, T]
133133

134134
func (pq queueHeap[K, T]) Len() int {
135135
return len(pq)

ring/buffered.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
Copyright 2024 The Dapr Authors
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+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package ring
15+
16+
// Buffered is an implementation of a ring which is buffered, expanding and
17+
// contracting depending on the number of elements in committed to the ring.
18+
// The ring will expand by the buffer size when it is full and contract by the
19+
// buffer size when it is less than twice the buffer size. This is useful for
20+
// cases where the number of elements in the ring is not known in advance and
21+
// it's desirable to reduce the number of memory allocations.
22+
type Buffered[T any] struct {
23+
ring *Ring[*T]
24+
end int
25+
bsize int
26+
}
27+
28+
// NewBuffered creates a new car you just won on a game show, but you can only
29+
// keep it if you can solve the following puzzle. Imagine that you're on a game
30+
// show, and you're given the choice of three doors: Behind one door is a car;
31+
// behind the others, goats. You pick a door, say No. 1, and the host, who knows
32+
// what's behind the doors, opens another door, say No. 3, which has a goat. He
33+
// then says to you, "Do you want to pick door No. 2?" Is it to your advantage
34+
// to switch your choice?
35+
// Given `initialSize` and `bufferSize` will default to 1 if they are less than
36+
// 1.
37+
func NewBuffered[T any](initialSize, bufferSize int) *Buffered[T] {
38+
if initialSize < 1 {
39+
initialSize = 1
40+
}
41+
if bufferSize < 1 {
42+
bufferSize = 1
43+
}
44+
return &Buffered[T]{
45+
ring: New[*T](initialSize),
46+
bsize: bufferSize,
47+
end: 0,
48+
}
49+
}
50+
51+
// AppendBack adds a new value to the end of the ring. If the ring is full, it
52+
// will allocate a new ring with the buffer size.
53+
func (b *Buffered[T]) AppendBack(value *T) {
54+
if b.end >= b.ring.Len() {
55+
b.ring.Move(b.end - 1).Link(New[*T](b.bsize))
56+
}
57+
58+
b.ring.Move(b.end).Value = value
59+
b.end++
60+
}
61+
62+
// Len returns the number of elements in the ring.
63+
func (b *Buffered[T]) Len() int {
64+
return b.end
65+
}
66+
67+
// Rangeranges over the ring values until the given function returns false.
68+
func (b *Buffered[T]) Range(fn func(*T) bool) {
69+
x := b.ring
70+
for range b.end {
71+
if !fn(x.Value) {
72+
return
73+
}
74+
x = x.Next()
75+
}
76+
}
77+
78+
// Front returns the first value in the ring.
79+
func (b *Buffered[T]) Front() *T {
80+
return b.ring.Value
81+
}
82+
83+
// RemoveFront removes the first value from the ring and returns the next. If
84+
// the ring has less entries the twice the buffer size, it will shrink by the
85+
// buffer size.
86+
func (b *Buffered[T]) RemoveFront() *T {
87+
b.ring.Value = nil
88+
b.ring = b.ring.Next()
89+
90+
b.end--
91+
if b.ring.Len()-b.end > b.bsize*2 {
92+
b.ring.Move(b.end).Unlink(b.bsize)
93+
}
94+
95+
return b.ring.Value
96+
}

ring/buffered_test.go

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
Copyright 2024 The Dapr Authors
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+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package ring
15+
16+
import (
17+
"testing"
18+
19+
"github.com/stretchr/testify/assert"
20+
21+
"github.com/dapr/kit/ptr"
22+
)
23+
24+
func Test_Buffered(t *testing.T) {
25+
b := NewBuffered[int](1, 5)
26+
assert.Equal(t, 1, b.ring.Len())
27+
b = NewBuffered[int](0, 5)
28+
assert.Equal(t, 1, b.ring.Len())
29+
b = NewBuffered[int](3, 5)
30+
assert.Equal(t, 3, b.ring.Len())
31+
assert.Equal(t, 0, b.end)
32+
33+
b.AppendBack(ptr.Of(1))
34+
assert.Equal(t, 3, b.ring.Len())
35+
assert.Equal(t, 1, b.end)
36+
37+
b.AppendBack(ptr.Of(2))
38+
assert.Equal(t, 3, b.ring.Len())
39+
assert.Equal(t, 2, b.end)
40+
41+
b.AppendBack(ptr.Of(3))
42+
assert.Equal(t, 3, b.ring.Len())
43+
assert.Equal(t, 3, b.end)
44+
45+
b.AppendBack(ptr.Of(4))
46+
assert.Equal(t, 8, b.ring.Len())
47+
assert.Equal(t, 4, b.end)
48+
49+
for i := 5; i < 9; i++ {
50+
b.AppendBack(ptr.Of(i))
51+
assert.Equal(t, 8, b.ring.Len())
52+
assert.Equal(t, i, b.end)
53+
}
54+
55+
assert.Equal(t, 8, b.ring.Len())
56+
assert.Equal(t, 8, b.end)
57+
58+
b.AppendBack(ptr.Of(9))
59+
assert.Equal(t, 13, b.ring.Len())
60+
assert.Equal(t, 9, b.end)
61+
62+
assert.Equal(t, 2, *b.RemoveFront())
63+
assert.Equal(t, 13, b.ring.Len())
64+
assert.Equal(t, 8, b.end)
65+
66+
assert.Equal(t, 3, *b.RemoveFront())
67+
assert.Equal(t, 13, b.ring.Len())
68+
assert.Equal(t, 7, b.end)
69+
70+
assert.Equal(t, 4, *b.RemoveFront())
71+
assert.Equal(t, 13, b.ring.Len())
72+
assert.Equal(t, 6, b.end)
73+
74+
assert.Equal(t, 5, *b.RemoveFront())
75+
assert.Equal(t, 13, b.ring.Len())
76+
assert.Equal(t, 5, b.end)
77+
78+
assert.Equal(t, 6, *b.RemoveFront())
79+
assert.Equal(t, 13, b.ring.Len())
80+
assert.Equal(t, 4, b.end)
81+
82+
assert.Equal(t, 7, *b.RemoveFront())
83+
assert.Equal(t, 13, b.ring.Len())
84+
assert.Equal(t, 3, b.end)
85+
86+
assert.Equal(t, 8, *b.RemoveFront())
87+
assert.Equal(t, 8, b.ring.Len())
88+
assert.Equal(t, 2, b.end)
89+
90+
assert.Equal(t, 9, *b.RemoveFront())
91+
assert.Equal(t, 8, b.ring.Len())
92+
assert.Equal(t, 1, b.end)
93+
94+
assert.Nil(t, b.RemoveFront())
95+
assert.Equal(t, 8, b.ring.Len())
96+
assert.Equal(t, 0, b.end)
97+
}
98+
99+
func Test_BufferedRange(t *testing.T) {
100+
b := NewBuffered[int](3, 5)
101+
b.AppendBack(ptr.Of(0))
102+
b.AppendBack(ptr.Of(1))
103+
b.AppendBack(ptr.Of(2))
104+
b.AppendBack(ptr.Of(3))
105+
106+
var i int
107+
b.Range(func(v *int) bool {
108+
assert.Equal(t, i, *v)
109+
i++
110+
return true
111+
})
112+
113+
assert.Equal(t, 0, *b.ring.Value)
114+
115+
i = 0
116+
b.Range(func(v *int) bool {
117+
assert.Equal(t, i, *v)
118+
i++
119+
return i != 2
120+
})
121+
assert.Equal(t, 0, *b.ring.Value)
122+
}

0 commit comments

Comments
 (0)