@ -33,23 +33,23 @@ const (
DefaultExpiration time . Duration = 0
DefaultExpiration time . Duration = 0
)
)
type Cache [ K comparable , T any ] struct {
type Cache [ T any ] struct {
* cache [ K, T]
* cache [ T]
// If this is confusing, see the comment at the bottom of New()
// If this is confusing, see the comment at the bottom of New()
}
}
type cache [ K comparable , T any ] struct {
type cache [ T any ] struct {
defaultExpiration time . Duration
defaultExpiration time . Duration
items map [ K ] Item [ T ]
items map [ string ] Item [ T ]
mu sync . RWMutex
mu sync . RWMutex
onEvicted func ( K , T )
onEvicted func ( string , T )
janitor * janitor [ K, T]
janitor * janitor [ T]
}
}
// Add an item to the cache, replacing any existing item. If the duration is 0
// Add an item to the cache, replacing any existing item. If the duration is 0
// (DefaultExpiration), the cache's default expiration time is used. If it is -1
// (DefaultExpiration), the cache's default expiration time is used. If it is -1
// (NoExpiration), the item never expires.
// (NoExpiration), the item never expires.
func ( c * cache [ K, T] ) Set ( k K , x T , d time . Duration ) {
func ( c * cache [ T] ) Set ( k string , x T , d time . Duration ) {
// "Inlining" of set
// "Inlining" of set
var e int64
var e int64
if d == DefaultExpiration {
if d == DefaultExpiration {
@ -68,7 +68,7 @@ func (c *cache[K, T]) Set(k K, x T, d time.Duration) {
c . mu . Unlock ( )
c . mu . Unlock ( )
}
}
func ( c * cache [ K, T] ) set ( k K , x T , d time . Duration ) {
func ( c * cache [ T] ) set ( k string , x T , d time . Duration ) {
var e int64
var e int64
if d == DefaultExpiration {
if d == DefaultExpiration {
d = c . defaultExpiration
d = c . defaultExpiration
@ -84,18 +84,18 @@ func (c *cache[K, T]) set(k K, x T, d time.Duration) {
// Add an item to the cache, replacing any existing item, using the default
// Add an item to the cache, replacing any existing item, using the default
// expiration.
// expiration.
func ( c * cache [ K, T] ) SetDefault ( k K , x T ) {
func ( c * cache [ T] ) SetDefault ( k string , x T ) {
c . Set ( k , x , DefaultExpiration )
c . Set ( k , x , DefaultExpiration )
}
}
// Add an item to the cache only if an item doesn't already exist for the given
// Add an item to the cache only if an item doesn't already exist for the given
// key, or if the existing item has expired. Returns an error otherwise.
// key, or if the existing item has expired. Returns an error otherwise.
func ( c * cache [ K, T] ) Add ( k K , x T , d time . Duration ) error {
func ( c * cache [ T] ) Add ( k string , x T , d time . Duration ) error {
c . mu . Lock ( )
c . mu . Lock ( )
_ , found := c . get ( k )
_ , found := c . get ( k )
if found {
if found {
c . mu . Unlock ( )
c . mu . Unlock ( )
return fmt . Errorf ( "Item %s already exists" , "key" )
return fmt . Errorf ( "Item %s already exists" , k )
}
}
c . set ( k , x , d )
c . set ( k , x , d )
c . mu . Unlock ( )
c . mu . Unlock ( )
@ -104,12 +104,12 @@ func (c *cache[K, T]) Add(k K, x T, d time.Duration) error {
// Set a new value for the cache key only if it already exists, and the existing
// Set a new value for the cache key only if it already exists, and the existing
// item hasn't expired. Returns an error otherwise.
// item hasn't expired. Returns an error otherwise.
func ( c * cache [ K, T] ) Replace ( k K , x T , d time . Duration ) error {
func ( c * cache [ T] ) Replace ( k string , x T , d time . Duration ) error {
c . mu . Lock ( )
c . mu . Lock ( )
_ , found := c . get ( k )
_ , found := c . get ( k )
if ! found {
if ! found {
c . mu . Unlock ( )
c . mu . Unlock ( )
return fmt . Errorf ( "Item %s doesn't exist" , "key" )
return fmt . Errorf ( "Item %s doesn't exist" , k )
}
}
c . set ( k , x , d )
c . set ( k , x , d )
c . mu . Unlock ( )
c . mu . Unlock ( )
@ -118,7 +118,7 @@ func (c *cache[K, T]) Replace(k K, x T, d time.Duration) error {
// Get an item from the cache. Returns the item or nil, and a bool indicating
// Get an item from the cache. Returns the item or nil, and a bool indicating
// whether the key was found.
// whether the key was found.
func ( c * cache [ K, T] ) Get ( k K ) ( T , bool ) {
func ( c * cache [ T] ) Get ( k string ) ( T , bool ) {
c . mu . RLock ( )
c . mu . RLock ( )
// "Inlining" of get and Expired
// "Inlining" of get and Expired
item , found := c . items [ k ]
item , found := c . items [ k ]
@ -140,7 +140,7 @@ func (c *cache[K, T]) Get(k K) (T, bool) {
// It returns the item or nil, the expiration time if one is set (if the item
// It returns the item or nil, the expiration time if one is set (if the item
// never expires a zero value for time.Time is returned), and a bool indicating
// never expires a zero value for time.Time is returned), and a bool indicating
// whether the key was found.
// whether the key was found.
func ( c * cache [ K, T] ) GetWithExpiration ( k K ) ( T , time . Time , bool ) {
func ( c * cache [ T] ) GetWithExpiration ( k string ) ( T , time . Time , bool ) {
c . mu . RLock ( )
c . mu . RLock ( )
// "Inlining" of get and Expired
// "Inlining" of get and Expired
item , found := c . items [ k ]
item , found := c . items [ k ]
@ -166,7 +166,7 @@ func (c *cache[K, T]) GetWithExpiration(k K) (T, time.Time, bool) {
return item . Object , time . Time { } , true
return item . Object , time . Time { } , true
}
}
func ( c * cache [ K, T] ) get ( k K ) ( T , bool ) {
func ( c * cache [ T] ) get ( k string ) ( T , bool ) {
item , found := c . items [ k ]
item , found := c . items [ k ]
if ! found {
if ! found {
return * new ( T ) , false
return * new ( T ) , false
@ -181,7 +181,7 @@ func (c *cache[K, T]) get(k K) (T, bool) {
}
}
// Delete an item from the cache. Does nothing if the key is not in the cache.
// Delete an item from the cache. Does nothing if the key is not in the cache.
func ( c * cache [ K, T] ) Delete ( k K ) {
func ( c * cache [ T] ) Delete ( k string ) {
c . mu . Lock ( )
c . mu . Lock ( )
v , evicted := c . delete ( k )
v , evicted := c . delete ( k )
c . mu . Unlock ( )
c . mu . Unlock ( )
@ -190,7 +190,7 @@ func (c *cache[K, T]) Delete(k K) {
}
}
}
}
func ( c * cache [ K, T] ) delete ( k K ) ( T , bool ) {
func ( c * cache [ T] ) delete ( k string ) ( T , bool ) {
if c . onEvicted != nil {
if c . onEvicted != nil {
if v , found := c . items [ k ] ; found {
if v , found := c . items [ k ] ; found {
delete ( c . items , k )
delete ( c . items , k )
@ -201,14 +201,14 @@ func (c *cache[K, T]) delete(k K) (T, bool) {
return * new ( T ) , false
return * new ( T ) , false
}
}
type keyAndValue [ K comparable , T any ] struct {
type keyAndValue [ T any ] struct {
key K
key string
value T
value T
}
}
// Delete all expired items from the cache.
// Delete all expired items from the cache.
func ( c * cache [ K, T] ) DeleteExpired ( ) {
func ( c * cache [ T] ) DeleteExpired ( ) {
var evictedItems [ ] keyAndValue [ K, T]
var evictedItems [ ] keyAndValue [ T]
now := time . Now ( ) . UnixNano ( )
now := time . Now ( ) . UnixNano ( )
c . mu . Lock ( )
c . mu . Lock ( )
for k , v := range c . items {
for k , v := range c . items {
@ -216,7 +216,7 @@ func (c *cache[K, T]) DeleteExpired() {
if v . Expiration > 0 && now > v . Expiration {
if v . Expiration > 0 && now > v . Expiration {
ov , evicted := c . delete ( k )
ov , evicted := c . delete ( k )
if evicted {
if evicted {
evictedItems = append ( evictedItems , keyAndValue [ K, T] { k , ov } )
evictedItems = append ( evictedItems , keyAndValue [ T] { k , ov } )
}
}
}
}
}
}
@ -229,7 +229,7 @@ func (c *cache[K, T]) DeleteExpired() {
// Sets an (optional) function that is called with the key and value when an
// Sets an (optional) function that is called with the key and value when an
// item is evicted from the cache. (Including when it is deleted manually, but
// item is evicted from the cache. (Including when it is deleted manually, but
// not when it is overwritten.) Set to nil to disable.
// not when it is overwritten.) Set to nil to disable.
func ( c * cache [ K, T] ) OnEvicted ( f func ( K , T ) ) {
func ( c * cache [ T] ) OnEvicted ( f func ( string , T ) ) {
c . mu . Lock ( )
c . mu . Lock ( )
c . onEvicted = f
c . onEvicted = f
c . mu . Unlock ( )
c . mu . Unlock ( )
@ -239,7 +239,7 @@ func (c *cache[K, T]) OnEvicted(f func(K, T)) {
//
//
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
// documentation for NewFrom().)
// documentation for NewFrom().)
func ( c * cache [ K, T] ) Save ( w io . Writer ) ( err error ) {
func ( c * cache [ T] ) Save ( w io . Writer ) ( err error ) {
enc := gob . NewEncoder ( w )
enc := gob . NewEncoder ( w )
c . mu . RLock ( )
c . mu . RLock ( )
defer c . mu . RUnlock ( )
defer c . mu . RUnlock ( )
@ -264,7 +264,7 @@ func (c *cache[K, T]) Save(w io.Writer) (err error) {
//
//
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
// documentation for NewFrom().)
// documentation for NewFrom().)
func ( c * cache [ K, T] ) SaveFile ( fname string ) error {
func ( c * cache [ T] ) SaveFile ( fname string ) error {
fp , err := os . Create ( fname )
fp , err := os . Create ( fname )
if err != nil {
if err != nil {
return err
return err
@ -282,9 +282,9 @@ func (c *cache[K, T]) SaveFile(fname string) error {
//
//
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
// documentation for NewFrom().)
// documentation for NewFrom().)
func ( c * cache [ K, T] ) Load ( r io . Reader ) error {
func ( c * cache [ T] ) Load ( r io . Reader ) error {
dec := gob . NewDecoder ( r )
dec := gob . NewDecoder ( r )
items := map [ K ] Item [ T ] { }
items := map [ string ] Item [ T ] { }
err := dec . Decode ( & items )
err := dec . Decode ( & items )
if err == nil {
if err == nil {
c . mu . Lock ( )
c . mu . Lock ( )
@ -304,7 +304,7 @@ func (c *cache[K, T]) Load(r io.Reader) error {
//
//
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
// documentation for NewFrom().)
// documentation for NewFrom().)
func ( c * cache [ K, T] ) LoadFile ( fname string ) error {
func ( c * cache [ T] ) LoadFile ( fname string ) error {
fp , err := os . Open ( fname )
fp , err := os . Open ( fname )
if err != nil {
if err != nil {
return err
return err
@ -318,10 +318,10 @@ func (c *cache[K, T]) LoadFile(fname string) error {
}
}
// Copies all unexpired items in the cache into a new map and returns it.
// Copies all unexpired items in the cache into a new map and returns it.
func ( c * cache [ K, T] ) Items ( ) map [ K ] Item [ T ] {
func ( c * cache [ T] ) Items ( ) map [ string ] Item [ T ] {
c . mu . RLock ( )
c . mu . RLock ( )
defer c . mu . RUnlock ( )
defer c . mu . RUnlock ( )
m := make ( map [ K ] Item [ T ] , len ( c . items ) )
m := make ( map [ string ] Item [ T ] , len ( c . items ) )
now := time . Now ( ) . UnixNano ( )
now := time . Now ( ) . UnixNano ( )
for k , v := range c . items {
for k , v := range c . items {
// "Inlining" of Expired
// "Inlining" of Expired
@ -337,7 +337,7 @@ func (c *cache[K, T]) Items() map[K]Item[T] {
// Returns the number of items in the cache. This may include items that have
// Returns the number of items in the cache. This may include items that have
// expired, but have not yet been cleaned up.
// expired, but have not yet been cleaned up.
func ( c * cache [ K, T] ) ItemCount ( ) int {
func ( c * cache [ T] ) ItemCount ( ) int {
c . mu . RLock ( )
c . mu . RLock ( )
n := len ( c . items )
n := len ( c . items )
c . mu . RUnlock ( )
c . mu . RUnlock ( )
@ -345,18 +345,18 @@ func (c *cache[K, T]) ItemCount() int {
}
}
// Delete all items from the cache.
// Delete all items from the cache.
func ( c * cache [ K, T] ) Flush ( ) {
func ( c * cache [ T] ) Flush ( ) {
c . mu . Lock ( )
c . mu . Lock ( )
c . items = map [ K ] Item [ T ] { }
c . items = map [ string ] Item [ T ] { }
c . mu . Unlock ( )
c . mu . Unlock ( )
}
}
type janitor [ K comparable , T any ] struct {
type janitor [ T any ] struct {
Interval time . Duration
Interval time . Duration
stop chan bool
stop chan bool
}
}
func ( j * janitor [ K, T] ) Run ( c * cache [ K , T ] ) {
func ( j * janitor [ T] ) Run ( c * cache [ T ] ) {
ticker := time . NewTicker ( j . Interval )
ticker := time . NewTicker ( j . Interval )
for {
for {
select {
select {
@ -369,12 +369,12 @@ func (j *janitor[K, T]) Run(c *cache[K, T]) {
}
}
}
}
func stopJanitor [ K comparable , T any ] ( c * Cache [ K , T ] ) {
func stopJanitor [ T any ] ( c * Cache [ T ] ) {
c . janitor . stop <- true
c . janitor . stop <- true
}
}
func runJanitor [ K comparable , T any ] ( c * cache [ K , T ] , ci time . Duration ) {
func runJanitor [ T any ] ( c * cache [ T ] , ci time . Duration ) {
j := & janitor [ K, T] {
j := & janitor [ T] {
Interval : ci ,
Interval : ci ,
stop : make ( chan bool ) ,
stop : make ( chan bool ) ,
}
}
@ -382,28 +382,28 @@ func runJanitor[K comparable, T any](c *cache[K, T], ci time.Duration) {
go j . Run ( c )
go j . Run ( c )
}
}
func newCache [ K comparable , T any ] ( de time . Duration , m map [ K ] Item [ T ] ) * cache [ K , T ] {
func newCache [ T any ] ( de time . Duration , m map [ string ] Item [ T ] ) * cache [ T ] {
if de == 0 {
if de == 0 {
de = - 1
de = - 1
}
}
c := & cache [ K, T] {
c := & cache [ T] {
defaultExpiration : de ,
defaultExpiration : de ,
items : m ,
items : m ,
}
}
return c
return c
}
}
func newCacheWithJanitor [ K comparable , T any ] ( de time . Duration , ci time . Duration , m map [ K ] Item [ T ] ) * Cache [ K , T ] {
func newCacheWithJanitor [ T any ] ( de time . Duration , ci time . Duration , m map [ string ] Item [ T ] ) * Cache [ T ] {
c := newCache [ K, T] ( de , m )
c := newCache [ T] ( de , m )
// This trick ensures that the janitor goroutine (which--granted it
// This trick ensures that the janitor goroutine (which--granted it
// was enabled--is running DeleteExpired on c forever) does not keep
// was enabled--is running DeleteExpired on c forever) does not keep
// the returned C object from being garbage collected. When it is
// the returned C object from being garbage collected. When it is
// garbage collected, the finalizer stops the janitor goroutine, after
// garbage collected, the finalizer stops the janitor goroutine, after
// which c can be collected.
// which c can be collected.
C := & Cache [ K, T] { c }
C := & Cache [ T] { c }
if ci > 0 {
if ci > 0 {
runJanitor ( c , ci )
runJanitor ( c , ci )
runtime . SetFinalizer ( C , stopJanitor [ K, T] )
runtime . SetFinalizer ( C , stopJanitor [ T] )
}
}
return C
return C
}
}
@ -413,9 +413,9 @@ func newCacheWithJanitor[K comparable, T any](de time.Duration, ci time.Duration
// the items in the cache never expire (by default), and must be deleted
// the items in the cache never expire (by default), and must be deleted
// manually. If the cleanup interval is less than one, expired items are not
// manually. If the cleanup interval is less than one, expired items are not
// deleted from the cache before calling c.DeleteExpired().
// deleted from the cache before calling c.DeleteExpired().
func New [ K comparable , T any ] ( defaultExpiration , cleanupInterval time . Duration ) * Cache [ K , T ] {
func New [ T any ] ( defaultExpiration , cleanupInterval time . Duration ) * Cache [ T ] {
items := make ( map [ K ] Item [ T ] )
items := make ( map [ string ] Item [ T ] )
return newCacheWithJanitor [ K, T] ( defaultExpiration , cleanupInterval , items )
return newCacheWithJanitor [ T] ( defaultExpiration , cleanupInterval , items )
}
}
// Return a new cache with a given default expiration duration and cleanup
// Return a new cache with a given default expiration duration and cleanup
@ -439,6 +439,6 @@ func New[K comparable, T any](defaultExpiration, cleanupInterval time.Duration)
// gob.Register() the individual types stored in the cache before encoding a
// gob.Register() the individual types stored in the cache before encoding a
// map retrieved with c.Items(), and to register those same types before
// map retrieved with c.Items(), and to register those same types before
// decoding a blob containing an items map.
// decoding a blob containing an items map.
func NewFrom [ K comparable , T any ] ( defaultExpiration , cleanupInterval time . Duration , items map [ K ] Item [ T ] ) * Cache [ K , T ] {
func NewFrom [ T any ] ( defaultExpiration , cleanupInterval time . Duration , items map [ string ] Item [ T ] ) * Cache [ T ] {
return newCacheWithJanitor ( defaultExpiration , cleanupInterval , items )
return newCacheWithJanitor ( defaultExpiration , cleanupInterval , items )
}
}