@thermopylae/lib.cache
Version:
116 lines (115 loc) • 4.06 kB
JavaScript
import { number } from '@thermopylae/lib.utils';
import { DoublyLinkedList } from "../../data-structures/list/doubly-linked.js";
import { createException } from "../../error.js";
const SEGMENT_SYM = Symbol('SEGMENT_SYM');
class SegmentedLRUEvictionPolicy {
segments;
deleteFromCache;
constructor(cacheMaxCapacity, protectedOverProbationRatio = 0.7) {
if (cacheMaxCapacity < 2) {
throw createException("INVALID_CACHE_MAX_CAPACITY" , `Capacity needs to be at least 2. Given: ${cacheMaxCapacity}.`);
}
const protectedSegmentSize = Math.round(number.percentage(cacheMaxCapacity, protectedOverProbationRatio)
);
if (protectedSegmentSize < 1) {
const context = { cacheMaxCapacity, protectedOverProbationRatio, protectedSegmentSize };
throw createException("INVALID_PROTECTED_SEGMENT_SIZE" , `Protected segment size needs to be at least 1. Context: ${JSON.stringify(context)}.`);
}
this.segments = {
[1 ]: {
capacity: protectedSegmentSize,
items: new DoublyLinkedList()
},
[0 ]: {
capacity: cacheMaxCapacity - protectedSegmentSize,
items: new DoublyLinkedList()
}
};
if (this.segments[0 ].capacity < 1) {
const context = {
cacheMaxCapacity,
protectedOverProbationRatio,
protectedSegmentSize,
probationSegmentSize: this.segments[0 ].capacity
};
throw createException("INVALID_PROBATION_SEGMENT_SIZE" , `Probation segment size needs to be at least 1. Context: ${JSON.stringify(context)}.`);
}
}
get size() {
let size = 0;
for (const segment of Object.keys(this.segments)) {
size += this.segments[segment].items.size;
}
return size;
}
get mostRecent() {
return this.getEntryFrom('head');
}
get leastRecent() {
return this.getEntryFrom('tail');
}
onHit(entry) {
this.promote(entry);
return 1 ;
}
onMiss() {
return undefined;
}
onSet(entry) {
this.demote(entry);
}
onUpdate() {
return undefined;
}
onDelete(entry) {
this.segments[entry[SEGMENT_SYM]].items.remove(entry);
entry[SEGMENT_SYM] = undefined;
}
onClear() {
for (const segment of Object.keys(this.segments)) {
this.segments[segment].items.clear();
}
}
setDeleter(deleter) {
this.deleteFromCache = deleter;
}
isFull(segment) {
return this.segments[segment].items.size === this.segments[segment].capacity;
}
demote(entry) {
if (this.isFull(0 )) {
this.deleteFromCache(this.segments[0 ].items.tail);
}
this.segments[0 ].items.unshift(entry);
entry[SEGMENT_SYM] = 0 ;
}
promote(entry) {
switch (entry[SEGMENT_SYM]) {
case 0 :
this.segments[0 ].items.remove(entry);
if (this.isFull(1 )) {
const tail = this.segments[1 ].items.tail;
this.segments[1 ].items.remove(tail);
this.demote(tail);
}
this.segments[1 ].items.unshift(entry);
entry[SEGMENT_SYM] = 1 ;
break;
case 1 :
this.segments[1 ].items.toFront(entry);
break;
default:
throw createException("UNKNOWN_SEGMENT_TYPE" , `Unknown segment type: ${entry[SEGMENT_SYM]} found in entry: ${JSON.stringify(entry)}.`);
}
}
getEntryFrom(pos) {
if (this.segments[1 ].items[pos] == null) {
if (this.segments[0 ].items[pos] == null) {
return null;
}
return this.segments[0 ].items[pos];
}
return this.segments[1 ].items[pos];
}
}
export { SegmentedLRUEvictionPolicy, SEGMENT_SYM };