Skip to content

Commit

Permalink
disposal
Browse files Browse the repository at this point in the history
fix: #188
  • Loading branch information
isaacs committed Jan 20, 2022
1 parent a9ea27a commit 16e8687
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 11 deletions.
50 changes: 39 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class LRUEntry {
}

const asInt = n => ~~n
const ifFunc = n => typeof n === 'function' ? n : null
const naiveLength = () => 1

class LRUCache {
Expand All @@ -23,14 +24,22 @@ class LRUCache {
this.current = new Map()
this.oldSize = 0
this.currentSize = 0
this.sizeCalculation = options.length || naiveLength
this.sizeCalculation = ifFunc(options.sizeCalculation) ||
ifFunc(options.length) ||
naiveLength
this.dispose = ifFunc(options.dispose)
}
get size () {
return this.oldSize + this.currentSize
}
set (key, value) {
const entry = new LRUEntry(value, this.sizeCalculation(value))
this.currentSize += entry.size
const entry = new LRUEntry(value, this.sizeCalculation(value, key))
const replace = this.current.get(key)
this.currentSize += entry.size - (replace ? replace.size : 0)
const { dispose } = this
if (dispose && replace && this.old.get(key) !== replace) {
dispose(key, replace.value)
}
this.current.set(key, entry)
this.prune()
}
Expand All @@ -52,6 +61,7 @@ class LRUCache {
}
}
delete (key) {
const { dispose } = this
const fromOld = this.old.get(key)
if (fromOld) {
this.old.delete(key)
Expand All @@ -62,6 +72,14 @@ class LRUCache {
this.current.delete(key)
this.currentSize -= fromCurrent.size
}
if (dispose && (fromOld || fromCurrent)) {
if (fromOld) {
dispose(key, fromOld.value)
}
if (fromCurrent && fromCurrent !== fromOld) {
dispose(key, fromCurrent.value)
}
}
}
has (key, updateRecency) {
if (this.current.has(key)) {
Expand All @@ -74,17 +92,27 @@ class LRUCache {
return !!oldHas
}
reset () {
this.old = new Map()
this.oldSize = 0
this.current = new Map()
this.currentSize = 0
this.swap()
this.swap()
}
prune () {
if (this.currentSize >= this.max) {
this.oldSize = this.currentSize
this.old = this.current
this.currentSize = 0
this.current = new Map()
this.swap()
}
}
swap () {
const { current, old, dispose } = this
this.oldSize = this.currentSize
this.old = this.current
this.currentSize = 0
this.current = new Map()
// do this *after* it's dropped from the cache
if (dispose) {
for (const [key, entry] of old.entries()) {
if (current.get(key) !== entry) {
dispose(key, entry.value)
}
}
}
}
}
Expand Down
71 changes: 71 additions & 0 deletions test/dispose.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const t = require('tap')
const LRU = require('../')

t.test('disposal', t => {
const disposed = []
const c = new LRU({max:5, dispose: (k,v) => disposed.push([k,v])})
for (let i = 0; i < 9; i++) {
c.set(i, i)
}
t.strictSame(disposed, [])
t.equal(c.size, 9)
t.equal(c.oldSize, 5)
t.equal(c.currentSize, 4)

c.set(9, 9)
t.strictSame(disposed, [
[0, 0],
[1, 1],
[2, 2],
[3, 3],
[4, 4],
])

disposed.length = 0
c.set('asdf', 'foo')
c.set('asdf', 'asdf')
t.strictSame(disposed, [['asdf', 'foo']])

disposed.length = 0
for (let i = 0; i < 5; i++) {
c.set(i, i)
}
t.strictSame(disposed, [[5, 5], [6, 6], [7, 7], [8, 8], [9, 9]])

// dispose both old and current
disposed.length = 0
c.set('asdf', 'foo')
c.delete('asdf')
t.strictSame(disposed, [['asdf', 'asdf'], ['asdf', 'foo']])

// delete non-existing key, no disposal
disposed.length = 0
c.delete('asdf')
t.strictSame(disposed, [])

// delete key that's only in new
disposed.length = 0
c.delete(4)
t.strictSame(disposed, [[4, 4]])

// delete key that's been promoted, only dispose one time
disposed.length = 0
t.equal(c.get(3), 3)
c.delete(3)
t.strictSame(disposed, [[3, 3]])

// no disposal if the entries stayed around in current,
// only for the entries that actually fell out
c.reset()
disposed.length = 0
for (let i = 0; i < 5; i++) {
c.set(i, i)
}
c.set(2, 'two')
for (let i = 0; i < 5; i++) {
t.equal(c.get(i), i === 2 ? 'two' : i)
}
t.strictSame(disposed, [[2, 2]])

t.end()
})
15 changes: 15 additions & 0 deletions test/size-calculation.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,20 @@ t.test('store strings, size = length', t => {
t.equal(c.current.size, 0)
t.equal(c.currentSize, 0)

c.set('repeated', 'i'.repeat(10))
c.set('repeated', 'j'.repeat(10))
c.set('repeated', 'i'.repeat(10))
c.set('repeated', 'j'.repeat(10))
c.set('repeated', 'i'.repeat(10))
c.set('repeated', 'j'.repeat(10))
c.set('repeated', 'i'.repeat(10))
c.set('repeated', 'j'.repeat(10))
t.equal(c.size, 60)
t.equal(c.old.size, 5)
t.equal(c.oldSize, 50)
t.equal(c.current.size, 1)
t.equal(c.currentSize, 10)
t.equal(c.get('repeated'), 'j'.repeat(10))

t.end()
})

0 comments on commit 16e8687

Please sign in to comment.