diff --git a/backendTestCase_test.go b/backendTestCase_test.go index c9b172f..9cfe98f 100644 --- a/backendTestCase_test.go +++ b/backendTestCase_test.go @@ -42,6 +42,8 @@ func NewBackendTestCase(t *testing.T, backend httpcache.Backend, tagsInResult bo func (tc *BackendTestCase) RunTests() { tc.testSetGetPurge() + tc.testSetTTL() + tc.testFlush() if _, ok := tc.backend.(httpcache.TagSupporting); ok { @@ -178,3 +180,12 @@ func (tc *BackendTestCase) buildEntry(content string, tags []string) httpcache.E Body: []byte(content), } } + +func (tc *BackendTestCase) testSetTTL() { + entry := tc.buildEntry("expires quickly", nil) + entry.Meta.GraceTime = time.Now().Add(200 * time.Millisecond) + entry.Meta.LifeTime = entry.Meta.GraceTime + tc.setEntry("EXPIRED_ENTRY", entry) + time.Sleep(300 * time.Millisecond) + tc.shouldNotExist("EXPIRED_ENTRY") +} diff --git a/inMemoryBackend.go b/inMemoryBackend.go index c1b890f..979a39c 100644 --- a/inMemoryBackend.go +++ b/inMemoryBackend.go @@ -7,13 +7,14 @@ import ( lru "github.com/hashicorp/golang-lru/v2" ) -const lurkerPeriod = 1 * time.Minute +const defaultLurkerPeriod = 1 * time.Minute type ( // MemoryBackend implements the cache backend interface with an "in memory" solution MemoryBackend struct { cacheMetrics Metrics pool *lru.TwoQueueCache[string, inMemoryCacheEntry] + lurkerPeriod time.Duration } // MemoryBackendConfig config @@ -25,6 +26,7 @@ type ( InMemoryBackendFactory struct { config MemoryBackendConfig frontendName string + lurkerPeriod time.Duration } inMemoryCacheEntry struct { @@ -41,6 +43,12 @@ func (f *InMemoryBackendFactory) SetConfig(config MemoryBackendConfig) *InMemory return f } +// SetLurkerPeriod sets the timeframe how often expired cache entries should be checked/cleaned up, if 0 is provided the default period of 1 minute is taken +func (f *InMemoryBackendFactory) SetLurkerPeriod(period time.Duration) *InMemoryBackendFactory { + f.lurkerPeriod = period + return f +} + // SetFrontendName used in Metrics func (f *InMemoryBackendFactory) SetFrontendName(frontendName string) *InMemoryBackendFactory { f.frontendName = frontendName @@ -51,10 +59,17 @@ func (f *InMemoryBackendFactory) SetFrontendName(frontendName string) *InMemoryB func (f *InMemoryBackendFactory) Build() (Backend, error) { cache, _ := lru.New2Q[string, inMemoryCacheEntry](f.config.Size) + lurkerPeriod := defaultLurkerPeriod + if f.lurkerPeriod > 0 { + lurkerPeriod = f.lurkerPeriod + } + memoryBackend := &MemoryBackend{ pool: cache, cacheMetrics: NewCacheMetrics("memory", f.frontendName), + lurkerPeriod: lurkerPeriod, } + go memoryBackend.lurker() return memoryBackend, nil @@ -115,7 +130,7 @@ func (m *MemoryBackend) Flush() error { } func (m *MemoryBackend) lurker() { - for range time.Tick(lurkerPeriod) { + for range time.Tick(m.lurkerPeriod) { m.cacheMetrics.recordEntries(int64(m.pool.Len())) for _, key := range m.pool.Keys() { diff --git a/inMemoryBackend_test.go b/inMemoryBackend_test.go index 27e81ee..2d96e20 100644 --- a/inMemoryBackend_test.go +++ b/inMemoryBackend_test.go @@ -2,6 +2,7 @@ package httpcache_test import ( "testing" + "time" "flamingo.me/httpcache" ) @@ -10,7 +11,7 @@ func Test_RunDefaultBackendTestCase_InMemoryBackend(t *testing.T) { t.Parallel() f := httpcache.InMemoryBackendFactory{} - backend, _ := f.SetConfig(httpcache.MemoryBackendConfig{Size: 100}).SetFrontendName("default").Build() + backend, _ := f.SetConfig(httpcache.MemoryBackendConfig{Size: 100}).SetFrontendName("default").SetLurkerPeriod(100 * time.Millisecond).Build() testCase := NewBackendTestCase(t, backend, true) testCase.RunTests() diff --git a/redisBackend.go b/redisBackend.go index ef5abad..10fa37f 100644 --- a/redisBackend.go +++ b/redisBackend.go @@ -217,16 +217,17 @@ func (b *RedisBackend) Set(key string, entry Entry) error { } err = conn.Send( - "SETEX", + "SET", b.createPrefixedKey(key, valuePrefix), - int(entry.Meta.GraceTime.Sub(time.Now().Round(time.Second))), buffer, + "PX", + time.Until(entry.Meta.GraceTime).Round(time.Millisecond).Milliseconds(), ) if err != nil { b.cacheMetrics.countError("SetFailed") b.logger.Error(fmt.Sprintf("Error setting key %q with timeout %v and buffer %v", key, entry.Meta.GraceTime, buffer)) - return fmt.Errorf("redis SETEX failed: %w", err) + return fmt.Errorf("redis SET PX failed: %w", err) } for _, tag := range entry.Meta.Tags { diff --git a/twoLevelBackend_test.go b/twoLevelBackend_test.go index f1d042b..ceaa80c 100644 --- a/twoLevelBackend_test.go +++ b/twoLevelBackend_test.go @@ -2,6 +2,7 @@ package httpcache_test import ( "testing" + "time" "flamingo.me/flamingo/v3/framework/flamingo" "github.com/stretchr/testify/assert" @@ -12,7 +13,7 @@ import ( func createInMemoryBackend() httpcache.Backend { return func() httpcache.Backend { f := httpcache.InMemoryBackendFactory{} - backend, _ := f.SetConfig(httpcache.MemoryBackendConfig{Size: 100}).SetFrontendName("default").Build() + backend, _ := f.SetConfig(httpcache.MemoryBackendConfig{Size: 100}).SetFrontendName("default").SetLurkerPeriod(100 * time.Millisecond).Build() return backend }()