Skip to content
This repository has been archived by the owner on Feb 20, 2021. It is now read-only.

close funct #4

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
322ac4e
Add optional extending life of cache
ikoroteev Feb 10, 2015
93d0853
Add ещ test optional extending life of cache.
ikoroteev Feb 10, 2015
8468f76
Update build status
ikoroteev Feb 10, 2015
0003c0e
Update Readme.md
ikoroteev Feb 10, 2015
ecfe582
Update Readme.md
ikoroteev Feb 10, 2015
fafc9f8
Update .travis.yml
ikoroteev Feb 10, 2015
7b8fd86
Update .travis.yml
ikoroteev Feb 10, 2015
5942cd6
Update .travis.yml
ikoroteev Feb 10, 2015
1a0a68f
Add ttl parameter to cache item. Delete common ttl for cache. Add cac…
Feb 11, 2015
d656abf
fix documentation
Feb 11, 2015
0956115
Update Godeps.json
ikoroteev Feb 13, 2015
33b77e6
add cleanAll function
Jun 19, 2015
cbfc329
fix
Jun 19, 2015
b756420
Merge pull request #1 from oxx/master
ikoroteev Jun 19, 2015
e1d6510
publish func
Jun 22, 2015
6bcebd8
Merge pull request #2 from oxx/master
ikoroteev Jun 22, 2015
4072ffb
counter get cache queries
Jun 26, 2015
e9495f5
fix
Jun 26, 2015
f6d769d
fix
Jun 26, 2015
618dfeb
Merge pull request #3 from oxx/master
ikoroteev Jun 26, 2015
1cb3af2
fix + add test
Jun 26, 2015
def66f1
Merge pull request #4 from oxx/master
ikoroteev Jun 26, 2015
1aae39e
Add delete method for cache
Sep 17, 2015
8f0dc53
Fix test name for auto start
ikoroteev Jan 26, 2017
051cefc
update go version in travis to 1.7
ikoroteev Feb 10, 2017
e6b8810
fix travis config
ikoroteev Feb 10, 2017
2ac460b
delete unused Godeps
May 26, 2017
3316d83
Merge branch 'master' of https://github.com/ikoroteev/ttlcache
May 26, 2017
7a3a3c1
remove godeps usage
ikoroteev May 26, 2017
9a1ecd1
Update .travis.yml
ikoroteev Feb 20, 2018
e0bd42b
Move unlock to defer
ikoroteev Feb 27, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 4 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
language: go

go:
- 1.2
- release
- tip
- 1.7

git:
depth: 1

install:
- go install -race std
- go get code.google.com/p/go.tools/cmd/cover
- go get golang.org/x/tools/cmd/cover
- go get github.com/golang/lint/golint
- go get github.com/tools/godep
- export PATH=$HOME/gopath/bin:$PATH

script:
- golint .
- godep go test -race ./...
- godep go test -cover ./...
- go test -race ./...
- go test -cover ./...
5 changes: 0 additions & 5 deletions Godeps/Godeps.json

This file was deleted.

2 changes: 0 additions & 2 deletions Godeps/_workspace/.gitignore

This file was deleted.

18 changes: 10 additions & 8 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
## TTLCache - an in-memory LRU cache with expiration
## TTLCache - an in-memory cache with expiration

TTLCache is a minimal wrapper over a string map in golang, entries of which are
TTLCache is a minimal wrapper over a map of custom in golang, entries of which are

1. Thread-safe
2. Auto-Expiring after a certain time
3. Auto-Extending expiration on `Get`s
3. Managed auto-extending expiration on `Get`s

