科学计算正确性: - 修复 Fortran 无-E 科学记数法(指数≥100 时 E 被挤掉,如 -1.35+118)导致 发散行被静默跳过、误判收敛的 bug;扩展大气无效检测覆盖 Inf 与 *** 溢出标记 - 种子匹配改为 CNO 有向距离(富金属方向重罚 4×、贫金属方向轻罚 1×), 基于 1191 个真实种子配对回测标定,回测净改善 314 个点 - GridAxisValue 反序列化拒绝非法文本(不再静默 NaN);chmax≤0 显式报错 - ions 行宽列宽对齐真实 fort.5 格式 调度与队列竞态: - 原子选点(IMMEDIATE 事务 SELECT+UPDATE)消除并发调度重复派发 (#5) - 调度互斥锁 + 冷启动优先策略(SeedStep 仅作失败后救援,不再正常路径热启动) - 毒消息 dead_letter 标记防出队死循环;clear_queue 保留 claimed 行 (#6) - 孤儿 running 点回收兜底;stale_sec 默认 7800→21600s(3 倍超时缓冲) 节点生命周期: - SIGTERM+SIGINT 双信号监听(修复 Docker stop 发 SIGTERM 不触发优雅退出) - SlotGuard RAII 防活动 slot 泄漏;子进程超时增加二级 30s wait 防 Fortran hang - SeedStep 种子下载 fail-fast + 沙盒私有副本解耦 LRU 清理竞争 - reqwest Client 增加连接/请求超时;启动清理残留 task_* 沙盒 安全加固: - 节点注册 registration_secret 二次凭据 (H8),恒定时间比对防时序旁路 - token 缓存 generation 机制消除 reissue 后旧 token TOCTOU 复活窗口 - 新增 /api/auth/logout 服务端 session 即时撤销;fail-closed 鉴权启动策略 - 前端 token 迁移 sessionStorage;YAML 高亮改 DOM API 消除 XSS 注入面 - 备份文件权限收紧 0600;点表动态值全面 escapeHtml 前端 Dashboard: - 收敛性分析从热力图重构为 Parallel Sets 平行集合图(6 维+状态轴,手写 SVG 零依赖) - 进度曲线横轴改为真实时间(服务端 now 锚定,停滞期诚实留白);轮询指数退避 - 移除 imported 收敛途径分类,导入点按实际途径 cold_run/seed_step 归类 - 初始化时 /api/auth/check 校验 token;401 toast 提示替代静默 reload 服务端恢复与工具链: - 启动恢复 initializing 态工作流;默认工作流 INSERT-only 不覆盖 API 编辑 - body limit 分层(10MB 不再截断 256MB report);multipart 显式错误处理 - 嵌入二进制原子写(tmp+rename)防半写损坏 - import_results 判定收敛途径透传 success_method;conv.json 格式对齐本项目 - push_import_results.sh 退出码修复 + .bat UTF-8 BOM + scp 上传 - Docker USE_MIRRORS 默认关闭;移除无用 assets 挂载;删除 hosts.ini 入库
200 lines
8.0 KiB
Rust
200 lines
8.0 KiB
Rust
//! 验证「历史种子导入的工作流名」与「正式工作流名」的隔离关系。
|
||
//!
|
||
//! 用户意图:import_results 把旧 Python 计算结果导入,标记为已完成,避免重算。
|
||
//! 关键问题:导入到工作流 A,之后正式启动工作流 B(同名/异名),B 能否看到 A 标记的 converged?
|
||
|
||
use common::config::GridConfig;
|
||
use common::models::GridPointParams;
|
||
use mq::sqlite_queue::SqliteTaskQueue;
|
||
use server::{db::Database, scheduler::GridScheduler};
|
||
use std::sync::Arc;
|
||
|
||
fn make_params() -> GridPointParams {
|
||
use common::models::GridAxisValue;
|
||
GridPointParams {
|
||
teff: GridAxisValue::from_value(20000.0),
|
||
logg: GridAxisValue::from_value(5.0),
|
||
loghe: GridAxisValue::from_value(-2.0),
|
||
logc: GridAxisValue::from_value(-4.0),
|
||
logn: GridAxisValue::from_value(-4.0),
|
||
logo: GridAxisValue::from_value(-4.0),
|
||
}
|
||
}
|
||
|
||
/// 构造只含一个网格点(t20000_g5.0_he-2_c-4_n-4_o-4)的 config。
|
||
fn make_grid_cfg() -> GridConfig {
|
||
let yaml = "grid:\n teff: [20000]\n logg: [5.0]\n loghe: [-2]\n logc: [-4]\n logn: [-4]\n logo: [-4]\n";
|
||
GridConfig::from_yaml_str(yaml).unwrap()
|
||
}
|
||
|
||
async fn setup() -> (Database, Arc<GridScheduler>) {
|
||
let tmp = tempfile::tempdir().unwrap();
|
||
let db = Database::new(&tmp.path().join("db.db").to_string_lossy())
|
||
.await
|
||
.unwrap();
|
||
let queue = Arc::new(
|
||
SqliteTaskQueue::new(&tmp.path().join("q.db").to_string_lossy())
|
||
.await
|
||
.unwrap(),
|
||
);
|
||
let sched = Arc::new(GridScheduler::new(db.clone(), queue));
|
||
(db, sched)
|
||
}
|
||
|
||
/// 场景 1(正确用法):导入到工作流 "sdB_cno",再用同名 config initialize_grid。
|
||
/// 期望:initialize_grid 的 ON CONFLICT(workflow_name, name) DO NOTHING 保留 converged 状态。
|
||
#[tokio::test]
|
||
async fn test_same_workflow_name_preserves_converged() {
|
||
let (db, sched) = setup().await;
|
||
let name = "t20000_g5.0_he-2_c-4_n-4_o-4";
|
||
let p = make_params();
|
||
|
||
// 模拟 import_seed:upsert + mark_imported,工作流名 = sdB_cno
|
||
db.upsert_grid_point_named(name, &p, 0, "sdB_cno")
|
||
.await
|
||
.unwrap();
|
||
db.mark_grid_point_imported(name, "sdB_cno", None, "cold_run")
|
||
.await
|
||
.unwrap();
|
||
|
||
// 之后正式启动同名工作流:initialize_grid(sdB_cno)
|
||
sched
|
||
.initialize_grid(&make_grid_cfg(), "sdB_cno")
|
||
.await
|
||
.unwrap();
|
||
|
||
let st = db.get_grid_point_status(name, "sdB_cno").await.unwrap();
|
||
assert_eq!(
|
||
st.unwrap().0,
|
||
"converged",
|
||
"同名工作流:导入的 converged 应被保留,避免重算"
|
||
);
|
||
println!("✓ 场景1(同名):status=converged,旧结果被保留,不会重算");
|
||
}
|
||
|
||
/// 场景 2(错误用法):导入到工作流 "imported",之后正式启动 "sdB_cno"。
|
||
/// 期望:sdB_cno 分区下是新插入的 pending 行,看不到 imported 分区的 converged。
|
||
#[tokio::test]
|
||
async fn test_different_workflow_name_causes_recompute() {
|
||
let (db, sched) = setup().await;
|
||
let name = "t20000_g5.0_he-2_c-4_n-4_o-4";
|
||
let p = make_params();
|
||
|
||
// 模拟 import_seed:导入到 "imported" 工作流
|
||
db.upsert_grid_point_named(name, &p, 0, "imported")
|
||
.await
|
||
.unwrap();
|
||
db.mark_grid_point_imported(name, "imported", None, "cold_run")
|
||
.await
|
||
.unwrap();
|
||
|
||
// 之后正式启动 "sdB_cno" 工作流
|
||
sched
|
||
.initialize_grid(&make_grid_cfg(), "sdB_cno")
|
||
.await
|
||
.unwrap();
|
||
|
||
// imported 分区:converged(种子库有,但不会被 sdB_cno 调度看到)
|
||
let st_imp = db.get_grid_point_status(name, "imported").await.unwrap();
|
||
assert_eq!(st_imp.unwrap().0, "converged");
|
||
|
||
// sdB_cno 分区:pending(重新算!看不到 imported 的 converged)
|
||
let st_real = db.get_grid_point_status(name, "sdB_cno").await.unwrap();
|
||
assert_eq!(
|
||
st_real.unwrap().0,
|
||
"pending",
|
||
"异名工作流:sdB_cno 看不到 imported 的 converged,会重算"
|
||
);
|
||
println!("✓ 场景2(异名):sdB_cno 分区 status=pending,会重复计算 —— 验证了工作流名必须匹配");
|
||
}
|
||
|
||
/// 场景 3(真实意图验证):旧网格有部分点已算完(导入为 converged),
|
||
/// 新网格比旧网格多了若干点。用同名工作流启动后:
|
||
/// - 旧点保持 converged(不重算)
|
||
/// - 新点是 pending(会被调度计算)
|
||
/// 这正是「同步旧结果避免重算」的核心语义。
|
||
#[tokio::test]
|
||
async fn test_mixed_grid_import_then_init_avoids_recompute() {
|
||
let (db, sched) = setup().await;
|
||
let p_old = make_params(); // t20000_g5.0_...
|
||
|
||
// 模拟 import_seed:旧网格里这个点已收敛,导入到 sdB_cno
|
||
db.upsert_grid_point_named("t20000_g5.0_he-2_c-4_n-4_o-4", &p_old, 0, "sdB_cno")
|
||
.await
|
||
.unwrap();
|
||
db.mark_grid_point_imported("t20000_g5.0_he-2_c-4_n-4_o-4", "sdB_cno", None, "cold_run")
|
||
.await
|
||
.unwrap();
|
||
|
||
// 正式启动 sdB_cno,config 比旧网格多了一个新点(t25000)
|
||
let yaml = "grid:\n teff: [20000, 25000]\n logg: [5.0]\n loghe: [-2]\n logc: [-4]\n logn: [-4]\n logo: [-4]\n";
|
||
let cfg = GridConfig::from_yaml_str(yaml).unwrap();
|
||
sched.initialize_grid(&cfg, "sdB_cno").await.unwrap();
|
||
|
||
// 旧点:converged(不重算)
|
||
let st_old = db
|
||
.get_grid_point_status("t20000_g5.0_he-2_c-4_n-4_o-4", "sdB_cno")
|
||
.await
|
||
.unwrap();
|
||
assert_eq!(
|
||
st_old.unwrap().0,
|
||
"converged",
|
||
"旧点应保持 converged 不重算"
|
||
);
|
||
|
||
// 新点:pending(会被调度)
|
||
let st_new = db
|
||
.get_grid_point_status("t25000_g5.0_he-2_c-4_n-4_o-4", "sdB_cno")
|
||
.await
|
||
.unwrap();
|
||
assert_eq!(st_new.unwrap().0, "pending", "新点应为 pending 等待计算");
|
||
|
||
println!("✓ 场景3(混合网格):旧点converged保留 + 新点pending待算 —— 完全符合避免重算的意图");
|
||
}
|
||
|
||
/// 场景 4(精度差异命门):旧 conv.json name=g5(无小数),但配置 logg=5.0 → model_name()=g5.0。
|
||
/// 导入工具必须把 name 重写为 g5.0 入库,否则 initialize_grid 插入的 g5.0 行与导入的 g5 行
|
||
/// 复合唯一键不匹配,导入的 converged 被孤立、g5.0 被重算。
|
||
/// 本测试直接模拟「入库的 grid_points.name = g5.0」(即工具重写后的状态),
|
||
/// 验证 initialize_grid(sdB_cno) 后该行保持 converged(不重算)。
|
||
#[tokio::test]
|
||
async fn test_precision_diff_import_then_init_preserves_converged() {
|
||
let (db, sched) = setup().await;
|
||
// 配置权威名(logg=5.0 → g5.0,保留小数)
|
||
let canonical = "t20000_g5.0_he-2_c-4_n-4_o-4";
|
||
use common::models::GridAxisValue;
|
||
let p = GridPointParams {
|
||
teff: GridAxisValue::from_value(20000.0),
|
||
logg: GridAxisValue::from_value(5.0),
|
||
loghe: GridAxisValue::from_value(-2.0),
|
||
logc: GridAxisValue::from_value(-4.0),
|
||
logn: GridAxisValue::from_value(-4.0),
|
||
logo: GridAxisValue::from_value(-4.0),
|
||
};
|
||
|
||
// 模拟 import_results 重写 name 后入库:grid_points.name = canonical(g5.0)
|
||
db.upsert_grid_point_named(canonical, &p, 0, "sdB_cno")
|
||
.await
|
||
.unwrap();
|
||
db.mark_grid_point_imported(canonical, "sdB_cno", None, "cold_run")
|
||
.await
|
||
.unwrap();
|
||
|
||
// 启动同名工作流:initialize_grid 用配置 model_name()(=g5.0) 插入
|
||
let yaml = "grid:\n teff: [20000]\n logg: [5.0]\n loghe: [-2]\n logc: [-4]\n logn: [-4]\n logo: [-4]\n";
|
||
let cfg = GridConfig::from_yaml_str(yaml).unwrap();
|
||
sched.initialize_grid(&cfg, "sdB_cno").await.unwrap();
|
||
|
||
// 关键断言:name=g5.0 的行保持 converged(ON CONFLICT DO NOTHING 命中)
|
||
let st = db
|
||
.get_grid_point_status(canonical, "sdB_cno")
|
||
.await
|
||
.unwrap();
|
||
assert_eq!(
|
||
st.unwrap().0,
|
||
"converged",
|
||
"精度一致(g5.0=g5.0)时导入的 converged 必须保留,不重算"
|
||
);
|
||
println!("✓ 场景4(精度差异命门):g5.0 入库 + initialize_grid → converged 保留,避免重算");
|
||
}
|