Refactor: Cross Realm Span

This commit is contained in:
SukkaW
2026-03-31 20:02:01 +08:00
parent d6fa78d49a
commit 09183a3cd1
4 changed files with 110 additions and 76 deletions

View File

@@ -147,7 +147,7 @@ export const DOMAIN_LISTS_EXTRA: HostsSource[] = [
true true
], ],
[ [
'https://raw.githubusercontent.com/DandelionSprout/adfilt/refs/heads/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareDomains.txt', 'https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareDomains.txt',
[], [],
true true
] ]

View File

@@ -25,7 +25,8 @@ import { downloadMockAssets } from './download-mock-assets';
import { buildCloudMounterRules } from './build-cloudmounter-rules'; import { buildCloudMounterRules } from './build-cloudmounter-rules';
import { createSpan, printTraceResult, whyIsNodeRunning } from './trace'; import { printStats, printTraceResult, whyIsNodeRunning } from './trace';
import type { TraceResult } from './trace';
import { buildDeprecateFiles } from './build-deprecate-files'; import { buildDeprecateFiles } from './build-deprecate-files';
import path from 'node:path'; import path from 'node:path';
import { ROOT_DIR } from './constants/dir'; import { ROOT_DIR } from './constants/dir';
@@ -66,8 +67,6 @@ const buildFinishedLock = path.join(ROOT_DIR, '.BUILD_FINISHED');
console.log(`Memory: ${os.totalmem() / (1024 * 1024)} MiB`); console.log(`Memory: ${os.totalmem() / (1024 * 1024)} MiB`);
const rootSpan = createSpan('root');
if (fs.existsSync(buildFinishedLock)) { if (fs.existsSync(buildFinishedLock)) {
fs.unlinkSync(buildFinishedLock); fs.unlinkSync(buildFinishedLock);
} }
@@ -78,39 +77,62 @@ const buildFinishedLock = path.join(ROOT_DIR, '.BUILD_FINISHED');
await import('why-is-node-running'); await import('why-is-node-running');
} }
const downloadPreviousBuildPromise = downloadPreviousBuild(rootSpan); const downloadPreviousBuildPromise = downloadPreviousBuild();
const buildCommonPromise = downloadPreviousBuildPromise.then(() => buildCommon(rootSpan));
await Promise.all([ await Promise.all([
downloadPreviousBuildPromise, downloadPreviousBuildPromise,
buildCommonPromise, downloadPreviousBuildPromise.then(() => buildCommon()),
downloadPreviousBuildPromise.then(() => buildRejectIPList(rootSpan)), downloadPreviousBuildPromise.then(() => buildRejectIPList()),
downloadPreviousBuildPromise.then(() => buildAppleCdn(rootSpan)), downloadPreviousBuildPromise.then(() => buildAppleCdn()),
downloadPreviousBuildPromise.then(() => buildCdnDownloadConf(rootSpan)), downloadPreviousBuildPromise.then(() => buildCdnDownloadConf()),
downloadPreviousBuildPromise.then(() => buildRejectDomainSet(rootSpan)), downloadPreviousBuildPromise.then(() => buildRejectDomainSet()),
downloadPreviousBuildPromise.then(() => buildTelegramCIDR(rootSpan)), downloadPreviousBuildPromise.then(() => buildTelegramCIDR()),
downloadPreviousBuildPromise.then(() => buildChnCidr(rootSpan)), downloadPreviousBuildPromise.then(() => buildChnCidr()),
downloadPreviousBuildPromise.then(() => buildSpeedtestDomainSet(rootSpan)), downloadPreviousBuildPromise.then(() => buildSpeedtestDomainSet()),
downloadPreviousBuildPromise.then(() => buildDomesticRuleset(rootSpan)), downloadPreviousBuildPromise.then(() => buildDomesticRuleset()),
downloadPreviousBuildPromise.then(() => buildGlobalRuleset(rootSpan)), downloadPreviousBuildPromise.then(() => buildGlobalRuleset()),
downloadPreviousBuildPromise.then(() => buildRedirectModule(rootSpan)), downloadPreviousBuildPromise.then(() => buildRedirectModule()),
downloadPreviousBuildPromise.then(() => buildAlwaysRealIPModule(rootSpan)), downloadPreviousBuildPromise.then(() => buildAlwaysRealIPModule()),
downloadPreviousBuildPromise.then(() => buildStreamService(rootSpan)), downloadPreviousBuildPromise.then(() => buildStreamService()),
downloadPreviousBuildPromise.then(() => buildMicrosoftCdn(rootSpan)), downloadPreviousBuildPromise.then(() => buildMicrosoftCdn()),
downloadPreviousBuildPromise.then(() => buildCloudMounterRules(rootSpan)), downloadPreviousBuildPromise.then(() => buildCloudMounterRules()),
downloadMockAssets(rootSpan) downloadMockAssets()
]); ]);
await buildDeprecateFiles(rootSpan); await buildDeprecateFiles();
await buildPublic(rootSpan); await buildPublic();
rootSpan.stop();
printTraceResult(rootSpan.traceResult);
// write a file to demonstrate that the build is finished // write a file to demonstrate that the build is finished
fs.writeFileSync(buildFinishedLock, 'BUILD_FINISHED\n'); fs.writeFileSync(buildFinishedLock, 'BUILD_FINISHED\n');
const traces: TraceResult[] = [];
[
downloadPreviousBuild,
downloadMockAssets,
buildCommon,
buildRejectIPList,
buildAppleCdn,
buildCdnDownloadConf,
buildRejectDomainSet,
buildTelegramCIDR,
buildChnCidr,
buildSpeedtestDomainSet,
buildDomesticRuleset,
buildGlobalRuleset,
buildRedirectModule,
buildAlwaysRealIPModule,
buildStreamService,
buildMicrosoftCdn,
buildCloudMounterRules,
buildPublic,
buildDeprecateFiles
].forEach((fn) => {
const trace = fn.getInternalTraceResult();
printTraceResult(trace);
traces.push(trace);
});
printStats(traces);
// Finish the build to avoid leaking timer/fetch ref // Finish the build to avoid leaking timer/fetch ref
await whyIsNodeRunning(); await whyIsNodeRunning();
process.exit(0); process.exit(0);

