mirror of
https://github.com/kubescape/kubescape.git
synced 2026-03-03 18:20:19 +00:00
1. Use time.Time for expiration to simplify expiration checks. 2. Added a stopChan and a Stop method to properly stop the invalidateTask goroutine instead of directly changing values 3. Used Ticker in invalidateTask for periodic expiration checks 4. Leak Prevention: The invalidateTask() loop checks the TTL value before continuing. If the TTL is zero, the goroutine exits gracefully, preventing a memory leak. Signed-off-by: sulemaanhamza <sulemanhamza97@gmail.com>
109 lines
2.2 KiB
Go
109 lines
2.2 KiB
Go
package policyhandler
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// TimedCache provides functionality for managing a timed cache.
|
|
// The timed cache holds a value for a specified time duration (TTL).
|
|
// After the TTL has passed, the value is invalidated.
|
|
//
|
|
// The cache is thread safe.
|
|
type TimedCache[T any] struct {
|
|
value T
|
|
isSet bool
|
|
ttl time.Duration
|
|
expiration time.Time
|
|
mutex sync.RWMutex
|
|
stopChan chan struct{} // to stop the invalidateTask goroutine
|
|
}
|
|
|
|
func NewTimedCache[T any](ttl time.Duration) *TimedCache[T] {
|
|
cache := &TimedCache[T]{
|
|
ttl: ttl,
|
|
isSet: false,
|
|
stopChan: make(chan struct{}),
|
|
}
|
|
|
|
// start the invalidate task only when the ttl is greater than 0 (cache is enabled)
|
|
if ttl > 0 {
|
|
go cache.invalidateTask()
|
|
}
|
|
|
|
return cache
|
|
}
|
|
|
|
func (c *TimedCache[T]) Set(value T) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
if c.ttl == 0 {
|
|
return
|
|
}
|
|
|
|
c.isSet = true
|
|
c.value = value
|
|
c.expiration = time.Now().Add(c.ttl)
|
|
|
|
// Signal invalidation to Get() if cache is already expired
|
|
if time.Now().After(c.expiration) {
|
|
c.Invalidate()
|
|
}
|
|
}
|
|
|
|
func (c *TimedCache[T]) Get() (T, bool) {
|
|
c.mutex.RLock()
|
|
defer c.mutex.RUnlock()
|
|
|
|
// If the invalidateTask() goroutine is currently invalidating the cache,
|
|
// the Get() method may return the stale cached value before the invalidation is complete.
|
|
// To avoid the stale cached value, we're requiring the Get() method to wait for the invalidation signal before returning the value.
|
|
select {
|
|
case <-c.stopChan:
|
|
return c.value, false
|
|
default:
|
|
if !c.isSet || time.Now().After(c.expiration) {
|
|
return c.value, false
|
|
}
|
|
return c.value, true
|
|
}
|
|
}
|
|
|
|
func (c *TimedCache[T]) invalidateTask() {
|
|
ticker := time.NewTicker(c.ttl)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
c.mutex.Lock()
|
|
expired := time.Now().After(c.expiration)
|
|
c.mutex.Unlock()
|
|
if expired {
|
|
c.Invalidate()
|
|
}
|
|
case <-c.stopChan:
|
|
return
|
|
default:
|
|
// Check if TTL is still non-zero and return if true, to avoid possible memory leaks
|
|
if c.ttl == 0 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *TimedCache[T]) Stop() {
|
|
close(c.stopChan)
|
|
}
|
|
|
|
func (c *TimedCache[T]) Invalidate() {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
c.isSet = false
|
|
close(c.stopChan)
|
|
c.stopChan = make(chan struct{})
|
|
}
|