Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement UpsertOrRemove method #86

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 29 additions & 15 deletions concurrent_map.go
Expand Up @@ -49,6 +49,29 @@ func (m ConcurrentMap) Set(key string, value interface{}) {
shard.Unlock()
}

// UpsertOrRemoveCb allows a callback to either insert, update, or
// remove an entry in the map. If upsert is true, then val will be
// updated/inserted for the given key. If remove is true, then the
// key will be deleted from the map. Otherwise, nothing happens.
type UpsertOrRemoveCb func(exist bool, valueInMap interface{}, newValue interface{}) (val interface{}, upsert bool, remove bool)

// Insert, Update, or Remove - updates or removes existing element or
// inserts a new one using UpsertOrRemoveCb
func (m ConcurrentMap) UpsertOrRemove(key string, value interface{}, cb UpsertOrRemoveCb) (res interface{}, upsert bool, remove bool) {
shard := m.GetShard(key)
shard.Lock()
v, ok := shard.items[key]
res, upsert, remove = cb(ok, v, value)
if remove {
delete(shard.items, key)
}
if upsert {
shard.items[key] = res
}
shard.Unlock()
return res, upsert, remove
}

// Callback to return new element to be inserted into the map
// 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
Expand All @@ -57,12 +80,9 @@ type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) int

// 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{}) {
shard := m.GetShard(key)
shard.Lock()
v, ok := shard.items[key]
res = cb(ok, v, value)
shard.items[key] = res
shard.Unlock()
res, _, _ = m.UpsertOrRemove(key, value, func(exists bool, valueInMap interface{}, newValue interface{}) (interface{}, bool, bool) {
return cb(exists, valueInMap, newValue), true, false
})
return res
}

Expand Down Expand Up @@ -130,15 +150,9 @@ type RemoveCb func(key string, v interface{}, exists bool) bool
// 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 {
// Try to get shard.
shard := m.GetShard(key)
shard.Lock()
v, ok := shard.items[key]
remove := cb(key, v, ok)
if remove && ok {
delete(shard.items, key)
}
shard.Unlock()
_, _, remove := m.UpsertOrRemove(key, nil, func(exists bool, valueInMap interface{}, newValue interface{}) (interface{}, bool, bool) {
return nil, false, cb(key, valueInMap, exists)
})
return remove
}

Expand Down
51 changes: 51 additions & 0 deletions concurrent_map_test.go
Expand Up @@ -537,6 +537,57 @@ func TestUpsert(t *testing.T) {
}
}

func TestUpsertOrRemove(t *testing.T) {
dolphin := Animal{"dolphin"}
whale := Animal{"whale"}
tiger := Animal{"tiger"}
lion := Animal{"lion"}
crow := Animal{"crow"}

// This callback removes the key from the map if the list ever
// contains more than one crow
cb := func(exists bool, valueInMap interface{}, newValue interface{}) (val interface{}, upsert bool, remove bool) {
nv := newValue.(Animal)
if !exists {
// Insert
return []Animal{nv}, true, false
}
res := valueInMap.([]Animal)
crowCount := 0
if nv == crow {
crowCount++
}
for _, animal := range res {
if animal == crow {
crowCount++
}
}
if crowCount > 1 {
// Remove
return nil, false, true
}
// Update
return append(res, nv), true, false
}

m := New()
m.UpsertOrRemove("marine", dolphin, cb)
m.UpsertOrRemove("marine", whale, cb)
m.UpsertOrRemove("predator", tiger, cb)
m.UpsertOrRemove("predator", lion, cb)
m.UpsertOrRemove("scavenger", crow, cb)

if m.Count() != 3 {
t.Error("map should contain exactly three elements")
}

m.UpsertOrRemove("scavenger", crow, cb)

if m.Count() != 2 {
t.Error("map should contain exactly two elements")
}
}

func TestKeysWhenRemoving(t *testing.T) {
m := New()

Expand Down