pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/cache/cache.go (about)

     1  // Package cache provides a simple in-memory key:value cache
     2  package cache
     3  
     4  // ////////////////////////////////////////////////////////////////////////////////// //
     5  //                                                                                    //
     6  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     7  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     8  //                                                                                    //
     9  // ////////////////////////////////////////////////////////////////////////////////// //
    10  
    11  import (
    12  	"sync"
    13  	"time"
    14  )
    15  
    16  // ////////////////////////////////////////////////////////////////////////////////// //
    17  
    18  // Cache is cache instance
    19  type Cache struct {
    20  	expiration     time.Duration
    21  	data           map[string]interface{}
    22  	expiry         map[string]int64
    23  	mu             *sync.RWMutex
    24  	isJanitorWorks bool
    25  }
    26  
    27  // ////////////////////////////////////////////////////////////////////////////////// //
    28  
    29  // New creates new cache instance
    30  func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
    31  	s := &Cache{
    32  		expiration: defaultExpiration,
    33  		data:       make(map[string]interface{}),
    34  		expiry:     make(map[string]int64),
    35  		mu:         &sync.RWMutex{},
    36  	}
    37  
    38  	if cleanupInterval != 0 {
    39  		s.isJanitorWorks = true
    40  		go s.janitor(cleanupInterval)
    41  	}
    42  
    43  	return s
    44  }
    45  
    46  // ////////////////////////////////////////////////////////////////////////////////// //
    47  
    48  // Has returns true if cache contains data for given key
    49  func (s *Cache) Has(key string) bool {
    50  	if s == nil {
    51  		return false
    52  	}
    53  
    54  	s.mu.RLock()
    55  
    56  	expiration, ok := s.expiry[key]
    57  
    58  	if !ok {
    59  		s.mu.RUnlock()
    60  		return false
    61  	}
    62  
    63  	if time.Now().UnixNano() > expiration {
    64  		s.mu.RUnlock()
    65  
    66  		if !s.isJanitorWorks {
    67  			s.Delete(key)
    68  		}
    69  
    70  		return false
    71  	}
    72  
    73  	s.mu.RUnlock()
    74  
    75  	return ok
    76  }
    77  
    78  // Size returns number of items in cache
    79  func (s *Cache) Size() int {
    80  	if s == nil {
    81  		return 0
    82  	}
    83  
    84  	s.mu.RLock()
    85  	defer s.mu.RUnlock()
    86  
    87  	return len(s.data)
    88  }
    89  
    90  // Expired returns number of exipred items in cache
    91  func (s *Cache) Expired() int {
    92  	if s == nil {
    93  		return 0
    94  	}
    95  
    96  	items := 0
    97  	now := time.Now().UnixNano()
    98  
    99  	s.mu.Lock()
   100  
   101  	for _, expiration := range s.expiry {
   102  		if now > expiration {
   103  			items++
   104  		}
   105  	}
   106  
   107  	s.mu.Unlock()
   108  
   109  	return items
   110  }
   111  
   112  // Set adds or updates item in cache
   113  func (s *Cache) Set(key string, data interface{}) {
   114  	if s == nil {
   115  		return
   116  	}
   117  
   118  	s.mu.Lock()
   119  
   120  	s.expiry[key] = time.Now().Add(s.expiration).UnixNano()
   121  	s.data[key] = data
   122  
   123  	s.mu.Unlock()
   124  }
   125  
   126  // Get returns item from cache or nil
   127  func (s *Cache) Get(key string) interface{} {
   128  	if s == nil {
   129  		return nil
   130  	}
   131  
   132  	s.mu.RLock()
   133  
   134  	expiration, ok := s.expiry[key]
   135  
   136  	if !ok {
   137  		s.mu.RUnlock()
   138  		return nil
   139  	}
   140  
   141  	if time.Now().UnixNano() > expiration {
   142  		s.mu.RUnlock()
   143  
   144  		if !s.isJanitorWorks {
   145  			s.Delete(key)
   146  		}
   147  
   148  		return nil
   149  	}
   150  
   151  	item := s.data[key]
   152  
   153  	s.mu.RUnlock()
   154  
   155  	return item
   156  }
   157  
   158  // GetWithExpiration returns item from cache and expiration date or nil
   159  func (s *Cache) GetWithExpiration(key string) (interface{}, time.Time) {
   160  	if s == nil {
   161  		return nil, time.Time{}
   162  	}
   163  
   164  	s.mu.RLock()
   165  
   166  	expiration, ok := s.expiry[key]
   167  
   168  	if !ok {
   169  		s.mu.RUnlock()
   170  		return nil, time.Time{}
   171  	}
   172  
   173  	if time.Now().UnixNano() > expiration {
   174  		s.mu.RUnlock()
   175  
   176  		if !s.isJanitorWorks {
   177  			s.Delete(key)
   178  		}
   179  
   180  		return nil, time.Time{}
   181  	}
   182  
   183  	item := s.data[key]
   184  
   185  	s.mu.RUnlock()
   186  
   187  	return item, time.Unix(0, expiration)
   188  }
   189  
   190  // Delete removes item from cache
   191  func (s *Cache) Delete(key string) {
   192  	if s == nil {
   193  		return
   194  	}
   195  
   196  	s.mu.Lock()
   197  
   198  	delete(s.data, key)
   199  	delete(s.expiry, key)
   200  
   201  	s.mu.Unlock()
   202  }
   203  
   204  // Flush removes all data from cache
   205  func (s *Cache) Flush() {
   206  	if s == nil {
   207  		return
   208  	}
   209  
   210  	s.mu.Lock()
   211  
   212  	s.data = make(map[string]interface{})
   213  	s.expiry = make(map[string]int64)
   214  
   215  	s.mu.Unlock()
   216  }
   217  
   218  // ////////////////////////////////////////////////////////////////////////////////// //
   219  
   220  func (s *Cache) janitor(interval time.Duration) {
   221  	for range time.NewTicker(interval).C {
   222  		if len(s.data) == 0 {
   223  			continue
   224  		}
   225  
   226  		now := time.Now().UnixNano()
   227  
   228  		s.mu.Lock()
   229  
   230  		for key, expiration := range s.expiry {
   231  			if now > expiration {
   232  				delete(s.data, key)
   233  				delete(s.expiry, key)
   234  			}
   235  		}
   236  
   237  		s.mu.Unlock()
   238  	}
   239  }