Commit 731e20de authored by Juan Batiz-Benet's avatar Juan Batiz-Benet

time cache

parent 2990467a
package timecache
import (
"sync"
"time"
ds "github.com/jbenet/go-datastore"
dsq "github.com/jbenet/go-datastore/query"
)
// op keys
var (
putKey = "put"
getKey = "get"
hasKey = "has"
deleteKey = "delete"
)
type datastore struct {
cache ds.Datastore
ttl time.Duration
ttlmu sync.Mutex
ttls map[ds.Key]time.Time
}
func WithTTL(ttl time.Duration) ds.Datastore {
return WithCache(ds.NewMapDatastore(), ttl)
}
// WithCache wraps a given datastore as a timecache.
// Get + Has requests are considered expired after a TTL.
func WithCache(d ds.Datastore, ttl time.Duration) ds.Datastore {
return &datastore{cache: d, ttl: ttl, ttls: make(map[ds.Key]time.Time)}
}
func (d *datastore) gc() {
var now = time.Now()
var del []ds.Key
// remove all expired ttls.
d.ttlmu.Lock()
for k, ttl := range d.ttls {
if now.After(ttl) {
delete(d.ttls, k)
del = append(del, k)
}
}
d.ttlmu.Unlock()
for _, k := range del {
d.cache.Delete(k)
}
}
func (d *datastore) ttlPut(key ds.Key) {
d.ttlmu.Lock()
d.ttls[key] = time.Now().Add(d.ttl)
d.ttlmu.Unlock()
}
func (d *datastore) ttlDelete(key ds.Key) {
d.ttlmu.Lock()
delete(d.ttls, key)
d.ttlmu.Unlock()
}
// Put stores the object `value` named by `key`.
func (d *datastore) Put(key ds.Key, value interface{}) (err error) {
err = d.cache.Put(key, value)
d.ttlPut(key)
return err
}
// Get retrieves the object `value` named by `key`.
func (d *datastore) Get(key ds.Key) (value interface{}, err error) {
d.gc()
return d.cache.Get(key)
}
// Has returns whether the `key` is mapped to a `value`.
func (d *datastore) Has(key ds.Key) (exists bool, err error) {
d.gc()
return d.cache.Has(key)
}
// Delete removes the value for given `key`.
func (d *datastore) Delete(key ds.Key) (err error) {
d.ttlDelete(key)
return d.cache.Delete(key)
}
// Query returns a list of keys in the datastore
func (d *datastore) Query(q dsq.Query) (dsq.Results, error) {
return d.cache.Query(q)
}
package timecache
import (
"testing"
"time"
ds "github.com/jbenet/go-datastore"
)
func testHas(t *testing.T, d ds.Datastore, k ds.Key, v interface{}) {
if v2, err := d.Get(k); err != nil {
t.Error(err)
} else if v2 != v {
t.Error("value incorrect", d, k, v, v2)
}
if has, err := d.Has(k); err != nil {
t.Error(err)
} else if !has {
t.Error("should have it", d, k, v)
}
}
func testNotHas(t *testing.T, d ds.Datastore, k ds.Key) {
if _, err := d.Get(k); err == nil {
t.Error("should not have it", d, k)
}
if has, err := d.Has(k); err != nil {
t.Error(err)
} else if has {
t.Error("should not have it", d, k)
}
}
func TestTimeCache(t *testing.T) {
ttl := time.Millisecond * 100
cache := WithTTL(ttl)
cache.Put(ds.NewKey("foo1"), "bar1")
cache.Put(ds.NewKey("foo2"), "bar2")
<-time.After(ttl / 2)
cache.Put(ds.NewKey("foo3"), "bar3")
cache.Put(ds.NewKey("foo4"), "bar4")
testHas(t, cache, ds.NewKey("foo1"), "bar1")
testHas(t, cache, ds.NewKey("foo2"), "bar2")
testHas(t, cache, ds.NewKey("foo3"), "bar3")
testHas(t, cache, ds.NewKey("foo4"), "bar4")
<-time.After(ttl / 2)
testNotHas(t, cache, ds.NewKey("foo1"))
testNotHas(t, cache, ds.NewKey("foo2"))
testHas(t, cache, ds.NewKey("foo3"), "bar3")
testHas(t, cache, ds.NewKey("foo4"), "bar4")
cache.Delete(ds.NewKey("foo3"))
testNotHas(t, cache, ds.NewKey("foo3"))
<-time.After(ttl / 2)
testNotHas(t, cache, ds.NewKey("foo1"))
testNotHas(t, cache, ds.NewKey("foo2"))
testNotHas(t, cache, ds.NewKey("foo3"))
testNotHas(t, cache, ds.NewKey("foo4"))
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment