123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const Cache = require("../Cache");
- const ProgressPlugin = require("../ProgressPlugin");
- /** @typedef {import("../Compiler")} Compiler */
- const BUILD_DEPENDENCIES_KEY = Symbol();
- class IdleFileCachePlugin {
- /**
- * @param {TODO} strategy cache strategy
- * @param {number} idleTimeout timeout
- * @param {number} idleTimeoutForInitialStore initial timeout
- * @param {number} idleTimeoutAfterLargeChanges timeout after changes
- */
- constructor(
- strategy,
- idleTimeout,
- idleTimeoutForInitialStore,
- idleTimeoutAfterLargeChanges
- ) {
- this.strategy = strategy;
- this.idleTimeout = idleTimeout;
- this.idleTimeoutForInitialStore = idleTimeoutForInitialStore;
- this.idleTimeoutAfterLargeChanges = idleTimeoutAfterLargeChanges;
- }
- /**
- * Apply the plugin
- * @param {Compiler} compiler the compiler instance
- * @returns {void}
- */
- apply(compiler) {
- let strategy = this.strategy;
- const idleTimeout = this.idleTimeout;
- const idleTimeoutForInitialStore = Math.min(
- idleTimeout,
- this.idleTimeoutForInitialStore
- );
- const idleTimeoutAfterLargeChanges = this.idleTimeoutAfterLargeChanges;
- const resolvedPromise = Promise.resolve();
- let timeSpendInBuild = 0;
- let timeSpendInStore = 0;
- let avgTimeSpendInStore = 0;
- /** @type {Map<string | typeof BUILD_DEPENDENCIES_KEY, () => Promise>} */
- const pendingIdleTasks = new Map();
- compiler.cache.hooks.store.tap(
- { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
- (identifier, etag, data) => {
- pendingIdleTasks.set(identifier, () =>
- strategy.store(identifier, etag, data)
- );
- }
- );
- compiler.cache.hooks.get.tapPromise(
- { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
- (identifier, etag, gotHandlers) => {
- const restore = () =>
- strategy.restore(identifier, etag).then(cacheEntry => {
- if (cacheEntry === undefined) {
- gotHandlers.push((result, callback) => {
- if (result !== undefined) {
- pendingIdleTasks.set(identifier, () =>
- strategy.store(identifier, etag, result)
- );
- }
- callback();
- });
- } else {
- return cacheEntry;
- }
- });
- const pendingTask = pendingIdleTasks.get(identifier);
- if (pendingTask !== undefined) {
- pendingIdleTasks.delete(identifier);
- return pendingTask().then(restore);
- }
- return restore();
- }
- );
- compiler.cache.hooks.storeBuildDependencies.tap(
- { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
- dependencies => {
- pendingIdleTasks.set(BUILD_DEPENDENCIES_KEY, () =>
- strategy.storeBuildDependencies(dependencies)
- );
- }
- );
- compiler.cache.hooks.shutdown.tapPromise(
- { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
- () => {
- if (idleTimer) {
- clearTimeout(idleTimer);
- idleTimer = undefined;
- }
- isIdle = false;
- const reportProgress = ProgressPlugin.getReporter(compiler);
- const jobs = Array.from(pendingIdleTasks.values());
- if (reportProgress) reportProgress(0, "process pending cache items");
- const promises = jobs.map(fn => fn());
- pendingIdleTasks.clear();
- promises.push(currentIdlePromise);
- const promise = Promise.all(promises);
- currentIdlePromise = promise.then(() => strategy.afterAllStored());
- if (reportProgress) {
- currentIdlePromise = currentIdlePromise.then(() => {
- reportProgress(1, `stored`);
- });
- }
- return currentIdlePromise.then(() => {
- // Reset strategy
- if (strategy.clear) strategy.clear();
- });
- }
- );
- /** @type {Promise<any>} */
- let currentIdlePromise = resolvedPromise;
- let isIdle = false;
- let isInitialStore = true;
- const processIdleTasks = () => {
- if (isIdle) {
- const startTime = Date.now();
- if (pendingIdleTasks.size > 0) {
- const promises = [currentIdlePromise];
- const maxTime = startTime + 100;
- let maxCount = 100;
- for (const [filename, factory] of pendingIdleTasks) {
- pendingIdleTasks.delete(filename);
- promises.push(factory());
- if (maxCount-- <= 0 || Date.now() > maxTime) break;
- }
- currentIdlePromise = Promise.all(promises);
- currentIdlePromise.then(() => {
- timeSpendInStore += Date.now() - startTime;
- // Allow to exit the process between
- idleTimer = setTimeout(processIdleTasks, 0);
- idleTimer.unref();
- });
- return;
- }
- currentIdlePromise = currentIdlePromise
- .then(async () => {
- await strategy.afterAllStored();
- timeSpendInStore += Date.now() - startTime;
- avgTimeSpendInStore =
- Math.max(avgTimeSpendInStore, timeSpendInStore) * 0.9 +
- timeSpendInStore * 0.1;
- timeSpendInStore = 0;
- timeSpendInBuild = 0;
- })
- .catch(err => {
- const logger = compiler.getInfrastructureLogger(
- "IdleFileCachePlugin"
- );
- logger.warn(`Background tasks during idle failed: ${err.message}`);
- logger.debug(err.stack);
- });
- isInitialStore = false;
- }
- };
- let idleTimer = undefined;
- compiler.cache.hooks.beginIdle.tap(
- { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
- () => {
- const isLargeChange = timeSpendInBuild > avgTimeSpendInStore * 2;
- if (isInitialStore && idleTimeoutForInitialStore < idleTimeout) {
- compiler
- .getInfrastructureLogger("IdleFileCachePlugin")
- .log(
- `Initial cache was generated and cache will be persisted in ${
- idleTimeoutForInitialStore / 1000
- }s.`
- );
- } else if (
- isLargeChange &&
- idleTimeoutAfterLargeChanges < idleTimeout
- ) {
- compiler
- .getInfrastructureLogger("IdleFileCachePlugin")
- .log(
- `Spend ${Math.round(timeSpendInBuild) / 1000}s in build and ${
- Math.round(avgTimeSpendInStore) / 1000
- }s in average in cache store. This is considered as large change and cache will be persisted in ${
- idleTimeoutAfterLargeChanges / 1000
- }s.`
- );
- }
- idleTimer = setTimeout(() => {
- idleTimer = undefined;
- isIdle = true;
- resolvedPromise.then(processIdleTasks);
- }, Math.min(isInitialStore ? idleTimeoutForInitialStore : Infinity, isLargeChange ? idleTimeoutAfterLargeChanges : Infinity, idleTimeout));
- idleTimer.unref();
- }
- );
- compiler.cache.hooks.endIdle.tap(
- { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
- () => {
- if (idleTimer) {
- clearTimeout(idleTimer);
- idleTimer = undefined;
- }
- isIdle = false;
- }
- );
- compiler.hooks.done.tap("IdleFileCachePlugin", stats => {
- // 10% build overhead is ignored, as it's not cacheable
- timeSpendInBuild *= 0.9;
- timeSpendInBuild += stats.endTime - stats.startTime;
- });
- }
- }
- module.exports = IdleFileCachePlugin;
|