Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple cache implementation #3

Merged
merged 24 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9fc57c3
create EvictType
knbr13 Mar 31, 2024
a442773
create baseCache type
knbr13 Mar 31, 2024
3a5f431
create CacheBuilder type
knbr13 Mar 31, 2024
77df3cb
create New(size uint) CacheBuilder
knbr13 Mar 31, 2024
31a38de
create (b *CacheBuilder) EvictType
knbr13 Mar 31, 2024
e20d6b2
make CacheBuilder generic
knbr13 Mar 31, 2024
2a227ef
add TimeInterval func
knbr13 Mar 31, 2024
42b5e2e
create (*CacheBuilder[K, V]).Build
knbr13 Mar 31, 2024
aa789b4
update the interface
knbr13 Mar 31, 2024
bb66dc2
add the baseCache to MCache type
knbr13 Mar 31, 2024
e271005
update cache name
knbr13 Mar 31, 2024
baf411d
create setValueWithTimeout func
knbr13 Mar 31, 2024
560c610
add setValueWithTimeout(K, valueWithTimeout[V]) to the Cache interface
knbr13 Mar 31, 2024
6027edb
update tests after updating cache name
knbr13 Mar 31, 2024
39532b6
remove default value for timeInterval in manual cache
knbr13 Mar 31, 2024
ad8a050
use WithTimeInterval option function to specify tmIvl value
knbr13 Mar 31, 2024
07876ec
remove expiryEnabled field
knbr13 Mar 31, 2024
d9b11e9
update function call to newManual
knbr13 Mar 31, 2024
987c40e
remove TestSetConcurrently, incorrect implementation
knbr13 Mar 31, 2024
e38ab84
remove option function in the newManual function
sig-seg-v13 Mar 31, 2024
8840e3e
remove TestSetValuesWithExpiryDisabled func
sig-seg-v13 Mar 31, 2024
4302b1a
remove log statements
sig-seg-v13 Mar 31, 2024
eb8ac83
retrieve non expired keys in (Cache).Keys function
sig-seg-v13 Mar 31, 2024
874a976
update README
sig-seg-v13 Mar 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ import (
)

