Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: isaacs/node-lru-cache
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.1.3
Choose a base ref
...
head repository: isaacs/node-lru-cache
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.2.0
Choose a head ref
  • 2 commits
  • 6 files changed
  • 1 contributor

Commits on Feb 8, 2022

  1. add reason to dispose() method calls

    Fix #153
    isaacs committed Feb 8, 2022
    Copy the full SHA
    2883ad7 View commit details
  2. 7.2.0

    isaacs committed Feb 8, 2022
    Copy the full SHA
    385bf73 View commit details
Showing with 68 additions and 35 deletions.
  1. +5 −1 CHANGELOG.md
  2. +17 −5 README.md
  3. +12 −6 index.js
  4. +2 −2 package-lock.json
  5. +1 −1 package.json
  6. +31 −20 test/dispose.js
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# cringe lorg

### 7.1.0 - 2022-02
## 7.2.0

* Add reason to dispose() calls.

## 7.1.0

* Add `ttlResolution` option
* Add `ttlAutopurge` option
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -131,18 +131,30 @@ If you put more stuff in it, then items will fall out.
Deprecated alias: `length`

* `dispose` Function that is called on items when they are dropped
from the cache. This can be handy if you want to close file
descriptors or do other cleanup tasks when items are no longer
stored in the cache.
from the cache, as `this.dispose(value, key, reason)`.

It is called *after* the item has been fully removed from the cache, so
if you want to put it right back in, that is safe to do.
This can be handy if you want to close file descriptors or do other
cleanup tasks when items are no longer stored in the cache.

**NOTE**: It is called *before* the item has been fully removed from
the cache, so if you want to put it right back in, you need to wait
until the next tick. If you try to add it back in during the
`dispose()` function call, it will break things in subtle and weird
ways.

Unlike several other options, this may _not_ be overridden by passing
an option to `set()`, for performance reasons. If disposal functions
may vary between cache entries, then the entire list must be scanned
on every cache swap, even if no disposal function is in use.

The `reason` will be one of the following strings, corresponding to the
reason for the item's deletion:

* `evict` Item was evicted to make space for a new addition
* `set` Item was overwritten by a new value
* `delete` Item was removed by explicit `cache.delete(key)` or by
calling `cache.clear()`, which deletes everything.

Optional, must be a function.

* `noDisposeOnSet` Set to `true` to suppress calling the `dispose()`
18 changes: 12 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -341,7 +341,7 @@ class LRUCache {
}
}

dispose (v, k) {}
dispose (v, k, reason) {}

set (k, v, {
ttl = this.ttl,
@@ -366,7 +366,7 @@ class LRUCache {
const oldVal = this.valList[index]
if (v !== oldVal) {
if (!noDisposeOnSet) {
this.dispose(oldVal, k)
this.dispose(oldVal, k, 'set')
}
this.removeItemSize(index)
this.valList[index] = v
@@ -405,7 +405,8 @@ class LRUCache {
evict () {
const head = this.head
const k = this.keyList[head]
this.dispose(this.valList[head], k)
const v = this.valList[head]
this.dispose(v, k, 'evict')
this.removeItemSize(head)
this.head = this.next[head]
this.keyMap.delete(k)
@@ -474,11 +475,11 @@ class LRUCache {
if (this.size !== 0) {
const index = this.keyMap.get(k)
if (index !== undefined) {
this.dispose(this.valList[index], k)
this.removeItemSize(index)
if (this.size === 1) {
this.clear()
} else {
this.removeItemSize(index)
this.dispose(this.valList[index], k, 'delete')
this.keyMap.delete(k)
this.keyList[index] = null
this.valList[index] = null
@@ -491,13 +492,18 @@ class LRUCache {
this.prev[this.next[index]] = this.prev[index]
}
this.size --
this.free.push(index)
}
this.free.push(index)
}
}
}

clear () {
if (this.dispose !== LRUCache.prototype.dispose) {
for (const index of this.rindexes()) {
this.dispose(this.valList[index], this.keyList[index], 'delete')
}
}
this.keyMap.clear()
this.valList.fill(null)
this.keyList.fill(null)
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "lru-cache",
"description": "A cache object that deletes the least-recently-used items.",
"version": "7.1.0",
"version": "7.2.0",
"author": "Isaac Z. Schlueter <i@izs.me>",
"keywords": [
"mru",
51 changes: 31 additions & 20 deletions test/dispose.js
Original file line number Diff line number Diff line change
@@ -3,59 +3,70 @@ const LRU = require('../')

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

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

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

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

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

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

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

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

// disposed because of being overwritten
c.clear()
@@ -64,11 +75,11 @@ t.test('disposal', t => {
c.set(i, i)
}
c.set(2, 'two')
t.strictSame(disposed, [[2, 2]])
t.strictSame(disposed, [[2, 2, 'set']])
for (let i = 0; i < 5; i++) {
t.equal(c.get(i), i === 2 ? 'two' : i)
}
t.strictSame(disposed, [[2, 2]])
t.strictSame(disposed, [[2, 2, 'set']])

c.noDisposeOnSet = true
c.clear()