Files
shields/lib/lru-cache.js
Paul Melnikow f271b82670 Nudge forward style checks a la eslint-config-standard (#1082)
Because I despise nitpicking stuff like indentation and spacing in pull request comments, I'd like to nudge forward our automated style checking, at least for new files being added.

I don't want to totally rewrite server.js just to get automated style checking… the blame tracking is just too useful. So let's it's just take care of that when we start splitting it out.

More discussion in #948.
2017-10-01 21:09:43 -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;