respect multi compiler `dependencies` and `parallelism` when using `invalidate`
This commit is contained in:
parent
068ce83947
commit
878ce6b11a
|
@ -318,16 +318,17 @@ module.exports = class MultiCompiler {
|
||||||
* @returns {SetupResult[]} result of setup
|
* @returns {SetupResult[]} result of setup
|
||||||
*/
|
*/
|
||||||
_runGraph(setup, run, callback) {
|
_runGraph(setup, run, callback) {
|
||||||
/** @typedef {{ compiler: Compiler, result: Stats, state: "pending" | "blocked" | "queued" | "running" | "running-outdated" | "done", children: Node[], parents: Node[] }} Node */
|
/** @typedef {{ compiler: Compiler, result: Stats, state: "pending" | "blocked" | "queued" | "starting" | "running" | "running-outdated" | "done", children: Node[], parents: Node[] }} Node */
|
||||||
|
|
||||||
// State transitions for nodes:
|
// State transitions for nodes:
|
||||||
// -> blocked (initial)
|
// -> blocked (initial)
|
||||||
// blocked -> queued [add to queue] (when all parents done)
|
// blocked -> starting [running++] (when all parents done)
|
||||||
// queued -> running [running++] (when processing the queue)
|
// queued -> starting [running++] (when processing the queue)
|
||||||
|
// starting -> running (when run has been called)
|
||||||
// running -> done [running--] (when compilation is done)
|
// running -> done [running--] (when compilation is done)
|
||||||
// done -> pending (when invalidated from file change)
|
// done -> pending (when invalidated from file change)
|
||||||
// pending -> blocked (when invalidated from aggregated changes)
|
// pending -> blocked [add to queue] (when invalidated from aggregated changes)
|
||||||
// done -> blocked (when invalidated, from parent invalidation)
|
// done -> blocked [add to queue] (when invalidated, from parent invalidation)
|
||||||
// running -> running-outdated (when invalidated, either from change or parent invalidation)
|
// running -> running-outdated (when invalidated, either from change or parent invalidation)
|
||||||
// running-outdated -> blocked [running--] (when compilation is done)
|
// running-outdated -> blocked [running--] (when compilation is done)
|
||||||
|
|
||||||
|
@ -351,6 +352,7 @@ module.exports = class MultiCompiler {
|
||||||
parent.children.push(node);
|
parent.children.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** @type {ArrayQueue<Node>} */
|
||||||
const queue = new ArrayQueue();
|
const queue = new ArrayQueue();
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (node.parents.length === 0) {
|
if (node.parents.length === 0) {
|
||||||
|
@ -388,13 +390,13 @@ module.exports = class MultiCompiler {
|
||||||
if (node.state === "running") {
|
if (node.state === "running") {
|
||||||
node.state = "done";
|
node.state = "done";
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
checkUnblocked(child);
|
if (child.state === "blocked") queue.enqueue(child);
|
||||||
}
|
}
|
||||||
} else if (node.state === "running-outdated") {
|
} else if (node.state === "running-outdated") {
|
||||||
node.state = "blocked";
|
node.state = "blocked";
|
||||||
checkUnblocked(node);
|
queue.enqueue(node);
|
||||||
}
|
}
|
||||||
process.nextTick(processQueue);
|
processQueue();
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* @param {Node} node node
|
* @param {Node} node node
|
||||||
|
@ -433,20 +435,9 @@ module.exports = class MultiCompiler {
|
||||||
if (node.state === "pending") {
|
if (node.state === "pending") {
|
||||||
node.state = "blocked";
|
node.state = "blocked";
|
||||||
}
|
}
|
||||||
checkUnblocked(node);
|
if (node.state === "blocked") {
|
||||||
processQueue();
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* @param {Node} node node
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const checkUnblocked = node => {
|
|
||||||
if (
|
|
||||||
node.state === "blocked" &&
|
|
||||||
node.parents.every(p => p.state === "done")
|
|
||||||
) {
|
|
||||||
node.state = "queued";
|
|
||||||
queue.enqueue(node);
|
queue.enqueue(node);
|
||||||
|
processQueue();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -457,20 +448,33 @@ module.exports = class MultiCompiler {
|
||||||
node.compiler,
|
node.compiler,
|
||||||
i,
|
i,
|
||||||
nodeDone.bind(null, node),
|
nodeDone.bind(null, node),
|
||||||
() => node.state !== "done" && node.state !== "running",
|
() => node.state !== "starting" && node.state !== "running",
|
||||||
() => nodeChange(node),
|
() => nodeChange(node),
|
||||||
() => nodeInvalid(node)
|
() => nodeInvalid(node)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
let processing = true;
|
||||||
const processQueue = () => {
|
const processQueue = () => {
|
||||||
|
if (processing) return;
|
||||||
|
processing = true;
|
||||||
|
process.nextTick(processQueueWorker);
|
||||||
|
};
|
||||||
|
const processQueueWorker = () => {
|
||||||
while (running < parallelism && queue.length > 0 && !errored) {
|
while (running < parallelism && queue.length > 0 && !errored) {
|
||||||
const node = queue.dequeue();
|
const node = queue.dequeue();
|
||||||
if (node.state !== "queued") continue;
|
if (
|
||||||
running++;
|
node.state === "queued" ||
|
||||||
node.state = "running";
|
(node.state === "blocked" &&
|
||||||
run(node.compiler, nodeDone.bind(null, node));
|
node.parents.every(p => p.state === "done"))
|
||||||
|
) {
|
||||||
|
running++;
|
||||||
|
node.state = "starting";
|
||||||
|
run(node.compiler, nodeDone.bind(null, node));
|
||||||
|
node.state = "running";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
processing = false;
|
||||||
if (
|
if (
|
||||||
!errored &&
|
!errored &&
|
||||||
running === 0 &&
|
running === 0 &&
|
||||||
|
@ -489,7 +493,7 @@ module.exports = class MultiCompiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
processQueue();
|
processQueueWorker();
|
||||||
return setupResults;
|
return setupResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,13 +159,16 @@ class Watching {
|
||||||
|
|
||||||
let stats = null;
|
let stats = null;
|
||||||
|
|
||||||
const handleError = err => {
|
const handleError = (err, cbs) => {
|
||||||
this.compiler.hooks.failed.call(err);
|
this.compiler.hooks.failed.call(err);
|
||||||
this.compiler.cache.beginIdle();
|
this.compiler.cache.beginIdle();
|
||||||
this.compiler.idle = true;
|
this.compiler.idle = true;
|
||||||
this.handler(err, stats);
|
this.handler(err, stats);
|
||||||
for (const cb of this.callbacks) cb();
|
if (!cbs) {
|
||||||
this.callbacks.length = 0;
|
cbs = this.callbacks;
|
||||||
|
this.callbacks = [];
|
||||||
|
}
|
||||||
|
for (const cb of cbs) cb(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -197,17 +200,19 @@ class Watching {
|
||||||
}
|
}
|
||||||
if (err) return handleError(err);
|
if (err) return handleError(err);
|
||||||
|
|
||||||
|
const cbs = this.callbacks;
|
||||||
|
this.callbacks = [];
|
||||||
logger.time("done hook");
|
logger.time("done hook");
|
||||||
this.compiler.hooks.done.callAsync(stats, err => {
|
this.compiler.hooks.done.callAsync(stats, err => {
|
||||||
logger.timeEnd("done hook");
|
logger.timeEnd("done hook");
|
||||||
if (err) return handleError(err);
|
if (err) return handleError(err, cbs);
|
||||||
this.handler(null, stats);
|
this.handler(null, stats);
|
||||||
logger.time("storeBuildDependencies");
|
logger.time("storeBuildDependencies");
|
||||||
this.compiler.cache.storeBuildDependencies(
|
this.compiler.cache.storeBuildDependencies(
|
||||||
compilation.buildDependencies,
|
compilation.buildDependencies,
|
||||||
err => {
|
err => {
|
||||||
logger.timeEnd("storeBuildDependencies");
|
logger.timeEnd("storeBuildDependencies");
|
||||||
if (err) return handleError(err);
|
if (err) return handleError(err, cbs);
|
||||||
logger.time("beginIdle");
|
logger.time("beginIdle");
|
||||||
this.compiler.cache.beginIdle();
|
this.compiler.cache.beginIdle();
|
||||||
this.compiler.idle = true;
|
this.compiler.idle = true;
|
||||||
|
@ -221,8 +226,7 @@ class Watching {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (const cb of this.callbacks) cb();
|
for (const cb of cbs) cb(null);
|
||||||
this.callbacks.length = 0;
|
|
||||||
this.compiler.hooks.afterDone.call(stats);
|
this.compiler.hooks.afterDone.call(stats);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -293,6 +297,7 @@ class Watching {
|
||||||
this._invalidReported = true;
|
this._invalidReported = true;
|
||||||
this.compiler.hooks.invalid.call(null, Date.now());
|
this.compiler.hooks.invalid.call(null, Date.now());
|
||||||
}
|
}
|
||||||
|
this._onChange();
|
||||||
this._invalidate();
|
this._invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,14 +340,6 @@ class Watching {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkUnblocked() {
|
|
||||||
if (this.blocked && !this._isBlocked()) {
|
|
||||||
this.blocked = false;
|
|
||||||
this._needWatcherInfo = true;
|
|
||||||
this._invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Callback<void>} callback signals when the watcher is closed
|
* @param {Callback<void>} callback signals when the watcher is closed
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
|
|
@ -375,6 +375,161 @@ describe("MultiCompiler", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should respect parallelism when using invalidate", done => {
|
||||||
|
const configs = [
|
||||||
|
{
|
||||||
|
name: "a",
|
||||||
|
mode: "development",
|
||||||
|
entry: { a: "./a.js" },
|
||||||
|
context: path.join(__dirname, "fixtures")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "b",
|
||||||
|
mode: "development",
|
||||||
|
entry: { b: "./b.js" },
|
||||||
|
context: path.join(__dirname, "fixtures")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
configs.parallelism = 1;
|
||||||
|
const compiler = webpack(configs);
|
||||||
|
|
||||||
|
const events = [];
|
||||||
|
compiler.compilers.forEach(c => {
|
||||||
|
c.hooks.invalid.tap("test", () => {
|
||||||
|
events.push(`${c.name} invalid`);
|
||||||
|
});
|
||||||
|
c.hooks.watchRun.tap("test", () => {
|
||||||
|
events.push(`${c.name} run`);
|
||||||
|
});
|
||||||
|
c.hooks.done.tap("test", () => {
|
||||||
|
events.push(`${c.name} done`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
compiler.watchFileSystem = { watch() {} };
|
||||||
|
compiler.outputFileSystem = createFsFromVolume(new Volume());
|
||||||
|
|
||||||
|
let state = 0;
|
||||||
|
const watching = compiler.watch({}, error => {
|
||||||
|
if (error) {
|
||||||
|
done(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state !== 0) return;
|
||||||
|
state++;
|
||||||
|
|
||||||
|
expect(events).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
"a run",
|
||||||
|
"a done",
|
||||||
|
"b run",
|
||||||
|
"b done",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
events.length = 0;
|
||||||
|
|
||||||
|
watching.invalidate(err => {
|
||||||
|
try {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
expect(events).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
"a invalid",
|
||||||
|
"b invalid",
|
||||||
|
"a run",
|
||||||
|
"a done",
|
||||||
|
"b run",
|
||||||
|
"b done",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
events.length = 0;
|
||||||
|
expect(state).toBe(1);
|
||||||
|
setTimeout(done, 1000);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
it("should respect dependencies when using invalidate", done => {
|
||||||
|
const compiler = webpack([
|
||||||
|
{
|
||||||
|
name: "a",
|
||||||
|
mode: "development",
|
||||||
|
entry: { a: "./a.js" },
|
||||||
|
context: path.join(__dirname, "fixtures"),
|
||||||
|
dependencies: ["b"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "b",
|
||||||
|
mode: "development",
|
||||||
|
entry: { b: "./b.js" },
|
||||||
|
context: path.join(__dirname, "fixtures")
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const events = [];
|
||||||
|
compiler.compilers.forEach(c => {
|
||||||
|
c.hooks.invalid.tap("test", () => {
|
||||||
|
events.push(`${c.name} invalid`);
|
||||||
|
});
|
||||||
|
c.hooks.watchRun.tap("test", () => {
|
||||||
|
events.push(`${c.name} run`);
|
||||||
|
});
|
||||||
|
c.hooks.done.tap("test", () => {
|
||||||
|
events.push(`${c.name} done`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
compiler.watchFileSystem = { watch() {} };
|
||||||
|
compiler.outputFileSystem = createFsFromVolume(new Volume());
|
||||||
|
|
||||||
|
let state = 0;
|
||||||
|
const watching = compiler.watch({}, error => {
|
||||||
|
if (error) {
|
||||||
|
done(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state !== 0) return;
|
||||||
|
state++;
|
||||||
|
|
||||||
|
expect(events).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
"b run",
|
||||||
|
"b done",
|
||||||
|
"a run",
|
||||||
|
"a done",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
events.length = 0;
|
||||||
|
|
||||||
|
watching.invalidate(err => {
|
||||||
|
try {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
expect(events).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
"a invalid",
|
||||||
|
"b invalid",
|
||||||
|
"b run",
|
||||||
|
"b done",
|
||||||
|
"a run",
|
||||||
|
"a done",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
events.length = 0;
|
||||||
|
expect(state).toBe(1);
|
||||||
|
setTimeout(done, 1000);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
it("shouldn't hang when invalidating watchers", done => {
|
it("shouldn't hang when invalidating watchers", done => {
|
||||||
const entriesA = { a: "./a.js" };
|
const entriesA = { a: "./a.js" };
|
||||||
const entriesB = { b: "./b.js" };
|
const entriesB = { b: "./b.js" };
|
||||||
|
|
Loading…
Reference in New Issue