Skip to content

Commit

Permalink
use time.Duration and create GoNoUpdate()
Browse files Browse the repository at this point in the history
* Use `time.Duration` instead of `int` for `maxTTL` and `pruneInterval` parameters
* Change all examples and tests to use `time.Duration`
* Create `GetNoUpdate()` which is the same as `Get()`, except that is does not update the `lastAccess` expiration time
* * This ignores the `refreshLastAccessOnGet` parameter
* Create TestGetNoUpdate() function
  • Loading branch information
jftuga committed Nov 7, 2023
1 parent ce9811c commit 96c4321
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 60 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ package main
import (
"fmt"
"time"

"github.com/jftuga/TtlMap"
)

func main() {
maxTTL := 4 // a key's time to live in seconds
startSize := 3 // initial number of items in map
pruneInterval := 1 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's 'lastAccessTime' on a .Get()
maxTTL := time.Duration(time.Second * 4) // a key's time to live in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 1) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's 'lastAccessTime' on a .Get()
t := TtlMap.New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer t.Close()

Expand All @@ -42,11 +41,12 @@ func main() {

sleepTime := maxTTL + pruneInterval
fmt.Printf("Sleeping %v seconds, items should be 'nil' after this time\n", sleepTime)
time.Sleep(time.Second * time.Duration(sleepTime))
time.Sleep(sleepTime)
fmt.Printf("[%9s] %v\n", "myString", t.Get("myString"))
fmt.Printf("[%9s] %v\n", "int_array", t.Get("int_array"))
fmt.Println("TtlMap length:", t.Len())
}

```

Output:
Expand All @@ -66,9 +66,12 @@ TtlMap length: 0

## API functions
* `New`: initialize a `TtlMap`
* `Close`: this stops the goroutine that checks for expired items; use with `defer`
* `Len`: return the number of items in the map
* `Put`: add a key/value
* `Get`: get the current value of the given key; return `nil` if the key is not found in the map
* `GetNoUpdate`: same as `Get`, but do not update the `lastAccess` expiration time
* * * This ignores the `refreshLastAccessOnGet` parameter
* `All`: returns a *copy* of all items in the map
* `Delete`: delete an item; return `true` if the item was deleted, `false` if the item was not found in the map
* `Clear`: remove all items from the map
Expand Down
18 changes: 10 additions & 8 deletions example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ type User struct {
}

func main() {
maxTTL := 4 // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := 1 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
maxTTL := time.Duration(time.Second * 4) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 1) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
t := TtlMap.New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer t.Close()

Expand Down Expand Up @@ -75,7 +75,7 @@ func main() {
fmt.Println()
fmt.Printf("Sleeping %v seconds, items should be removed after this time, except for the '%v' key\n", sleepTime, dontExpireKey)
fmt.Println()
time.Sleep(time.Second * time.Duration(sleepTime))
time.Sleep(sleepTime)

// these items have expired and therefore should be nil, except for 'dontExpireKey'
fmt.Printf("[%9s] %v\n", "string", t.Get("string"))
Expand All @@ -94,7 +94,7 @@ func main() {
if t.Get("int") == nil {
fmt.Println("[int] is nil")
}
fmt.Println("TtlMap length:", t.Len())
fmt.Println("TtlMap length:", t.Len(), " (should equal 1)")
fmt.Println()

fmt.Println()
Expand All @@ -104,14 +104,16 @@ func main() {
fmt.Printf("Manually deleting '%v' key again; should NOT be successful this time\n", dontExpireKey)
success = t.Delete(TtlMap.CustomKeyType(dontExpireKey))
fmt.Printf(" successful? %v\n", success)
fmt.Println("TtlMap length:", t.Len())
fmt.Println("TtlMap length:", t.Len(), " (should equal 0)")
fmt.Println()

fmt.Println("Adding 2 items and then running Clear()")
t.Put("string", "a b c")
t.Put("int", 3)
fmt.Println("TtlMap length:", t.Len())
fmt.Println("running Clear()")

fmt.Println()
fmt.Println("Running Clear()")
t.Clear()
fmt.Println("TtlMap length:", t.Len())
fmt.Println()
Expand Down
10 changes: 5 additions & 5 deletions example/small/small.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
)

func main() {
maxTTL := 4 // a key's time to live in seconds
startSize := 3 // initial number of items in map
pruneInterval := 1 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's 'lastAccessTime' on a .Get()
maxTTL := time.Duration(time.Second * 4) // a key's time to live in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 1) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's 'lastAccessTime' on a .Get()
t := TtlMap.New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer t.Close()

Expand All @@ -29,7 +29,7 @@ func main() {

sleepTime := maxTTL + pruneInterval
fmt.Printf("Sleeping %v seconds, items should be 'nil' after this time\n", sleepTime)
time.Sleep(time.Second * time.Duration(sleepTime))
time.Sleep(sleepTime)
fmt.Printf("[%9s] %v\n", "myString", t.Get("myString"))
fmt.Printf("[%9s] %v\n", "int_array", t.Get("int_array"))
fmt.Println("TtlMap length:", t.Len())
Expand Down
21 changes: 16 additions & 5 deletions ttlMap.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
"time"
)

const version string = "1.3.0"
const version string = "1.4.0"

type CustomKeyType string

Expand All @@ -48,28 +48,30 @@ type TtlMap struct {
stop chan bool
}

func New(maxTTL int, ln int, pruneInterval int, refreshLastAccessOnGet bool) (m *TtlMap) {
func New(maxTTL time.Duration, ln int, pruneInterval time.Duration, refreshLastAccessOnGet bool) (m *TtlMap) {
// if pruneInterval > maxTTL {
// print("WARNING: TtlMap: pruneInterval > maxTTL\n")
// }
m = &TtlMap{m: make(map[CustomKeyType]*item, ln), stop: make(chan bool)}
m.refresh = refreshLastAccessOnGet
maxTTL /= 1000000000
// print("maxTTL: ", maxTTL, "\n")
go func() {
for {
select {
case <-m.stop:
return
case now := <-time.Tick(time.Second * time.Duration(pruneInterval)):
case now := <-time.Tick(pruneInterval):
currentTime := now.Unix()
m.l.Lock()
for k, v := range m.m {
//print("TICK:", currentTime, " ", v.lastAccess, " ", (currentTime - v.lastAccess), " ", maxTTL, " ", k, "\n")
// print("TICK:", currentTime, " ", v.lastAccess, " ", currentTime-v.lastAccess, " ", maxTTL, " ", k, "\n")
if currentTime-v.lastAccess >= int64(maxTTL) {
delete(m.m, k)
// print("deleting: ", k, "\n")
}
}
// print("\n")
// print("----\n")
m.l.Unlock()
}
}
Expand Down Expand Up @@ -104,6 +106,15 @@ func (m *TtlMap) Get(k CustomKeyType) (v interface{}) {
return
}

func (m *TtlMap) GetNoUpdate(k CustomKeyType) (v interface{}) {
m.l.Lock()
if it, ok := m.m[k]; ok {
v = it.Value
}
m.l.Unlock()
return
}

func (m *TtlMap) Delete(k CustomKeyType) bool {
m.l.Lock()
_, ok := m.m[k]
Expand Down
104 changes: 68 additions & 36 deletions ttlMap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,50 @@ import (
)

func TestAllItemsExpired(t *testing.T) {
maxTTL := 4 // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := 1 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
maxTTL := time.Duration(time.Second * 4) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 1) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
tm := New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer tm.Close()

// populate the TtlMap
tm.Put("myString", "a b c")
tm.Put("int_array", []int{1, 2, 3})

sleepTime := maxTTL + pruneInterval
time.Sleep(time.Second * time.Duration(sleepTime))
time.Sleep(maxTTL + pruneInterval)
t.Logf("tm.len: %v\n", tm.Len())
if tm.Len() > 0 {
t.Errorf("t.Len should be 0, but actually equals %v\n", tm.Len())
}
}

func TestNoItemsExpired(t *testing.T) {
maxTTL := 2 // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := 3 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
maxTTL := time.Duration(time.Second * 2) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 3) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
tm := New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer tm.Close()

// populate the TtlMap
tm.Put("myString", "a b c")
tm.Put("int_array", []int{1, 2, 3})

sleepTime := maxTTL
time.Sleep(time.Second * time.Duration(sleepTime))
time.Sleep(maxTTL)
t.Logf("tm.len: %v\n", tm.Len())
if tm.Len() != 2 {
t.Fatalf("t.Len should equal 2, but actually equals %v\n", tm.Len())
}
}

func TestKeepFloat(t *testing.T) {
maxTTL := 2 // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := 1 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
maxTTL := time.Duration(time.Second * 2) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 1) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
tm := New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer tm.Close()

// populate the TtlMap
tm.Put("myString", "a b c")
Expand All @@ -63,8 +64,7 @@ func TestKeepFloat(t *testing.T) {
}
}()

sleepTime := maxTTL + pruneInterval
time.Sleep(time.Second * time.Duration(sleepTime))
time.Sleep(maxTTL + pruneInterval)
if tm.Len() != 1 {
t.Fatalf("t.Len should equal 1, but actually equals %v\n", tm.Len())
}
Expand All @@ -77,11 +77,12 @@ func TestKeepFloat(t *testing.T) {
}

func TestWithNoRefresh(t *testing.T) {
maxTTL := 4 // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := 1 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := false // do NOT update item's lastAccessTime on a .Get()
maxTTL := time.Duration(time.Second * 4) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 1) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := false // do NOT update item's lastAccessTime on a .Get()
tm := New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer tm.Close()

// populate the TtlMap
tm.Put("myString", "a b c")
Expand All @@ -94,20 +95,20 @@ func TestWithNoRefresh(t *testing.T) {
}
}()

sleepTime := maxTTL + pruneInterval
time.Sleep(time.Second * time.Duration(sleepTime))
time.Sleep(maxTTL + pruneInterval)
t.Logf("tm.Len: %v\n", tm.Len())
if tm.Len() != 0 {
t.Errorf("t.Len should be 0, but actually equals %v\n", tm.Len())
}
}

func TestDelete(t *testing.T) {
maxTTL := 2 // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := 4 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
maxTTL := time.Duration(time.Second * 2) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 4) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
tm := New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer tm.Close()

// populate the TtlMap
tm.Put("myString", "a b c")
Expand All @@ -127,11 +128,12 @@ func TestDelete(t *testing.T) {
}

func TestClear(t *testing.T) {
maxTTL := 2 // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := 4 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
maxTTL := time.Duration(time.Second * 2) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 4) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
tm := New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer tm.Close()

// populate the TtlMap
tm.Put("myString", "a b c")
Expand All @@ -149,11 +151,12 @@ func TestClear(t *testing.T) {
}

func TestAllFunc(t *testing.T) {
maxTTL := 2 // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := 4 // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
maxTTL := time.Duration(time.Second * 2) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 4) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
tm := New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer tm.Close()

// populate the TtlMap
tm.Put("myString", "a b c")
Expand All @@ -177,3 +180,32 @@ func TestAllFunc(t *testing.T) {
t.Fatalf("allItems and tm.m are not equal\n")
}
}

func TestGetNoUpdate(t *testing.T) {
maxTTL := time.Duration(time.Second * 2) // time in seconds
startSize := 3 // initial number of items in map
pruneInterval := time.Duration(time.Second * 4) // search for expired items every 'pruneInterval' seconds
refreshLastAccessOnGet := true // update item's lastAccessTime on a .Get()
tm := New(maxTTL, startSize, pruneInterval, refreshLastAccessOnGet)
defer tm.Close()

// populate the TtlMap
tm.Put("myString", "a b c")
tm.Put("int", 1234)
tm.Put("floatPi", 3.1415)
tm.Put("int_array", []int{1, 2, 3})
tm.Put("boolean", true)

go func() {
for range time.Tick(time.Second) {
tm.GetNoUpdate("myString")
tm.GetNoUpdate("int_array")
}
}()

time.Sleep(maxTTL + pruneInterval)
t.Logf("tm.Len: %v\n", tm.Len())
if tm.Len() != 0 {
t.Errorf("t.Len should be 0, but actually equals %v\n", tm.Len())
}
}

0 comments on commit 96c4321

Please sign in to comment.