MemoryWithGcCachePlugin.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Cache = require("../Cache");
  7. /** @typedef {import("webpack-sources").Source} Source */
  8. /** @typedef {import("../Cache").Etag} Etag */
  9. /** @typedef {import("../Compiler")} Compiler */
  10. /** @typedef {import("../Module")} Module */
  11. class MemoryWithGcCachePlugin {
  12. /**
  13. * @param {object} options Options
  14. * @param {number} options.maxGenerations max generations
  15. */
  16. constructor({ maxGenerations }) {
  17. this._maxGenerations = maxGenerations;
  18. }
  19. /**
  20. * Apply the plugin
  21. * @param {Compiler} compiler the compiler instance
  22. * @returns {void}
  23. */
  24. apply(compiler) {
  25. const maxGenerations = this._maxGenerations;
  26. /** @type {Map<string, { etag: Etag | null, data: any } | undefined | null>} */
  27. const cache = new Map();
  28. /** @type {Map<string, { entry: { etag: Etag | null, data: any } | null, until: number }>} */
  29. const oldCache = new Map();
  30. let generation = 0;
  31. let cachePosition = 0;
  32. const logger = compiler.getInfrastructureLogger("MemoryWithGcCachePlugin");
  33. compiler.hooks.afterDone.tap("MemoryWithGcCachePlugin", () => {
  34. generation++;
  35. let clearedEntries = 0;
  36. let lastClearedIdentifier;
  37. // Avoid coverage problems due indirect changes
  38. /* istanbul ignore next */
  39. for (const [identifier, entry] of oldCache) {
  40. if (entry.until > generation) break;
  41. oldCache.delete(identifier);
  42. if (cache.get(identifier) === undefined) {
  43. cache.delete(identifier);
  44. clearedEntries++;
  45. lastClearedIdentifier = identifier;
  46. }
  47. }
  48. if (clearedEntries > 0 || oldCache.size > 0) {
  49. logger.log(
  50. `${cache.size - oldCache.size} active entries, ${
  51. oldCache.size
  52. } recently unused cached entries${
  53. clearedEntries > 0
  54. ? `, ${clearedEntries} old unused cache entries removed e. g. ${lastClearedIdentifier}`
  55. : ""
  56. }`
  57. );
  58. }
  59. let i = (cache.size / maxGenerations) | 0;
  60. let j = cachePosition >= cache.size ? 0 : cachePosition;
  61. cachePosition = j + i;
  62. for (const [identifier, entry] of cache) {
  63. if (j !== 0) {
  64. j--;
  65. continue;
  66. }
  67. if (entry !== undefined) {
  68. // We don't delete the cache entry, but set it to undefined instead
  69. // This reserves the location in the data table and avoids rehashing
  70. // when constantly adding and removing entries.
  71. // It will be deleted when removed from oldCache.
  72. cache.set(identifier, undefined);
  73. oldCache.delete(identifier);
  74. oldCache.set(identifier, {
  75. entry,
  76. until: generation + maxGenerations
  77. });
  78. if (i-- === 0) break;
  79. }
  80. }
  81. });
  82. compiler.cache.hooks.store.tap(
  83. { name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY },
  84. (identifier, etag, data) => {
  85. cache.set(identifier, { etag, data });
  86. }
  87. );
  88. compiler.cache.hooks.get.tap(
  89. { name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY },
  90. (identifier, etag, gotHandlers) => {
  91. const cacheEntry = cache.get(identifier);
  92. if (cacheEntry === null) {
  93. return null;
  94. } else if (cacheEntry !== undefined) {
  95. return cacheEntry.etag === etag ? cacheEntry.data : null;
  96. }
  97. const oldCacheEntry = oldCache.get(identifier);
  98. if (oldCacheEntry !== undefined) {
  99. const cacheEntry = oldCacheEntry.entry;
  100. if (cacheEntry === null) {
  101. oldCache.delete(identifier);
  102. cache.set(identifier, cacheEntry);
  103. return null;
  104. } else {
  105. if (cacheEntry.etag !== etag) return null;
  106. oldCache.delete(identifier);
  107. cache.set(identifier, cacheEntry);
  108. return cacheEntry.data;
  109. }
  110. }
  111. gotHandlers.push((result, callback) => {
  112. if (result === undefined) {
  113. cache.set(identifier, null);
  114. } else {
  115. cache.set(identifier, { etag, data: result });
  116. }
  117. return callback();
  118. });
  119. }
  120. );
  121. compiler.cache.hooks.shutdown.tap(
  122. { name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY },
  123. () => {
  124. cache.clear();
  125. oldCache.clear();
  126. }
  127. );
  128. }
  129. }
  130. module.exports = MemoryWithGcCachePlugin;