summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conn.go215
-rw-r--r--conn_test.go120
-rw-r--r--encoding.go16
-rw-r--r--encoding_test.go16
-rw-r--r--evict.go2
-rw-r--r--examples/eviction_policy_persistant/main.go94
-rw-r--r--store.go4
7 files changed, 359 insertions, 108 deletions
diff --git a/conn.go b/conn.go
index 15f06f1..0800ac9 100644
--- a/conn.go
+++ b/conn.go
@@ -21,16 +21,22 @@ type cache struct {
err error
}
-// Option is a function type for configuring the db.
+// Option is a function type for configuring the cache.
type Option func(*cache) error
-// openFile opens a file-backed cache database with the given options.
-func openFile(filename string, options ...Option) (*cache, error) {
- ret, err := openMem(options...)
- if err != nil {
+// open opens a file-backed cache database with the given options.
+func open(filename string, options ...Option) (*cache, error) {
+ ret := &cache{}
+ ret.Store.Init()
+
+ if err := ret.SetConfig(options...); err != nil {
return nil, err
}
+ if filename == "" {
+ return ret, nil
+ }
+
file, err := lockedfile.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o666)
if err != nil {
return nil, err
@@ -58,18 +64,6 @@ func openFile(filename string, options ...Option) (*cache, error) {
return ret, nil
}
-// openMem initializes an in-memory cache database with the given options.
-func openMem(options ...Option) (*cache, error) {
- ret := &cache{}
- ret.Store.Init()
-
- if err := ret.SetConfig(options...); err != nil {
- return nil, err
- }
-
- return ret, nil
-}
-
// start begins the background worker for periodic tasks.
func (c *cache) start() {
c.Stop = make(chan struct{})
@@ -79,7 +73,7 @@ func (c *cache) start() {
go c.backgroundWorker()
}
-// SetConfig applies configuration options to the db.
+// SetConfig applies configuration options to the cache.
func (c *cache) SetConfig(options ...Option) error {
c.Store.Lock.Lock()
defer c.Store.Lock.Unlock()
@@ -209,34 +203,131 @@ func (c *cache) Clear() {
var ErrKeyNotFound = errors.New("key not found") // ErrKeyNotFound is returned when a key is not found in the cache.
-// The Cache database. Can be initialized by either OpenFile or OpenMem. Uses per Cache Locks.
+// Get retrieves a value from the cache by key and returns its TTL.
+func (c *cache) Get(key []byte, value *[]byte) (time.Duration, error) {
+ v, ttl, err := c.GetValue(key)
+ *value = v
+
+ return ttl, err
+}
+
+// GetValue retrieves a value from the cache by key and returns the value and its TTL.
+func (c *cache) GetValue(key []byte) ([]byte, time.Duration, error) {
+ if err := c.err; err != nil {
+ return zero[[]byte](), 0, err
+ }
+
+ v, ttl, ok := c.Store.Get(key)
+ if !ok {
+ return v, 0, ErrKeyNotFound
+ }
+
+ return v, ttl, nil
+}
+
+// Set adds a key-value pair to the cache with a specified TTL.
+func (c *cache) Set(key, value []byte, ttl time.Duration) error {
+ if err := c.err; err != nil {
+ return err
+ }
+
+ c.Store.Set(key, value, ttl)
+
+ return nil
+}
+
+// Delete removes a key-value pair from the cache.
+func (c *cache) Delete(key []byte) error {
+ ok := c.Store.Delete(key)
+ if !ok {
+ return ErrKeyNotFound
+ }
+
+ return nil
+}
+
+// UpdateInPlace retrieves a value from the cache, processes it using the provided function,
+// and then sets the result back into the cache with the same key.
+func (c *cache) UpdateInPlace(key []byte, processFunc func([]byte) ([]byte, error), ttl time.Duration) error {
+ if err := c.err; err != nil {
+ return err
+ }
+
+ return c.Store.UpdateInPlace(key, processFunc, ttl)
+}
+
+// Memorize attempts to retrieve a value from the cache. If the retrieval fails,
+// it sets the result of the factory function into the cache and returns that result.
+func (c *cache) Memorize(key []byte, factoryFunc func() ([]byte, error), ttl time.Duration) ([]byte, error) {
+ if err := c.err; err != nil {
+ return []byte{}, err
+ }
+
+ return c.Store.Memorize(key, factoryFunc, ttl)
+}
+
+// The Cache database. Can be initialized by either Open or OpenFile or OpenMem. Uses per Cache Locks.
// Cache represents a generic cache database with key-value pairs.
type Cache[K any, V any] struct {
*cache
}
-// OpenFile opens a file-backed cache database with the specified options.
-func OpenFile[K any, V any](filename string, options ...Option) (Cache[K, V], error) {
- ret, err := openFile(filename, options...)
+// The CacheRaw database. Can be initialized by either OpenRaw or OpenRawFile or OpenRawMem. Uses per Cache Locks.
+// CacheRaw represents a binary cache database with key-value pairs.
+type CacheRaw struct {
+ *cache
+}
+
+// OpenRaw opens a binary cache database with the specified options. If filename is empty then in-memory otherwise file backed.
+func OpenRaw(filename string, options ...Option) (CacheRaw, error) {
+ ret, err := open(filename, options...)
if err != nil {
- return zero[Cache[K, V]](), err
+ return zero[CacheRaw](), err
}
ret.start()
- return Cache[K, V]{cache: ret}, nil
+ return CacheRaw{cache: ret}, nil
}
-// OpenMem initializes an in-memory cache database with the specified options.
-func OpenMem[K any, V any](options ...Option) (Cache[K, V], error) {
- ret, err := openMem(options...)
+var ErrEmptyFilename = errors.New("cannot open empty filename")
+
+// OpenRawFile opens a binary file-backed cache database with the specified options.
+func OpenRawFile(filename string, options ...Option) (CacheRaw, error) {
+ if filename == "" {
+ return zero[CacheRaw](), ErrEmptyFilename
+ }
+
+ return OpenRaw(filename, options...)
+}
+
+// OpenRawMem initializes a binary in-memory cache database with the specified options.
+func OpenRawMem(options ...Option) (CacheRaw, error) {
+ return OpenRaw("", options...)
+}
+
+// Open opens a cache database with the specified options. If filename is empty then in-memory otherwise file backed.
+func Open[K, V any](filename string, options ...Option) (Cache[K, V], error) {
+ ret, err := OpenRaw(filename, options...)
if err != nil {
return zero[Cache[K, V]](), err
}
- ret.start()
+ return Cache[K, V]{cache: ret.cache}, nil
+}
+
+// OpenFile opens a file-backed cache database with the specified options.
+func OpenFile[K, V any](filename string, options ...Option) (Cache[K, V], error) {
+ if filename == "" {
+ return zero[Cache[K, V]](), ErrEmptyFilename
+ }
- return Cache[K, V]{cache: ret}, nil
+ return Open[K, V](filename, options...)
+}
+
+// OpenMem initializes an in-memory cache database with the specified options.
+func OpenMem[K, V any](options ...Option) (Cache[K, V], error) {
+ return Open[K, V]("", options...)
}
// marshal serializes a value using msgpack.
@@ -250,21 +341,17 @@ func unmarshal[T any](data []byte, v *T) error {
}
// Get retrieves a value from the cache by key and returns its TTL.
-func (c *Cache[K, V]) Get(key K, value *V) (time.Duration, error) {
+func (c Cache[K, V]) Get(key K, value *V) (time.Duration, error) {
keyData, err := marshal(key)
if err != nil {
return 0, err
}
- if err := c.err; err != nil {
+ v, ttl, err := c.cache.GetValue(keyData)
+ if err != nil {
return 0, err
}
- v, ttl, ok := c.Store.Get(keyData)
- if !ok {
- return 0, ErrKeyNotFound
- }
-
if v != nil {
if err = unmarshal(v, value); err != nil {
return 0, err
@@ -275,7 +362,7 @@ func (c *Cache[K, V]) Get(key K, value *V) (time.Duration, error) {
}
// GetValue retrieves a value from the cache by key and returns the value and its TTL.
-func (c *Cache[K, V]) GetValue(key K) (V, time.Duration, error) {
+func (c Cache[K, V]) GetValue(key K) (V, time.Duration, error) {
value := zero[V]()
ttl, err := c.Get(key, &value)
@@ -283,7 +370,7 @@ func (c *Cache[K, V]) GetValue(key K) (V, time.Duration, error) {
}
// Set adds a key-value pair to the cache with a specified TTL.
-func (c *Cache[K, V]) Set(key K, value V, ttl time.Duration) error {
+func (c Cache[K, V]) Set(key K, value V, ttl time.Duration) error {
keyData, err := marshal(key)
if err != nil {
return err
@@ -294,47 +381,28 @@ func (c *Cache[K, V]) Set(key K, value V, ttl time.Duration) error {
return err
}
- if err := c.err; err != nil {
- return err
- }
-
- c.Store.Set(keyData, valueData, ttl)
-
- return nil
+ return c.cache.Set(keyData, valueData, ttl)
}
// Delete removes a key-value pair from the cache.
-func (c *Cache[K, V]) Delete(key K) error {
+func (c Cache[K, V]) Delete(key K) error {
keyData, err := marshal(key)
if err != nil {
return err
}
- if err := c.err; err != nil {
- return err
- }
-
- ok := c.Store.Delete(keyData)
- if !ok {
- return ErrKeyNotFound
- }
-
- return nil
+ return c.cache.Delete(keyData)
}
// UpdateInPlace retrieves a value from the cache, processes it using the provided function,
// and then sets the result back into the cache with the same key.
-func (c *Cache[K, V]) UpdateInPlace(key K, processFunc func(V) (V, error), ttl time.Duration) error {
+func (c Cache[K, V]) UpdateInPlace(key K, processFunc func(V) (V, error), ttl time.Duration) error {
keyData, err := marshal(key)
if err != nil {
return err
}
- if err := c.err; err != nil {
- return err
- }
-
- return c.Store.UpdateInPlace(keyData, func(data []byte) ([]byte, error) {
+ return c.cache.UpdateInPlace(keyData, func(data []byte) ([]byte, error) {
var value V
if err := unmarshal(data, &value); err != nil {
return nil, err
@@ -351,17 +419,13 @@ func (c *Cache[K, V]) UpdateInPlace(key K, processFunc func(V) (V, error), ttl t
// Memorize attempts to retrieve a value from the cache. If the retrieval fails,
// it sets the result of the factory function into the cache and returns that result.
-func (c *Cache[K, V]) Memorize(key K, factoryFunc func() (V, error), ttl time.Duration) (V, error) {
+func (c Cache[K, V]) Memorize(key K, factoryFunc func() (V, error), ttl time.Duration) (V, error) {
keyData, err := marshal(key)
if err != nil {
return zero[V](), err
}
- if err := c.err; err != nil {
- return zero[V](), err
- }
-
- data, err := c.Store.Memorize(keyData, func() ([]byte, error) {
+ data, err := c.cache.Memorize(keyData, func() ([]byte, error) {
value, err := factoryFunc()
if err != nil {
return nil, err
@@ -380,3 +444,18 @@ func (c *Cache[K, V]) Memorize(key K, factoryFunc func() (V, error), ttl time.Du
return value, nil
}
+
+type Cacher[K any, V any] interface {
+ Clear()
+ Close() error
+ Cost() uint64
+ Delete(key K) error
+ Error() error
+ Flush() error
+ Get(key K, value *V) (time.Duration, error)
+ GetValue(key K) (V, time.Duration, error)
+ Memorize(key K, factoryFunc func() (V, error), ttl time.Duration) (V, error)
+ Set(key K, value V, ttl time.Duration) error
+ SetConfig(options ...Option) error
+ UpdateInPlace(key K, processFunc func(V) (V, error), ttl time.Duration) error
+}
diff --git a/conn_test.go b/conn_test.go
index 47bdde4..cfcad13 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -7,7 +7,7 @@ import (
"time"
)
-func setupTestDB[K any, V any](tb testing.TB) *Cache[K, V] {
+func setupTestCache[K, V any](tb testing.TB) *Cache[K, V] {
tb.Helper()
db, err := OpenMem[K, V]()
@@ -24,13 +24,91 @@ func setupTestDB[K any, V any](tb testing.TB) *Cache[K, V] {
return &db
}
-func TestDBGetSet(t *testing.T) {
+func TestCacheSetConfig(t *testing.T) {
+ tests := []struct {
+ name string
+ options []Option
+ wantErr bool
+ expectedPolicy EvictionPolicyType
+ expectedMaxCost uint64
+ snapshotTime time.Duration
+ cleanupTime time.Duration
+ }{
+ {
+ name: "Set all valid options",
+ options: []Option{
+ WithPolicy(PolicyLRU),
+ WithMaxCost(10000),
+ SetSnapshotTime(2 * time.Minute),
+ SetCleanupTime(30 * time.Second),
+ },
+ wantErr: false,
+ expectedPolicy: PolicyLRU,
+ expectedMaxCost: 10000,
+ snapshotTime: 2 * time.Minute,
+ cleanupTime: 30 * time.Second,
+ },
+ {
+ name: "Invalid policy returns error",
+ options: []Option{
+ WithPolicy(-1),
+ },
+ wantErr: true,
+ },
+ {
+ name: "Set only max cost",
+ options: []Option{
+ WithMaxCost(2048),
+ },
+ wantErr: false,
+ expectedMaxCost: 2048,
+ },
+ {
+ name: "Set only snapshot and cleanup",
+ options: []Option{
+ SetSnapshotTime(15 * time.Second),
+ SetCleanupTime(1 * time.Minute),
+ },
+ wantErr: false,
+ snapshotTime: 15 * time.Second,
+ cleanupTime: 1 * time.Minute,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := setupTestCache[string, string](t)
+
+ err := c.SetConfig(tt.options...)
+ if (err != nil) != tt.wantErr {
+ t.Fatalf("SetConfig() error = %v, wantErr = %v", err, tt.wantErr)
+ }
+
+ if !tt.wantErr {
+ if c.Store.Policy.Type != tt.expectedPolicy {
+ t.Errorf("Expected policy %v, got %v", tt.expectedPolicy, c.Store.Policy.Type)
+ }
+ if tt.expectedMaxCost != 0 && c.Store.MaxCost != tt.expectedMaxCost {
+ t.Errorf("Expected MaxCost %d, got %d", tt.expectedMaxCost, c.Store.MaxCost)
+ }
+ if tt.snapshotTime != 0 && c.Store.SnapshotTicker.GetDuration() != tt.snapshotTime {
+ t.Errorf("Expected SnapshotTime %v, got %v", tt.snapshotTime, c.Store.SnapshotTicker.GetDuration())
+ }
+ if tt.cleanupTime != 0 && c.Store.CleanupTicker.GetDuration() != tt.cleanupTime {
+ t.Errorf("Expected CleanupTime %v, got %v", tt.cleanupTime, c.Store.CleanupTicker.GetDuration())
+ }
+ }
+ })
+ }
+}
+
+func TestCacheGetSet(t *testing.T) {
t.Parallel()
t.Run("Exists", func(t *testing.T) {
t.Parallel()
- db := setupTestDB[string, string](t)
+ db := setupTestCache[string, string](t)
want := "Value"
@@ -55,7 +133,7 @@ func TestDBGetSet(t *testing.T) {
t.Run("Not Exists", func(t *testing.T) {
t.Parallel()
- db := setupTestDB[string, string](t)
+ db := setupTestCache[string, string](t)
if _, _, err := db.GetValue("Key"); !errors.Is(err, ErrKeyNotFound) {
t.Fatalf("expected error: %v, got: %v", ErrKeyNotFound, err)
@@ -65,7 +143,7 @@ func TestDBGetSet(t *testing.T) {
t.Run("Update", func(t *testing.T) {
t.Parallel()
- db := setupTestDB[string, string](t)
+ db := setupTestCache[string, string](t)
if err := db.Set("Key", "Other", 0); err != nil {
t.Fatalf("expected no error, got: %v", err)
@@ -89,7 +167,7 @@ func TestDBGetSet(t *testing.T) {
t.Run("Key Expiry", func(t *testing.T) {
t.Parallel()
- db := setupTestDB[string, string](t)
+ db := setupTestCache[string, string](t)
if err := db.Set("Key", "Value", 500*time.Millisecond); err != nil {
t.Fatalf("unexpected error: %v", err)
@@ -103,13 +181,13 @@ func TestDBGetSet(t *testing.T) {
})
}
-func TestDBDelete(t *testing.T) {
+func TestCacheDelete(t *testing.T) {
t.Parallel()
t.Run("Exists", func(t *testing.T) {
t.Parallel()
- db := setupTestDB[string, string](t)
+ db := setupTestCache[string, string](t)
want := "Value"
if err := db.Set("Key", want, 0); err != nil {
@@ -128,7 +206,7 @@ func TestDBDelete(t *testing.T) {
t.Run("Not Exists", func(t *testing.T) {
t.Parallel()
- db := setupTestDB[string, string](t)
+ db := setupTestCache[string, string](t)
if err := db.Delete("Key"); !errors.Is(err, ErrKeyNotFound) {
t.Fatalf("expected error: %v, got: %v", ErrKeyNotFound, err)
@@ -136,13 +214,13 @@ func TestDBDelete(t *testing.T) {
})
}
-func TestDBUpdateInPlace(t *testing.T) {
+func TestCacheUpdateInPlace(t *testing.T) {
t.Parallel()
t.Run("Exists", func(t *testing.T) {
t.Parallel()
- store := setupTestDB[string, string](t)
+ store := setupTestCache[string, string](t)
want := "Value"
@@ -171,7 +249,7 @@ func TestDBUpdateInPlace(t *testing.T) {
t.Run("Not Exists", func(t *testing.T) {
t.Parallel()
- store := setupTestDB[string, string](t)
+ store := setupTestCache[string, string](t)
want := "Value"
@@ -185,13 +263,13 @@ func TestDBUpdateInPlace(t *testing.T) {
})
}
-func TestDBMemoize(t *testing.T) {
+func TestCacheMemoize(t *testing.T) {
t.Parallel()
t.Run("Cache Miss", func(t *testing.T) {
t.Parallel()
- store := setupTestDB[string, string](t)
+ store := setupTestCache[string, string](t)
want := "Value"
@@ -221,7 +299,7 @@ func TestDBMemoize(t *testing.T) {
t.Run("Cache Hit", func(t *testing.T) {
t.Parallel()
- store := setupTestDB[string, string](t)
+ store := setupTestCache[string, string](t)
want := "NewValue"
@@ -244,10 +322,10 @@ func TestDBMemoize(t *testing.T) {
})
}
-func BenchmarkDBGet(b *testing.B) {
+func BenchmarkCacheGet(b *testing.B) {
for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
- db := setupTestDB[int, int](b)
+ db := setupTestCache[int, int](b)
for i := range n {
if err := db.Set(i, i, 0); err != nil {
b.Fatalf("unexpected error: %v", err)
@@ -265,10 +343,10 @@ func BenchmarkDBGet(b *testing.B) {
}
}
-func BenchmarkDBSet(b *testing.B) {
+func BenchmarkCacheSet(b *testing.B) {
for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
- db := setupTestDB[int, int](b)
+ db := setupTestCache[int, int](b)
for i := range n - 1 {
if err := db.Set(i, i, 0); err != nil {
b.Fatalf("unexpected error: %v", err)
@@ -286,10 +364,10 @@ func BenchmarkDBSet(b *testing.B) {
}
}
-func BenchmarkDBDelete(b *testing.B) {
+func BenchmarkCacheDelete(b *testing.B) {
for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
- db := setupTestDB[int, int](b)
+ db := setupTestCache[int, int](b)
for i := range n - 1 {
if err := db.Set(i, i, 0); err != nil {
b.Fatalf("unexpected error: %v", err)
diff --git a/encoding.go b/encoding.go
index a8778ab..48d0d57 100644
--- a/encoding.go
+++ b/encoding.go
@@ -231,12 +231,14 @@ func (d *decoder) DecodeStore(s *store) error {
return nil
}
-func (s *store) Snapshot(w io.WriteSeeker) error {
+func (s *store) Snapshot(w io.Writer) error {
s.Lock.RLock()
defer s.Lock.RUnlock()
- if _, err := w.Seek(0, io.SeekStart); err != nil {
- return err
+ if seeker, ok := w.(io.Seeker); ok {
+ if _, err := seeker.Seek(0, io.SeekStart); err != nil {
+ return err
+ }
}
wr := newEncoder(w)
@@ -249,9 +251,11 @@ func (s *store) Snapshot(w io.WriteSeeker) error {
return wr.Flush()
}
-func (s *store) LoadSnapshot(r io.ReadSeeker) error {
- if _, err := r.Seek(0, io.SeekStart); err != nil {
- return err
+func (s *store) LoadSnapshot(r io.Reader) error {
+ if seeker, ok := r.(io.Seeker); ok {
+ if _, err := seeker.Seek(0, io.SeekStart); err != nil {
+ return err
+ }
}
d := newDecoder(r)
diff --git a/encoding_test.go b/encoding_test.go
index 43abb46..037a4fd 100644
--- a/encoding_test.go
+++ b/encoding_test.go
@@ -252,7 +252,7 @@ func TestEncodeDecodeNode(t *testing.T) {
}
}
-func TestEncodeDecodeStrorage(t *testing.T) {
+func TestStoreSnapshot(t *testing.T) {
t.Parallel()
tests := []struct {
@@ -297,7 +297,6 @@ func TestEncodeDecodeStrorage(t *testing.T) {
t.Parallel()
var buf bytes.Buffer
- e := newEncoder(&buf)
want := setupTestStore(t)
want.MaxCost = uint64(tt.maxCost)
@@ -310,18 +309,15 @@ func TestEncodeDecodeStrorage(t *testing.T) {
want.Set([]byte(k), []byte(v), 0)
}
- if err := e.EncodeStore(want); err != nil {
+ if err := want.Snapshot(&buf); err != nil {
t.Errorf("unexpected error: %v", err)
}
- if err := e.Flush(); err != nil {
- t.Errorf("unexpected error: %v", err)
- }
+ reader := bytes.NewReader(buf.Bytes())
- decoder := newDecoder(bytes.NewReader(buf.Bytes()))
got := setupTestStore(t)
- if err := decoder.DecodeStore(got); err != nil {
+ if err := got.LoadSnapshot(reader); err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -399,7 +395,7 @@ func BenchmarkStoreSnapshot(b *testing.B) {
b.Fatalf("unexpected error: %v", err)
}
- b.SetBytes(int64(fileInfo.Size()))
+ b.SetBytes(fileInfo.Size())
b.ReportAllocs()
for b.Loop() {
@@ -433,7 +429,7 @@ func BenchmarkStoreLoadSnapshot(b *testing.B) {
b.Fatalf("unexpected error: %v", err)
}
- b.SetBytes(int64(fileInfo.Size()))
+ b.SetBytes(fileInfo.Size())
b.ReportAllocs()
for b.Loop() {
diff --git a/evict.go b/evict.go
index eff626c..e355a11 100644
--- a/evict.go
+++ b/evict.go
@@ -34,7 +34,7 @@ type evictionPolicy struct {
}
// pushEvict adds a node to the eviction list.
-func pushEvict(node *node, sentinnel *node) {
+func pushEvict(node, sentinnel *node) {
node.EvictPrev = sentinnel
node.EvictNext = node.EvictPrev.EvictNext
node.EvictNext.EvictPrev = node
diff --git a/examples/eviction_policy_persistant/main.go b/examples/eviction_policy_persistant/main.go
new file mode 100644
index 0000000..fc7cc32
--- /dev/null
+++ b/examples/eviction_policy_persistant/main.go
@@ -0,0 +1,94 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/marcthe12/cache"
+)
+
+func main() {
+ // Create an in-memory cache with LRU eviction policy
+ db, err := cache.Open[int, int](
+ "cache.db",
+ cache.WithPolicy(cache.PolicyLRU),
+ cache.WithMaxCost(20),
+ cache.SetCleanupTime(1*time.Second),
+ )
+ if err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ defer func() {
+ err := db.Close()
+ if err != nil {
+ fmt.Println("Error:", err)
+ }
+ }()
+
+ fmt.Println("Loaded Cache")
+ // Check which keys are present
+ for n := range 4 {
+ key := n + 1
+ value, _, err := db.GetValue(key)
+
+ if err != nil {
+ fmt.Printf("Key %d not found\n", key)
+ } else {
+ fmt.Printf("Key %d found with value: %d\n", key, value)
+ }
+ }
+
+ fmt.Println("Set Values")
+ // Set values
+ if err := db.Set(1, -1, 10*time.Second); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ if err := db.Set(2, -2, 10*time.Second); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ if err := db.Set(3, -3, 10*time.Second); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ // Access some keys
+ if _, _, err := db.GetValue(1); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ if _, _, err := db.GetValue(2); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ fmt.Println("Add Key 4")
+ // Add another key to trigger eviction
+ if err := db.Set(4, -4, 0); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ fmt.Println("Sleep")
+ time.Sleep(2 * time.Second)
+
+ fmt.Println("Resume")
+ // Check which keys are present
+ for n := range 4 {
+ key := n + 1
+ value, _, err := db.GetValue(key)
+
+ if err != nil {
+ fmt.Printf("Key %d not found\n", key)
+ } else {
+ fmt.Printf("Key %d found with value: %d\n", key, value)
+ }
+ }
+}
diff --git a/store.go b/store.go
index aa44dc4..ccda46d 100644
--- a/store.go
+++ b/store.go
@@ -228,7 +228,7 @@ func (s *store) Evict() bool {
}
// insert adds a new key-value pair to the store.
-func (s *store) insert(key []byte, value []byte, ttl time.Duration) {
+func (s *store) insert(key, value []byte, ttl time.Duration) {
idx, hash := lookupIdx(s, key)
bucket := &s.Bucket[idx]
@@ -264,7 +264,7 @@ func (s *store) insert(key []byte, value []byte, ttl time.Duration) {
}
// Set adds or updates a key-value pair in the store with locking.
-func (s *store) Set(key []byte, value []byte, ttl time.Duration) {
+func (s *store) Set(key, value []byte, ttl time.Duration) {
s.Lock.Lock()
defer s.Lock.Unlock()