Skip to content

Commit

Permalink
Merge pull request #1 from patpicos/feat/disable_overwrite_on_set
Browse files Browse the repository at this point in the history
Feat/disable overwrite on set
  • Loading branch information
patpicos authored Sep 7, 2022
2 parents 40f09cf + c2ba154 commit d8a4626
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 9 deletions.
18 changes: 13 additions & 5 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func New[K comparable, V any](opts ...Option[K, V]) *Cache[K, V] {

// updateExpirations updates the expiration queue and notifies
// the cache auto cleaner if needed.
// Not concurrently safe.
// Not concurrency safe.
func (c *Cache[K, V]) updateExpirations(fresh bool, elem *list.Element) {
var oldExpiresAt time.Time

Expand Down Expand Up @@ -125,13 +125,19 @@ func (c *Cache[K, V]) updateExpirations(fresh bool, elem *list.Element) {
}

// set creates a new item, adds it to the cache and then returns it.
// Not concurrently safe.
// Not concurrency safe.
func (c *Cache[K, V]) set(key K, value V, ttl time.Duration) *Item[K, V] {
if ttl == DefaultTTL {
ttl = c.options.ttl
}

elem := c.get(key, false)
// return existing item if overwrite is disabled
if elem != nil && c.options.disableOverwriteOnSet {
c.updateExpirations(false, elem)

return elem.Value.(*Item[K, V])
}
if elem != nil {
// update/overwrite an existing item
item := elem.Value.(*Item[K, V])
Expand Down Expand Up @@ -168,7 +174,7 @@ func (c *Cache[K, V]) set(key K, value V, ttl time.Duration) *Item[K, V] {
// get retrieves an item from the cache and extends its expiration
// time if 'touch' is set to true.
// It returns nil if the item is not found or is expired.
// Not concurrently safe.
// Not concurrency safe.
func (c *Cache[K, V]) get(key K, touch bool) *list.Element {
elem := c.items.values[key]
if elem == nil {
Expand All @@ -193,7 +199,7 @@ func (c *Cache[K, V]) get(key K, touch bool) *list.Element {
// evict deletes items from the cache.
// If no items are provided, all currently present cache items
// are evicted.
// Not concurrently safe.
// Not concurrency safe.
func (c *Cache[K, V]) evict(reason EvictionReason, elems ...*list.Element) {
if len(elems) > 0 {
c.metricsMu.Lock()
Expand Down Expand Up @@ -237,7 +243,9 @@ func (c *Cache[K, V]) evict(reason EvictionReason, elems ...*list.Element) {

// Set creates a new item from the provided key and value, adds
// it to the cache and then returns it. If an item associated with the
// provided key already exists, the new item overwrites the existing one.
// provided key already exists, the new item overwrites the existing one
// unless you opt to use the option `options.disableOverwriteOnSet` which will
// prevent overwrites
func (c *Cache[K, V]) Set(key K, value V, ttl time.Duration) *Item[K, V] {
c.items.mu.Lock()
defer c.items.mu.Unlock()
Expand Down
12 changes: 12 additions & 0 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,18 @@ func Test_Cache_Set(t *testing.T) {
assert.Same(t, item, cache.items.values["test1"].Value)
}

func Test_Cache_Set_disableOverwriteOnSet(t *testing.T) {
cache := prepCache(time.Hour, "test1", "test2", "test3")
cache.options.disableOverwriteOnSet = true
item := cache.Set("hello", "value123", time.Minute)
require.NotNil(t, item)
assert.Same(t, item, cache.items.values["hello"].Value)

item2 := cache.Set("hello", "value345", time.Minute)
require.NotNil(t, item2)
assert.Same(t, item2, cache.items.values["hello"].Value)
}

func Test_Cache_Get(t *testing.T) {
const notFoundKey, foundKey = "notfound", "test1"
cc := map[string]struct {
Expand Down
20 changes: 16 additions & 4 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ func (fn optionFunc[K, V]) apply(opts *options[K, V]) {

// options holds all available cache configuration options.
type options[K comparable, V any] struct {
capacity uint64
ttl time.Duration
loader Loader[K, V]
disableTouchOnHit bool
capacity uint64
ttl time.Duration
loader Loader[K, V]
disableTouchOnHit bool
disableOverwriteOnSet bool
}

// applyOptions applies the provided option values to the option struct.
Expand Down Expand Up @@ -65,3 +66,14 @@ func WithDisableTouchOnHit[K comparable, V any]() Option[K, V] {
opts.disableTouchOnHit = true
})
}

// WithDisableOverwriteOnSet prevents the cache instance from
// overwriting an item when setting the value. This effectively makes
// the cache a first-insert wins
// When passing into Get(), it overrides the default value of the
// cache.
func WithDisableOverwriteOnSet[K comparable, V any]() Option[K, V] {
return optionFunc[K, V](func(opts *options[K, V]) {
opts.disableOverwriteOnSet = true
})
}
7 changes: 7 additions & 0 deletions options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,10 @@ func Test_WithDisableTouchOnHit(t *testing.T) {
WithDisableTouchOnHit[string, string]().apply(&opts)
assert.True(t, opts.disableTouchOnHit)
}

func Test_WithDisableOverwriteOnSet(t *testing.T) {
var opts options[string, string]

WithDisableOverwriteOnSet[string, string]().apply(&opts)
assert.True(t, opts.disableOverwriteOnSet)
}

0 comments on commit d8a4626

Please sign in to comment.