Coder Social home page Coder Social logo

ttlcache's Introduction

TTLCache - an in-memory cache with item expiration and generics

Go Reference Build Status Coverage Status Go Report Card

Features

  • Simple API
  • Type parameters
  • Item expiration and automatic deletion
  • Automatic expiration time extension on each Get call
  • Loader interface that may be used to load/lazily initialize missing cache items
  • Event handlers (insertion and eviction)
  • Metrics

Installation

go get github.com/jellydator/ttlcache/v3

Usage

The main type of ttlcache is Cache. It represents a single in-memory data store.

To create a new instance of ttlcache.Cache, the ttlcache.New() function should be called:

func main() {
	cache := ttlcache.New[string, string]()
}

Note that by default, a new cache instance does not let any of its items to expire or be automatically deleted. However, this feature can be activated by passing a few additional options into the ttlcache.New() function and calling the cache.Start() method:

func main() {
	cache := ttlcache.New[string, string](
		ttlcache.WithTTL[string, string](30 * time.Minute),
	)

	go cache.Start() // starts automatic expired item deletion
}

Even though the cache.Start() method handles expired item deletion well, there may be times when the system that uses ttlcache needs to determine when to delete the expired items itself. For example, it may need to delete them only when the resource load is at its lowest (e.g., after midnight, when the number of users/HTTP requests drops). So, in situations like these, instead of calling cache.Start(), the system could periodically call cache.DeleteExpired():

func main() {
	cache := ttlcache.New[string, string](
		ttlcache.WithTTL[string, string](30 * time.Minute),
	)

	for {
		time.Sleep(4 * time.Hour)
		cache.DeleteExpired()
	}
}

The data stored in ttlcache.Cache can be retrieved, checked and updated with Set, Get, Delete, Has etc. methods:

func main() {
	cache := ttlcache.New[string, string](
		ttlcache.WithTTL[string, string](30 * time.Minute),
	)

	// insert data
	cache.Set("first", "value1", ttlcache.DefaultTTL)
	cache.Set("second", "value2", ttlcache.NoTTL)
	cache.Set("third", "value3", ttlcache.DefaultTTL)

	// retrieve data
	item := cache.Get("first")
	fmt.Println(item.Value(), item.ExpiresAt())

	// check key 
	ok := cache.Has("third")
	
	// delete data
	cache.Delete("second")
	cache.DeleteExpired()
	cache.DeleteAll()

	// retrieve data if in cache otherwise insert data
	item, retrieved := cache.GetOrSet("fourth", "value4", WithTTL[string, string](ttlcache.DefaultTTL))

	// retrieve and delete data
	item, present := cache.GetAndDelete("fourth")
}

To subscribe to insertion and eviction events, cache.OnInsertion() and cache.OnEviction() methods should be used:

func main() {
	cache := ttlcache.New[string, string](
		ttlcache.WithTTL[string, string](30 * time.Minute),
		ttlcache.WithCapacity[string, string](300),
	)

	cache.OnInsertion(func(ctx context.Context, item *ttlcache.Item[string, string]) {
		fmt.Println(item.Value(), item.ExpiresAt())
	})
	cache.OnEviction(func(ctx context.Context, reason ttlcache.EvictionReason, item *ttlcache.Item[string, string]) {
		if reason == ttlcache.EvictionReasonCapacityReached {
			fmt.Println(item.Key(), item.Value())
		}
	})

	cache.Set("first", "value1", ttlcache.DefaultTTL)
	cache.DeleteAll()
}

To load data when the cache does not have it, a custom or existing implementation of ttlcache.Loader can be used:

func main() {
	loader := ttlcache.LoaderFunc[string, string](
		func(c *ttlcache.Cache[string, string], key string) *ttlcache.Item[string, string] {
			// load from file/make an HTTP request
			item := c.Set("key from file", "value from file")
			return item
		},
	)
	cache := ttlcache.New[string, string](
		ttlcache.WithLoader[string, string](loader),
	)

	item := cache.Get("key from file")
}

ttlcache's People

Contributors

chenyahui avatar davseby avatar dependabot[bot] avatar diegobernardes avatar doubledi avatar fabiant7t avatar fernandobixly avatar froodian avatar gozeloglu avatar hkadakia avatar iczc avatar jspri avatar natehart avatar netroy avatar nikhilk1701 avatar renekroon avatar satta avatar savid avatar shivamkumar2002 avatar swithek avatar tfonfara avatar twoabove avatar witchu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ttlcache's Issues

what will happen if i update a key with ttl already set?

cache.SetWithTTL(key, value, 300 * time.Second)
// do something else
cache.SetWithTTL(key, updatedValue, 300 * time.Second)

if this key expires at second 300 * time.Second?

cache.SetWithTTL(key, value, 300 * time.Second)
// do sth else
cache.Set(key, value)

if this key expires at first 300 * time.Second?

Returning loader errors

With new version v3 there is currently no way to return loader (either default or getter) error as Get returns only item and there is no way to propagate errors back to caller.

Add Version method to item type

It should return a uint64 that determines how many times an item has been updated. This method may be useful in situations when a task's execution depends on whether the item has changed or not and when due to the duration of another task a transaction/mutex isn't acceptible.

item := cache.Get("key")
version := item.Version()
// long task
if item.Version() == version {
     // execute only if the item hasn't been updated during the long task's execution
}

Add Godoc

A GoDoc badge would be super nice for each access to docs

Add update method

I'd like to update an existing cache item in an atomic fashion. Since there is currently no such method I tried to work around it by first calling GetWithTTL() and then updating the returned item using SetWithTTL().

Unfortunately doing this I had a race condition because another Go routine would access/modify it between the Get and Set call. I don't see a workaround because I can't access the mutex.

go1.18 and generics

Are there any plans to upgrade the project to go1.18 (when it's released) and replace interface{} types with generic types?

I can open a draft PR with changes made using gotip in the near future if this is something you'd be interested in @ReneKroon.

got panic

panic: runtime error: index out of range [0] with length 0

github.com/ReneKroon/ttlcache/v2.(*Cache).SetWithTTL(0xc0001e8000, 0xc0145aa1e0, 0x20, 0x968da0, 0xc01707c480, 0xd18c2e2800, 0x0, 0x97cc40),
	/go/pkg/mod/github.com/!rene!kroon/ttlcache/[email protected]/cache.go:249 +0x3f9,
github.com/ReneKroon/ttlcache/v2.(*Cache).invokeLoader(0xc0001e8000, 0xc0145aa1e0, 0x20, 0x9f1d60, 0xc0004b4088, 0x866700, 0x96a2a0, 0xc00007a0a8),
	/go/pkg/mod/github.com/!rene!kroon/ttlcache/[email protected]/cache.go:357 +0xca,
github.com/ReneKroon/ttlcache/v2.(*Cache).GetByLoader(0xc0001e8000, 0xc0145aa1e0, 0x20, 0x0, 0x9, 0x1, 0x3, 0xc0145aa1e

RFC - Expiry based on passed in function (option during cache creation)

This is a request for comments / brainstorming...

Currently, ttlCache expires items if:

  • a TTL threshold is met
  • cache size is met

Would it be too much of an expansion to have a more dynamic expiry to include a passed in function?

For example, the function could check the content of the item and return a bool of whether it should be expired.

This could be configured as an option during cache creation to pass the function. The checks could be done in item.isExpiredUnsafe()

A workaround would be to do this outside of the cache by regularly iterating the cache item and running the function on each item and issuing delete if the criteria is met. However, that expiry logic would be nice to offload to the cache.

Please add a license

Cheers,

it would be great if you could add a license - I could not find anything on that topic, neither in source nor in other files.

TIA

Martin

Const errors are currently declared as variable.

var (
	// ErrClosed is raised when operating on a cache where Close() has already been called.
	ErrClosed = errors.New("cache already closed")
	// ErrNotFound indicates that the requested key is not present in the cache
	ErrNotFound = errors.New("key not found")
)

are variables and would be better of as const. However have to check compatibility of that first (assuming it's minor now)

Add option to disable auto-extending of expiration with Get

I'm planning to use this library as a backend for DNS caching in a proxy server.

After the TTL expires, I plan to fetch a new and possibly updated value from the DNS server. The auto-extending of TTL with Get() will result in the cache never being updated and potentially having stale values.

Export / Import functionality

I'm trying to move away from patrickmn cache usage in my projects. And I'd like to implement yours where needed. I miss an export and import cache function to persist cache between runs in your cache. Can I add this as a feature request? If you don't have the time and like the idea, I'm willing to help also.

Thanks in advance

Feature request: ability to know why an expiry has occurred

Currently if you register an expiry callback, that callback gets called in one of 3 different scenarios:

  1. The key expires due to the TTL
  2. The key is manually removed via a call to Remove
  3. The cache is closed via Close

However, you have no way of knowing from within the expiry callback which scenario has occurred.

For context, I have a scenario where I am maintaining a list of items that can be added and removed by users, and if they are removed by users, the cleanup process is already handled as part of handling the users request. However, items need to expire as well and in that case, the users need to be informed that their items have been removed automatically. In the current implementation, if a user removes their own items, they also get informed their item was removed automatically due to the expiration callback firing.

My proposal is for the expiration callback to take an additional argument reason which can be checked against to see if it was triggered by a Remove, Close or TTL expiry, which then gives me the option of handling it differently for each scenario. Alternatively, having a flag much like the SkipTTLExtensionOnHit such as ExpireOnTTLOnly to allow me to strictly support callbacks firing only expirations due to actual expiry rather than removal from the cache would be useful.

Eviction callbacks stop after a certain point allowing unconstrained growth

// Setup the TTL cache
	cache := ttlcache.NewCache()
	cache.SetTTL(time.Second * 10)
	cache.SetExpirationCallback(func(key string, value interface{}) {
		fmt.Printf("This key(%s) has expired\n", key)
	})
	for i := 0; ; i++ {
		cache.Set(fmt.Sprintf("item_%d", i), clientPlatformService{})
		time.Sleep(time.Millisecond * 100)
		log.Println("Cache size:", cache.Count())
	}
2017/07/31 10:30:57 Cache size: 1
2017/07/31 10:30:57 Cache size: 2
2017/07/31 10:30:57 Cache size: 3
2017/07/31 10:30:57 Cache size: 4
2017/07/31 10:30:58 Cache size: 5
2017/07/31 10:30:58 Cache size: 6
2017/07/31 10:30:58 Cache size: 7
2017/07/31 10:30:58 Cache size: 8
2017/07/31 10:30:58 Cache size: 9
2017/07/31 10:30:58 Cache size: 10
2017/07/31 10:30:58 Cache size: 11
2017/07/31 10:30:58 Cache size: 12
2017/07/31 10:30:58 Cache size: 13
2017/07/31 10:30:58 Cache size: 14
2017/07/31 10:30:59 Cache size: 15
2017/07/31 10:30:59 Cache size: 16
2017/07/31 10:30:59 Cache size: 17
2017/07/31 10:30:59 Cache size: 18
2017/07/31 10:30:59 Cache size: 19
2017/07/31 10:30:59 Cache size: 20
2017/07/31 10:30:59 Cache size: 21
2017/07/31 10:30:59 Cache size: 22
2017/07/31 10:30:59 Cache size: 23
2017/07/31 10:31:00 Cache size: 24
2017/07/31 10:31:00 Cache size: 25
2017/07/31 10:31:00 Cache size: 26
2017/07/31 10:31:00 Cache size: 27
2017/07/31 10:31:00 Cache size: 28
2017/07/31 10:31:00 Cache size: 29
2017/07/31 10:31:00 Cache size: 30
2017/07/31 10:31:00 Cache size: 31
2017/07/31 10:31:00 Cache size: 32
2017/07/31 10:31:00 Cache size: 33
2017/07/31 10:31:01 Cache size: 34
2017/07/31 10:31:01 Cache size: 35
2017/07/31 10:31:01 Cache size: 36
2017/07/31 10:31:01 Cache size: 37
2017/07/31 10:31:01 Cache size: 38
2017/07/31 10:31:01 Cache size: 39
2017/07/31 10:31:01 Cache size: 40
2017/07/31 10:31:01 Cache size: 41
2017/07/31 10:31:01 Cache size: 42
2017/07/31 10:31:01 Cache size: 43
2017/07/31 10:31:02 Cache size: 44
2017/07/31 10:31:02 Cache size: 45
2017/07/31 10:31:02 Cache size: 46
2017/07/31 10:31:02 Cache size: 47
2017/07/31 10:31:02 Cache size: 48
2017/07/31 10:31:02 Cache size: 49
2017/07/31 10:31:02 Cache size: 50
2017/07/31 10:31:02 Cache size: 51
2017/07/31 10:31:02 Cache size: 52
2017/07/31 10:31:02 Cache size: 53
2017/07/31 10:31:03 Cache size: 54
2017/07/31 10:31:03 Cache size: 55
2017/07/31 10:31:03 Cache size: 56
2017/07/31 10:31:03 Cache size: 57
2017/07/31 10:31:03 Cache size: 58
2017/07/31 10:31:03 Cache size: 59
2017/07/31 10:31:03 Cache size: 60
2017/07/31 10:31:03 Cache size: 61
2017/07/31 10:31:03 Cache size: 62
2017/07/31 10:31:04 Cache size: 63
2017/07/31 10:31:04 Cache size: 64
2017/07/31 10:31:04 Cache size: 65
2017/07/31 10:31:04 Cache size: 66
2017/07/31 10:31:04 Cache size: 67
2017/07/31 10:31:04 Cache size: 68
2017/07/31 10:31:04 Cache size: 69
2017/07/31 10:31:04 Cache size: 70
2017/07/31 10:31:04 Cache size: 71
2017/07/31 10:31:04 Cache size: 72
2017/07/31 10:31:05 Cache size: 73
2017/07/31 10:31:05 Cache size: 74
2017/07/31 10:31:05 Cache size: 75
2017/07/31 10:31:05 Cache size: 76
2017/07/31 10:31:05 Cache size: 77
2017/07/31 10:31:05 Cache size: 78
2017/07/31 10:31:05 Cache size: 79
2017/07/31 10:31:05 Cache size: 80
2017/07/31 10:31:05 Cache size: 81
2017/07/31 10:31:05 Cache size: 82
2017/07/31 10:31:06 Cache size: 83
2017/07/31 10:31:06 Cache size: 84
2017/07/31 10:31:06 Cache size: 85
2017/07/31 10:31:06 Cache size: 86
2017/07/31 10:31:06 Cache size: 87
2017/07/31 10:31:06 Cache size: 88
2017/07/31 10:31:06 Cache size: 89
2017/07/31 10:31:06 Cache size: 90
2017/07/31 10:31:06 Cache size: 91
2017/07/31 10:31:07 Cache size: 92
2017/07/31 10:31:07 Cache size: 93
2017/07/31 10:31:07 Cache size: 94
2017/07/31 10:31:07 Cache size: 95
2017/07/31 10:31:07 Cache size: 96
2017/07/31 10:31:07 Cache size: 97
This key(item_0) has expired
2017/07/31 10:31:07 Cache size: 97
This key(item_1) has expired
2017/07/31 10:31:07 Cache size: 97
This key(item_2) has expired
2017/07/31 10:31:07 Cache size: 97
This key(item_3) has expired
2017/07/31 10:31:07 Cache size: 97
This key(item_4) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_5) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_6) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_7) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_8) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_9) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_10) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_11) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_12) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_13) has expired
2017/07/31 10:31:08 Cache size: 97
This key(item_14) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_15) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_16) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_17) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_18) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_19) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_20) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_21) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_22) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_23) has expired
2017/07/31 10:31:09 Cache size: 97
This key(item_24) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_25) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_26) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_27) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_28) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_29) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_30) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_31) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_32) has expired
2017/07/31 10:31:10 Cache size: 97
This key(item_33) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_34) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_35) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_36) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_37) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_38) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_39) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_40) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_41) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_42) has expired
2017/07/31 10:31:11 Cache size: 97
This key(item_43) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_44) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_45) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_46) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_47) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_48) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_49) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_50) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_51) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_52) has expired
2017/07/31 10:31:12 Cache size: 97
This key(item_53) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_54) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_55) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_56) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_57) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_58) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_59) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_60) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_61) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_62) has expired
2017/07/31 10:31:13 Cache size: 97
This key(item_63) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_64) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_65) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_66) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_67) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_68) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_69) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_70) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_71) has expired
2017/07/31 10:31:14 Cache size: 97
This key(item_72) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_73) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_74) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_75) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_76) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_77) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_78) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_79) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_80) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_81) has expired
2017/07/31 10:31:15 Cache size: 97
This key(item_82) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_83) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_84) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_85) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_86) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_87) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_88) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_89) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_90) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_91) has expired
2017/07/31 10:31:16 Cache size: 97
This key(item_92) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_93) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_94) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_95) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_96) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_97) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_98) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_99) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_100) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_101) has expired
2017/07/31 10:31:17 Cache size: 97
This key(item_102) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_103) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_104) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_105) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_106) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_107) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_108) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_109) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_110) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_111) has expired
2017/07/31 10:31:18 Cache size: 97
This key(item_112) has expired
2017/07/31 10:31:19 Cache size: 97
This key(item_113) has expired
2017/07/31 10:31:19 Cache size: 97
This key(item_114) has expired
2017/07/31 10:31:19 Cache size: 97
This key(item_115) has expired
2017/07/31 10:31:19 Cache size: 97
This key(item_116) has expired
2017/07/31 10:31:19 Cache size: 97
This key(item_117) has expired
2017/07/31 10:31:19 Cache size: 97
This key(item_118) has expired
2017/07/31 10:31:19 Cache size: 97
This key(item_119) has expired
2017/07/31 10:31:19 Cache size: 97
This key(item_120) has expired
2017/07/31 10:31:19 Cache size: 97
2017/07/31 10:31:19 Cache size: 98
This key(item_121) has expired
2017/07/31 10:31:20 Cache size: 98
This key(item_122) has expired
2017/07/31 10:31:20 Cache size: 98
This key(item_123) has expired
2017/07/31 10:31:20 Cache size: 98
This key(item_124) has expired
2017/07/31 10:31:20 Cache size: 98
This key(item_125) has expired
2017/07/31 10:31:20 Cache size: 98
This key(item_126) has expired
2017/07/31 10:31:20 Cache size: 98
This key(item_127) has expired
This key(item_128) has expired
2017/07/31 10:31:20 Cache size: 97
This key(item_129) has expired
2017/07/31 10:31:20 Cache size: 97
2017/07/31 10:31:20 Cache size: 97
This key(item_130) has expired
2017/07/31 10:31:21 Cache size: 97
This key(item_131) has expired
2017/07/31 10:31:21 Cache size: 98
This key(item_132) has expired
2017/07/31 10:31:21 Cache size: 98
This key(item_133) has expired
This key(item_134) has expired
2017/07/31 10:31:21 Cache size: 97
2017/07/31 10:31:21 Cache size: 98
This key(item_135) has expired
This key(item_136) has expired
2017/07/31 10:31:21 Cache size: 97
2017/07/31 10:31:21 Cache size: 97
This key(item_137) has expired
2017/07/31 10:31:21 Cache size: 98
This key(item_138) has expired
2017/07/31 10:31:21 Cache size: 98
This key(item_139) has expired
2017/07/31 10:31:21 Cache size: 98
This key(item_140) has expired
2017/07/31 10:31:22 Cache size: 98
This key(item_141) has expired
2017/07/31 10:31:22 Cache size: 98
This key(item_142) has expired
2017/07/31 10:31:22 Cache size: 98
This key(item_143) has expired
2017/07/31 10:31:22 Cache size: 98
This key(item_144) has expired
2017/07/31 10:31:22 Cache size: 98
This key(item_145) has expired
2017/07/31 10:31:22 Cache size: 98
This key(item_146) has expired
2017/07/31 10:31:22 Cache size: 98
This key(item_147) has expired
2017/07/31 10:31:22 Cache size: 97
This key(item_148) has expired
2017/07/31 10:31:22 Cache size: 97
This key(item_149) has expired
This key(item_150) has expired
2017/07/31 10:31:22 Cache size: 97
2017/07/31 10:31:23 Cache size: 98
This key(item_151) has expired
2017/07/31 10:31:23 Cache size: 98
This key(item_152) has expired
2017/07/31 10:31:23 Cache size: 98
This key(item_153) has expired
2017/07/31 10:31:23 Cache size: 98
This key(item_154) has expired
2017/07/31 10:31:23 Cache size: 97
This key(item_155) has expired
2017/07/31 10:31:23 Cache size: 98
This key(item_156) has expired
2017/07/31 10:31:23 Cache size: 98
This key(item_157) has expired
This key(item_158) has expired
2017/07/31 10:31:23 Cache size: 97
2017/07/31 10:31:23 Cache size: 98
This key(item_159) has expired
2017/07/31 10:31:23 Cache size: 98
This key(item_160) has expired
2017/07/31 10:31:24 Cache size: 98
This key(item_161) has expired
2017/07/31 10:31:24 Cache size: 97
This key(item_162) has expired
This key(item_163) has expired
2017/07/31 10:31:24 Cache size: 97
2017/07/31 10:31:24 Cache size: 97
This key(item_164) has expired
2017/07/31 10:31:24 Cache size: 98
This key(item_165) has expired
This key(item_166) has expired
2017/07/31 10:31:24 Cache size: 97
This key(item_167) has expired
2017/07/31 10:31:24 Cache size: 97
This key(item_168) has expired
2017/07/31 10:31:24 Cache size: 97
This key(item_169) has expired
2017/07/31 10:31:24 Cache size: 97
2017/07/31 10:31:25 Cache size: 98
This key(item_170) has expired
This key(item_171) has expired
2017/07/31 10:31:25 Cache size: 97
This key(item_172) has expired
2017/07/31 10:31:25 Cache size: 97
This key(item_173) has expired
2017/07/31 10:31:25 Cache size: 97
This key(item_174) has expired
2017/07/31 10:31:25 Cache size: 97
2017/07/31 10:31:25 Cache size: 98
This key(item_175) has expired
2017/07/31 10:31:25 Cache size: 98
This key(item_176) has expired
2017/07/31 10:31:25 Cache size: 98
This key(item_177) has expired
2017/07/31 10:31:25 Cache size: 98
This key(item_178) has expired
2017/07/31 10:31:25 Cache size: 98
This key(item_179) has expired
2017/07/31 10:31:26 Cache size: 97
This key(item_180) has expired
This key(item_181) has expired
2017/07/31 10:31:26 Cache size: 97
2017/07/31 10:31:26 Cache size: 98
This key(item_182) has expired
2017/07/31 10:31:26 Cache size: 98
This key(item_183) has expired
2017/07/31 10:31:26 Cache size: 97
This key(item_184) has expired
This key(item_185) has expired
2017/07/31 10:31:26 Cache size: 97
2017/07/31 10:31:26 Cache size: 98
This key(item_186) has expired
2017/07/31 10:31:26 Cache size: 98
This key(item_187) has expired
2017/07/31 10:31:26 Cache size: 98
This key(item_188) has expired
This key(item_189) has expired
2017/07/31 10:31:26 Cache size: 97
This key(item_190) has expired
2017/07/31 10:31:27 Cache size: 97
2017/07/31 10:31:27 Cache size: 98
This key(item_191) has expired
2017/07/31 10:31:27 Cache size: 97
This key(item_192) has expired
2017/07/31 10:31:27 Cache size: 97
This key(item_193) has expired
2017/07/31 10:31:27 Cache size: 98
This key(item_194) has expired
This key(item_195) has expired
2017/07/31 10:31:27 Cache size: 97
This key(item_196) has expired
2017/07/31 10:31:27 Cache size: 97
This key(item_197) has expired
2017/07/31 10:31:27 Cache size: 97
2017/07/31 10:31:27 Cache size: 98
This key(item_198) has expired
This key(item_199) has expired
2017/07/31 10:31:27 Cache size: 97
This key(item_200) has expired
2017/07/31 10:31:28 Cache size: 97
This key(item_201) has expired
2017/07/31 10:31:28 Cache size: 97
2017/07/31 10:31:28 Cache size: 98
This key(item_202) has expired
This key(item_203) has expired
2017/07/31 10:31:28 Cache size: 97
2017/07/31 10:31:28 Cache size: 98
This key(item_204) has expired
2017/07/31 10:31:28 Cache size: 97
This key(item_205) has expired
This key(item_206) has expired
2017/07/31 10:31:28 Cache size: 97
This key(item_207) has expired
2017/07/31 10:31:28 Cache size: 97
2017/07/31 10:31:28 Cache size: 98
This key(item_208) has expired
2017/07/31 10:31:28 Cache size: 98
This key(item_209) has expired
This key(item_210) has expired
2017/07/31 10:31:29 Cache size: 98
This key(item_211) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_212) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_213) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_214) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_215) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_216) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_217) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_218) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_219) has expired
2017/07/31 10:31:29 Cache size: 97
This key(item_220) has expired
2017/07/31 10:31:30 Cache size: 97
This key(item_221) has expired
2017/07/31 10:31:30 Cache size: 97
This key(item_222) has expired
2017/07/31 10:31:30 Cache size: 97
2017/07/31 10:31:30 Cache size: 97
This key(item_223) has expired
2017/07/31 10:31:30 Cache size: 98
This key(item_224) has expired
2017/07/31 10:31:30 Cache size: 98
This key(item_225) has expired
2017/07/31 10:31:30 Cache size: 98
This key(item_226) has expired
This key(item_227) has expired
2017/07/31 10:31:30 Cache size: 97
2017/07/31 10:31:30 Cache size: 98
This key(item_228) has expired
2017/07/31 10:31:31 Cache size: 98
This key(item_229) has expired
2017/07/31 10:31:31 Cache size: 98
This key(item_230) has expired
2017/07/31 10:31:31 Cache size: 98
This key(item_231) has expired
2017/07/31 10:31:31 Cache size: 98
This key(item_232) has expired
This key(item_233) has expired
2017/07/31 10:31:31 Cache size: 97
2017/07/31 10:31:31 Cache size: 98
This key(item_234) has expired
2017/07/31 10:31:31 Cache size: 98
This key(item_235) has expired
2017/07/31 10:31:31 Cache size: 98
This key(item_236) has expired
2017/07/31 10:31:31 Cache size: 98
This key(item_237) has expired
This key(item_238) has expired
2017/07/31 10:31:31 Cache size: 97
This key(item_239) has expired
2017/07/31 10:31:32 Cache size: 97
2017/07/31 10:31:32 Cache size: 98
This key(item_240) has expired
This key(item_241) has expired
2017/07/31 10:31:32 Cache size: 97
2017/07/31 10:31:32 Cache size: 97
This key(item_242) has expired
This key(item_243) has expired
2017/07/31 10:31:32 Cache size: 97
2017/07/31 10:31:32 Cache size: 98
This key(item_244) has expired
2017/07/31 10:31:32 Cache size: 98
This key(item_245) has expired
2017/07/31 10:31:32 Cache size: 98
This key(item_246) has expired
2017/07/31 10:31:32 Cache size: 98
This key(item_247) has expired
2017/07/31 10:31:32 Cache size: 98
This key(item_248) has expired
This key(item_249) has expired
2017/07/31 10:31:33 Cache size: 97
This key(item_250) has expired
2017/07/31 10:31:33 Cache size: 97
2017/07/31 10:31:33 Cache size: 98
This key(item_251) has expired
2017/07/31 10:31:33 Cache size: 98
This key(item_252) has expired
2017/07/31 10:31:33 Cache size: 98
This key(item_253) has expired
2017/07/31 10:31:33 Cache size: 98
This key(item_254) has expired
This key(item_255) has expired
2017/07/31 10:31:33 Cache size: 97
This key(item_256) has expired
2017/07/31 10:31:33 Cache size: 97
This key(item_257) has expired
2017/07/31 10:31:33 Cache size: 97
This key(item_258) has expired
2017/07/31 10:31:33 Cache size: 97
2017/07/31 10:31:34 Cache size: 98
This key(item_259) has expired
This key(item_260) has expired
2017/07/31 10:31:34 Cache size: 97
2017/07/31 10:31:34 Cache size: 97
This key(item_261) has expired
2017/07/31 10:31:34 Cache size: 97
This key(item_262) has expired
This key(item_263) has expired
2017/07/31 10:31:34 Cache size: 97
2017/07/31 10:31:34 Cache size: 98
This key(item_264) has expired
This key(item_265) has expired
2017/07/31 10:31:34 Cache size: 97
This key(item_266) has expired
2017/07/31 10:31:34 Cache size: 97
2017/07/31 10:31:34 Cache size: 97
This key(item_267) has expired
2017/07/31 10:31:35 Cache size: 98
This key(item_268) has expired
2017/07/31 10:31:35 Cache size: 98
This key(item_269) has expired
2017/07/31 10:31:35 Cache size: 97
This key(item_270) has expired
2017/07/31 10:31:35 Cache size: 98
This key(item_271) has expired
This key(item_272) has expired
2017/07/31 10:31:35 Cache size: 97
This key(item_273) has expired
2017/07/31 10:31:35 Cache size: 97
This key(item_274) has expired
2017/07/31 10:31:35 Cache size: 97
This key(item_275) has expired
2017/07/31 10:31:35 Cache size: 97
2017/07/31 10:31:35 Cache size: 97
This key(item_276) has expired
2017/07/31 10:31:35 Cache size: 98
This key(item_277) has expired
2017/07/31 10:31:36 Cache size: 97
This key(item_278) has expired
2017/07/31 10:31:36 Cache size: 98
This key(item_279) has expired
2017/07/31 10:31:36 Cache size: 98
This key(item_280) has expired
This key(item_281) has expired
2017/07/31 10:31:36 Cache size: 97
This key(item_282) has expired
2017/07/31 10:31:36 Cache size: 97
2017/07/31 10:31:36 Cache size: 98
This key(item_283) has expired
2017/07/31 10:31:36 Cache size: 98
This key(item_284) has expired
2017/07/31 10:31:36 Cache size: 98
This key(item_285) has expired
2017/07/31 10:31:36 Cache size: 98
This key(item_286) has expired
This key(item_287) has expired
2017/07/31 10:31:36 Cache size: 97
2017/07/31 10:31:37 Cache size: 98
This key(item_288) has expired
This key(item_289) has expired
2017/07/31 10:31:37 Cache size: 97
2017/07/31 10:31:37 Cache size: 98
This key(item_290) has expired
2017/07/31 10:31:37 Cache size: 98
This key(item_291) has expired
2017/07/31 10:31:37 Cache size: 98
This key(item_292) has expired
2017/07/31 10:31:37 Cache size: 98
This key(item_293) has expired
2017/07/31 10:31:37 Cache size: 98
This key(item_294) has expired
2017/07/31 10:31:37 Cache size: 98
This key(item_295) has expired
2017/07/31 10:31:37 Cache size: 97
This key(item_296) has expired
2017/07/31 10:31:37 Cache size: 98
This key(item_297) has expired
2017/07/31 10:31:38 Cache size: 97
This key(item_298) has expired
2017/07/31 10:31:38 Cache size: 98
This key(item_299) has expired
2017/07/31 10:31:38 Cache size: 98
This key(item_300) has expired
2017/07/31 10:31:38 Cache size: 97
This key(item_301) has expired
This key(item_302) has expired
2017/07/31 10:31:38 Cache size: 97
2017/07/31 10:31:38 Cache size: 97
This key(item_303) has expired
This key(item_304) has expired
2017/07/31 10:31:38 Cache size: 97
2017/07/31 10:31:38 Cache size: 98
This key(item_305) has expired
2017/07/31 10:31:38 Cache size: 98
This key(item_306) has expired
2017/07/31 10:31:38 Cache size: 98
This key(item_307) has expired
2017/07/31 10:31:39 Cache size: 98
This key(item_308) has expired
2017/07/31 10:31:39 Cache size: 98
This key(item_309) has expired
2017/07/31 10:31:39 Cache size: 98
This key(item_310) has expired
2017/07/31 10:31:39 Cache size: 98
This key(item_311) has expired
2017/07/31 10:31:39 Cache size: 98
This key(item_312) has expired
2017/07/31 10:31:39 Cache size: 97
This key(item_313) has expired
2017/07/31 10:31:39 Cache size: 98
This key(item_314) has expired
This key(item_315) has expired
2017/07/31 10:31:39 Cache size: 97
This key(item_316) has expired
2017/07/31 10:31:39 Cache size: 97
This key(item_317) has expired
2017/07/31 10:31:39 Cache size: 97
2017/07/31 10:31:40 Cache size: 98
This key(item_318) has expired
This key(item_319) has expired
2017/07/31 10:31:40 Cache size: 97
This key(item_320) has expired
2017/07/31 10:31:40 Cache size: 97
2017/07/31 10:31:40 Cache size: 98
This key(item_321) has expired
2017/07/31 10:31:40 Cache size: 98
This key(item_322) has expired
2017/07/31 10:31:40 Cache size: 98
This key(item_323) has expired
This key(item_324) has expired
2017/07/31 10:31:40 Cache size: 97
2017/07/31 10:31:40 Cache size: 98
This key(item_325) has expired
This key(item_326) has expired
2017/07/31 10:31:40 Cache size: 97
2017/07/31 10:31:41 Cache size: 98
This key(item_327) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_328) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_329) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_330) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_331) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_332) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_333) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_334) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_335) has expired
2017/07/31 10:31:41 Cache size: 98
This key(item_336) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_337) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_338) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_339) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_340) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_341) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_342) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_343) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_344) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_345) has expired
2017/07/31 10:31:42 Cache size: 98
This key(item_346) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_347) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_348) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_349) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_350) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_351) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_352) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_353) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_354) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_355) has expired
2017/07/31 10:31:43 Cache size: 98
This key(item_356) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_357) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_358) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_359) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_360) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_361) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_362) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_363) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_364) has expired
2017/07/31 10:31:44 Cache size: 98
This key(item_365) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_366) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_367) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_368) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_369) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_370) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_371) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_372) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_373) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_374) has expired
2017/07/31 10:31:45 Cache size: 98
This key(item_375) has expired
2017/07/31 10:31:46 Cache size: 97
This key(item_376) has expired
This key(item_377) has expired
2017/07/31 10:31:46 Cache size: 97
2017/07/31 10:31:46 Cache size: 98
This key(item_378) has expired
This key(item_379) has expired
2017/07/31 10:31:46 Cache size: 97
2017/07/31 10:31:46 Cache size: 98
This key(item_380) has expired
2017/07/31 10:31:46 Cache size: 97
This key(item_381) has expired
2017/07/31 10:31:46 Cache size: 98
This key(item_382) has expired
This key(item_383) has expired
2017/07/31 10:31:46 Cache size: 97
2017/07/31 10:31:46 Cache size: 98
This key(item_384) has expired
2017/07/31 10:31:46 Cache size: 98
This key(item_385) has expired
2017/07/31 10:31:47 Cache size: 98
This key(item_386) has expired
This key(item_387) has expired
2017/07/31 10:31:47 Cache size: 97
2017/07/31 10:31:47 Cache size: 98
This key(item_388) has expired
2017/07/31 10:31:47 Cache size: 98
This key(item_389) has expired
2017/07/31 10:31:47 Cache size: 98
2017/07/31 10:31:47 Cache size: 99
2017/07/31 10:31:47 Cache size: 100
2017/07/31 10:31:47 Cache size: 101
2017/07/31 10:31:47 Cache size: 102
2017/07/31 10:31:47 Cache size: 103
2017/07/31 10:31:48 Cache size: 104
2017/07/31 10:31:48 Cache size: 105

