diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7800102..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -go.mod \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 145bede..34e131c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ language: go # You don't need to test on very old version of the Go compiler. It's the user's # responsibility to keep their compilers up to date. go: - - 1.12.x + - 1.18 # Only clone the most recent commit. git: @@ -29,4 +29,4 @@ before_script: # .golangci.yml file at the top level of your repo. script: - golangci-lint run # run a bunch of code checkers/linters in parallel - - go test -v -race ./... # Run all the tests with the race detector enabled \ No newline at end of file + - go test -v -race ./... # Run all the tests with the race detector enabled diff --git a/concurrent_map.go b/concurrent_map.go index 7e7f1e4..b3f8f2c 100644 --- a/concurrent_map.go +++ b/concurrent_map.go @@ -9,29 +9,29 @@ var SHARD_COUNT = 32 // A "thread" safe map of type string:Anything. // To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards. -type ConcurrentMap []*ConcurrentMapShared +type ConcurrentMap[V any] []*ConcurrentMapShared[V] // A "thread" safe string to anything map. -type ConcurrentMapShared struct { - items map[string]interface{} +type ConcurrentMapShared[V any] struct { + items map[string]V sync.RWMutex // Read Write mutex, guards access to internal map. } // Creates a new concurrent map. -func New() ConcurrentMap { - m := make(ConcurrentMap, SHARD_COUNT) +func New[V any]() ConcurrentMap[V] { + m := make(ConcurrentMap[V], SHARD_COUNT) for i := 0; i < SHARD_COUNT; i++ { - m[i] = &ConcurrentMapShared{items: make(map[string]interface{})} + m[i] = &ConcurrentMapShared[V]{items: make(map[string]V)} } return m } // GetShard returns shard under given key -func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared { +func (m ConcurrentMap[V]) GetShard(key string) *ConcurrentMapShared[V] { return m[uint(fnv32(key))%uint(SHARD_COUNT)] } -func (m ConcurrentMap) MSet(data map[string]interface{}) { +func (m ConcurrentMap[V]) MSet(data map[string]V) { for key, value := range data { shard := m.GetShard(key) shard.Lock() @@ -41,7 +41,7 @@ func (m ConcurrentMap) MSet(data map[string]interface{}) { } // Sets the given value under the specified key. -func (m ConcurrentMap) Set(key string, value interface{}) { +func (m ConcurrentMap[V]) Set(key string, value V) { // Get map shard. shard := m.GetShard(key) shard.Lock() @@ -53,10 +53,10 @@ func (m ConcurrentMap) Set(key string, value interface{}) { // It is called while lock is held, therefore it MUST NOT // try to access other keys in same map, as it can lead to deadlock since // Go sync.RWLock is not reentrant -type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{} +type UpsertCb[V any] func(exist bool, valueInMap V, newValue V) V // Insert or Update - updates existing element or inserts a new one using UpsertCb -func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) { +func (m ConcurrentMap[V]) Upsert(key string, value V, cb UpsertCb[V]) (res V) { shard := m.GetShard(key) shard.Lock() v, ok := shard.items[key] @@ -67,7 +67,7 @@ func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res i } // Sets the given value under the specified key if no value was associated with it. -func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool { +func (m ConcurrentMap[V]) SetIfAbsent(key string, value V) bool { // Get map shard. shard := m.GetShard(key) shard.Lock() @@ -80,7 +80,7 @@ func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool { } // Get retrieves an element from map under given key. -func (m ConcurrentMap) Get(key string) (interface{}, bool) { +func (m ConcurrentMap[V]) Get(key string) (V, bool) { // Get shard shard := m.GetShard(key) shard.RLock() @@ -91,7 +91,7 @@ func (m ConcurrentMap) Get(key string) (interface{}, bool) { } // Count returns the number of elements within the map. -func (m ConcurrentMap) Count() int { +func (m ConcurrentMap[V]) Count() int { count := 0 for i := 0; i < SHARD_COUNT; i++ { shard := m[i] @@ -103,7 +103,7 @@ func (m ConcurrentMap) Count() int { } // Looks up an item under specified key -func (m ConcurrentMap) Has(key string) bool { +func (m ConcurrentMap[V]) Has(key string) bool { // Get shard shard := m.GetShard(key) shard.RLock() @@ -114,7 +114,7 @@ func (m ConcurrentMap) Has(key string) bool { } // Remove removes an element from the map. -func (m ConcurrentMap) Remove(key string) { +func (m ConcurrentMap[V]) Remove(key string) { // Try to get shard. shard := m.GetShard(key) shard.Lock() @@ -124,12 +124,12 @@ func (m ConcurrentMap) Remove(key string) { // RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held // If returns true, the element will be removed from the map -type RemoveCb func(key string, v interface{}, exists bool) bool +type RemoveCb[V any] func(key string, v V, exists bool) bool // RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params // If callback returns true and element exists, it will remove it from the map // Returns the value returned by the callback (even if element was not present in the map) -func (m ConcurrentMap) RemoveCb(key string, cb RemoveCb) bool { +func (m ConcurrentMap[V]) RemoveCb(key string, cb RemoveCb[V]) bool { // Try to get shard. shard := m.GetShard(key) shard.Lock() @@ -143,7 +143,7 @@ func (m ConcurrentMap) RemoveCb(key string, cb RemoveCb) bool { } // Pop removes an element from the map and returns it -func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) { +func (m ConcurrentMap[V]) Pop(key string) (v V, exists bool) { // Try to get shard. shard := m.GetShard(key) shard.Lock() @@ -154,40 +154,40 @@ func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) { } // IsEmpty checks if map is empty. -func (m ConcurrentMap) IsEmpty() bool { +func (m ConcurrentMap[V]) IsEmpty() bool { return m.Count() == 0 } // Used by the Iter & IterBuffered functions to wrap two variables together over a channel, -type Tuple struct { +type Tuple[V any] struct { Key string - Val interface{} + Val V } // Iter returns an iterator which could be used in a for range loop. // // Deprecated: using IterBuffered() will get a better performence -func (m ConcurrentMap) Iter() <-chan Tuple { +func (m ConcurrentMap[V]) Iter() <-chan Tuple[V] { chans := snapshot(m) - ch := make(chan Tuple) + ch := make(chan Tuple[V]) go fanIn(chans, ch) return ch } // IterBuffered returns a buffered iterator which could be used in a for range loop. -func (m ConcurrentMap) IterBuffered() <-chan Tuple { +func (m ConcurrentMap[V]) IterBuffered() <-chan Tuple[V] { chans := snapshot(m) total := 0 for _, c := range chans { total += cap(c) } - ch := make(chan Tuple, total) + ch := make(chan Tuple[V], total) go fanIn(chans, ch) return ch } // Clear removes all items from map. -func (m ConcurrentMap) Clear() { +func (m ConcurrentMap[V]) Clear() { for item := range m.IterBuffered() { m.Remove(item.Key) } @@ -197,23 +197,23 @@ func (m ConcurrentMap) Clear() { // which likely takes a snapshot of `m`. // It returns once the size of each buffered channel is determined, // before all the channels are populated using goroutines. -func snapshot(m ConcurrentMap) (chans []chan Tuple) { +func snapshot[V any](m ConcurrentMap[V]) (chans []chan Tuple[V]) { //When you access map items before initializing. - if len(m) == 0{ + if len(m) == 0 { panic(`cmap.ConcurrentMap is not initialized. Should run New() before usage.`) } - chans = make([]chan Tuple, SHARD_COUNT) + chans = make([]chan Tuple[V], SHARD_COUNT) wg := sync.WaitGroup{} wg.Add(SHARD_COUNT) // Foreach shard. for index, shard := range m { - go func(index int, shard *ConcurrentMapShared) { + go func(index int, shard *ConcurrentMapShared[V]) { // Foreach key, value pair. shard.RLock() - chans[index] = make(chan Tuple, len(shard.items)) + chans[index] = make(chan Tuple[V], len(shard.items)) wg.Done() for key, val := range shard.items { - chans[index] <- Tuple{key, val} + chans[index] <- Tuple[V]{key, val} } shard.RUnlock() close(chans[index]) @@ -224,11 +224,11 @@ func snapshot(m ConcurrentMap) (chans []chan Tuple) { } // fanIn reads elements from channels `chans` into channel `out` -func fanIn(chans []chan Tuple, out chan Tuple) { +func fanIn[V any](chans []chan Tuple[V], out chan Tuple[V]) { wg := sync.WaitGroup{} wg.Add(len(chans)) for _, ch := range chans { - go func(ch chan Tuple) { + go func(ch chan Tuple[V]) { for t := range ch { out <- t } @@ -239,9 +239,9 @@ func fanIn(chans []chan Tuple, out chan Tuple) { close(out) } -// Items returns all items as map[string]interface{} -func (m ConcurrentMap) Items() map[string]interface{} { - tmp := make(map[string]interface{}) +// Items returns all items as map[string]V +func (m ConcurrentMap[V]) Items() map[string]V { + tmp := make(map[string]V) // Insert items to temporary map. for item := range m.IterBuffered() { @@ -251,15 +251,15 @@ func (m ConcurrentMap) Items() map[string]interface{} { return tmp } -// Iterator callback,called for every key,value found in +// Iterator callbacalled for every key,value found in // maps. RLock is held for all calls for a given shard // therefore callback sess consistent view of a shard, // but not across the shards -type IterCb func(key string, v interface{}) +type IterCb[V any] func(key string, v V) // Callback based iterator, cheapest way to read // all elements in a map. -func (m ConcurrentMap) IterCb(fn IterCb) { +func (m ConcurrentMap[V]) IterCb(fn IterCb[V]) { for idx := range m { shard := (m)[idx] shard.RLock() @@ -271,7 +271,7 @@ func (m ConcurrentMap) IterCb(fn IterCb) { } // Keys returns all keys as []string -func (m ConcurrentMap) Keys() []string { +func (m ConcurrentMap[V]) Keys() []string { count := m.Count() ch := make(chan string, count) go func() { @@ -279,7 +279,7 @@ func (m ConcurrentMap) Keys() []string { wg := sync.WaitGroup{} wg.Add(SHARD_COUNT) for _, shard := range m { - go func(shard *ConcurrentMapShared) { + go func(shard *ConcurrentMapShared[V]) { // Foreach key, value pair. shard.RLock() for key := range shard.items { @@ -302,9 +302,9 @@ func (m ConcurrentMap) Keys() []string { } //Reviles ConcurrentMap "private" variables to json marshal. -func (m ConcurrentMap) MarshalJSON() ([]byte, error) { +func (m ConcurrentMap[V]) MarshalJSON() ([]byte, error) { // Create a temporary map, which will hold all item spread across shards. - tmp := make(map[string]interface{}) + tmp := make(map[string]V) // Insert items to temporary map. for item := range m.IterBuffered() { @@ -326,13 +326,13 @@ func fnv32(key string) uint32 { // Concurrent map uses Interface{} as its value, therefor JSON Unmarshal // will probably won't know which to type to unmarshal into, in such case -// we'll end up with a value of type map[string]interface{}, In most cases this isn't +// we'll end up with a value of type map[string]V, In most cases this isn't // out value type, this is why we've decided to remove this functionality. // func (m *ConcurrentMap) UnmarshalJSON(b []byte) (err error) { // // Reverse process of Marshal. -// tmp := make(map[string]interface{}) +// tmp := make(map[string]V) // // Unmarshal into a single map. // if err := json.Unmarshal(b, &tmp); err != nil { diff --git a/concurrent_map_bench_test.go b/concurrent_map_bench_test.go index b6508f2..80f8978 100644 --- a/concurrent_map_bench_test.go +++ b/concurrent_map_bench_test.go @@ -7,7 +7,7 @@ import ( ) func BenchmarkItems(b *testing.B) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 10000; i++ { @@ -19,7 +19,7 @@ func BenchmarkItems(b *testing.B) { } func BenchmarkMarshalJson(b *testing.B) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 10000; i++ { @@ -40,7 +40,7 @@ func BenchmarkStrconv(b *testing.B) { } func BenchmarkSingleInsertAbsent(b *testing.B) { - m := New() + m := New[string]() b.ResetTimer() for i := 0; i < b.N; i++ { m.Set(strconv.Itoa(i), "value") @@ -56,7 +56,7 @@ func BenchmarkSingleInsertAbsentSyncMap(b *testing.B) { } func BenchmarkSingleInsertPresent(b *testing.B) { - m := New() + m := New[string]() m.Set("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { @@ -74,7 +74,7 @@ func BenchmarkSingleInsertPresentSyncMap(b *testing.B) { } func benchmarkMultiInsertDifferent(b *testing.B) { - m := New() + m := New[string]() finished := make(chan struct{}, b.N) _, set := GetSet(m, finished) b.ResetTimer() @@ -89,7 +89,7 @@ func benchmarkMultiInsertDifferent(b *testing.B) { func BenchmarkMultiInsertDifferentSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, b.N) - _, set := GetSetSyncMap(&m, finished) + _, set := GetSetSyncMap[string](&m, finished) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -114,7 +114,7 @@ func BenchmarkMultiInsertDifferent_256_Shard(b *testing.B) { } func BenchmarkMultiInsertSame(b *testing.B) { - m := New() + m := New[string]() finished := make(chan struct{}, b.N) _, set := GetSet(m, finished) m.Set("key", "value") @@ -130,7 +130,7 @@ func BenchmarkMultiInsertSame(b *testing.B) { func BenchmarkMultiInsertSameSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, b.N) - _, set := GetSetSyncMap(&m, finished) + _, set := GetSetSyncMap[string](&m, finished) m.Store("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { @@ -142,7 +142,7 @@ func BenchmarkMultiInsertSameSyncMap(b *testing.B) { } func BenchmarkMultiGetSame(b *testing.B) { - m := New() + m := New[string]() finished := make(chan struct{}, b.N) get, _ := GetSet(m, finished) m.Set("key", "value") @@ -158,7 +158,7 @@ func BenchmarkMultiGetSame(b *testing.B) { func BenchmarkMultiGetSameSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, b.N) - get, _ := GetSetSyncMap(&m, finished) + get, _ := GetSetSyncMap[string](&m, finished) m.Store("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { @@ -170,7 +170,7 @@ func BenchmarkMultiGetSameSyncMap(b *testing.B) { } func benchmarkMultiGetSetDifferent(b *testing.B) { - m := New() + m := New[string]() finished := make(chan struct{}, 2*b.N) get, set := GetSet(m, finished) m.Set("-1", "value") @@ -187,7 +187,7 @@ func benchmarkMultiGetSetDifferent(b *testing.B) { func BenchmarkMultiGetSetDifferentSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, 2*b.N) - get, set := GetSetSyncMap(&m, finished) + get, set := GetSetSyncMap[string](&m, finished) m.Store("-1", "value") b.ResetTimer() for i := 0; i < b.N; i++ { @@ -213,7 +213,7 @@ func BenchmarkMultiGetSetDifferent_256_Shard(b *testing.B) { } func benchmarkMultiGetSetBlock(b *testing.B) { - m := New() + m := New[string]() finished := make(chan struct{}, 2*b.N) get, set := GetSet(m, finished) for i := 0; i < b.N; i++ { @@ -232,7 +232,7 @@ func benchmarkMultiGetSetBlock(b *testing.B) { func BenchmarkMultiGetSetBlockSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, 2*b.N) - get, set := GetSetSyncMap(&m, finished) + get, set := GetSetSyncMap[string](&m, finished) for i := 0; i < b.N; i++ { m.Store(strconv.Itoa(i%100), "value") } @@ -259,13 +259,13 @@ func BenchmarkMultiGetSetBlock_256_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetBlock, b, 256) } -func GetSet(m ConcurrentMap, finished chan struct{}) (set func(key, value string), get func(key, value string)) { - return func(key, value string) { +func GetSet[V any](m ConcurrentMap[V], finished chan struct{}) (set func(key string, value V), get func(key string, value V)) { + return func(key string, value V) { for i := 0; i < 10; i++ { m.Get(key) } finished <- struct{}{} - }, func(key, value string) { + }, func(key string, value V) { for i := 0; i < 10; i++ { m.Set(key, value) } @@ -273,14 +273,14 @@ func GetSet(m ConcurrentMap, finished chan struct{}) (set func(key, value string } } -func GetSetSyncMap(m *sync.Map, finished chan struct{}) (get func(key, value string), set func(key, value string)) { - get = func(key, value string) { +func GetSetSyncMap[V any](m *sync.Map, finished chan struct{}) (get func(key string, value V), set func(key string, value V)) { + get = func(key string, value V) { for i := 0; i < 10; i++ { m.Load(key) } finished <- struct{}{} } - set = func(key, value string) { + set = func(key string, value V) { for i := 0; i < 10; i++ { m.Store(key, value) } @@ -297,7 +297,7 @@ func runWithShards(bench func(b *testing.B), b *testing.B, shardsCount int) { } func BenchmarkKeys(b *testing.B) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 10000; i++ { diff --git a/concurrent_map_test.go b/concurrent_map_test.go index 18fbb09..78753de 100644 --- a/concurrent_map_test.go +++ b/concurrent_map_test.go @@ -13,7 +13,7 @@ type Animal struct { } func TestMapCreation(t *testing.T) { - m := New() + m := New[string]() if m == nil { t.Error("map is null.") } @@ -24,7 +24,7 @@ func TestMapCreation(t *testing.T) { } func TestInsert(t *testing.T) { - m := New() + m := New[Animal]() elephant := Animal{"elephant"} monkey := Animal{"monkey"} @@ -37,7 +37,7 @@ func TestInsert(t *testing.T) { } func TestInsertAbsent(t *testing.T) { - m := New() + m := New[Animal]() elephant := Animal{"elephant"} monkey := Animal{"monkey"} @@ -48,7 +48,7 @@ func TestInsertAbsent(t *testing.T) { } func TestGet(t *testing.T) { - m := New() + m := New[Animal]() // Get a missing element. val, ok := m.Get("Money") @@ -57,7 +57,7 @@ func TestGet(t *testing.T) { t.Error("ok should be false when item is missing from map.") } - if val != nil { + if (val != Animal{}) { t.Error("Missing values should return as null.") } @@ -65,23 +65,18 @@ func TestGet(t *testing.T) { m.Set("elephant", elephant) // Retrieve inserted element. - tmp, ok := m.Get("elephant") + elephant, ok = m.Get("elephant") if ok == false { t.Error("ok should be true for item stored within the map.") } - elephant, ok = tmp.(Animal) // Type assertion. - if !ok { - t.Error("expecting an element, not null.") - } - if elephant.name != "elephant" { t.Error("item was modified.") } } func TestHas(t *testing.T) { - m := New() + m := New[Animal]() // Get a missing element. if m.Has("Money") == true { @@ -97,7 +92,7 @@ func TestHas(t *testing.T) { } func TestRemove(t *testing.T) { - m := New() + m := New[Animal]() monkey := Animal{"monkey"} m.Set("monkey", monkey) @@ -114,7 +109,7 @@ func TestRemove(t *testing.T) { t.Error("Expecting ok to be false for missing items.") } - if temp != nil { + if (temp != Animal{}) { t.Error("Expecting item to be nil after its removal.") } @@ -123,7 +118,7 @@ func TestRemove(t *testing.T) { } func TestRemoveCb(t *testing.T) { - m := New() + m := New[Animal]() monkey := Animal{"monkey"} m.Set("monkey", monkey) @@ -132,18 +127,15 @@ func TestRemoveCb(t *testing.T) { var ( mapKey string - mapVal interface{} + mapVal Animal wasFound bool ) - cb := func(key string, val interface{}, exists bool) bool { + cb := func(key string, val Animal, exists bool) bool { mapKey = key mapVal = val wasFound = exists - if animal, ok := val.(Animal); ok { - return animal.name == "monkey" - } - return false + return val.name == "monkey" } // Monkey should be removed @@ -200,7 +192,7 @@ func TestRemoveCb(t *testing.T) { t.Error("Wrong key was provided to the callback") } - if mapVal != nil { + if (mapVal != Animal{}) { t.Errorf("Wrong value was provided to the value") } @@ -214,27 +206,20 @@ func TestRemoveCb(t *testing.T) { } func TestPop(t *testing.T) { - m := New() + m := New[Animal]() monkey := Animal{"monkey"} m.Set("monkey", monkey) v, exists := m.Pop("monkey") - if !exists { + if !exists || v != monkey { t.Error("Pop didn't find a monkey.") } - m1, ok := v.(Animal) - - if !ok || m1 != monkey { - t.Error("Pop found something else, but monkey.") - } - v2, exists2 := m.Pop("monkey") - m1, ok = v2.(Animal) - if exists2 || ok || m1 == monkey { + if exists2 || v2 == monkey { t.Error("Pop keeps finding monkey") } @@ -248,13 +233,13 @@ func TestPop(t *testing.T) { t.Error("Expecting ok to be false for missing items.") } - if temp != nil { + if (temp != Animal{}) { t.Error("Expecting item to be nil after its removal.") } } func TestCount(t *testing.T) { - m := New() + m := New[Animal]() for i := 0; i < 100; i++ { m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) } @@ -265,7 +250,7 @@ func TestCount(t *testing.T) { } func TestIsEmpty(t *testing.T) { - m := New() + m := New[Animal]() if m.IsEmpty() == false { t.Error("new map should be empty") @@ -279,7 +264,7 @@ func TestIsEmpty(t *testing.T) { } func TestIterator(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 100; i++ { @@ -291,7 +276,7 @@ func TestIterator(t *testing.T) { for item := range m.Iter() { val := item.Val - if val == nil { + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ @@ -303,7 +288,7 @@ func TestIterator(t *testing.T) { } func TestBufferedIterator(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 100; i++ { @@ -315,7 +300,7 @@ func TestBufferedIterator(t *testing.T) { for item := range m.IterBuffered() { val := item.Val - if val == nil { + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ @@ -327,7 +312,7 @@ func TestBufferedIterator(t *testing.T) { } func TestClear(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 100; i++ { @@ -342,7 +327,7 @@ func TestClear(t *testing.T) { } func TestIterCb(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 100; i++ { @@ -351,12 +336,7 @@ func TestIterCb(t *testing.T) { counter := 0 // Iterate over elements. - m.IterCb(func(key string, v interface{}) { - _, ok := v.(Animal) - if !ok { - t.Error("Expecting an animal object") - } - + m.IterCb(func(key string, v Animal) { counter++ }) if counter != 100 { @@ -365,7 +345,7 @@ func TestIterCb(t *testing.T) { } func TestItems(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 100; i++ { @@ -380,7 +360,7 @@ func TestItems(t *testing.T) { } func TestConcurrent(t *testing.T) { - m := New() + m := New[int]() ch := make(chan int) const iterations = 1000 var a [iterations]int @@ -395,7 +375,7 @@ func TestConcurrent(t *testing.T) { val, _ := m.Get(strconv.Itoa(i)) // Write to channel inserted value. - ch <- val.(int) + ch <- val } // Call go routine with current index. }() @@ -408,7 +388,7 @@ func TestConcurrent(t *testing.T) { val, _ := m.Get(strconv.Itoa(i)) // Write to channel inserted value. - ch <- val.(int) + ch <- val } // Call go routine with current index. }() @@ -444,7 +424,7 @@ func TestJsonMarshal(t *testing.T) { SHARD_COUNT = 32 }() expected := "{\"a\":1,\"b\":2}" - m := New() + m := New[int]() m.Set("a", 1) m.Set("b", 2) j, err := json.Marshal(m) @@ -459,7 +439,7 @@ func TestJsonMarshal(t *testing.T) { } func TestKeys(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. for i := 0; i < 100; i++ { @@ -473,11 +453,11 @@ func TestKeys(t *testing.T) { } func TestMInsert(t *testing.T) { - animals := map[string]interface{}{ + animals := map[string]Animal{ "elephant": Animal{"elephant"}, "monkey": Animal{"monkey"}, } - m := New() + m := New[Animal]() m.MSet(animals) if m.Count() != 2 { @@ -505,17 +485,16 @@ func TestUpsert(t *testing.T) { tiger := Animal{"tiger"} lion := Animal{"lion"} - cb := func(exists bool, valueInMap interface{}, newValue interface{}) interface{} { - nv := newValue.(Animal) + cb := func(exists bool, valueInMap Animal, newValue Animal) Animal { if !exists { - return []Animal{nv} + return newValue } - res := valueInMap.([]Animal) - return append(res, nv) + valueInMap.name += newValue.name + return valueInMap } - m := New() - m.Set("marine", []Animal{dolphin}) + m := New[Animal]() + m.Set("marine", dolphin) m.Upsert("marine", whale, cb) m.Upsert("predator", tiger, cb) m.Upsert("predator", lion, cb) @@ -524,36 +503,19 @@ func TestUpsert(t *testing.T) { t.Error("map should contain exactly two elements.") } - compare := func(a, b []Animal) bool { - if a == nil || b == nil { - return false - } - - if len(a) != len(b) { - return false - } - - for i, v := range a { - if v != b[i] { - return false - } - } - return true - } - marineAnimals, ok := m.Get("marine") - if !ok || !compare(marineAnimals.([]Animal), []Animal{dolphin, whale}) { + if marineAnimals.name != "dolphinwhale" || !ok { t.Error("Set, then Upsert failed") } predators, ok := m.Get("predator") - if !ok || !compare(predators.([]Animal), []Animal{tiger, lion}) { + if !ok || predators.name != "tigerlion" { t.Error("Upsert, then Upsert failed") } } func TestKeysWhenRemoving(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. Total := 100 @@ -564,7 +526,7 @@ func TestKeysWhenRemoving(t *testing.T) { // Remove 10 elements concurrently. Num := 10 for i := 0; i < Num; i++ { - go func(c *ConcurrentMap, n int) { + go func(c *ConcurrentMap[Animal], n int) { c.Remove(strconv.Itoa(n)) }(&m, i) } @@ -578,7 +540,7 @@ func TestKeysWhenRemoving(t *testing.T) { // func TestUnDrainedIter(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. Total := 100 for i := 0; i < Total; i++ { @@ -590,7 +552,7 @@ func TestUnDrainedIter(t *testing.T) { for item := range ch { val := item.Val - if val == nil { + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ @@ -604,7 +566,7 @@ func TestUnDrainedIter(t *testing.T) { for item := range ch { val := item.Val - if val == nil { + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ @@ -618,7 +580,7 @@ func TestUnDrainedIter(t *testing.T) { for item := range m.IterBuffered() { val := item.Val - if val == nil { + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ @@ -630,7 +592,7 @@ func TestUnDrainedIter(t *testing.T) { } func TestUnDrainedIterBuffered(t *testing.T) { - m := New() + m := New[Animal]() // Insert 100 elements. Total := 100 for i := 0; i < Total; i++ { @@ -642,7 +604,7 @@ func TestUnDrainedIterBuffered(t *testing.T) { for item := range ch { val := item.Val - if val == nil { + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ @@ -656,7 +618,7 @@ func TestUnDrainedIterBuffered(t *testing.T) { for item := range ch { val := item.Val - if val == nil { + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ @@ -670,7 +632,7 @@ func TestUnDrainedIterBuffered(t *testing.T) { for item := range m.IterBuffered() { val := item.Val - if val == nil { + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7f4e628 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/orcaman/concurrent-map + +go 1.18