Skip to content

Commit

Permalink
Merge pull request #1 from knbr13/make-time-interval-configurable
Browse files Browse the repository at this point in the history
Make time interval configurable
  • Loading branch information
knbr13 authored Mar 27, 2024
2 parents 5bc2934 + 2a10816 commit 151280c
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 11 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@ package main

import (
"fmt"
"time"

inmemdb "github.com/knbr13/in-memdb"
)

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

// Set key-value pairs
db.Set("key1", "value1")
db.Set("key2", "value2")

db.SetWithTimeout("key3", "value3", time.Second)

// Get values by key
value1, ok1 := db.Get("key1")
value2, ok2 := db.Get("key2")
Expand Down Expand Up @@ -58,6 +62,10 @@ func main() {

keys = copyDB.Keys()
fmt.Println("Keys in copyDB:", keys)

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

Expand Down
53 changes: 43 additions & 10 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,48 @@ import (
"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
stopCh chan struct{} // Channel to signal timeout goroutine to stop
m map[K]valueWithTimeout[V]
mu sync.RWMutex
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 {
value V
expireAt *time.Time
}

func New[K comparable, V any]() *DB[K, V] {
// 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]),
stopCh: make(chan struct{}),
m: make(map[K]valueWithTimeout[V]),
stopCh: make(chan struct{}),
timeInterval: time.Second * 10,
expiryEnable: true,
}
for _, opt := range opts {
opt(db)
}
if db.timeInterval > 0 {
go db.expireKeys()
} else {
db.expiryEnable = false
}
go db.expireKeys()
return db
}

Expand All @@ -41,6 +66,10 @@ func (d *DB[K, V]) Set(k K, v V) {
// 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
}
d.mu.Lock()
defer d.mu.Unlock()

Expand Down Expand Up @@ -115,7 +144,7 @@ func (d *DB[K, V]) Keys() []K {
// 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() {
ticker := time.NewTicker(time.Second * 5)
ticker := time.NewTicker(d.timeInterval)
defer ticker.Stop()
for {
select {
Expand All @@ -133,8 +162,12 @@ func (d *DB[K, V]) expireKeys() {
}
}

// Close signals the expiration goroutine to stop and releases associated resources.
// Close signals the expiration goroutine to stop.
// It should be called when the database is no longer needed.
func (d *DB[K, V]) Close() {
d.stopCh <- struct{}{} // Signal the expiration goroutine to stop
if d.expiryEnable {
d.stopCh <- struct{}{} // Signal the expiration goroutine to stop
close(d.stopCh)
}
d.m = nil
}
23 changes: 22 additions & 1 deletion db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,35 @@ func TestSetWithTimeout(t *testing.T) {
t.Errorf("SetWithTimeout failed")
}

time.Sleep(timeout * 7)
time.Sleep(db.timeInterval + time.Second)

v, ok = db.Get(key)
if v != "" || ok {
t.Errorf("SetWithTimeout failed")
}
}

func TestSetValuesWithExpiryDisabled(t *testing.T) {
db := New(WithTimeInterval[string, string](0))
key := "test"
value := "test value"
timeout := time.Second

db.SetWithTimeout(key, value, timeout)

v, ok := db.Get(key)
if value != v || !ok {
t.Errorf("SetWithTimeout failed")
}

time.Sleep(timeout)

v, ok = db.Get(key)
if value != v || !ok {
t.Errorf("SetWithTimeout failed")
}
}

func TestGet(t *testing.T) {
db := New[string, string]()

Expand Down

0 comments on commit 151280c

Please sign in to comment.