caching.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { WeakLRUCache, clearKeptObjects } from './native.js';
  2. import { FAILED_CONDITION, ABORT, IF_EXISTS } from './write.js';
  3. import { UNMODIFIED } from './read.js';
  4. import { when } from './util/when.js';
  5. let getLastVersion, getLastTxnId;
  6. const mapGet = Map.prototype.get;
  7. export const CachingStore = (Store, env) => {
  8. let childTxnChanges;
  9. return class LMDBStore extends Store {
  10. constructor(dbName, options) {
  11. super(dbName, options);
  12. if (!env.cacheCommitter) {
  13. env.cacheCommitter = true;
  14. this.on('aftercommit', ({ next, last, txnId }) => {
  15. do {
  16. let meta = next.meta;
  17. let store = meta && meta.store;
  18. if (store) {
  19. if (next.flag & FAILED_CONDITION)
  20. store.cache.delete(meta.key); // just delete it from the map
  21. else {
  22. let expirationPriority = meta.valueSize >> 10;
  23. let cache = store.cache;
  24. let entry = mapGet.call(cache, meta.key);
  25. if (entry && !entry.txnId) {
  26. entry.txnId = txnId;
  27. cache.used(entry, expirationPriority + 4); // this will enter it into the LRFU (with a little lower priority than a read)
  28. }
  29. }
  30. }
  31. } while (next != last && (next = next.next));
  32. });
  33. }
  34. this.db.cachingDb = this;
  35. if (options.cache.clearKeptInterval)
  36. options.cache.clearKeptObjects = clearKeptObjects;
  37. this.cache = new WeakLRUCache(options.cache);
  38. if (options.cache.validated) this.cache.validated = true;
  39. }
  40. get isCaching() {
  41. return true;
  42. }
  43. get(id, options) {
  44. let value;
  45. if (this.cache.validated) {
  46. let entry = this.cache.get(id);
  47. if (entry) {
  48. let cachedValue = entry.value;
  49. if (entry.txnId != null) {
  50. value = super.get(id, {
  51. ifNotTxnId: entry.txnId,
  52. transaction: options && options.transaction,
  53. });
  54. if (value === UNMODIFIED) return cachedValue;
  55. } // with no txn id we do not validate; this is the state of a cached value after a write before it transacts
  56. else return cachedValue;
  57. } else value = super.get(id, options);
  58. } else if (options && options.transaction) {
  59. return super.get(id, options);
  60. } else {
  61. value = this.cache.getValue(id);
  62. if (value !== undefined) {
  63. return value;
  64. }
  65. value = super.get(id);
  66. }
  67. if (
  68. value &&
  69. typeof value === 'object' &&
  70. !options &&
  71. typeof id !== 'object'
  72. ) {
  73. let entry = this.cache.setValue(id, value, this.lastSize >> 10);
  74. if (this.useVersions) {
  75. entry.version = getLastVersion();
  76. }
  77. if (this.cache.validated) entry.txnId = getLastTxnId();
  78. }
  79. return value;
  80. }
  81. getEntry(id, options) {
  82. let entry, value;
  83. if (this.cache.validated) {
  84. entry = this.cache.get(id);
  85. if (entry) {
  86. if (entry.txnId != null) {
  87. value = super.get(id, {
  88. ifNotTxnId: entry.txnId,
  89. transaction: options && options.transaction,
  90. });
  91. if (value === UNMODIFIED) return entry;
  92. } // with no txn id we do not validate; this is the state of a cached value after a write before it transacts
  93. else return entry;
  94. } else value = super.get(id, options);
  95. } else if (options && options.transaction) {
  96. return super.getEntry(id, options);
  97. } else {
  98. entry = this.cache.get(id);
  99. if (entry !== undefined) {
  100. return entry;
  101. }
  102. value = super.get(id);
  103. }
  104. if (value === undefined) return;
  105. if (value && typeof value === 'object' && typeof id !== 'object') {
  106. entry = this.cache.setValue(id, value, this.lastSize >> 10);
  107. } else entry = { value };
  108. if (this.useVersions) entry.version = getLastVersion();
  109. if (this.cache.validated) entry.txnId = getLastTxnId();
  110. return entry;
  111. }
  112. putEntry(id, entry, ifVersion) {
  113. let result = super.put(id, entry.value, entry.version, ifVersion);
  114. if (typeof id === 'object') return result;
  115. if (result && result.then)
  116. this.cache.setManually(id, entry); // set manually so we can keep it pinned in memory until it is committed
  117. // sync operation, immediately add to cache
  118. else this.cache.set(id, entry);
  119. }
  120. put(id, value, version, ifVersion) {
  121. let result = super.put(id, value, version, ifVersion);
  122. if (typeof id !== 'object') {
  123. if (value && value['\x10binary-data\x02']) {
  124. // don't cache binary data, since it will be decoded on get
  125. this.cache.delete(id);
  126. return result;
  127. }
  128. let entry;
  129. if (this.cachePuts === false) {
  130. // we are not caching puts, clear the entry at least
  131. this.cache.delete(id);
  132. } else {
  133. if (result?.isSync) {
  134. // sync operation, immediately add to cache
  135. if (result.result)
  136. // if it succeeds
  137. entry = this.cache.setValue(id, value, 0);
  138. else {
  139. this.cache.delete(id);
  140. return result;
  141. } // sync failure
  142. // otherwise keep it pinned in memory until it is committed
  143. } else entry = this.cache.setValue(id, value, -1);
  144. }
  145. if (childTxnChanges) childTxnChanges.add(id);
  146. if (version !== undefined && entry)
  147. entry.version =
  148. typeof version === 'object' ? version.version : version;
  149. }
  150. return result;
  151. }
  152. putSync(id, value, version, ifVersion) {
  153. let result = super.putSync(id, value, version, ifVersion);
  154. if (id !== 'object') {
  155. // sync operation, immediately add to cache, otherwise keep it pinned in memory until it is committed
  156. if (
  157. value &&
  158. this.cachePuts !== false &&
  159. typeof value === 'object' &&
  160. result
  161. ) {
  162. let entry = this.cache.setValue(id, value);
  163. if (childTxnChanges) childTxnChanges.add(id);
  164. if (version !== undefined) {
  165. entry.version =
  166. typeof version === 'object' ? version.version : version;
  167. }
  168. } // it is possible that a value used to exist here
  169. else this.cache.delete(id);
  170. }
  171. return result;
  172. }
  173. remove(id, ifVersion) {
  174. this.cache.delete(id);
  175. return super.remove(id, ifVersion);
  176. }
  177. removeSync(id, ifVersion) {
  178. this.cache.delete(id);
  179. return super.removeSync(id, ifVersion);
  180. }
  181. clearAsync(callback) {
  182. this.cache.clear();
  183. return super.clearAsync(callback);
  184. }
  185. clearSync() {
  186. this.cache.clear();
  187. super.clearSync();
  188. }
  189. childTransaction(callback) {
  190. return super.childTransaction(() => {
  191. let cache = this.cache;
  192. let previousChanges = childTxnChanges;
  193. try {
  194. childTxnChanges = new Set();
  195. return when(
  196. callback(),
  197. (result) => {
  198. if (result === ABORT) return abort();
  199. childTxnChanges = previousChanges;
  200. return result;
  201. },
  202. abort,
  203. );
  204. } catch (error) {
  205. abort(error);
  206. }
  207. function abort(error) {
  208. // if the transaction was aborted, remove all affected entries from cache
  209. for (let id of childTxnChanges) cache.delete(id);
  210. childTxnChanges = previousChanges;
  211. if (error) throw error;
  212. else return ABORT;
  213. }
  214. });
  215. }
  216. doesExist(key, versionOrValue) {
  217. let entry = this.cache.get(key);
  218. if (entry) {
  219. if (versionOrValue == null) {
  220. return versionOrValue !== null;
  221. } else if (this.useVersions) {
  222. return (
  223. versionOrValue === IF_EXISTS || entry.version === versionOrValue
  224. );
  225. }
  226. }
  227. return super.doesExist(key, versionOrValue);
  228. }
  229. };
  230. };
  231. export function setGetLastVersion(get, getTxnId) {
  232. getLastVersion = get;
  233. getLastTxnId = getTxnId;
  234. }