func main() {
// Create a new in-memory database
db := inmemdb.New[string, string]()
defer db.Close()
// Create a new Cache Builder
cb := inmemdb.New[string, string](10)

// Build the Cache
db := cb.Build()

fmt.Println("keys:", db.Keys())

// Set key-value pairs
db.Set("key1", "value1")
Expand All @@ -47,13 +51,16 @@ func main() {

// Delete a key
db.Delete("key1")
time.Sleep(time.Second)

// Transfer data to another database
anotherDB := inmemdb.New[string, string]()
anotherCB := inmemdb.New[string, string](2)
anotherDB := anotherCB.Build()
db.TransferTo(anotherDB)

// Copy data to another database
copyDB := inmemdb.New[string, string]()
copyCB := inmemdb.New[string, string](2)
copyDB := copyCB.Build()
anotherDB.CopyTo(copyDB)

// Retrieve keys
Expand All @@ -63,9 +70,9 @@ func main() {
keys = copyDB.Keys()
fmt.Println("Keys in copyDB:", keys)

time.Sleep(time.Second * 11)
time.Sleep(time.Second)
value3, ok3 := copyDB.Get("key3")
fmt.Printf("ok = %v, value = %v\n", ok3, value3) // ok = false, value =
fmt.Printf("ok = %v, value = %v\n", ok3, value3) // ok = false, value =
}
```

Expand Down
53 changes: 50 additions & 3 deletions cache.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package inmemdb

import "time"
import (
"sync"
"time"
)

// Cache represents a generic caching interface for key-value pairs.
// Different cache implementations can be created by implementing this interface.
Expand Down Expand Up @@ -30,14 +33,58 @@ type Cache[K comparable, V any] interface {
Delete(K)

// TransferTo transfers all key-value pairs from the source cache to the provided destination cache.
TransferTo(*DB[K, V])
TransferTo(Cache[K, V])

// CopyTo copies all key-value pairs from the source cache to the provided destination cache.
CopyTo(*DB[K, V])
CopyTo(Cache[K, V])

// Keys returns a slice containing the keys of the cache in arbitrary order.
Keys() []K

// Count returns the number of key-value pairs in the cache.
Count() int

setValueWithTimeout(K, valueWithTimeout[V])
}

type CacheBuilder[K comparable, V any] struct {
et EvictType
size uint
tmIvl time.Duration
}

func New[K comparable, V any](size uint) CacheBuilder[K, V] {
return CacheBuilder[K, V]{
size: size,
et: Manual,
}
}

func (cb CacheBuilder[K, V]) TimeInterval(t time.Duration) CacheBuilder[K, V] {
cb.tmIvl = t
return cb
}

func (b *CacheBuilder[K, V]) EvictType(evictType EvictType) {
b.et = evictType
}

func (b *CacheBuilder[K, V]) Build() Cache[K, V] {
switch b.et {
case Manual:
return newManual[K, V](b.tmIvl)
default:
panic("in-memdb: unknown evict-type")
}
}

type baseCache[K comparable, V any] struct {
m map[K]valueWithTimeout[V]
mu sync.RWMutex
}

type EvictType string

const (
Manual EvictType = "manual"
)
103 changes: 43 additions & 60 deletions db.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
package inmemdb

import (
"sync"
"time"
)

// Option is a functional option type for configuring the behavior of the in-memory database.
type Option[K comparable, V any] func(*DB[K, V])

// WithTimeInterval sets the time interval for the expiration goroutine to sleep before checking for expired keys again.
// It accepts a time.Duration value representing the interval duration.
// By default, the time interval is set to 10 seconds.
func WithTimeInterval[K comparable, V any](t time.Duration) Option[K, V] {
return func(db *DB[K, V]) {
db.timeInterval = t
}
}

type DB[K comparable, V any] struct {
m map[K]valueWithTimeout[V]
mu sync.RWMutex
type MCache[K comparable, V any] struct {
baseCache[K, V]
stopCh chan struct{} // Channel to signal timeout goroutine to stop
timeInterval time.Duration // Time interval to sleep the goroutine that checks for expired keys
expiryEnable bool // Whether the database can contain keys that have expiry time or not
}

type valueWithTimeout[V any] struct {
Expand All @@ -32,28 +17,24 @@ type valueWithTimeout[V any] struct {

// New creates a new in-memory database instance with optional configuration provided by the specified options.
// The database starts a background goroutine to periodically check for expired keys based on the configured time interval.
func New[K comparable, V any](opts ...Option[K, V]) *DB[K, V] {
db := &DB[K, V]{
m: make(map[K]valueWithTimeout[V]),
func newManual[K comparable, V any](timeInterval time.Duration) *MCache[K, V] {
db := &MCache[K, V]{
baseCache: baseCache[K, V]{
m: make(map[K]valueWithTimeout[V]),
},
stopCh: make(chan struct{}),
timeInterval: time.Second * 10,
expiryEnable: true,
}
for _, opt := range opts {
opt(db)
timeInterval: timeInterval,
}
if db.timeInterval > 0 {
go db.expireKeys()
} else {
db.expiryEnable = false
}
return db
}

// Set adds or updates a key-value pair in the database without setting an expiration time.
// If the key already exists, its value will be overwritten with the new value.
// This function is safe for concurrent use.
func (d *DB[K, V]) Set(k K, v V) {
func (d *MCache[K, V]) Set(k K, v V) {
d.mu.Lock()
defer d.mu.Unlock()
d.m[k] = valueWithTimeout[V]{
Expand All @@ -62,8 +43,14 @@ func (d *DB[K, V]) Set(k K, v V) {
}
}

func (d *MCache[K, V]) setValueWithTimeout(k K, v valueWithTimeout[V]) {
d.mu.Lock()
defer d.mu.Unlock()
d.m[k] = v
}

// NotFoundSet adds a key-value pair to the database if the key does not already exist and returns true. Otherwise, it does nothing and returns false.
func (d *DB[K, V]) NotFoundSet(k K, v V) bool {
func (d *MCache[K, V]) NotFoundSet(k K, v V) bool {
d.mu.Lock()
defer d.mu.Unlock()
_, ok := d.m[k]
Expand All @@ -79,12 +66,7 @@ func (d *DB[K, V]) NotFoundSet(k K, v V) bool {
// SetWithTimeout adds or updates a key-value pair in the database with an expiration time.
// If the timeout duration is zero or negative, the key-value pair will not have an expiration time.
// This function is safe for concurrent use.
func (d *DB[K, V]) SetWithTimeout(k K, v V, timeout time.Duration) {
if !d.expiryEnable {
d.Set(k, v)
return
}

func (d *MCache[K, V]) SetWithTimeout(k K, v V, timeout time.Duration) {
if timeout > 0 {
d.mu.Lock()
defer d.mu.Unlock()
Expand All @@ -102,10 +84,7 @@ func (d *DB[K, V]) SetWithTimeout(k K, v V, timeout time.Duration) {
// NotFoundSetWithTimeout adds a key-value pair to the database with an expiration time if the key does not already exist and returns true. Otherwise, it does nothing and returns false.
// If the timeout is zero or negative, the key-value pair will not have an expiration time.
// If expiry is disabled, it behaves like NotFoundSet.
func (d *DB[K, V]) NotFoundSetWithTimeout(k K, v V, timeout time.Duration) bool {
if !d.expiryEnable {
return d.NotFoundSet(k, v)
}
func (d *MCache[K, V]) NotFoundSetWithTimeout(k K, v V, timeout time.Duration) bool {
d.mu.Lock()
defer d.mu.Unlock()

Expand All @@ -131,7 +110,7 @@ func (d *DB[K, V]) NotFoundSetWithTimeout(k K, v V, timeout time.Duration) bool
return !ok
}

func (d *DB[K, V]) Get(k K) (v V, b bool) {
func (d *MCache[K, V]) Get(k K) (v V, b bool) {
d.mu.Lock()
defer d.mu.Unlock()
val, ok := d.m[k]
Expand All @@ -145,7 +124,7 @@ func (d *DB[K, V]) Get(k K) (v V, b bool) {
return val.value, ok
}

func (d *DB[K, V]) Delete(k K) {
func (d *MCache[K, V]) Delete(k K) {
d.mu.Lock()
defer d.mu.Unlock()
delete(d.m, k)
Expand All @@ -155,13 +134,11 @@ func (d *DB[K, V]) Delete(k K) {
//
// The source DB is locked during the entire operation, and the destination DB is locked for the duration of the function call.
// The function is safe to call concurrently with other operations on any of the source DB or Destination DB.
func (src *DB[K, V]) TransferTo(dst *DB[K, V]) {
dst.mu.Lock()
func (src *MCache[K, V]) TransferTo(dst Cache[K, V]) {
src.mu.Lock()
defer dst.mu.Unlock()
defer src.mu.Unlock()
for k, v := range src.m {
dst.m[k] = v
dst.setValueWithTimeout(k, v)
}
src.m = make(map[K]valueWithTimeout[V])
}
Expand All @@ -170,31 +147,37 @@ func (src *DB[K, V]) TransferTo(dst *DB[K, V]) {
//
// The source DB is locked during the entire operation, and the destination DB is locked for the duration of the function call.
// The function is safe to call concurrently with other operations on any of the source DB or Destination DB.
func (src *DB[K, V]) CopyTo(dst *DB[K, V]) {
dst.mu.Lock()
func (src *MCache[K, V]) CopyTo(dst Cache[K, V]) {
src.mu.RLock()
defer dst.mu.Unlock()
defer src.mu.RUnlock()
for k, v := range src.m {
dst.m[k] = v
dst.setValueWithTimeout(k, v)
}
}

// Keys returns a slice containing the keys of the map in random order.
func (d *DB[K, V]) Keys() []K {
d.mu.RLock()
defer d.mu.RUnlock()
keys := make([]K, 0, len(d.m))
for k := range d.m {
keys = append(keys, k)
func (d *MCache[K, V]) Keys() []K {
d.mu.Lock()
defer d.mu.Unlock()

keys := make([]K, len(d.m))
i := len(d.m) - 1

for k, v := range d.m {
if v.expireAt != nil && v.expireAt.Before(time.Now()) {
delete(d.m, k)
continue
}
keys[i] = k
i--
}
return keys
return keys[i+1:]
}

// expireKeys is a background goroutine that periodically checks for expired keys and removes them from the database.
// It runs until the Close method is called.
// This function is not intended to be called directly by users.
func (d *DB[K, V]) expireKeys() {
func (d *MCache[K, V]) expireKeys() {
ticker := time.NewTicker(d.timeInterval)
defer ticker.Stop()
for {
Expand All @@ -215,16 +198,16 @@ func (d *DB[K, V]) expireKeys() {

// Close signals the expiration goroutine to stop.
// It should be called when the database is no longer needed.
func (d *DB[K, V]) Close() {
if d.expiryEnable {
func (d *MCache[K, V]) Close() {
if d.timeInterval > 0 {
d.stopCh <- struct{}{} // Signal the expiration goroutine to stop
close(d.stopCh)
}
d.m = nil
}

// Count returns the number of key-value pairs in the database.
func (d *DB[K, V]) Count() int {
func (d *MCache[K, V]) Count() int {
d.mu.RLock()
defer d.mu.RUnlock()
return len(d.m)
Expand Down
Loading
Loading