tangled
alpha
login
or
join now
joh.dev
/
leftright
1
fork
atom
A map using a lock-free concurrency using left-right algo, written in Go
1
fork
atom
overview
issues
pulls
pipelines
Test wether weak pointers / cleanup works
joh.dev
9 months ago
d067e4fa
6a52563c
+67
-1
2 changed files
expand all
collapse all
unified
split
lrmap.go
lrmap_test.go
+29
-1
lrmap.go
···
20
20
redoLog []operation[K, V]
21
21
readHandlers map[weak.Pointer[ReadHandler[K, V]]]struct{}
22
22
readHandlerPool sync.Pool
23
23
+
metrics *metrics
24
24
+
}
25
25
+
26
26
+
metrics struct {
27
27
+
readHandlersCreated uint64
28
28
+
readHandlersClosed uint64
29
29
+
readHandlersRecycled uint64
30
30
+
readHandlersCleanedUp uint64
23
31
}
24
32
25
33
arena[K comparable, V any] map[K]V
···
38
46
39
47
return m
40
48
}
49
49
+
50
50
+
// recordMetrics enables the collection of metrics for read handlers. This is not thread-safe, you must
51
51
+
// call this before sharing the LRMap with other goroutines.
52
52
+
func (m *LRMap[K, V]) recordMetrics() { m.metrics = new(metrics) }
41
53
42
54
func (m *LRMap[K, V]) Set(key K, value V) {
43
55
m.mu.Lock()
···
120
132
rh := &ReadHandler[K, V]{lrmap: m}
121
133
122
134
wp := weak.Make(rh)
135
135
+
rh.wp = wp
123
136
124
137
m.mu.Lock()
125
138
m.readHandlers[wp] = struct{}{}
···
129
142
lrmap.mu.Lock()
130
143
defer lrmap.mu.Unlock()
131
144
145
145
+
if lrmap.metrics != nil {
146
146
+
atomic.AddUint64(&lrmap.metrics.readHandlersCleanedUp, 1)
147
147
+
}
148
148
+
132
149
delete(lrmap.readHandlers, wp)
133
150
}, m)
134
151
135
135
-
rh.wp = wp
152
152
+
if m.metrics != nil {
153
153
+
atomic.AddUint64(&m.metrics.readHandlersCreated, 1)
154
154
+
}
155
155
+
136
156
return rh
137
157
}
138
158
···
278
298
func (rh *ReadHandler[K, V]) Close() {
279
299
rh.assertReady()
280
300
301
301
+
if rh.lrmap.metrics != nil {
302
302
+
atomic.AddUint64(&rh.lrmap.metrics.readHandlersClosed, 1)
303
303
+
}
304
304
+
281
305
rh.ready = false
282
306
283
307
rh.lrmap.mu.Lock()
···
293
317
294
318
if rh.entered() {
295
319
panic("reader illegal state: must call Leave() before recycling")
320
320
+
}
321
321
+
322
322
+
if rh.lrmap.metrics != nil {
323
323
+
atomic.AddUint64(&rh.lrmap.metrics.readHandlersRecycled, 1)
296
324
}
297
325
298
326
rh.ready = false
+38
lrmap_test.go
···
2
2
3
3
import (
4
4
"math"
5
5
+
"runtime"
6
6
+
"sync/atomic"
5
7
"testing"
6
8
)
7
9
···
128
130
t.Errorf("overflow(%d) is odd", overflow)
129
131
}
130
132
}
133
133
+
134
134
+
func TestCleanup(t *testing.T) {
135
135
+
const n = 1000
136
136
+
137
137
+
lrmap := New[int, int]()
138
138
+
lrmap.recordMetrics()
139
139
+
140
140
+
readHandlers := make([]*ReadHandler[int, int], n)
141
141
+
for i := 0; i < n; i++ {
142
142
+
readHandlers[i] = lrmap.NewReadHandler()
143
143
+
}
144
144
+
145
145
+
runtime.GC()
146
146
+
147
147
+
if created, cleanedUp := atomic.LoadUint64(&lrmap.metrics.readHandlersCreated), atomic.LoadUint64(&lrmap.metrics.readHandlersCleanedUp); created != n || cleanedUp != 0 {
148
148
+
t.Errorf("created %d read handlers, cleaned up %d read handlers", created, cleanedUp)
149
149
+
}
150
150
+
151
151
+
if len(lrmap.readHandlers) != n {
152
152
+
t.Errorf("expected %d read handlers, got %d", n, len(lrmap.readHandlers))
153
153
+
}
154
154
+
155
155
+
for i := range readHandlers {
156
156
+
readHandlers[i] = nil
157
157
+
}
158
158
+
159
159
+
runtime.GC()
160
160
+
161
161
+
if created, cleanedUp := atomic.LoadUint64(&lrmap.metrics.readHandlersCreated), atomic.LoadUint64(&lrmap.metrics.readHandlersCleanedUp); created != n || cleanedUp != n {
162
162
+
t.Errorf("created %d read handlers, cleaned up %d read handlers", created, cleanedUp)
163
163
+
}
164
164
+
165
165
+
if len(lrmap.readHandlers) != 0 {
166
166
+
t.Errorf("expected no read handlers, got %d", len(lrmap.readHandlers))
167
167
+
}
168
168
+
}