MemoryWithGcCachePlugin.js 3.9 KB

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