Export Touch function

By using ttlcache.Cache#SkipTTLExtensionOnHit(bool) you can disable touching the cache entry when using Get. This offers an additional level over control TTL behaviour.

To offer even more control of this mechanism, I would like to propose to export a touch function as well:

func (cache *Cache) Touch(key string) error {
	cache.mutex.Lock()
	item, exists := cache.items[key]
	if !exists {
		return ErrNotFound
	}
	
	item.touch()
	cache.mutex.Unlock()
	return nil
}

Or something similar ๐Ÿ˜„

This is a lot more convenient that manually getting and setting the item with a new TTL.

Add a method that activates transaction mode

It would be nice to have a method that would accept a function and execute it with a new global transaction mutex locked:

func (c *Cache[K, V]) Tx(fn func(*Tx[K, V])) {
         c.txMu.Lock()
         tx := c.beginTx()
         fn(tx)
         c.txMu.Unlock()
}

This was addressed in this issue, however, due to some limitations of the previous versions it was closed.

Migrating from ReneKroon

Coming from the ReneKroon version of this cache, I'm hitting a few issues:

  • Where is the documentation? Maybe add a link to it in the README? Found it, just hidden. And the godoc isn't answering my questions either.
  • Add an example how to Get without extending the TTL.
  • Is it really necessary to start another goroutine for every cache?
  • Get doesn't return an error anymore when the object can't be found in the cache.

