Files
shields/lib/lru-cache.js
Paul Melnikow 5a3516c687 Clean up some helpers (#1117)
Address comments from #1109 and make several code-quality fixes which were caught by a local run of eslint-config-standard.
2017-10-06 13:11:29 -04:00

133 lines
3.3 KiB
JavaScript

'use strict';
// In-memory KV, remove the oldest data when the capacity is reached.
const typeEnum = {
unit: 0,
heap: 1,
};
function CacheSlot(key, value) {
this.key = key;
this.value = value;
this.older = null; // Newest slot that is older than this slot.
this.newer = null; // Oldest slot that is newer than this slot.
}
function Cache(capacity, type) {
if (!(this instanceof Cache)) { return new Cache(capacity, type); }
type = type || 'unit';
this.capacity = capacity;
this.type = typeEnum[type];
this.cache = new Map(); // Maps cache keys to CacheSlots.
this.newest = null; // Newest slot in the cache.
this.oldest = null;
}
Cache.prototype = {
set: function addToCache(cacheKey, cached) {
let slot = this.cache.get(cacheKey);
if (slot === undefined) {
slot = new CacheSlot(cacheKey, cached);
this.cache.set(cacheKey, slot);
}
this.makeNewest(slot);
const numItemsToRemove = this.limitReached();
if (numItemsToRemove > 0) {
for (let i = 0; i < numItemsToRemove; i++) {
this.removeOldest();
}
}
},
get: function getFromCache(cacheKey) {
const slot = this.cache.get(cacheKey);
if (slot !== undefined) {
this.makeNewest(slot);
return slot.value;
}
},
has: function hasInCache(cacheKey) {
return this.cache.has(cacheKey);
},
makeNewest: function makeNewestSlot(slot) {
const previousNewest = this.newest;
if (previousNewest === slot) { return; }
const older = slot.older;
const newer = slot.newer;
if (older !== null) {
older.newer = newer;
} else if (newer !== null) {
this.oldest = newer;
}
if (newer !== null) {
newer.older = older;
}
this.newest = slot;
if (previousNewest !== null) {
slot.older = previousNewest;
slot.newer = null;
previousNewest.newer = slot;
} else {
// If previousNewest is null, the cache used to be empty.
this.oldest = slot;
}
},
removeOldest: function removeOldest() {
const cacheKey = this.oldest.key;
if (this.oldest !== null) {
this.oldest = this.oldest.newer;
if (this.oldest !== null) {
this.oldest.older = null;
}
}
this.cache.delete(cacheKey);
},
// Returns the number of elements to remove if we're past the limit.
limitReached: function heuristic() {
if (this.type === typeEnum.unit) {
// Remove the excess.
return Math.max(0, (this.cache.size - this.capacity));
} else if (this.type === typeEnum.heap) {
if (getHeapSize() >= this.capacity) {
console.log('LRU HEURISTIC heap:', getHeapSize());
// Remove half of them.
return (this.cache.size >> 1);
} else { return 0; }
} else {
console.error("Unknown heuristic '" + this.type + "' for LRU cache.");
return 1;
}
},
clear: function () {
this.cache.clear();
this.newest = null;
this.oldest = null;
}
};
// In bytes.
let heapSize;
let heapSizeTimeout;
function getHeapSize() {
if (heapSizeTimeout == null) {
// Compute the heap size every 60 seconds.
heapSizeTimeout = setInterval(computeHeapSize, 60 * 1000);
return computeHeapSize();
} else {
return heapSize;
}
}
function computeHeapSize() {
return (heapSize = process.memoryUsage().heapTotal);
}
module.exports = Cache;