feat(all): 任务引擎双阶段解耦、僵尸涡旋修复、动态 CPU 配额与前端详情页重构

将 TLUSTY/SYNSPEC 拆为各自独立的 enabled/policy/strategies 阶段,
以策略链自动弹栈取代单级 seed_step 布尔回退;定向修复 2026-08-02
僵尸任务涡旋事故;新增节点并发配额热调;前端详情页从 1412 行巨型
视图拆为薄控制器 + detail 子模块,并补齐工具层与单测。

引擎与调度(task_engine_decoupling_design.md)
- models.rs: 新增 StagePolicy / EngineStageConfig / TaskSpec 阶段字段、
  normalize_compat() 校正旧版在途消息策略链、failed_stage 归因
- scheduler.rs: resolve_dispatchable_chain 派发门控、
  trigger_strategy_fallback 按 failed_stage 精确弹栈;启动期
  force_recompute/skip_converged(默认)/skip_failed 三策略
- db.rs: tasks 表 +7 列持久化阶段配置;终态守卫
  (mark_grid_point_running 仅 pending/queued→running;
  record_task_report 拒绝迟到失败翻黑 converged);策略弹栈快照

僵尸涡旋修复(runbook-20260802-zombie-vortex-fix.md)
- 全链路跨库活性交叉校验:派发/claim/孤儿回收/回退统一查 MQ 队列活性,
  活则放行、死则清僵尸,结构性消除"每点重复派发"
- stop/重启卫生:清队列同步 delete_tasks_by_ids,杜绝遗留 pending 行
- report_task: 幂等吸收 + 409 区分迟到冗余结果,仅 state_changed 时回退
- MQ: NULL workflow_name 回填 __legacy__、requeue 后迟到上报被 403 竞态修复

动态 CPU 配额(dynamic_cpu_slots_design.md)
- admin.rs: POST /admin/nodes/:id/quota(Option<Option<i32>> 区分
  缺字段/显式 null);nodes 表 +admin_max_slots
- worker.rs: effective_max_slots = min(admin, physical),心跳下发原子生效

科学产物保全(tlusty_result_artifacts.md)
- runner.rs: SYNSPEC 启动前快照 fort.12/fort.14 → .bfac/.emflux 防覆盖
- 半失败点(大气收敛+光谱失败)改判 Failed 并写入 note;仅 SYNSPEC
  场景不再恒判失败;撤销归档 LRU 200 上限改为永久保留
- executor.rs: 透传 synspec_params 数值参数(此前固定 None)

前端(dashboard/)
- workflowDetail.js 1412→328 行,拆出 views/detail/{ctx,overview,
  pointsTable,parSets,pointPanel}.js,AbortController 治理监听/请求生命周期
- 删除 wfActions.js,新增 wfEnginePanel.js(双阶段三维配置编辑面板)
- 新增 utils/{errors,format,icons,polling,yamlStage}.js 纯函数模块
- 路由级动态 import 代码分割;节点配额三点菜单 + Modal 管理
- 首次引入 node:test 单测(format/polling/yamlStage/psCache,644 行)
- 系统性补齐 a11y:skip-link、ARIA、Tab 键盘漫游、toast 关闭、退出动画

文档与工具
- 新增 6 篇设计/调研:引擎解耦、动态配额、涡旋 runbook、
  光谱正确性分析、收敛判断、产物归档
- PIPELINE/design/api/database 等协同重写为分布式 C/S 架构口径
- scripts/fetch_results.sh 跨节点产物备份;import_results 按 cno 升序导入
- workflows/sdB_cno.yaml: 新增 tlusty/synspec_stage 配置块,修正 wstart 笔误
This commit is contained in:
fmq
2026-08-04 23:40:52 +08:00
parent c8fd24b120
commit cd370d88e7
74 changed files with 13386 additions and 3033 deletions
+19 -48
View File
@@ -1,65 +1,36 @@
/* DCTS Dashboard Application State & Polling Scheduler */
/* DCTS Dashboard Application State & Polling Scheduler
*
* 首页全局轮询:5s 基准、指数退避上限 60s、Tab 切入后台自动暂停——
* 均由 utils/polling.js 的 createPoller 统一承担(与详情页作用域轮询共用一套实现)。
*/
import { fetchClusterStatus, fetchWorkflowsList, fetchNodesList } from './api.js';
import { renderNodesTable } from './components/nodesTable.js';
import { renderWorkflows } from './components/workflows.js';
import { createPoller } from './utils/polling.js';
import { logError } from './utils/errors.js';
let isPolling = false;
let pollTimer = null;
// 连续失败退避计数:服务端持续不可达时拉长轮询间隔(指数退避,上限 60s),
// 避免空轮询刷请求;恢复正常即重置为基准 5s。
let pollFailCount = 0;
// 最近一次 cluster 状态拉取是否成功(由 updateServerStatus 维护,供 fetchAllData 判定退避)。
let serverOnline = false;
// 监听浏览器 Tab 标签页切换,切入后台时暂停轮询,切回前台时恢复并立即刷新
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
if (pollTimer) {
clearTimeout(pollTimer);
pollTimer = null;
}
} else if (isPolling) {
scheduleFetchData();
}
});
// 全局轮询:fetchAllData 返回布尔(以 cluster 状态能否获取为整体健康判定)。
const poller = createPoller(fetchAllData, { baseMs: 5000, maxMs: 60000 });
export function isAppPolling() {
return isPolling;
return poller.isRunning();
}
export function startPolling() {
if (isPolling) return;
isPolling = true;
scheduleFetchData();
poller.start();
}
export function stopPolling() {
isPolling = false;
if (pollTimer) {
clearTimeout(pollTimer);
pollTimer = null;
}
poller.stop();
}
export async function scheduleFetchData() {
if (pollTimer) {
clearTimeout(pollTimer);
pollTimer = null;
}
if (document.hidden) return;
const ok = await fetchAllData();
if (ok) {
pollFailCount = 0;
} else {
pollFailCount = Math.min(pollFailCount + 1, 4);
}
// 指数退避:5s * 2^failCountclamp 到 [5s, 60s]
const delay = Math.min(5000 * Math.pow(2, pollFailCount), 60000);
if (isPolling && !document.hidden) {
pollTimer = setTimeout(scheduleFetchData, delay);
}
/** 手动立即触发一次全量拉取(兼容旧调用方:header 刷新 / 建工作流后 / wfActions 已改由
* isAppPolling 守卫,本函数保留给需要无条件刷新的调用点)。 */
export function scheduleFetchData() {
poller.triggerNow();
}
// 返回主状态拉取是否成功(用于轮询退避计数)。三项并行拉取,
@@ -80,7 +51,7 @@ export async function fetchStatus() {
updateUI(data);
updateServerStatus(true);
} catch (err) {
console.warn('获取 DCTS 状态失败:', err);
logError(err, '获取 DCTS 状态失败');
updateServerStatus(false);
}
}
@@ -92,7 +63,7 @@ export async function fetchWorkflows() {
renderWorkflows(json.data);
}
} catch (err) {
console.warn('获取工作流列表失败:', err);
logError(err, '获取工作流列表失败');
}
}
@@ -103,7 +74,7 @@ export async function fetchNodes() {
renderNodesTable(json.data);
}
} catch (err) {
console.warn('获取计算节点列表失败:', err);
logError(err, '获取计算节点列表失败');
}
}