Limit on no. of keys stored in ttlcache?

is there a limit on no. of keys that can be stored using ttlcache library?
In our service we recently faced increase in response time when ttlcache was being used.

Add a GetWithTTL method

Since we have a SetWithTTL it would be nice to know how much remain time an item has for expired when we recover it from the cache.
func GetWithTTL(key) (data, ttl, err)

Feature request: Add option to set how many "oldest items" should be purged when the cache limit is reached

From what I can tell, if upon calling a cache.Set(...) with an item not already in the cache, but the cache is at the configured limit (e.g. 100), only the single oldest item (closest to reaching its TTL) is removed.

Could there be an option we can set on the cache, controlling the amount of items that are removed whenever a cache.Set(...) is called that would add a new item over the limit?
E.g. if the limit is set to 1000, we're adding an item #1001, and then instead of just deleting 1 item, we can set it to removing the oldest 100 items, reducing the cache back to 901 current items.

I think this would be more efficient, especially for environments where there are a lot of unique items, but most appear rarely, so the oldest items in the cache don't matter as much and can safely be deleted at any time the limit is reached, in one batch.

[enhancement] support for invalidate

it would be amazing feature if ttlcache would additionally support invalidation of a given cache entry besides the time based expiration.
invalidation could be sync or async (depending of a boolean flag). upon invalidation, the expiration callback could be executed.

