├── LICENSE ├── README.md ├── concurrent_map.go ├── concurrent_map_bench_test.go └── concurrent_map_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 streamrail 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # concurrent map [![Circle CI](https://circleci.com/gh/streamrail/concurrent-map.png?style=badge)](https://circleci.com/gh/streamrail/concurrent-map) 2 | 3 | As explained [here](http://golang.org/doc/faq#atomic_maps) and [here](http://blog.golang.org/go-maps-in-action), the `map` type in Go doesn't support concurrent reads and writes. `concurrent-map` provides a high-performance solution to this by sharding the map with minimal time spent waiting for locks. 4 | 5 | ## usage 6 | 7 | Import the package: 8 | 9 | ```go 10 | import ( 11 | "github.com/streamrail/concurrent-map" 12 | ) 13 | 14 | ``` 15 | 16 | ```bash 17 | go get "github.com/streamrail/concurrent-map" 18 | ``` 19 | 20 | The package is now imported under the "cmap" namespace. 21 | 22 | ## example 23 | 24 | ```go 25 | 26 | // Create a new map. 27 | map := cmap.New() 28 | 29 | // Sets item within map, sets "bar" under key "foo" 30 | map.Set("foo", "bar") 31 | 32 | // Retrieve item from map. 33 | if tmp, ok := map.Get("foo"); ok { 34 | bar := tmp.(string) 35 | } 36 | 37 | // Removes item under key "foo" 38 | map.Remove("foo") 39 | 40 | ``` 41 | 42 | For more examples have a look at concurrent_map_test.go. 43 | 44 | 45 | Running tests: 46 | 47 | ```bash 48 | go test "github.com/streamrail/concurrent-map" 49 | ``` 50 | 51 | 52 | ## license 53 | MIT (see [LICENSE](https://github.com/streamrail/concurrent-map/blob/master/LICENSE) file) 54 | -------------------------------------------------------------------------------- /concurrent_map.go: -------------------------------------------------------------------------------- 1 | package cmap 2 | 3 | import ( 4 | "encoding/json" 5 | "sync" 6 | ) 7 | 8 | var SHARD_COUNT = 32 9 | 10 | // A "thread" safe map of type string:Anything. 11 | // To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards. 12 | type ConcurrentMap []*ConcurrentMapShared 13 | 14 | // A "thread" safe string to anything map. 15 | type ConcurrentMapShared struct { 16 | items map[string]interface{} 17 | sync.RWMutex // Read Write mutex, guards access to internal map. 18 | } 19 | 20 | // Creates a new concurrent map. 21 | func New() ConcurrentMap { 22 | m := make(ConcurrentMap, SHARD_COUNT) 23 | for i := 0; i < SHARD_COUNT; i++ { 24 | m[i] = &ConcurrentMapShared{items: make(map[string]interface{})} 25 | } 26 | return m 27 | } 28 | 29 | // Returns shard under given key 30 | func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared { 31 | return m[uint(fnv32(key))%uint(SHARD_COUNT)] 32 | } 33 | 34 | func (m ConcurrentMap) MSet(data map[string]interface{}) { 35 | for key, value := range data { 36 | shard := m.GetShard(key) 37 | shard.Lock() 38 | shard.items[key] = value 39 | shard.Unlock() 40 | } 41 | } 42 | 43 | // Sets the given value under the specified key. 44 | func (m *ConcurrentMap) Set(key string, value interface{}) { 45 | // Get map shard. 46 | shard := m.GetShard(key) 47 | shard.Lock() 48 | shard.items[key] = value 49 | shard.Unlock() 50 | } 51 | 52 | // Callback to return new element to be inserted into the map 53 | // It is called while lock is held, therefore it MUST NOT 54 | // try to access other keys in same map, as it can lead to deadlock since 55 | // Go sync.RWLock is not reentrant 56 | type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{} 57 | 58 | // Insert or Update - updates existing element or inserts a new one using UpsertCb 59 | func (m *ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) { 60 | shard := m.GetShard(key) 61 | shard.Lock() 62 | v, ok := shard.items[key] 63 | res = cb(ok, v, value) 64 | shard.items[key] = res 65 | shard.Unlock() 66 | return res 67 | } 68 | 69 | // Sets the given value under the specified key if no value was associated with it. 70 | func (m *ConcurrentMap) SetIfAbsent(key string, value interface{}) bool { 71 | // Get map shard. 72 | shard := m.GetShard(key) 73 | shard.Lock() 74 | _, ok := shard.items[key] 75 | if !ok { 76 | shard.items[key] = value 77 | } 78 | shard.Unlock() 79 | return !ok 80 | } 81 | 82 | // Retrieves an element from map under given key. 83 | func (m ConcurrentMap) Get(key string) (interface{}, bool) { 84 | // Get shard 85 | shard := m.GetShard(key) 86 | shard.RLock() 87 | // Get item from shard. 88 | val, ok := shard.items[key] 89 | shard.RUnlock() 90 | return val, ok 91 | } 92 | 93 | // Returns the number of elements within the map. 94 | func (m ConcurrentMap) Count() int { 95 | count := 0 96 | for i := 0; i < SHARD_COUNT; i++ { 97 | shard := m[i] 98 | shard.RLock() 99 | count += len(shard.items) 100 | shard.RUnlock() 101 | } 102 | return count 103 | } 104 | 105 | // Looks up an item under specified key 106 | func (m *ConcurrentMap) Has(key string) bool { 107 | // Get shard 108 | shard := m.GetShard(key) 109 | shard.RLock() 110 | // See if element is within shard. 111 | _, ok := shard.items[key] 112 | shard.RUnlock() 113 | return ok 114 | } 115 | 116 | // Removes an element from the map. 117 | func (m *ConcurrentMap) Remove(key string) { 118 | // Try to get shard. 119 | shard := m.GetShard(key) 120 | shard.Lock() 121 | delete(shard.items, key) 122 | shard.Unlock() 123 | } 124 | 125 | // Removes an element from the map and returns it 126 | func (m *ConcurrentMap) Pop(key string) (v interface{}, exists bool) { 127 | // Try to get shard. 128 | shard := m.GetShard(key) 129 | shard.Lock() 130 | v, exists = shard.items[key] 131 | delete(shard.items, key) 132 | shard.Unlock() 133 | return v, exists 134 | } 135 | 136 | // Checks if map is empty. 137 | func (m *ConcurrentMap) IsEmpty() bool { 138 | return m.Count() == 0 139 | } 140 | 141 | // Used by the Iter & IterBuffered functions to wrap two variables together over a channel, 142 | type Tuple struct { 143 | Key string 144 | Val interface{} 145 | } 146 | 147 | // Returns an iterator which could be used in a for range loop. 148 | // 149 | // Deprecated: using IterBuffered() will get a better performence 150 | func (m ConcurrentMap) Iter() <-chan Tuple { 151 | ch := make(chan Tuple) 152 | go func() { 153 | wg := sync.WaitGroup{} 154 | wg.Add(SHARD_COUNT) 155 | // Foreach shard. 156 | for _, shard := range m { 157 | go func(shard *ConcurrentMapShared) { 158 | // Foreach key, value pair. 159 | shard.RLock() 160 | for key, val := range shard.items { 161 | ch <- Tuple{key, val} 162 | } 163 | shard.RUnlock() 164 | wg.Done() 165 | }(shard) 166 | } 167 | wg.Wait() 168 | close(ch) 169 | }() 170 | return ch 171 | } 172 | 173 | // Returns a buffered iterator which could be used in a for range loop. 174 | func (m ConcurrentMap) IterBuffered() <-chan Tuple { 175 | ch := make(chan Tuple, m.Count()) 176 | go func() { 177 | wg := sync.WaitGroup{} 178 | wg.Add(SHARD_COUNT) 179 | // Foreach shard. 180 | for _, shard := range m { 181 | go func(shard *ConcurrentMapShared) { 182 | // Foreach key, value pair. 183 | shard.RLock() 184 | for key, val := range shard.items { 185 | ch <- Tuple{key, val} 186 | } 187 | shard.RUnlock() 188 | wg.Done() 189 | }(shard) 190 | } 191 | wg.Wait() 192 | close(ch) 193 | }() 194 | return ch 195 | } 196 | 197 | // Returns all items as map[string]interface{} 198 | func (m ConcurrentMap) Items() map[string]interface{} { 199 | tmp := make(map[string]interface{}) 200 | 201 | // Insert items to temporary map. 202 | for item := range m.IterBuffered() { 203 | tmp[item.Key] = item.Val 204 | } 205 | 206 | return tmp 207 | } 208 | 209 | // Iterator callback,called for every key,value found in 210 | // maps. RLock is held for all calls for a given shard 211 | // therefore callback sess consistent view of a shard, 212 | // but not across the shards 213 | type IterCb func(key string, v interface{}) 214 | 215 | // Callback based iterator, cheapest way to read 216 | // all elements in a map. 217 | func (m *ConcurrentMap) IterCb(fn IterCb) { 218 | for idx := range *m { 219 | shard := (*m)[idx] 220 | shard.RLock() 221 | for key, value := range shard.items { 222 | fn(key, value) 223 | } 224 | shard.RUnlock() 225 | } 226 | } 227 | 228 | // Return all keys as []string 229 | func (m ConcurrentMap) Keys() []string { 230 | count := m.Count() 231 | ch := make(chan string, count) 232 | go func() { 233 | // Foreach shard. 234 | wg := sync.WaitGroup{} 235 | wg.Add(SHARD_COUNT) 236 | for _, shard := range m { 237 | go func(shard *ConcurrentMapShared) { 238 | // Foreach key, value pair. 239 | shard.RLock() 240 | for key := range shard.items { 241 | ch <- key 242 | } 243 | shard.RUnlock() 244 | wg.Done() 245 | }(shard) 246 | } 247 | wg.Wait() 248 | close(ch) 249 | }() 250 | 251 | // Generate keys 252 | keys := make([]string, count) 253 | for i := 0; i < count; i++ { 254 | keys[i] = <-ch 255 | } 256 | return keys 257 | } 258 | 259 | //Reviles ConcurrentMap "private" variables to json marshal. 260 | func (m ConcurrentMap) MarshalJSON() ([]byte, error) { 261 | // Create a temporary map, which will hold all item spread across shards. 262 | tmp := make(map[string]interface{}) 263 | 264 | // Insert items to temporary map. 265 | for item := range m.IterBuffered() { 266 | tmp[item.Key] = item.Val 267 | } 268 | return json.Marshal(tmp) 269 | } 270 | 271 | func fnv32(key string) uint32 { 272 | hash := uint32(2166136261) 273 | const prime32 = uint32(16777619) 274 | for i := 0; i < len(key); i++ { 275 | hash *= prime32 276 | hash ^= uint32(key[i]) 277 | } 278 | return hash 279 | } 280 | 281 | // Concurrent map uses Interface{} as its value, therefor JSON Unmarshal 282 | // will probably won't know which to type to unmarshal into, in such case 283 | // we'll end up with a value of type map[string]interface{}, In most cases this isn't 284 | // out value type, this is why we've decided to remove this functionality. 285 | 286 | // func (m *ConcurrentMap) UnmarshalJSON(b []byte) (err error) { 287 | // // Reverse process of Marshal. 288 | 289 | // tmp := make(map[string]interface{}) 290 | 291 | // // Unmarshal into a single map. 292 | // if err := json.Unmarshal(b, &tmp); err != nil { 293 | // return nil 294 | // } 295 | 296 | // // foreach key,value pair in temporary map insert into our concurrent map. 297 | // for key, val := range tmp { 298 | // m.Set(key, val) 299 | // } 300 | // return nil 301 | // } 302 | -------------------------------------------------------------------------------- /concurrent_map_bench_test.go: -------------------------------------------------------------------------------- 1 | package cmap 2 | 3 | import "testing" 4 | import "strconv" 5 | 6 | func BenchmarkItems(b *testing.B) { 7 | m := New() 8 | 9 | // Insert 100 elements. 10 | for i := 0; i < 10000; i++ { 11 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 12 | } 13 | for i := 0; i < b.N; i++ { 14 | m.Items() 15 | } 16 | } 17 | 18 | func BenchmarkMarshalJson(b *testing.B) { 19 | m := New() 20 | 21 | // Insert 100 elements. 22 | for i := 0; i < 10000; i++ { 23 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 24 | } 25 | for i := 0; i < b.N; i++ { 26 | m.MarshalJSON() 27 | } 28 | } 29 | 30 | func BenchmarkStrconv(b *testing.B) { 31 | for i := 0; i < b.N; i++ { 32 | strconv.Itoa(i) 33 | } 34 | } 35 | 36 | func BenchmarkSingleInsertAbsent(b *testing.B) { 37 | m := New() 38 | b.ResetTimer() 39 | for i := 0; i < b.N; i++ { 40 | m.Set(strconv.Itoa(i), "value") 41 | } 42 | } 43 | 44 | func BenchmarkSingleInsertPresent(b *testing.B) { 45 | m := New() 46 | m.Set("key", "value") 47 | b.ResetTimer() 48 | for i := 0; i < b.N; i++ { 49 | m.Set("key", "value") 50 | } 51 | } 52 | 53 | func benchmarkMultiInsertDifferent(b *testing.B) { 54 | m := New() 55 | finished := make(chan struct{}, b.N) 56 | _, set := GetSet(m, finished) 57 | b.ResetTimer() 58 | for i := 0; i < b.N; i++ { 59 | set(strconv.Itoa(i), "value") 60 | } 61 | for i := 0; i < b.N; i++ { 62 | <-finished 63 | } 64 | } 65 | 66 | func BenchmarkMultiInsertDifferent_1_Shard(b *testing.B) { 67 | runWithShards(benchmarkMultiInsertDifferent, b, 1) 68 | } 69 | func BenchmarkMultiInsertDifferent_16_Shard(b *testing.B) { 70 | runWithShards(benchmarkMultiInsertDifferent, b, 16) 71 | } 72 | func BenchmarkMultiInsertDifferent_32_Shard(b *testing.B) { 73 | runWithShards(benchmarkMultiInsertDifferent, b, 32) 74 | } 75 | func BenchmarkMultiInsertDifferent_256_Shard(b *testing.B) { 76 | runWithShards(benchmarkMultiGetSetDifferent, b, 256) 77 | } 78 | 79 | func BenchmarkMultiInsertSame(b *testing.B) { 80 | m := New() 81 | finished := make(chan struct{}, b.N) 82 | _, set := GetSet(m, finished) 83 | m.Set("key", "value") 84 | b.ResetTimer() 85 | for i := 0; i < b.N; i++ { 86 | set("key", "value") 87 | } 88 | for i := 0; i < b.N; i++ { 89 | <-finished 90 | } 91 | } 92 | 93 | func BenchmarkMultiGetSame(b *testing.B) { 94 | m := New() 95 | finished := make(chan struct{}, b.N) 96 | get, _ := GetSet(m, finished) 97 | m.Set("key", "value") 98 | b.ResetTimer() 99 | for i := 0; i < b.N; i++ { 100 | get("key", "value") 101 | } 102 | for i := 0; i < b.N; i++ { 103 | <-finished 104 | } 105 | } 106 | 107 | func benchmarkMultiGetSetDifferent(b *testing.B) { 108 | m := New() 109 | finished := make(chan struct{}, 2*b.N) 110 | get, set := GetSet(m, finished) 111 | m.Set("-1", "value") 112 | b.ResetTimer() 113 | for i := 0; i < b.N; i++ { 114 | set(strconv.Itoa(i-1), "value") 115 | get(strconv.Itoa(i), "value") 116 | } 117 | for i := 0; i < 2*b.N; i++ { 118 | <-finished 119 | } 120 | } 121 | 122 | func BenchmarkMultiGetSetDifferent_1_Shard(b *testing.B) { 123 | runWithShards(benchmarkMultiGetSetDifferent, b, 1) 124 | } 125 | func BenchmarkMultiGetSetDifferent_16_Shard(b *testing.B) { 126 | runWithShards(benchmarkMultiGetSetDifferent, b, 16) 127 | } 128 | func BenchmarkMultiGetSetDifferent_32_Shard(b *testing.B) { 129 | runWithShards(benchmarkMultiGetSetDifferent, b, 32) 130 | } 131 | func BenchmarkMultiGetSetDifferent_256_Shard(b *testing.B) { 132 | runWithShards(benchmarkMultiGetSetDifferent, b, 256) 133 | } 134 | 135 | func benchmarkMultiGetSetBlock(b *testing.B) { 136 | m := New() 137 | finished := make(chan struct{}, 2*b.N) 138 | get, set := GetSet(m, finished) 139 | for i := 0; i < b.N; i++ { 140 | m.Set(strconv.Itoa(i%100), "value") 141 | } 142 | b.ResetTimer() 143 | for i := 0; i < b.N; i++ { 144 | set(strconv.Itoa(i%100), "value") 145 | get(strconv.Itoa(i%100), "value") 146 | } 147 | for i := 0; i < 2*b.N; i++ { 148 | <-finished 149 | } 150 | } 151 | 152 | func BenchmarkMultiGetSetBlock_1_Shard(b *testing.B) { 153 | runWithShards(benchmarkMultiGetSetBlock, b, 1) 154 | } 155 | func BenchmarkMultiGetSetBlock_16_Shard(b *testing.B) { 156 | runWithShards(benchmarkMultiGetSetBlock, b, 16) 157 | } 158 | func BenchmarkMultiGetSetBlock_32_Shard(b *testing.B) { 159 | runWithShards(benchmarkMultiGetSetBlock, b, 32) 160 | } 161 | func BenchmarkMultiGetSetBlock_256_Shard(b *testing.B) { 162 | runWithShards(benchmarkMultiGetSetBlock, b, 256) 163 | } 164 | 165 | func GetSet(m ConcurrentMap, finished chan struct{}) (set func(key, value string), get func(key, value string)) { 166 | return func(key, value string) { 167 | for i := 0; i < 10; i++ { 168 | m.Get(key) 169 | } 170 | finished <- struct{}{} 171 | }, func(key, value string) { 172 | for i := 0; i < 10; i++ { 173 | m.Set(key, value) 174 | } 175 | finished <- struct{}{} 176 | } 177 | } 178 | 179 | func runWithShards(bench func(b *testing.B), b *testing.B, shardsCount int) { 180 | oldShardsCount := SHARD_COUNT 181 | SHARD_COUNT = shardsCount 182 | bench(b) 183 | SHARD_COUNT = oldShardsCount 184 | } 185 | 186 | func BenchmarkKeys(b *testing.B) { 187 | m := New() 188 | 189 | // Insert 100 elements. 190 | for i := 0; i < 10000; i++ { 191 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 192 | } 193 | for i := 0; i < b.N; i++ { 194 | m.Keys() 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /concurrent_map_test.go: -------------------------------------------------------------------------------- 1 | package cmap 2 | 3 | import ( 4 | "encoding/json" 5 | "hash/fnv" 6 | "sort" 7 | "strconv" 8 | "testing" 9 | ) 10 | 11 | type Animal struct { 12 | name string 13 | } 14 | 15 | func TestMapCreation(t *testing.T) { 16 | m := New() 17 | if m == nil { 18 | t.Error("map is null.") 19 | } 20 | 21 | if m.Count() != 0 { 22 | t.Error("new map should be empty.") 23 | } 24 | } 25 | 26 | func TestInsert(t *testing.T) { 27 | m := New() 28 | elephant := Animal{"elephant"} 29 | monkey := Animal{"monkey"} 30 | 31 | m.Set("elephant", elephant) 32 | m.Set("monkey", monkey) 33 | 34 | if m.Count() != 2 { 35 | t.Error("map should contain exactly two elements.") 36 | } 37 | } 38 | 39 | func TestInsertAbsent(t *testing.T) { 40 | m := New() 41 | elephant := Animal{"elephant"} 42 | monkey := Animal{"monkey"} 43 | 44 | m.SetIfAbsent("elephant", elephant) 45 | if ok := m.SetIfAbsent("elephant", monkey); ok { 46 | t.Error("map set a new value even the entry is already present") 47 | } 48 | } 49 | 50 | func TestGet(t *testing.T) { 51 | m := New() 52 | 53 | // Get a missing element. 54 | val, ok := m.Get("Money") 55 | 56 | if ok == true { 57 | t.Error("ok should be false when item is missing from map.") 58 | } 59 | 60 | if val != nil { 61 | t.Error("Missing values should return as null.") 62 | } 63 | 64 | elephant := Animal{"elephant"} 65 | m.Set("elephant", elephant) 66 | 67 | // Retrieve inserted element. 68 | 69 | tmp, ok := m.Get("elephant") 70 | elephant = tmp.(Animal) // Type assertion. 71 | 72 | if ok == false { 73 | t.Error("ok should be true for item stored within the map.") 74 | } 75 | 76 | if &elephant == nil { 77 | t.Error("expecting an element, not null.") 78 | } 79 | 80 | if elephant.name != "elephant" { 81 | t.Error("item was modified.") 82 | } 83 | } 84 | 85 | func TestHas(t *testing.T) { 86 | m := New() 87 | 88 | // Get a missing element. 89 | if m.Has("Money") == true { 90 | t.Error("element shouldn't exists") 91 | } 92 | 93 | elephant := Animal{"elephant"} 94 | m.Set("elephant", elephant) 95 | 96 | if m.Has("elephant") == false { 97 | t.Error("element exists, expecting Has to return True.") 98 | } 99 | } 100 | 101 | func TestRemove(t *testing.T) { 102 | m := New() 103 | 104 | monkey := Animal{"monkey"} 105 | m.Set("monkey", monkey) 106 | 107 | m.Remove("monkey") 108 | 109 | if m.Count() != 0 { 110 | t.Error("Expecting count to be zero once item was removed.") 111 | } 112 | 113 | temp, ok := m.Get("monkey") 114 | 115 | if ok != false { 116 | t.Error("Expecting ok to be false for missing items.") 117 | } 118 | 119 | if temp != nil { 120 | t.Error("Expecting item to be nil after its removal.") 121 | } 122 | 123 | // Remove a none existing element. 124 | m.Remove("noone") 125 | } 126 | 127 | func TestPop(t *testing.T) { 128 | m := New() 129 | 130 | monkey := Animal{"monkey"} 131 | m.Set("monkey", monkey) 132 | 133 | v, exists := m.Pop("monkey") 134 | 135 | if !exists { 136 | t.Error("Pop didn't find a monkey.") 137 | } 138 | 139 | m1, ok := v.(Animal) 140 | 141 | if !ok || m1 != monkey { 142 | t.Error("Pop found something else, but monkey.") 143 | } 144 | 145 | v2, exists2 := m.Pop("monkey") 146 | m1, ok = v2.(Animal) 147 | 148 | if exists2 || ok || m1 == monkey { 149 | t.Error("Pop keeps finding monkey") 150 | } 151 | 152 | if m.Count() != 0 { 153 | t.Error("Expecting count to be zero once item was Pop'ed.") 154 | } 155 | 156 | temp, ok := m.Get("monkey") 157 | 158 | if ok != false { 159 | t.Error("Expecting ok to be false for missing items.") 160 | } 161 | 162 | if temp != nil { 163 | t.Error("Expecting item to be nil after its removal.") 164 | } 165 | } 166 | 167 | func TestCount(t *testing.T) { 168 | m := New() 169 | for i := 0; i < 100; i++ { 170 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 171 | } 172 | 173 | if m.Count() != 100 { 174 | t.Error("Expecting 100 element within map.") 175 | } 176 | } 177 | 178 | func TestIsEmpty(t *testing.T) { 179 | m := New() 180 | 181 | if m.IsEmpty() == false { 182 | t.Error("new map should be empty") 183 | } 184 | 185 | m.Set("elephant", Animal{"elephant"}) 186 | 187 | if m.IsEmpty() != false { 188 | t.Error("map shouldn't be empty.") 189 | } 190 | } 191 | 192 | func TestIterator(t *testing.T) { 193 | m := New() 194 | 195 | // Insert 100 elements. 196 | for i := 0; i < 100; i++ { 197 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 198 | } 199 | 200 | counter := 0 201 | // Iterate over elements. 202 | for item := range m.Iter() { 203 | val := item.Val 204 | 205 | if val == nil { 206 | t.Error("Expecting an object.") 207 | } 208 | counter++ 209 | } 210 | 211 | if counter != 100 { 212 | t.Error("We should have counted 100 elements.") 213 | } 214 | } 215 | 216 | func TestBufferedIterator(t *testing.T) { 217 | m := New() 218 | 219 | // Insert 100 elements. 220 | for i := 0; i < 100; i++ { 221 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 222 | } 223 | 224 | counter := 0 225 | // Iterate over elements. 226 | for item := range m.IterBuffered() { 227 | val := item.Val 228 | 229 | if val == nil { 230 | t.Error("Expecting an object.") 231 | } 232 | counter++ 233 | } 234 | 235 | if counter != 100 { 236 | t.Error("We should have counted 100 elements.") 237 | } 238 | } 239 | 240 | func TestIterCb(t *testing.T) { 241 | m := New() 242 | 243 | // Insert 100 elements. 244 | for i := 0; i < 100; i++ { 245 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 246 | } 247 | 248 | counter := 0 249 | // Iterate over elements. 250 | m.IterCb(func(key string, v interface{}) { 251 | _, ok := v.(Animal) 252 | if !ok { 253 | t.Error("Expecting an animal object") 254 | } 255 | 256 | counter++ 257 | }) 258 | if counter != 100 { 259 | t.Error("We should have counted 100 elements.") 260 | } 261 | } 262 | 263 | func TestItems(t *testing.T) { 264 | m := New() 265 | 266 | // Insert 100 elements. 267 | for i := 0; i < 100; i++ { 268 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 269 | } 270 | 271 | items := m.Items() 272 | 273 | if len(items) != 100 { 274 | t.Error("We should have counted 100 elements.") 275 | } 276 | } 277 | 278 | func TestConcurrent(t *testing.T) { 279 | m := New() 280 | ch := make(chan int) 281 | const iterations = 1000 282 | var a [iterations]int 283 | 284 | // Using go routines insert 1000 ints into our map. 285 | go func() { 286 | for i := 0; i < iterations/2; i++ { 287 | // Add item to map. 288 | m.Set(strconv.Itoa(i), i) 289 | 290 | // Retrieve item from map. 291 | val, _ := m.Get(strconv.Itoa(i)) 292 | 293 | // Write to channel inserted value. 294 | ch <- val.(int) 295 | } // Call go routine with current index. 296 | }() 297 | 298 | go func() { 299 | for i := iterations / 2; i < iterations; i++ { 300 | // Add item to map. 301 | m.Set(strconv.Itoa(i), i) 302 | 303 | // Retrieve item from map. 304 | val, _ := m.Get(strconv.Itoa(i)) 305 | 306 | // Write to channel inserted value. 307 | ch <- val.(int) 308 | } // Call go routine with current index. 309 | }() 310 | 311 | // Wait for all go routines to finish. 312 | counter := 0 313 | for elem := range ch { 314 | a[counter] = elem 315 | counter++ 316 | if counter == iterations { 317 | break 318 | } 319 | } 320 | 321 | // Sorts array, will make is simpler to verify all inserted values we're returned. 322 | sort.Ints(a[0:iterations]) 323 | 324 | // Make sure map contains 1000 elements. 325 | if m.Count() != iterations { 326 | t.Error("Expecting 1000 elements.") 327 | } 328 | 329 | // Make sure all inserted values we're fetched from map. 330 | for i := 0; i < iterations; i++ { 331 | if i != a[i] { 332 | t.Error("missing value", i) 333 | } 334 | } 335 | } 336 | 337 | func TestJsonMarshal(t *testing.T) { 338 | SHARD_COUNT = 2 339 | defer func() { 340 | SHARD_COUNT = 32 341 | }() 342 | expected := "{\"a\":1,\"b\":2}" 343 | m := New() 344 | m.Set("a", 1) 345 | m.Set("b", 2) 346 | j, err := json.Marshal(m) 347 | if err != nil { 348 | t.Error(err) 349 | } 350 | 351 | if string(j) != expected { 352 | t.Error("json", string(j), "differ from expected", expected) 353 | return 354 | } 355 | } 356 | 357 | func TestKeys(t *testing.T) { 358 | m := New() 359 | 360 | // Insert 100 elements. 361 | for i := 0; i < 100; i++ { 362 | m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) 363 | } 364 | 365 | keys := m.Keys() 366 | if len(keys) != 100 { 367 | t.Error("We should have counted 100 elements.") 368 | } 369 | } 370 | 371 | func TestMInsert(t *testing.T) { 372 | animals := map[string]interface{}{ 373 | "elephant": Animal{"elephant"}, 374 | "monkey": Animal{"monkey"}, 375 | } 376 | m := New() 377 | m.MSet(animals) 378 | 379 | if m.Count() != 2 { 380 | t.Error("map should contain exactly two elements.") 381 | } 382 | } 383 | 384 | func TestFnv32(t *testing.T) { 385 | key := []byte("ABC") 386 | 387 | hasher := fnv.New32() 388 | hasher.Write(key) 389 | if fnv32(string(key)) != hasher.Sum32() { 390 | t.Errorf("Bundled fnv32 produced %d, expected result from hash/fnv32 is %d", fnv32(string(key)), hasher.Sum32()) 391 | } 392 | } 393 | 394 | func TestUpsert(t *testing.T) { 395 | dolphin := Animal{"dolphin"} 396 | whale := Animal{"whale"} 397 | tiger := Animal{"tiger"} 398 | lion := Animal{"lion"} 399 | 400 | cb := func(exists bool, valueInMap interface{}, newValue interface{}) interface{} { 401 | nv := newValue.(Animal) 402 | if !exists { 403 | return []Animal{nv} 404 | } 405 | res := valueInMap.([]Animal) 406 | return append(res, nv) 407 | } 408 | 409 | m := New() 410 | m.Set("marine", []Animal{dolphin}) 411 | m.Upsert("marine", whale, cb) 412 | m.Upsert("predator", tiger, cb) 413 | m.Upsert("predator", lion, cb) 414 | 415 | if m.Count() != 2 { 416 | t.Error("map should contain exactly two elements.") 417 | } 418 | 419 | compare := func(a, b []Animal) bool { 420 | if a == nil || b == nil { 421 | return false 422 | } 423 | 424 | if len(a) != len(b) { 425 | return false 426 | } 427 | 428 | for i, v := range a { 429 | if v != b[i] { 430 | return false 431 | } 432 | } 433 | return true 434 | } 435 | 436 | marineAnimals, ok := m.Get("marine") 437 | if !ok || !compare(marineAnimals.([]Animal), []Animal{dolphin, whale}) { 438 | t.Error("Set, then Upsert failed") 439 | } 440 | 441 | predators, ok := m.Get("predator") 442 | if !ok || !compare(predators.([]Animal), []Animal{tiger, lion}) { 443 | t.Error("Upsert, then Upsert failed") 444 | } 445 | } 446 | --------------------------------------------------------------------------------