Skip to content

Commit

Permalink
Implement cache serialization
Browse files Browse the repository at this point in the history
Implement dump method

implement load
  • Loading branch information
Javier Mendiara Cañardo authored and isaacs committed Sep 11, 2015
1 parent 7062a0c commit aa9178f
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 28 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,13 @@ away.
Return total quantity of objects currently in cache. Note, that
`stale` (see options) items are returned as part of this item
count.

* `dump()`

Return an array of the cache entries ready for serialization and usage
with 'destinationCache.load(arr)`.

* `load(cacheEntriesArray)`

Loads another cache entries array, obtained with `sourceCache.dump()`,
into the cache. The destination cache is reset before loading new entries
40 changes: 37 additions & 3 deletions lib/lru-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,24 @@ LRUCache.prototype.reset = function () {
this._itemCount = 0
}

// Provided for debugging/dev purposes only. No promises whatsoever that
// this API stays stable.
LRUCache.prototype.dump = function () {
return this._cache
var arr = []
var i = 0

for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
var hit = this._lruList[k]
if (!isStale(this, hit)) {
//Do not store staled hits
++i
arr.push({
k: hit.key,
v: hit.value,
e: hit.now + (hit.maxAge || 0)
});
}
}
//arr has the most read first
return arr
}

LRUCache.prototype.dumpLru = function () {
Expand Down Expand Up @@ -209,6 +223,26 @@ LRUCache.prototype.del = function (key) {
del(this, this._cache[key])
}

LRUCache.prototype.load = function (arr) {
//reset the cache
this.reset();

var now = Date.now()
//A previous serialized cache has the most recent items first
for (var l = arr.length - 1; l >= 0; l-- ) {
var hit = arr[l]
var expiresAt = hit.e || 0
if (expiresAt === 0) {
//the item was created without expiration in a non aged cache
this.set(hit.k, hit.v)
} else {
var maxAge = expiresAt - now
//dont add already expired items
if (maxAge > 0) this.set(hit.k, hit.v, maxAge)
}
}
}

function get (self, key, doUse) {
var hit = self._cache[key]
if (hit) {
Expand Down
25 changes: 0 additions & 25 deletions test/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,31 +93,6 @@ test("reset", function (t) {
})


// Note: `<cache>.dump()` is a debugging tool only. No guarantees are made
// about the format/layout of the response.
test("dump", function (t) {
var cache = new LRU(10)
var d = cache.dump();
t.equal(Object.keys(d).length, 0, "nothing in dump for empty cache")
cache.set("a", "A")
var d = cache.dump() // { a: { key: "a", value: "A", lu: 0 } }
t.ok(d.a)
t.equal(d.a.key, "a")
t.equal(d.a.value, "A")
t.equal(d.a.lu, 0)

cache.set("b", "B")
cache.get("b")
d = cache.dump()
t.ok(d.b)
t.equal(d.b.key, "b")
t.equal(d.b.value, "B")
t.equal(d.b.lu, 2)

t.end()
})


test("basic with weighed length", function (t) {
var cache = new LRU({
max: 100,
Expand Down
216 changes: 216 additions & 0 deletions test/serialize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
var test = require('tap').test
var LRU = require('../')

test('dump', function (t) {
var cache = new LRU()

t.equal(cache.dump().length, 0, "nothing in dump for empty cache")

cache.set("a", "A")
cache.set("b", "B")
t.deepEqual(cache.dump(), [
{ k: "b", v: "B", e: 0 },
{ k: "a", v: "A", e: 0 }
])

cache.set("a", "A");
t.deepEqual(cache.dump(), [
{ k: "a", v: "A", e: 0 },
{ k: "b", v: "B", e: 0 }
])

cache.get("b");
t.deepEqual(cache.dump(), [
{ k: "b", v: "B", e: 0 },
{ k: "a", v: "A", e: 0 }
])

cache.del("a");
t.deepEqual(cache.dump(), [
{ k: "b", v: "B", e: 0 }
])

t.end()
})

test("do not dump stale items", function(t) {
var cache = new LRU({
max: 5,
maxAge: 50
})

//expires at 50
cache.set("a", "A")

setTimeout(function () {
//expires at 75
cache.set("b", "B")
var s = cache.dump()
t.equal(s.length, 2)
t.equal(s[0].k, "b")
t.equal(s[1].k, "a")
}, 25)

setTimeout(function () {
//expires at 110
cache.set("c", "C")
var s = cache.dump()
t.equal(s.length, 2)
t.equal(s[0].k, "c")
t.equal(s[1].k, "b")
}, 60)

setTimeout(function () {
//expires at 130
cache.set("d", "D", 40)
var s = cache.dump()
t.equal(s.length, 2)
t.equal(s[0].k, "d")
t.equal(s[1].k, "c")
}, 90)

setTimeout(function () {
var s = cache.dump()
t.equal(s.length, 1)
t.equal(s[0].k, "d")
}, 120)

setTimeout(function () {
var s = cache.dump()
t.deepEqual(s, [])
t.end()
}, 155)
})

test("load basic cache", function(t) {
var cache = new LRU(),
copy = new LRU()

cache.set("a", "A")
cache.set("b", "B")

copy.load(cache.dump())
t.deepEquals(cache.dump(), copy.dump())

t.end()
})


test("load staled cache", function(t) {
var cache = new LRU({maxAge: 50}),
copy = new LRU({maxAge: 50}),
arr

//expires at 50
cache.set("a", "A")
setTimeout(function () {
//expires at 80
cache.set("b", "B")
arr = cache.dump()
t.equal(arr.length, 2)
}, 30)

setTimeout(function () {
copy.load(arr)
t.equal(copy.get("a"), undefined)
t.equal(copy.get("b"), "B")
}, 60)

setTimeout(function () {
t.equal(copy.get("b"), undefined)
t.end()
}, 90)
})

test("load to other size cache", function(t) {
var cache = new LRU({max: 2}),
copy = new LRU({max: 1})

cache.set("a", "A")
cache.set("b", "B")

copy.load(cache.dump())
t.equal(copy.get("a"), undefined)
t.equal(copy.get("b"), "B")

//update the last read from original cache
cache.get("a")
copy.load(cache.dump())
t.equal(copy.get("a"), "A")
t.equal(copy.get("b"), undefined)

t.end()
})


test("load to other age cache", function(t) {
var cache = new LRU({maxAge: 50}),
aged = new LRU({maxAge: 100}),
simple = new LRU(),
arr,
expired

//created at 0
//a would be valid till 0 + 50
cache.set("a", "A")
setTimeout(function () {
//created at 20
//b would be valid till 20 + 50
cache.set("b", "B")
//b would be valid till 20 + 70
cache.set("c", "C", 70)
arr = cache.dump()
t.equal(arr.length, 3)
}, 20)

setTimeout(function () {
t.equal(cache.get("a"), undefined)
t.equal(cache.get("b"), "B")
t.equal(cache.get("c"), "C")

aged.load(arr)
t.equal(aged.get("a"), undefined)
t.equal(aged.get("b"), "B")
t.equal(aged.get("c"), "C")

simple.load(arr)
t.equal(simple.get("a"), undefined)
t.equal(simple.get("b"), "B")
t.equal(simple.get("c"), "C")
}, 60)

setTimeout(function () {
t.equal(cache.get("a"), undefined)
t.equal(cache.get("b"), undefined)
t.equal(cache.get("c"), "C")

aged.load(arr)
t.equal(aged.get("a"), undefined)
t.equal(aged.get("b"), undefined)
t.equal(aged.get("c"), "C")

simple.load(arr)
t.equal(simple.get("a"), undefined)
t.equal(simple.get("b"), undefined)
t.equal(simple.get("c"), "C")
}, 80)

setTimeout(function () {
t.equal(cache.get("a"), undefined)
t.equal(cache.get("b"), undefined)
t.equal(cache.get("c"), undefined)

aged.load(arr)
t.equal(aged.get("a"), undefined)
t.equal(aged.get("b"), undefined)
t.equal(aged.get("c"), undefined)

simple.load(arr)
t.equal(simple.get("a"), undefined)
t.equal(simple.get("b"), undefined)
t.equal(simple.get("c"), undefined)
t.end()
}, 100)

})

0 comments on commit aa9178f

Please sign in to comment.