ExpireCallback calls on Remove() item

ExpireCallback is a common callback for two cases:

  1. Item expired by TTL
  2. Item removed.
    It's not documented behaviour (but it's easy to check through the code).
    I guess, you should add separated callback for Remove() call, or configure cache to do not call ExpireCallback on removing an item, because there's no way to remove the item with TTL without calling the expiration callback if it was set.

func (cache *Cache) startExpirationProcessing() - Never Exits and Leaves residual Goroutines.

Hey, I noticed there is no exit case in your Goroutine here:
https://github.com/ReneKroon/ttlcache/blob/49e7d8e56413a8ee1f826b064e7268f6e10ca2d3/cache.go#L52

I was developing an application that start multiple caches as child-struct. But when the parent is removed this function - func (cache *Cache) startExpirationProcessing() - keeps running forever.

Do you perhaps have a way of killing it somewhere in your code ? I like your package and I would love to keep using it, but in the current state I can not :(

Low timeout loader function race

After running this for a while I noticed it's possible to get a key not found error (ErrNotFound), even when using a loader function. Repro below.

// Cache sometimes returns key not found under parallel access with a loader function
func TestCache_TestLoaderFunctionParallelKeyAccess(t *testing.T) {
	t.Parallel()
	cache := NewCache()

	cache.SetLoaderFunction(func(key string) (data interface{}, ttl time.Duration, err error) {
		time.Sleep(time.Millisecond*300)
		return "1", 1*time.Nanosecond, nil
	})


	wg := sync.WaitGroup{}
	errCount := uint64(0)
	for i:=0; i<200; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			value, found := cache.Get("foo")
			if value != "1" || found != nil { // Use an atomic to avoid spamming logs
				atomic.AddUint64(&errCount, 1)
			}
		}()

	}

	wg.Wait()

	assert.Equalf(t, uint64(0), errCount, "expected 0 errs, got %d", errCount)

	cache.Close()
}

I spent a while trying to distill this down further, and I think it occurs when:

  • The TTL is low (10ms when I first noticed the bug - probably low enough that it could be triggered by a GC cycle)
  • Multiple goroutines try to access the same key at the same time
  • Some goroutines block while waiting for the loaderFunction
  • By the time they unblock the freshly set key has already expired!

I think it would be more consistent if the blocked goroutines always got the output of the loaderfunction that they were blocked by. I would probably use singleflight, but there might be a cleaner way to do it.

If you think it's a good idea then I'm happy to put together a PR.

Add Range/ForEach method

It should iterate over all cache items and pass each of them into a func that is provided as a parameter:

func (c *Cache[K, V]) Range(fn func(*Item[K, V]) bool) {
     // ...
}

The bool return value should indicate whether the iteration should continue or be stopped.

Unexported expireCallback type makes it difficult to mock the SetExpireCallback method

I'm using ttlcache but want to test the behaviour of the code using it without relying on actual time passing in a reliable way, and therefore want to use a mock cache. However, I can't make an interface with a compatible SetExpireCallback method, because it takes a specific, unexported type expireCallback instead of a plain func(string, interface{}) type.

Ideally I'd either have the SetExpireCallback method signature use the plain func(string, interface{}) type for it's parameter, though a less pleasant alternative is to have the expireCallback type exported as ExpireCallback so that my interface can use it.

