Skip to content

Commit ae07f0b

Browse files
authored
fix: unify timing report for concurrent linting (#20188)
* fix: unify timing report for concurrent linting * fix: handle missing worker_threads in non-Node environments * refactor: export `disableDisplay()` and call it in `worker.js` * chore: restore blank line after `use strict` * fix: don’t print empty timing table on errors * fix: suppress timing output if any worker fails
1 parent b165d47 commit ae07f0b

File tree

3 files changed

+67
-3
lines changed

3 files changed

+67
-3
lines changed

lib/eslint/eslint.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const { pathToFileURL } = require("node:url");
1717
const { SHARE_ENV, Worker } = require("node:worker_threads");
1818
const { version } = require("../../package.json");
1919
const { defaultConfig } = require("../config/default-config");
20+
const timing = require("../linter/timing");
2021

2122
const {
2223
createDebug,
@@ -503,6 +504,11 @@ async function runWorkers(
503504
worstNetLintingRatio,
504505
netLintingRatio,
505506
);
507+
508+
if (timing.enabled && indexedResults.timings) {
509+
timing.mergeData(indexedResults.timings);
510+
}
511+
506512
for (const result of indexedResults) {
507513
const { index } = result;
508514
delete result.index;
@@ -522,7 +528,17 @@ async function runWorkers(
522528
for (let index = 0; index < workerCount; ++index) {
523529
promises[index] = new Promise(workerExecutor);
524530
}
525-
await Promise.all(promises);
531+
532+
try {
533+
await Promise.all(promises);
534+
} catch (error) {
535+
/*
536+
* If any worker fails, suppress timing display in the main thread
537+
* to avoid printing partial or misleading timing output.
538+
*/
539+
timing.disableDisplay();
540+
throw error;
541+
}
526542

527543
if (worstNetLintingRatio < LOW_NET_LINTING_RATIO) {
528544
warnOnLowNetLintingRatio();

lib/eslint/worker.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const {
2929
processOptions,
3030
} = require("./eslint-helpers");
3131
const { WarningService } = require("../services/warning-service");
32+
const timing = require("../linter/timing");
3233

3334
const depsLoadedTime = hrtimeBigint();
3435

@@ -39,7 +40,7 @@ const depsLoadedTime = hrtimeBigint();
3940
/** @typedef {import("../types").ESLint.LintResult} LintResult */
4041
/** @typedef {import("../types").ESLint.Options} ESLintOptions */
4142
/** @typedef {LintResult & { index?: number; }} IndexedLintResult */
42-
/** @typedef {IndexedLintResult[] & { netLintingDuration: bigint; }} WorkerLintResults */
43+
/** @typedef {IndexedLintResult[] & { netLintingDuration: bigint; timings?: Record<string, number>; }} WorkerLintResults */
4344
/**
4445
* @typedef {Object} WorkerData - Data passed to the worker thread.
4546
* @property {ESLintOptions | string} eslintOptionsOrURL - The unprocessed ESLint options or the URL of the options module.
@@ -57,6 +58,12 @@ const debug = createDebug(`eslint:worker:thread-${threadId}`);
5758
// Main
5859
//------------------------------------------------------------------------------
5960

61+
/*
62+
* Prevent timing module from printing profiling output from worker threads.
63+
* The main thread is responsible for displaying any aggregated timings.
64+
*/
65+
timing.disableDisplay();
66+
6067
debug("Dependencies loaded in %t", depsLoadedTime - startTime);
6168

6269
(async () => {
@@ -158,5 +165,9 @@ debug("Dependencies loaded in %t", depsLoadedTime - startTime);
158165
indexedResults.netLintingDuration =
159166
lintingDuration - loadConfigTotalDuration - readFileCounter.duration;
160167

168+
if (timing.enabled) {
169+
indexedResults.timings = timing.getData();
170+
}
171+
161172
parentPort.postMessage(indexedResults);
162173
})();

lib/linter/timing.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ function display(data) {
131131
/* c8 ignore next */
132132
module.exports = (function () {
133133
const data = Object.create(null);
134+
let displayEnabled = true;
134135

135136
/**
136137
* Time the run
@@ -158,15 +159,51 @@ module.exports = (function () {
158159
};
159160
}
160161

162+
/**
163+
* Returns a shallow copy of the collected timings data.
164+
* @returns {Record<string, number>} mapping of ruleId to total time in ms
165+
*/
166+
function getData() {
167+
return { ...data };
168+
}
169+
170+
/**
171+
* Merges rule timing totals collected elsewhere into this process' totals.
172+
* @param {Record<string, number>} dataToMerge mapping of ruleId to total time in ms
173+
* @returns {void}
174+
*/
175+
function mergeData(dataToMerge) {
176+
for (const [key, value] of Object.entries(dataToMerge)) {
177+
if (typeof data[key] === "undefined") {
178+
data[key] = 0;
179+
}
180+
data[key] += value;
181+
}
182+
}
183+
184+
/**
185+
* Disables printing of timing data on process exit.
186+
* Intended for worker threads or non-main contexts.
187+
* @returns {void}
188+
*/
189+
function disableDisplay() {
190+
displayEnabled = false;
191+
}
192+
161193
if (enabled) {
162194
process.on("exit", () => {
163-
display(data);
195+
if (displayEnabled && Object.keys(data).length > 0) {
196+
display(data);
197+
}
164198
});
165199
}
166200

167201
return {
168202
time,
169203
enabled,
170204
getListSize,
205+
getData,
206+
mergeData,
207+
disableDisplay,
171208
};
172209
})();

0 commit comments

Comments
 (0)