View File

@@ -28,11 +28,11 @@ export function processDomainListsWithPreload(
const downloadPromise = fetchAssets(domainListsUrl, mirrors, true, allowEmptyRemote); const downloadPromise = fetchAssets(domainListsUrl, mirrors, true, allowEmptyRemote);
const lineCb = includeAllSubDomain ? domainListLineCbIncludeAllSubdomain : domainListLineCb; const lineCb = includeAllSubDomain ? domainListLineCbIncludeAllSubdomain : domainListLineCb;
return (span: Span) => span.traceChildAsync(`process domainlist: ${domainListsUrl}`, async (span) => { return (span: Span) => span.traceChildAsync(`process domainlist: ${domainListsUrl}`, async (childSpan) => {
const filterRules = await span.traceChildPromise('download', downloadPromise); const filterRules = await childSpan.traceChildPromise('download', downloadPromise);
const domainSets: string[] = []; const domainSets: string[] = [];
span.traceChildSync('parse domain list', () => { childSpan.traceChildSync('parse domain list', () => {
for (let i = 0, len = filterRules.length; i < len; i++) { for (let i = 0, len = filterRules.length; i < len; i++) {
lineCb(filterRules[i], domainSets, domainListsUrl, fastNormalizeDomainWithoutWww); lineCb(filterRules[i], domainSets, domainListsUrl, fastNormalizeDomainWithoutWww);
} }

View File

@@ -4,8 +4,8 @@ import { basename, extname } from 'node:path';
import process from 'node:process'; import process from 'node:process';
import picocolors from 'picocolors'; import picocolors from 'picocolors';
const SPAN_STATUS_START = 0; export const SPAN_STATUS_START = 0;
const SPAN_STATUS_END = 1; export const SPAN_STATUS_END = 1;
const spanTag = Symbol('span'); const spanTag = Symbol('span');
@@ -16,12 +16,11 @@ export interface TraceResult {
children: TraceResult[] children: TraceResult[]
} }
const rootTraceResult: TraceResult = { /** Pure data object — safe to transfer across Worker Thread boundaries. */
name: 'root', export interface RawSpan {
start: 0, traceResult: TraceResult,
end: 0, status: typeof SPAN_STATUS_START | typeof SPAN_STATUS_END
children: [] }
};
export interface Span { export interface Span {
[spanTag]: true, [spanTag]: true,
@@ -36,37 +35,23 @@ export interface Span {
readonly traceResult: TraceResult readonly traceResult: TraceResult
} }
export function createSpan(name: string, parentTraceResult?: TraceResult): Span { /**
const start = performance.now(); * Wraps a serializable {@link RawSpan} with all span methods.
* Use this on a worker thread after receiving a {@link RawSpan} (or {@link TraceResult})
let curTraceResult: TraceResult; * transferred from another thread.
*/
if (parentTraceResult == null) { export function makeSpan(rawSpan: RawSpan): Span {
curTraceResult = rootTraceResult; const { traceResult } = rawSpan;
} else {
curTraceResult = {
name,
start,
end: 0,
children: []
};
parentTraceResult.children.push(curTraceResult);
}
let status: typeof SPAN_STATUS_START | typeof SPAN_STATUS_END = SPAN_STATUS_START;
const stop = (time?: number) => { const stop = (time?: number) => {
if (status === SPAN_STATUS_END) { if (rawSpan.status === SPAN_STATUS_END) {
throw new Error(`span already stopped: ${name}`); throw new Error(`span already stopped: ${traceResult.name}`);
} }
const end = time ?? performance.now(); traceResult.end = time ?? performance.now();
rawSpan.status = SPAN_STATUS_END;
curTraceResult.end = end;
status = SPAN_STATUS_END;
}; };
const traceChild = (name: string) => createSpan(name, curTraceResult); const traceChild = (name: string) => createSpan(name, traceResult);
const span: Span = { const span: Span = {
[spanTag]: true, [spanTag]: true,
@@ -82,7 +67,7 @@ export function createSpan(name: string, parentTraceResult?: TraceResult): Span
span.stop(); span.stop();
return res; return res;
}, },
traceResult: curTraceResult, traceResult,
async tracePromise<T>(promise: Promise<T>): Promise<T> { async tracePromise<T>(promise: Promise<T>): Promise<T> {
const res = await promise; const res = await promise;
span.stop(); span.stop();
@@ -97,18 +82,35 @@ export function createSpan(name: string, parentTraceResult?: TraceResult): Span
return span; return span;
} }
export function createSpan(name: string, parentTraceResult?: TraceResult): Span {
const rawSpan: RawSpan = {
traceResult: {
name,
start: performance.now(),
end: 0,
children: []
},
status: SPAN_STATUS_START
};
parentTraceResult?.children.push(rawSpan.traceResult);
return makeSpan(rawSpan);
}
export const dummySpan = createSpan('dummy'); export const dummySpan = createSpan('dummy');
export function task(importMetaMain: boolean, importMetaPath: string) { export function task(importMetaMain: boolean, importMetaPath: string) {
return <T>(fn: (span: Span, onCleanup: (cb: () => Promise<void> | void) => void) => Promise<T>, customName?: string) => { return (fn: (span: Span, onCleanup: (cb: () => Promise<void> | void) => void) => Promise<unknown>, customName?: string) => {
const taskName = customName ?? basename(importMetaPath, extname(importMetaPath)); const taskName = customName ?? basename(importMetaPath, extname(importMetaPath));
let cleanup: () => Promise<void> | void = noop; let cleanup: () => Promise<void> | void = noop;
const onCleanup = (cb: () => void) => { const onCleanup = (cb: () => void) => {
cleanup = cb; cleanup = cb;
}; };
const innerSpan = createSpan(taskName);
if (importMetaMain) { if (importMetaMain) {
const innerSpan = createSpan(taskName);
process.on('uncaughtException', (error) => { process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error); console.error('Uncaught exception:', error);
process.exit(1); process.exit(1);
@@ -126,15 +128,26 @@ export function task(importMetaMain: boolean, importMetaPath: string) {
}); });
} }
function run(span?: Span | null): Promise<T> { let runSpan: Span;
return fn(span || innerSpan, onCleanup).finally(() => { async function run(parentSpan?: Span | null): Promise<TraceResult> {
(span || innerSpan).stop(); if (parentSpan) {
runSpan = parentSpan.traceChild(taskName);
} else {
runSpan = createSpan(taskName);
}
try {
await fn(runSpan, onCleanup);
} finally {
runSpan.stop();
cleanup(); cleanup();
}); }
return runSpan.traceResult;
} }
return Object.assign(run, { return Object.assign(run, {
getInternalTraceResult: () => innerSpan.traceResult getInternalTraceResult: () => runSpan.traceResult
}); });
}; };
} }
@@ -159,8 +172,7 @@ export async function whyIsNodeRunning() {
// }; // };
// }; // };
export function printTraceResult(traceResult: TraceResult = rootTraceResult) { export function printTraceResult(traceResult: TraceResult) {
printStats(traceResult.children);
printTree( printTree(
traceResult, traceResult,
node => { node => {
@@ -206,7 +218,7 @@ function printTree(initialTree: TraceResult, printNode: (node: TraceResult, bran
printBranch(initialTree, '', true, false); printBranch(initialTree, '', true, false);
} }
function printStats(stats: TraceResult[]): void { export function printStats(stats: TraceResult[]): void {
const longestTaskName = Math.max(...stats.map(i => i.name.length)); const longestTaskName = Math.max(...stats.map(i => i.name.length));
const realStart = Math.min(...stats.map(i => i.start)); const realStart = Math.min(...stats.map(i => i.start));
const realEnd = Math.max(...stats.map(i => i.end)); const realEnd = Math.max(...stats.map(i => i.end));