expired behavior strange?

I have the expirationCallback and an expire-time of 5 seconds
Let's say I put a key in at 12:00:00
when I keep getting the key from the cache within 5 seconds, the cache expiration message is never shown.
My assumption was, that the expiration was measured from the moment it was put into the cache.
so it would expire at 12:00:05
or... am I doing something wrong?

Auxiliary data with the cache so that LoaderFunc can be versatile

Defining a custom loader is great however its usability is pretty low as you have no access to any user_data that most of the time you need in order to let's say do HTTP(s) requests, file reads etc. This could be solved by being able to add a whatever user_data object when you are creating the ttlcache.Cache object. Exposing this user_data object to the loader along with the key will give the ability to create really powerful loader functions.

Remove() causes panic: runtime error: index out of range

This:

cache = ttlcache.NewCache()
cache.Set("key", "value")
//cache.Remove("key")
count = cache.Count()
fmt.Printf("cache has %d keys\n", count)

prints this:

cache has 1 keys

But if you uncomment the Remove() call, you get this:

panic: runtime error: index out of range
goroutine 1 [running]:
panic(0x76ed00, 0xc820014120)
/software/vertres/installs/go/src/runtime/panic.go:464 +0x3e6
github.com/diegobernardes/ttlcache.priorityQueue.Swap(0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff)
/nfs/users/nfs_s/sb10/src/go/src/github.com/diegobernardes/ttlcache/priority_queue.go:54 +0x1f9
github.com/diegobernardes/ttlcache.(_priorityQueue).Swap(0xc8200d8020, 0x0, 0xffffffffffffffff)
:3 +0xaa
container/heap.Remove(0x7f595f9ac750, 0xc8200d8020, 0x0, 0x0, 0x0)
/software/vertres/installs/go/src/container/heap/heap.go:74 +0x6c
github.com/diegobernardes/ttlcache.(_priorityQueue).remove(0xc8200d8020, 0xc8200e0000)
/nfs/users/nfs_s/sb10/src/go/src/github.com/diegobernardes/ttlcache/priority_queue.go:35 +0x56
github.com/diegobernardes/ttlcache.(*Cache).Remove(0xc8200de000, 0x7daeb8, 0x3, 0x6dd860)
/nfs/users/nfs_s/sb10/src/go/src/github.com/diegobernardes/ttlcache/cache.go:167 +0xed
[...]

Enhancement Req - Add option to the cache to prevent updates when using Set()

Use case:
I use the cache to capture data i receive over UDP. In some cases, i have competing producers that will send the same information for a Key. I want the first message to win and be the one persisted in the cache and not have further messages overwrite.

Looking at the cache.set() function, it searches for an existing element and overwrites it.
https://github.com/jellydator/ttlcache/blob/master/cache.go#L134-L142

I would like a new option for the cache set on creation to prevent overwrites

cache := ttlcache.New[string, string](
		ttlcache.WithTTL[string, string](30 * time.Minute),
                ttlcache.WithoutOverwrite(),
	)

//Pseudo-code to show the expectation
cache.Set("first", "value1", ttlcache.DefaultTTL)
cache.Set("first", "value2", ttlcache.DefaultTTL)
cache.Set("first", "value3", ttlcache.DefaultTTL)

v:= cache.Get("first").Value()
assert.Equal(t, "value1",v)

Panic with SetCheckExpirationCallback

hi, @ReneKroo

I have encountered some problems with ttlcache, I don't know why?
Can help solve this problem?
thanks.

  • The panic appears when this program runs below
    `
    func main() {
    cacheAD := ttlcache.NewCache()
    cacheAD.SetTTL(time.Second * time.Duration(5))
    cacheAD.SetCheckExpirationCallback(func(key string, value interface{}) bool {
    v := value.(*int)
    log.Printf("key=%v, value=%d\n", key, *v)
    return true
    })

    i := 2
    cacheAD.Set("a", &i)
    ch := make(chan struct{})
    <-ch
    }
    `

  • Error message
    2018/09/07 15:58:52 key=a, value=2 panic: runtime error: index out of range goroutine 18 [running]: github.com/ReneKroon/ttlcache.(*Cache).startExpirationProcessing(0xc04208c000) D:/workspace-go/busAnalyze/src/github.com/ReneKroon/ttlcache/cache.go:84 +0x4f8 created by github.com/ReneKroon/ttlcache.NewCache D:/workspace-go/busAnalyze/src/github.com/ReneKroon/ttlcache/cache.go:232 +0x11e

Feature request: Add option to get list of keys

Currently I have to know (by key) which cache entry I want to remove. My use case is that at some event multiple keys should get invalidated (e.g. all keys with prefix xy). Currently I can maintain my own list of keys using SetNewItemCallback and SetExpirationReasonCallback but as I would have to deal with concurrency there as well I would love to see something similar in this library!

Untested draft:

func (cache *Cache) GetKeys() ([]string, error) {
    cache.mutex.Lock()
    if cache.isShutDown {
        cache.mutex.Unlock()
        return nil, ErrClosed
    }

    keys := make([]string, len(cache.items))
    i := 0
    for k := range cache.items {
        keys[i] = k
        i++
    }

    cache.mutex.Unlock()

    return keys, nil
}

Cache Never Expires Over Certain TTL Value

I noticed while playing with this module that I can specify a TTL value from 10 to 50 seconds and everything works great!
However, if I specify a TTL value like 300, the cache never seems to expire. Apologies of I'm missing something simple here!

Code Snippet:

