fix: 修复任务生命周期与调度就绪,优化执行和构建开销 (#3069)

* fix: harden task lifecycle and scheduler readiness

* fix: confine log writes to the configured log directory

* fix: verify complete build artifacts and untracked inputs

* fix: reconcile scheduler state and make stop win startup races

* fix: isolate cron generations and serialize scheduler recovery
This commit is contained in:
whyour
2026-09-13 00:32:41 +08:00
committed by GitHub
parent d62d8f3025
commit a94e665054
61 changed files with 4214 additions and 608 deletions
+178
View File
@@ -0,0 +1,178 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawn, spawnSync } = require('node:child_process');
const helper = path.resolve('shell/node_path_cache.sh');
const hasFlock = spawnSync('/bin/bash', ['-c', 'type -P flock']).status === 0;
function fixture(t) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-path-lock-'));
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const bin = path.join(root, 'bin');
fs.mkdirSync(bin);
fs.symlinkSync(process.execPath, path.join(bin, 'node'));
fs.writeFileSync(
path.join(bin, 'pnpm'),
'#!/bin/bash\nprintf "call\\n" >> "$CALLS"\n[[ -n "${UMASK_FILE:-}" ]] && umask > "$UMASK_FILE"\nsleep "${LOOKUP_DELAY:-0.2}"\n[[ "${FAIL:-0}" == 1 ]] && exit 17\nprintf "%s\\n" "${npm_config_global_dir:-/test/global/node_modules}"\n',
{ mode: 0o755 },
);
const env = {
...process.env,
HOME: root,
PATH: bin + ':' + process.env.PATH,
dir_tmp: path.join(root, 'cache'),
CALLS: path.join(root, 'calls'),
HELPER: helper,
};
const command = '. "$HELPER"; ql_get_node_global_path';
const run = (extra = {}, code = command) =>
spawnSync('/bin/bash', ['-euc', code], {
cwd: root,
env: { ...env, ...extra },
encoding: 'utf8',
});
const asyncRun = (extra = {}) =>
new Promise((resolve, reject) => {
const child = spawn('/bin/bash', ['-euc', command], {
cwd: root,
env: { ...env, ...extra },
});
let out = '',
err = '';
child.stdout.on('data', (c) => (out += c));
child.stderr.on('data', (c) => (err += c));
child.on('error', reject);
child.on('close', (status) =>
resolve({ status, stdout: out, stderr: err }),
);
});
const count = () =>
fs.existsSync(env.CALLS)
? fs.readFileSync(env.CALLS, 'utf8').trim().split('\n').length
: 0;
const cache = path.join(env.dir_tmp, `pnpm-root-${process.getuid()}.cache`);
return {
root,
bin,
env,
command,
run,
asyncRun,
count,
cache,
lock: cache + '.lock',
};
}
function check(r, output = '/test/global/node_modules') {
assert.equal(r.status, 0, r.stderr);
assert.equal(r.stdout.trim(), output);
}
test(
'simultaneous cold and expired lookups share one successful refresh',
{ skip: !hasFlock },
async (t) => {
const f = fixture(t);
for (let batch = 0; batch < 2; batch++) {
if (batch) {
const lines = fs.readFileSync(f.cache, 'utf8').split('\n');
lines[1] = '0';
fs.writeFileSync(f.cache, lines.join('\n'));
}
const rows = await Promise.all(
Array.from({ length: 8 }, () => f.asyncRun({ LOOKUP_DELAY: '0.5' })),
);
rows.forEach((r) => check(r));
assert.equal(f.count(), batch + 1);
}
assert.equal(fs.statSync(f.lock).mode & 0o777, 0o600);
check(f.run());
assert.equal(f.count(), 2);
},
);
test(
'waiters with different configuration do not reuse another key',
{ skip: !hasFlock },
async (t) => {
const f = fixture(t);
const rows = await Promise.all(
['/first', '/second'].map((v) =>
f.asyncRun({ npm_config_global_dir: v }),
),
);
rows.forEach((r, i) => check(r, ['/first', '/second'][i]));
assert.equal(f.count(), 2);
},
);
test(
'lock timeout falls back while a holder is still alive',
{ skip: !hasFlock },
async (t) => {
const f = fixture(t);
fs.mkdirSync(f.env.dir_tmp);
const holder = spawn(
'/bin/bash',
['-c', 'exec 9>> "$LOCK"; flock 9; echo ready; sleep 30'],
{ env: { ...f.env, LOCK: f.lock }, detached: true },
);
t.after(() => {
try {
process.kill(-holder.pid, 'SIGKILL');
} catch {}
});
await new Promise((resolve, reject) => {
holder.stdout.once('data', resolve);
holder.once('error', reject);
});
const start = Date.now();
check(f.run());
assert.ok(Date.now() - start >= 1800);
assert.equal(holder.exitCode, null);
assert.equal(f.count(), 1);
},
);
test('absent or unsupported flock and unsafe lock paths preserve discovery', (t) => {
const f = fixture(t);
check(
f.run(
{},
'type(){ if [[ "$*" == "-P flock" ]]; then return 1; fi; builtin type "$@"; }; ' +
f.command,
),
);
fs.rmSync(f.cache);
fs.writeFileSync(path.join(f.bin, 'flock'), '#!/bin/bash\nexit 64\n', {
mode: 0o755,
});
check(f.run());
fs.rmSync(f.cache);
fs.rmSync(f.lock, { force: true });
const victim = path.join(f.root, 'victim');
fs.writeFileSync(victim, 'untouched');
fs.symlinkSync(victim, f.lock);
check(f.run());
assert.equal(fs.readFileSync(victim, 'utf8'), 'untouched');
assert.equal(f.count(), 3);
});
test('failed refresh releases lock and does not publish a success', (t) => {
const f = fixture(t);
assert.equal(f.run({ FAIL: '1' }).status, 17);
assert.equal(fs.existsSync(f.cache), false);
check(f.run());
assert.equal(f.count(), 2);
});
test('refresh leaves the caller file descriptor and umask unchanged', (t) => {
const f = fixture(t);
const sentinel = path.join(f.root, 'fd');
const mask = path.join(f.root, 'mask');
const r = f.run(
{ SENTINEL: sentinel, UMASK_FILE: mask },
'umask 022; exec 9> "$SENTINEL"; ' +
f.command +
' >/dev/null; printf preserved >&9; umask',
);
assert.equal(r.status, 0, r.stderr);
assert.equal(r.stdout.trim(), '0022');
assert.equal(fs.readFileSync(sentinel, 'utf8'), 'preserved');
assert.equal(fs.readFileSync(mask, 'utf8').trim(), '0022');
});