Skip to content

Commit 0ef3ba6

Browse files
authored
sync map (#3)
1 parent 392cbc4 commit 0ef3ba6

File tree

6 files changed

+255
-14
lines changed

6 files changed

+255
-14
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@
1717
*.env
1818

1919
cmd/example
20-
cmd/dipdup-gen/*.json
20+
cmd/dipdup-gen/*.json
21+
22+
# IDE and other 3th party tools
23+
.idea

pkg/modules/input.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
package modules
22

3-
import (
4-
"errors"
5-
)
6-
7-
// errors
8-
var (
9-
ErrUnknownInput = errors.New("unknown input")
10-
ErrUnknownOutput = errors.New("unknown output")
11-
)
12-
133
// Input -
144
type Input struct {
155
data chan any

pkg/modules/module.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ package modules
22

33
import (
44
"context"
5+
"github.com/pkg/errors"
56
"io"
67
)
78

8-
// Module is the interface which modules has to implement.
9+
// errors
10+
var (
11+
ErrUnknownInput = errors.New("unknown input")
12+
ErrUnknownOutput = errors.New("unknown output")
13+
)
14+
15+
// Module is the interface which modules have to implement.
916
type Module interface {
1017
io.Closer
1118

pkg/modules/output.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ func (output *Output) Name() string {
5353

5454
// Connect -
5555
func Connect(outputModule, inputModule Module, outputName, inputName string) error {
56-
everySecond, err := inputModule.Input(inputName)
56+
input, err := inputModule.Input(inputName)
5757
if err != nil {
5858
log.Panic(err)
5959
}
60-
return outputModule.AttachTo(outputName, everySecond)
60+
return outputModule.AttachTo(outputName, input)
6161
}

pkg/sync/map.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package sync
2+
3+
import "sync"
4+
5+
type Map[K comparable, V any] struct {
6+
m map[K]V
7+
mx *sync.RWMutex
8+
}
9+
10+
func NewMap[K comparable, V any]() Map[K, V] {
11+
return Map[K, V]{
12+
m: make(map[K]V),
13+
mx: new(sync.RWMutex),
14+
}
15+
}
16+
17+
func (m Map[K, V]) Get(key K) (V, bool) {
18+
m.mx.RLock()
19+
val, ok := m.m[key]
20+
m.mx.RUnlock()
21+
return val, ok
22+
}
23+
24+
func (m Map[K, V]) Delete(key K) {
25+
m.mx.Lock()
26+
delete(m.m, key)
27+
m.mx.Unlock()
28+
}
29+
30+
func (m Map[K, V]) Set(key K, value V) {
31+
m.mx.Lock()
32+
m.m[key] = value
33+
m.mx.Unlock()
34+
}
35+
36+
// Range (WARN) does not support nested ranges with Delete in them.
37+
func (m Map[K, V]) Range(handler func(key K, value V) (error, bool)) error {
38+
if handler == nil {
39+
return nil
40+
}
41+
m.mx.RLock()
42+
defer m.mx.RUnlock()
43+
44+
for k, v := range m.m {
45+
err, br := handler(k, v)
46+
if err != nil {
47+
return err
48+
}
49+
if br {
50+
return nil
51+
}
52+
}
53+
return nil
54+
}
55+
56+
func (m Map[K, V]) Clear() {
57+
m.mx.Lock()
58+
// clear(m.m) TODO: rewrite on go 1.21
59+
for k := range m.m {
60+
delete(m.m, k)
61+
}
62+
m.mx.Unlock()
63+
}
64+
65+
func (m Map[K, V]) Len() int {
66+
m.mx.RLock()
67+
defer m.mx.RUnlock()
68+
return len(m.m)
69+
}

pkg/sync/map_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package sync
2+
3+
import (
4+
"math/rand"
5+
"runtime"
6+
"sync"
7+
"testing"
8+
)
9+
10+
func TestMap_Get(t *testing.T) {
11+
m := NewMap[int, string]()
12+
m.Set(10, "hello sdk sync map based on RWMutex")
13+
14+
value, ok := m.Get(10)
15+
if !ok {
16+
t.Fatal("existing key was not found")
17+
}
18+
19+
if value != "hello sdk sync map based on RWMutex" {
20+
t.Fatal("found value is incorrect")
21+
}
22+
}
23+
24+
func TestMap_Delete(t *testing.T) {
25+
m := NewMap[int, string]()
26+
m.Set(10, "hello sdk sync map based on RWMutex")
27+
m.Set(11, "this value and key will be deleted")
28+
29+
m.Delete(11)
30+
31+
_, ok := m.Get(11)
32+
if ok {
33+
t.Fatal("non-existing key was found")
34+
}
35+
}
36+
37+
func TestMap_Range(t *testing.T) {
38+
m := NewMap[int, string]()
39+
m.Set(10, "hello sdk sync map based on RWMutex")
40+
m.Set(11, "second value")
41+
42+
checkData := map[int]*struct {
43+
checked bool
44+
value string
45+
}{
46+
10: {value: "hello sdk sync map based on RWMutex"},
47+
11: {value: "second value"},
48+
}
49+
50+
handler := func(k int, v string) (error, bool) {
51+
toCheck, ok := checkData[k]
52+
if !ok {
53+
t.Fatal("found non-existing key")
54+
return nil, true
55+
}
56+
57+
if v != toCheck.value {
58+
t.Fatalf("found value is incorrect for key=%d with value=%s, looking for value=%s", k, v, toCheck.value)
59+
return nil, true
60+
}
61+
62+
toCheck.checked = true
63+
return nil, false
64+
}
65+
66+
if err := m.Range(handler); err != nil {
67+
t.Fatalf("error occured in Range %+v", err)
68+
}
69+
70+
for k, v := range checkData {
71+
if !v.checked {
72+
t.Fatalf("key %d was not applied in Range", k)
73+
}
74+
}
75+
}
76+
77+
func TestMap_ConcurrentRange(t *testing.T) {
78+
const mapSize = 1 << 10
79+
80+
m := NewMap[int64, int64]()
81+
for n := int64(1); n <= mapSize; n++ {
82+
m.Set(n, n)
83+
}
84+
85+
done := make(chan struct{})
86+
var wg sync.WaitGroup
87+
defer func() {
88+
close(done)
89+
wg.Wait()
90+
}()
91+
92+
for g := int64(runtime.GOMAXPROCS(0)); g > 0; g-- {
93+
r := rand.New(rand.NewSource(g))
94+
wg.Add(1)
95+
go func(g int64) {
96+
defer wg.Done()
97+
for i := int64(0); ; i++ {
98+
select {
99+
case <-done:
100+
return
101+
default:
102+
}
103+
for n := int64(1); n < mapSize; n++ {
104+
if r.Int63n(mapSize) == 0 {
105+
m.Set(n, n*i*g)
106+
} else {
107+
m.Get(n)
108+
}
109+
}
110+
}
111+
}(g)
112+
}
113+
114+
for n := 16; n > 0; n-- {
115+
seen := make(map[int64]bool, mapSize)
116+
117+
err := m.Range(func(k, v int64) (error, bool) {
118+
if v%k != 0 {
119+
t.Fatalf("while Setting multiples of %v, Range saw value %v", k, v)
120+
}
121+
if seen[k] {
122+
t.Fatalf("Range visited key %v twice", k)
123+
}
124+
seen[k] = true
125+
return nil, false
126+
})
127+
128+
if len(seen) != mapSize {
129+
t.Fatalf("Range visited %v elements of %v-element Map", len(seen), mapSize)
130+
}
131+
132+
if err != nil {
133+
t.Fatalf("error occured in Range %+v", err)
134+
}
135+
}
136+
}
137+
138+
func TestMap_Clear(t *testing.T) {
139+
m := NewMap[int, string]()
140+
for i, v := range [3]string{"clear", "sync", "map"} {
141+
m.Set(i, v)
142+
}
143+
144+
m.Clear()
145+
146+
length := 0
147+
err := m.Range(func(key int, value string) (error, bool) {
148+
length++
149+
return nil, false
150+
})
151+
152+
if err != nil {
153+
t.Fatalf("error occured in checking length of Range %+v", err)
154+
}
155+
156+
if length != 0 {
157+
t.Fatalf("unexpected map size, got %v want %v", length, 0)
158+
}
159+
}
160+
161+
func TestMap_Len(t *testing.T) {
162+
m := NewMap[int, string]()
163+
for i, v := range [3]string{"len", "sync", "map"} {
164+
m.Set(i, v)
165+
}
166+
167+
length := m.Len()
168+
169+
if length != 3 {
170+
t.Fatalf("unexpected map size, got %v want %v", length, 3)
171+
}
172+
}

0 commit comments

Comments
 (0)