index.cjs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. const PINNED_IN_MEMORY = 0x7fffffff;
  4. const NOT_IN_LRU = 0x40000000;
  5. const EXPIRED_ENTRY = {
  6. description: 'This cache entry value has been expired from the LRFU cache, and is waiting for garbage collection to be removed.'
  7. };
  8. /* bit pattern:
  9. * < is-in-lru 1 bit > ...< mask/or bits 6 bits > <lru index 2 bits > < position in cache - 22 bits >
  10. */
  11. class LRFUExpirer {
  12. constructor(options) {
  13. this.lruSize = options && options.lruSize || 0x2000;
  14. if (this.lruSize > 0x400000)
  15. throw new Error('The LRU/cache size was larger than the maximum cache size of 16777216 (LRU size of 4194304)')
  16. this.reset();
  17. startTimedCleanup(new WeakRef(this), options && options.cleanupInterval || 60000);
  18. }
  19. delete(entry) {
  20. if (entry.position < NOT_IN_LRU) {
  21. this.lru[(entry.position >> 22) & 3][entry.position & 0x3fffff] = null;
  22. }
  23. entry.position |= NOT_IN_LRU;
  24. }
  25. used(entry, expirationPriority) {
  26. let originalPosition = entry.position;
  27. if (expirationPriority < 0) {
  28. // pin this in memory, first remove from LRFU and then mark it as pinned in memory
  29. if (entry.position < NOT_IN_LRU) {
  30. this.lru[(entry.position >> 22) & 3][entry.position & 0x3fffff] = null;
  31. }
  32. entry.position = PINNED_IN_MEMORY;
  33. return
  34. } else if (entry.position == PINNED_IN_MEMORY && expirationPriority == undefined) {
  35. return
  36. } else if (expirationPriority >= 0) {
  37. let bits = 0;
  38. if (expirationPriority > (this.lruSize >> 2))
  39. expirationPriority = this.lruSize >> 2;
  40. while (expirationPriority > 0) {
  41. expirationPriority = expirationPriority >> 1;
  42. bits++;
  43. }
  44. expirationPriority = bits;
  45. } else {
  46. if (originalPosition >= 0)
  47. expirationPriority = (originalPosition >> 24) & 0x3f;
  48. else
  49. expirationPriority = 0;
  50. }
  51. let lruPosition;
  52. let lruIndex;
  53. if (originalPosition < NOT_IN_LRU) {
  54. lruIndex = (originalPosition >> 22) & 3;
  55. if (lruIndex >= 3)
  56. return // can't get any higher than this, don't do anything
  57. let lru = this.lru[lruIndex];
  58. // check to see if it is in the same generation
  59. lruPosition = lru.position;
  60. if ((originalPosition > lruPosition ? lruPosition + this.lruSize : lruPosition) - originalPosition < (this.lruSize >> 2))
  61. return // only recently added, don't promote
  62. lru[originalPosition & 0x3fffff] = null; // remove it, we are going to move/promote it
  63. lruIndex++;
  64. } else
  65. lruIndex = 0;
  66. this.insertEntry(entry, lruIndex, expirationPriority);
  67. }
  68. insertEntry(entry, lruIndex, expirationPriority) {
  69. let lruPosition, nextLru = this.lru[lruIndex];
  70. let orMask = 0x3fffff >> (22 - expirationPriority);
  71. do {
  72. // put it in the next lru
  73. lruPosition = nextLru.position | orMask;
  74. let previousEntry = nextLru[lruPosition & 0x3fffff];
  75. nextLru[lruPosition & 0x3fffff] = entry;
  76. if (entry)
  77. entry.position = lruPosition | (expirationPriority << 24);
  78. nextLru.position = ++lruPosition;
  79. if ((lruPosition & 0x3fffff) >= this.lruSize) {
  80. // reset at the beginning of the lru cache
  81. lruPosition &= 0x7fc00000;
  82. nextLru.position = lruPosition;
  83. nextLru.cycles++;
  84. }
  85. entry = previousEntry;
  86. if (entry && (nextLru = this.lru[--lruIndex])) {
  87. expirationPriority = ((entry.position || 0) >> 24) & 0x3f;
  88. orMask = 0x3fffff >> (22 - expirationPriority);
  89. } else
  90. break
  91. } while (true)
  92. if (entry) {// this one was removed
  93. entry.position |= NOT_IN_LRU;
  94. if (entry.cache)
  95. entry.cache.onRemove(entry);
  96. else if (entry.deref) // if we have already registered the entry in the finalization registry, just clear it
  97. entry.value = EXPIRED_ENTRY;
  98. }
  99. }
  100. reset() {
  101. /* if (this.lru) {
  102. for (let i = 0; i < 4; i++) {
  103. for (let j = 0, l = this.lru.length; j < l; j++) {
  104. let entry = this.lru[i][j]
  105. if (entry) {// this one was removed
  106. entry.position |= NOT_IN_LRU
  107. if (entry.cache)
  108. entry.cache.onRemove(entry)
  109. else if (entry.deref) // if we have already registered the entry in the finalization registry, just clear it
  110. entry.value = EXPIRED_ENTRY
  111. }
  112. }
  113. }
  114. }*/
  115. this.lru = [];
  116. for (let i = 0; i < 4; i++) {
  117. this.lru[i] = new Array(this.lruSize);
  118. this.lru[i].position = i << 22;
  119. this.lru[i].cycles = 0;
  120. }
  121. }
  122. cleanup() { // clean out a portion of the cache, so we can clean up over time if idle
  123. let toClear = this.lruSize >> 4; // 1/16 of the lru cache at a time
  124. for (let i = 3; i >= 0; i--) {
  125. let lru = this.lru[i];
  126. for (let j = 0, l = toClear; j < l; j++) {
  127. if (lru[lru.position & 0x3fffff]) {
  128. toClear--;
  129. this.insertEntry(null, i, 0);
  130. } else {
  131. if ((++lru.position & 0x3fffff) >= this.lruSize) {
  132. // reset at the beginning of the lru cache
  133. lru.position &= 0x7fc00000;
  134. lru.cycles++;
  135. }
  136. }
  137. }
  138. }
  139. }
  140. }
  141. function startTimedCleanup(reference, cleanupInterval) {
  142. let interval = setInterval(() => {
  143. let expirer = reference.deref();
  144. if (expirer)
  145. expirer.cleanup();
  146. else
  147. clearInterval(interval);
  148. }, cleanupInterval);
  149. if (interval.unref)
  150. interval.unref();
  151. }
  152. let defaultExpirer;
  153. class WeakLRUCache extends Map {
  154. constructor(options) {
  155. super();
  156. this.hits = 0;
  157. this.misses = 0;
  158. if (options && options.cacheSize) {
  159. options.lruSize = options.cacheSize >> 2;
  160. }
  161. if (options && options.clearKeptInterval) {
  162. this.clearKeptInterval = options.clearKeptInterval;
  163. this.clearKeptCount = 0;
  164. this.clearKeptObjects = options.clearKeptObjects;
  165. }
  166. this.expirer = (options ? options.expirer === false ? defaultNoLRUExpirer : options.expirer : null) || defaultExpirer || (defaultExpirer = new LRFUExpirer(options));
  167. this.deferRegister = Boolean(options && options.deferRegister);
  168. let registry = this.registry = new FinalizationRegistry(key => {
  169. let entry = super.get(key);
  170. if (entry && entry.deref && entry.deref() === undefined)
  171. super.delete(key);
  172. });
  173. }
  174. onRemove(entry) {
  175. let target = entry.deref && entry.deref();
  176. if (target) {
  177. // remove strong reference, so only a weak reference, wait until it is finalized to remove
  178. this.registry.register(target, entry.key);
  179. entry.value = undefined;
  180. } else if (entry.key) {
  181. let currentEntry = super.get(entry.key);
  182. if (currentEntry === entry)
  183. super.delete(entry.key);
  184. }
  185. }
  186. get(key, mode) {
  187. let entry = super.get(key);
  188. let value;
  189. if (entry) {
  190. this.hits++;
  191. value = entry.value;
  192. if (value === EXPIRED_ENTRY) {
  193. value = entry.deref && entry.deref();
  194. if (value === undefined)
  195. super.delete(key);
  196. else {
  197. entry.value = value;
  198. if (this.clearKeptInterval)
  199. this.incrementClearKeptCount();
  200. if (mode !== 1)
  201. this.expirer.used(entry);
  202. return mode === 2 ? value : entry
  203. }
  204. }
  205. else {
  206. if (mode !== 1)
  207. this.expirer.used(entry);
  208. return mode === 2 ? value : entry
  209. }
  210. } else
  211. this.misses++;
  212. }
  213. getValue(key) {
  214. return this.get(key, 2)
  215. }
  216. setValue(key, value, expirationPriority) {
  217. let entry;
  218. if (value && typeof value == 'object') {
  219. entry = new WeakRef(value);
  220. if (this.clearKeptInterval)
  221. this.incrementClearKeptCount();
  222. entry.value = value;
  223. if (this.deferRegister) {
  224. entry.key = key;
  225. entry.cache = this;
  226. } else
  227. this.registry.register(value, key);
  228. } else if (value !== undefined)
  229. entry = { value, key, cache: this };
  230. // else entry is undefined
  231. this.set(key, entry, expirationPriority);
  232. return entry
  233. }
  234. incrementClearKeptCount() {
  235. if (++this.clearKeptCount >= this.clearKeptInterval) {
  236. this.clearKeptCount = 0;
  237. if (this.clearKeptObjects)
  238. this.clearKeptObjects();
  239. if (this.registry.cleanupSome)
  240. this.registry.cleanupSome();
  241. }
  242. }
  243. set(key, entry, expirationPriority) {
  244. let oldEntry = super.get(key);
  245. if (oldEntry)
  246. this.expirer.delete(oldEntry);
  247. return this.insert(key, entry, expirationPriority)
  248. }
  249. insert(key, entry, expirationPriority) {
  250. if (entry) {
  251. this.expirer.used(entry, expirationPriority);
  252. }
  253. return super.set(key, entry)
  254. }
  255. delete(key) {
  256. let oldEntry = super.get(key);
  257. if (oldEntry) {
  258. this.expirer.delete(oldEntry);
  259. }
  260. return super.delete(key)
  261. }
  262. used(entry, expirationPriority) {
  263. this.expirer.used(entry, expirationPriority);
  264. }
  265. clear() {
  266. for (let [ key, entry ] of this) {
  267. this.expirer.delete(entry);
  268. super.delete(key);
  269. }
  270. }
  271. }
  272. class NoLRUExpirer {
  273. used(entry) {
  274. if (entry.cache)
  275. entry.cache.onRemove(entry);
  276. else if (entry.deref) // if we have already registered the entry in the finalization registry, just mark it expired from the beginning
  277. entry.value = EXPIRED_ENTRY;
  278. }
  279. delete(entry) {
  280. // nothing to do here, we don't have a separate cache here
  281. }
  282. }
  283. const defaultNoLRUExpirer = new NoLRUExpirer();
  284. exports.LRFUExpirer = LRFUExpirer;
  285. exports.WeakLRUCache = WeakLRUCache;