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
+85
View File
@@ -0,0 +1,85 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const fs = require('node:fs/promises');
const path = require('node:path');
const os = require('node:os');
const { spawn } = require('node:child_process');
const load = require('../helpers/load-security-module.cjs');
const { LogStreamManager } = require('../../back/shared/logStreamManager');
test(
'manual execution captures a short child before slow status storage and flushes before release',
{ timeout: 5000 },
async (t) => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-manual-'));
t.after(() => fs.rm(root, { recursive: true, force: true }));
const logs = new LogStreamManager(root);
const updates = [];
let active = 0;
let failSpawn = false;
const CronService = load(path.resolve('back/services/cron.ts'), {
'../config': { logPath: root },
'../data/cron': {
CrontabStatus: { queued: 3, running: 0, idle: 1 },
CrontabModel: {
update: async (data, options) => {
await new Promise((resolve) => setTimeout(resolve, 40));
updates.push({ data, options });
return [1];
},
},
},
'../data/runningInstance': {
RunningInstanceModel: {},
InstanceStatus: {},
},
'../config/util': { getUniqPath: async () => 'task' },
'../config/const': { TASK_PREFIX: 'task ', QL_PREFIX: 'ql ' },
'../schedule/client': {},
'../shared/pLimit': {
manualRunWithCronLimit: async (fn) => {
active++;
try {
return await fn();
} finally {
active--;
}
},
},
'../shared/utils': {},
'../shared/i18n': { t: (s) => s },
'../shared/logReader': {},
'../shared/logStreamManager': { logStreamManager: logs },
'cross-spawn': {
spawn: () =>
failSpawn
? spawn('true', { shell: '/nonexistent-ql-manual-shell' })
: spawn(process.execPath, [
'-e',
'process.stdout.write("末尾\\n")',
]),
},
}).default;
const service = new CronService({ info() {}, error() {} });
service.getDb = async () => ({
id: 1,
status: 3,
command: 'ignored',
log_path: '',
});
service.makeCommand = () => 'ignored';
await service.runSingle(1);
const [file] = await fs.readdir(path.join(root, 'task'));
assert.equal(
await fs.readFile(path.join(root, 'task', file), 'utf8'),
'末尾\n',
);
assert.equal(logs.getOpenStreamCount(), 0);
assert.equal(active, 0);
assert.equal(updates.at(-1).data.status, 1);
failSpawn = true;
await service.runSingle(1);
assert.equal(active, 0);
assert.equal(updates.at(-1).data.status, 1);
},
);