[![Build Status](https://travis-ci.org/wunderlist/ttlcache.svg)](https://travis-ci.org/wunderlist/ttlcache)
[![Build Status](https://travis-ci.org/ikoroteev/ttlcache.svg)](https://travis-ci.org/ikoroteev/ttlcache)

#### Usage
```go
import (
"time"
"github.com/wunderlist/ttlcache"
"github.com/ikoroteev/ttlcache"
)

func main () {
cache := ttlcache.NewCache(time.Second)
cache.Set("key", "value")
value, exists := cache.Get("key")
cache := ttlcache.NewCache()
cache.Set("key", "value", time.Second)
cache.Set("key1", 24, time.Duration(500) * time.Millisecond)
cache.Set("key3", time.Second, time.Second)
value, exists := cache.Get("key", true) // true - extend cache ttl, otherwise false
count := cache.Count()
}
```
59 changes: 42 additions & 17 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,45 @@ import (

// Cache is a synchronised map of items that auto-expire once stale
type Cache struct {
mutex sync.RWMutex
ttl time.Duration
items map[string]*Item
mutex sync.RWMutex
items map[string]*Item
counter uint64
}

// Set is a thread-safe way to add new items to the map
func (cache *Cache) Set(key string, data string) {
func (cache *Cache) Set(key string, data interface{}, ttl time.Duration) {
cache.mutex.Lock()
item := &Item{data: data}
item.touch(cache.ttl)
defer cache.mutex.Unlock()
item := &Item{data: data, ttl: ttl}
item.touch()
cache.items[key] = item
cache.mutex.Unlock()
}

// Get is a thread-safe way to lookup items
// Every lookup, also touches the item, hence extending it's life
func (cache *Cache) Get(key string) (data string, found bool) {
// Every lookup, if touch set to true, touches the item, hence extending it's life
func (cache *Cache) Get(key string, touch bool) (data interface{}, found bool) {
cache.mutex.Lock()
defer cache.mutex.Unlock()
item, exists := cache.items[key]
if !exists || item.expired() {
data = ""
found = false
} else {
item.touch(cache.ttl)
if touch {
item.touch()
}
cache.counter++
data = item.data
found = true
}
cache.mutex.Unlock()
return
}

//GetCounter return cache hit count
func (cache *Cache) GetCounter() uint64 {
return cache.counter
}

// Count returns the number of items in the cache
// (helpful for tracking memory leaks)
func (cache *Cache) Count() int {
Expand All @@ -49,18 +57,20 @@ func (cache *Cache) Count() int {

func (cache *Cache) cleanup() {
cache.mutex.Lock()
defer cache.mutex.Unlock()
for key, item := range cache.items {
if item.expired() {
delete(cache.items, key)
}
}
cache.mutex.Unlock()
}

func (cache *Cache) startCleanupTimer() {
duration := cache.ttl
if duration < time.Second {
duration = time.Second
duration := time.Second
for i := range cache.items {
if cache.items[i].ttl < time.Second {
duration = cache.items[i].ttl
}
}
ticker := time.Tick(duration)
go (func() {
Expand All @@ -73,10 +83,25 @@ func (cache *Cache) startCleanupTimer() {
})()
}

//CleanAll delete all data from cache
func (cache *Cache) CleanAll() {
cache.mutex.Lock()
defer cache.mutex.Unlock()
for key := range cache.items {
delete(cache.items, key)
}
}

//Delete cache item by key
func (cache *Cache) Delete(key string) {
cache.mutex.Lock()
delete(cache.items, key)
cache.mutex.Unlock()
}

// NewCache is a helper to create instance of the Cache struct
func NewCache(duration time.Duration) *Cache {
func NewCache() *Cache {
cache := &Cache{
ttl: duration,
items: map[string]*Item{},
}
cache.startCleanupTimer()
Expand Down
36 changes: 27 additions & 9 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,52 @@ import (

func TestGet(t *testing.T) {
cache := &Cache{
ttl: time.Second,
items: map[string]*Item{},
}

data, exists := cache.Get("hello")
data, exists := cache.Get("hello", true)
if exists || data != "" {
t.Errorf("Expected empty cache to return no data")
}

cache.Set("hello", "world")
data, exists = cache.Get("hello")
cache.Set("hello", "world", time.Second)
data, exists = cache.Get("hello", true)
if !exists {
t.Errorf("Expected cache to return data for `hello`")
}
if data != "world" {
t.Errorf("Expected cache to return `world` for `hello`")
}
if cache.GetCounter() != 1 {
t.Errorf("Expected cache get counter is equal to 1")
}
}

func TestDelete(t *testing.T) {
cache := &Cache{
items: map[string]*Item{},
}
cache.Set("Test", "Delete", time.Second)
_, exists := cache.Get("Test", true)
if !exists {
t.Errorf("Expected cache to return data for `Test`")
}
cache.Delete("Test")
_, exists = cache.Get("Test", true)
if exists {
t.Errorf("Expected cache to delete data for `Test`")
}

}

func TestExpiration(t *testing.T) {
cache := &Cache{
ttl: time.Second,
items: map[string]*Item{},
}

cache.Set("x", "1")
cache.Set("y", "z")
cache.Set("z", "3")
cache.Set("x", "1", time.Second)
cache.Set("y", 123, time.Second)
cache.Set("z", time.Second, time.Second)
cache.startCleanupTimer()

count := cache.Count()
Expand All @@ -44,7 +62,7 @@ func TestExpiration(t *testing.T) {

<-time.After(500 * time.Millisecond)
cache.mutex.Lock()
cache.items["y"].touch(time.Second)
cache.items["y"].touch()
item, exists := cache.items["x"]
cache.mutex.Unlock()
if !exists || item.data != "1" || item.expired() {
Expand Down
7 changes: 4 additions & 3 deletions item.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import (
// Item represents a record in the cache map
type Item struct {
sync.RWMutex
data string
data interface{}
expires *time.Time
ttl time.Duration
}

func (item *Item) touch(duration time.Duration) {
func (item *Item) touch() {
item.Lock()
expiration := time.Now().Add(duration)
expiration := time.Now().Add(item.ttl)
item.expires = &expiration
item.Unlock()
}
Expand Down
4 changes: 2 additions & 2 deletions item_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func TestExpired(t *testing.T) {
}

func TestTouch(t *testing.T) {
item := &Item{data: "blahblah"}
item.touch(time.Second)
item := &Item{data: "blahblah", ttl: time.Second}
item.touch()
if item.expired() {
t.Errorf("Expected item to not be expired once touched")
}
Expand Down