cache := ttlcache.NewCache()
ttl, err := strconv.Atoi(cacheTTL)
if err != nil {
    log.Fatal("Invalid TTL value: ", err)
}
cache.SetTTL(time.Duration(ttl) * time.Second)

expirationCallback := func(key string, _ ttlcache.EvictionReason, _ interface{}) {
    log.Info("Cache expired for key: ", key)
}
cache.SetExpirationReasonCallback(expirationCallback)

Crash

Jun 17 03:32:09 pro-boqs-app-003 boqs: goroutine 484637918 [running]:
Jun 17 03:32:09 pro-boqs-app-003 boqs: net/http.(*conn).serve.func1(0xc029a32a00)
Jun 17 03:32:09 pro-boqs-app-003 boqs: /usr/local/go/src/net/http/server.go:1769 +0x139
Jun 17 03:32:09 pro-boqs-app-003 boqs: panic(0xc8b580, 0x1517740)
Jun 17 03:32:09 pro-boqs-app-003 boqs: /usr/local/go/src/runtime/panic.go:522 +0x1b5
Jun 17 03:32:09 pro-boqs-app-003 boqs: github.com/bolcom/boqs/vendor/github.com/ReneKroon/ttlcache.priorityQueue.Less(0xc0009cd800, 0x47, 0x100, 0x47, 0x23, 0x5d06ed99)
Jun 17 03:32:09 pro-boqs-app-003 boqs: /go/src/github.com/bolcom/boqs/vendor/github.com/ReneKroon/ttlcache/priority_queue.go:43 +0x16e
Jun 17 03:32:09 pro-boqs-app-003 boqs: container/heap.up(0xef6c60, 0xc000968480, 0x47)
Jun 17 03:32:09 pro-boqs-app-003 boqs: /usr/local/go/src/container/heap/heap.go:93 +0x9f
Jun 17 03:32:09 pro-boqs-app-003 boqs: container/heap.Fix(0xef6c60, 0xc000968480, 0x47)
Jun 17 03:32:09 pro-boqs-app-003 boqs: /usr/local/go/src/container/heap/heap.go:86 +0x94
Jun 17 03:32:09 pro-boqs-app-003 boqs: github.com/bolcom/boqs/vendor/github.com/ReneKroon/ttlcache.(*priorityQueue).update(...)
Jun 17 03:32:09 pro-boqs-app-003 boqs: /go/src/github.com/bolcom/boqs/vendor/github.com/ReneKroon/ttlcache/priority_queue.go:18
Jun 17 03:32:09 pro-boqs-app-003 boqs: github.com/bolcom/boqs/vendor/github.com/ReneKroon/ttlcache.(*Cache).getItem(0xc0001852d0, 0xc0162dbd60, 0x1c, 0x0, 0xed0f58cf0)
Jun 17 03:32:09 pro-boqs-app-003 boqs: /go/src/github.com/bolcom/boqs/vendor/github.com/ReneKroon/ttlcache/cache.go:45 +0xeb
Jun 17 03:32:09 pro-boqs-app-003 boqs: github.com/bolcom/boqs/vendor/github.com/ReneKroon/ttlcache.(*Cache).Get(0xc0001852d0, 0xc0162dbd60, 0x1c, 0x0, 0x0, 0xeeea00)
Jun 17 03:32:09 pro-boqs-app-003 boqs: /go/src/github.com/bolcom/boqs/vendor/github.com/ReneKroon/ttlcache/cache.go:173 +0x50
Jun 17 03:32:09 pro-boqs-app-003 boqs: github.com/bolcom/boqs/internal/dao/cluster.(*PgTopologyCache).SubscribedQueues(0xc000972200, 0xc0162dbd60, 0x1c, 0x183ad9b0, 0xed176645f, 0x152b840, 0xc034089290, 0x3, 0x0, 0x120, ...)

Upgrade to v3

Before the stable release of v3, some additional, non-backwards compatible changes could be made. Here is a list of my proposed changes:

  • Update README.md
  • Allow TTL extension to be enabled/disabled per Get() call, rather than per cache instance. Having them both could also work: the global cache value would be used as the default one.
  • Use functional options to set cache options.
  • Remove the SimpleCache interface, since it serves no purpose, at least in this package. The idiomatic approach is to allow the client/user of the package to create their own interface, with their own limited set of methods. However, if there are clear and useful examples of where this is may be needed, this interface should be at least renamed to something more appropriate.
  • Rename the event methods and make them follow On[Noun](fn) naming pattern (e.g. OnExpiration(fn)). The provided functions should also be stored in a slice rather than a simple field. A good example of this is bolt's OnCommit.
  • Separate expiration queue and LRU item list.
  • Rename existing methods.
  • Remove evictionreason_enumer.go since it seems to add very little value yet creates quite a bit of unidiomatic code noise.
  • Clean up some of the code/add proper docs.
  • Add Loader interface and its helper types/funcs.
  • Improve mutex usage.
  • Make auto cleaner process optional. It should also be possible to stop and then restart it without closing the cache instance.
  • Remove Close(), replace it with Start() and Stop()
  • Remove error return values (most of them return only ErrClosed).
  • Add a method for manual expired item deletion.
  • Return/pass the whole Item type rather than its fields (applies to Get/Set/event functions).
  • Rewrite/improve tests.

I may have some more ideas later on.

What do you think @ReneKroon?

[enhancement] support for loader function

it would be awesome, if ttlcache could be given a loader function.
Upon a cache miss (key not present in the cache), the cache would call this loader func, and create/load the value for the key, and would put it in the cache with the default global expiration.

Remove() breaks expiration

Hi, this looks really promising, but has some unexpected behaviour:

    cache = ttlcache.NewCache()
    cache.SetExpirationCallback(func(key string, value interface{}) {
        fmt.Printf("This key(%s) has expired\n", key)
    })

    cache.SetWithTTL("keyWithTTL", "value", time.Duration(2 * time.Second))
    cache.Set("key", "value")
    //result = cache.Remove("key")

    value, exists = cache.Get("keyWithTTL")
    if exists {
        fmt.Printf("got %s for keyWithTTL\n", value)
    }
    count = cache.Count()
    fmt.Printf("cache has %d keys\n", count)

    <-time.After(3 * time.Second)

    value, exists = cache.Get("keyWithTTL")
    if exists {
        fmt.Printf("got %s for keyWithTTL\n", value)
    } else {
        fmt.Println("keyWithTTL has gone")
    }
    count = cache.Count()
    fmt.Printf("cache has %d keys\n", count)

The above prints:

got value for keyWithTTL
cache has 2 keys
This key(keyWithTTL) has expired
keyWithTTL has gone
cache has 1 keys

as expected. But if you uncomment the Remove() call, you get:

got value for keyWithTTL
cache has 1 keys
keyWithTTL has gone
cache has 1 keys

so it's in some weird inconsistent state, where you can't get the key, but Count() suggests it's there, and the ExpirationCallback never fired.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.