feat(all): 科学计算正确性修复、调度竞态消除、节点优雅退出、安全加固与收敛分析重构
科学计算正确性: - 修复 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 入库
This commit is contained in:
+3
-2
@@ -756,7 +756,8 @@ fi
|
||||
if [ "${ENV_MODE}" = "local" ] && [ "${BACKEND_MODE}" = "compose" ]; then
|
||||
echo -e "${CYAN}>>> 预先创建挂载目录并配置读取与写入权限 (UID 65532)...${NC}"
|
||||
mkdir -p ./data ./data/logs ./data/runtime ./data/work ./data/seeds ./data/result ./assets ./workflows
|
||||
chmod -R 777 ./data 2>/dev/null || true
|
||||
# 注:不再 chmod -R 777 ./data —— 该目录含 dcts.db / 队列库 / 备份 / .node_token 明文,
|
||||
# 777 会让同机任意用户可读凭据并写删数据库。容器内由 UID 65532 运行,下方 chown 即满足权限。
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker run --rm -v "$(pwd)":/work alpine chown -R 65532:65532 /work/data 2>/dev/null || true
|
||||
fi
|
||||
@@ -855,7 +856,7 @@ elif [ "${ENV_MODE}" = "remote" ] && [ "${BACKEND_MODE}" = "compose" ]; then
|
||||
win_load_cmd="${win_load_cmd}Write-Output done"
|
||||
ssh -p "${REMOTE_PORT}" ${SSH_OPTS} "${REMOTE_USER}@${REMOTE_IP}" "powershell -Command \"Set-Location '${REMOTE_DIR}'; New-Item -ItemType Directory -Force -Path 'data/logs','data/seeds','data/result','data/node_work','assets','workflows' -ErrorAction SilentlyContinue; ${win_load_cmd}; docker compose up -d --force-recreate ${SERVICES}; Start-Sleep -Seconds 3; docker compose ps\""
|
||||
else
|
||||
ssh -p "${REMOTE_PORT}" ${SSH_OPTS} "${REMOTE_USER}@${REMOTE_IP}" "cd '${REMOTE_DIR}' && mkdir -p ./data ./data/logs ./data/seeds ./data/result ./data/node_work ./assets ./workflows 2>/dev/null || true && (chmod -R 777 ./data 2>/dev/null || true; docker run --rm -v \"\$(pwd)\":/work alpine chown -R 65532:65532 /work/data 2>/dev/null || true) && ${remote_load_cmd} && docker compose up -d --force-recreate ${SERVICES} && sleep 3 && docker compose ps"
|
||||
ssh -p "${REMOTE_PORT}" ${SSH_OPTS} "${REMOTE_USER}@${REMOTE_IP}" "cd '${REMOTE_DIR}' && mkdir -p ./data ./data/logs ./data/seeds ./data/result ./data/node_work ./assets ./workflows 2>/dev/null || true && (docker run --rm -v \"\$(pwd)\":/work alpine chown -R 65532:65532 /work/data 2>/dev/null || true) && ${remote_load_cmd} && docker compose up -d --force-recreate ${SERVICES} && sleep 3 && docker compose ps"
|
||||
fi
|
||||
|
||||
if [ -z "${PREBUILT_TAR}" ]; then
|
||||
|
||||
@@ -176,11 +176,18 @@ echo -e "${GREEN}[√] 源码同步完成${NC}"
|
||||
if [ "$DO_BUILD" = true ]; then
|
||||
echo -e "\n${CYAN}=== 远端编译 import_results (release) ===${NC}"
|
||||
echo -e "${YELLOW} (首次编译会拉 crate 依赖,可能耗时数分钟,请耐心等待...)${NC}"
|
||||
# 用 ssh 透传的退出码判断成败,不依赖输出最后一行文本:
|
||||
# cargo 把 Compiling/Finished 进度写到 stderr,与 PowerShell 打印 $LASTEXITCODE 的 stdout
|
||||
# 在 2>&1 合并后顺序会交错,tail -1 可能抓到 "Finished ..." 而非 "0",导致把成功误判成失败。
|
||||
# 这里让 powershell `exit $LASTEXITCODE` → ssh 透传 → ${PIPESTATUS[0]} 取管道首段退出码。
|
||||
# 暂时关掉 errexit/pipefail:否则 cargo 失败时管道整体非零会先触发 set -e 退出,丢失自定义提示。
|
||||
set +e +o pipefail
|
||||
ssh -p "${REMOTE_PORT}" ${SSH_OPTS} "${REMOTE_USER}@${REMOTE_IP}" \
|
||||
"powershell -NoProfile -Command \"Set-Location '${REMOTE_SRC_DIR}'; cargo build --release -p import_results 2>&1; \$LASTEXITCODE\"" 2>&1 | tee /tmp/import_build.log | tail -20
|
||||
BUILD_RC=$(tail -1 /tmp/import_build.log | tr -d '[:space:]')
|
||||
"powershell -NoProfile -Command \"Set-Location '${REMOTE_SRC_DIR}'; cargo build --release -p import_results; exit \$LASTEXITCODE\"" 2>&1 | tee /tmp/import_build.log | tail -20
|
||||
BUILD_RC=${PIPESTATUS[0]}
|
||||
set -e -o pipefail
|
||||
rm -f /tmp/import_build.log
|
||||
if [ "$BUILD_RC" != "0" ]; then
|
||||
if [ "$BUILD_RC" -ne 0 ]; then
|
||||
echo -e "${RED}[✗] 远端编译失败 (exit=${BUILD_RC})${NC}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -192,37 +199,63 @@ fi
|
||||
# =============================================================================
|
||||
# 在远端生成 run_import.bat(Windows 批处理),用户双击或在 cmd 里运行即可,无需手敲命令
|
||||
# =============================================================================
|
||||
# 交互式 .bat 各参数的默认值(来自 profile 推导,用户双击时可逐项覆盖,回车即用默认)
|
||||
EXE_PATH_WIN=$(echo "${REMOTE_SRC_DIR}/target/release/import_results.exe" | sed 's|/|\\|g')
|
||||
CONFIG_PATH="${REMOTE_SRC_DIR}/workflows/sdB_cno.yaml"
|
||||
WORK_DIR_WIN=$(echo "${REMOTE_SRC_DIR}" | sed 's|/|\\|g')
|
||||
GRID_DIR="E:/fmq/grid" # 旧版 run_grid.py 的 results 根目录
|
||||
WORKFLOW_NAME="sdB_cno" # 工作流名,须匹配将来在 Dashboard 创建/启动的工作流
|
||||
|
||||
# .bat 内容:用 quoted heredoc 避免反引号/% 被 bash 解释,占位符再替换。
|
||||
# .bat 用 set /p 交互读取 admin token(用户无需手动编辑文件),空回车则跳过入库只迁移 result。
|
||||
# .bat 内容:用 quoted heredoc 避免反引号/%/$ 被 bash 解释,占位符稍后替换。
|
||||
# 每个参数都用 set /p 交互读取,空回车则采用 [方括号] 里的默认值;token 空回车则只迁移 result 不入库。
|
||||
BAT_CONTENT=$(cat <<'BATEOF'
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
setlocal
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
cd /d "__WORK_DIR__"
|
||||
|
||||
echo ============================================================
|
||||
echo DCTS 历史计算结果导入工具
|
||||
echo ============================================================
|
||||
echo 旧数据目录 : E:\fmq\grid
|
||||
echo workflow : __CONFIG__
|
||||
echo 结果目录 : __RESULT__
|
||||
echo 服务端 : __SERVER__
|
||||
echo 工作流名 : sdB_cno
|
||||
echo 以下每项直接回车即采用 [方括号] 内的默认值
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
set /p ADMIN_TOKEN="请输入 Admin Token (DCTS_ADMIN_TOKEN,直接回车则只迁移 result 不入库): "
|
||||
set /p DATA_DIR="旧数据目录 (run_grid.py results 根) [__DATA_DIR__]: "
|
||||
if "!DATA_DIR!"=="" set "DATA_DIR=__DATA_DIR__"
|
||||
|
||||
if "%ADMIN_TOKEN%"=="" (
|
||||
echo [i] 未输入 token,仅迁移产物树到 result (入库会因无鉴权失败但不阻断迁移)
|
||||
"__EXE__" -d E:/fmq/grid -c "__CONFIG__" -r "__RESULT__" -s __SERVER__ -w sdB_cno
|
||||
set /p CONFIG="workflow 配置文件 [__CONFIG__]: "
|
||||
if "!CONFIG!"=="" set "CONFIG=__CONFIG__"
|
||||
|
||||
set /p RESULT_DIR="结果目录 [__RESULT__]: "
|
||||
if "!RESULT_DIR!"=="" set "RESULT_DIR=__RESULT__"
|
||||
|
||||
set /p SERVER="服务端地址 [__SERVER__]: "
|
||||
if "!SERVER!"=="" set "SERVER=__SERVER__"
|
||||
|
||||
set /p WORKFLOW="工作流名 -w (须匹配将来启动的工作流) [__WORKFLOW__]: "
|
||||
if "!WORKFLOW!"=="" set "WORKFLOW=__WORKFLOW__"
|
||||
|
||||
set /p ADMIN_TOKEN="Admin Token (直接回车则只迁移 result 不入库): "
|
||||
|
||||
echo.
|
||||
echo ============================================================
|
||||
echo 即将执行,请确认:
|
||||
echo 旧数据目录 : !DATA_DIR!
|
||||
echo workflow : !CONFIG!
|
||||
echo 结果目录 : !RESULT_DIR!
|
||||
echo 服务端 : !SERVER!
|
||||
echo 工作流名 : !WORKFLOW!
|
||||
echo Token : !ADMIN_TOKEN!
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
if "!ADMIN_TOKEN!"=="" (
|
||||
echo [i] 未输入 token,仅迁移产物树到 result(入库会因无鉴权失败,但不阻断迁移)
|
||||
"__EXE__" -d "!DATA_DIR!" -c "!CONFIG!" -r "!RESULT_DIR!" -s "!SERVER!" -w "!WORKFLOW!"
|
||||
) else (
|
||||
"__EXE__" -d E:/fmq/grid -c "__CONFIG__" -r "__RESULT__" -s __SERVER__ -w sdB_cno -t "%ADMIN_TOKEN%"
|
||||
"__EXE__" -d "!DATA_DIR!" -c "!CONFIG!" -r "!RESULT_DIR!" -s "!SERVER!" -w "!WORKFLOW!" -t "!ADMIN_TOKEN!"
|
||||
)
|
||||
|
||||
echo.
|
||||
@@ -233,17 +266,38 @@ BATEOF
|
||||
)
|
||||
BAT_CONTENT="${BAT_CONTENT//__WORK_DIR__/${WORK_DIR_WIN}}"
|
||||
BAT_CONTENT="${BAT_CONTENT//__EXE__/${EXE_PATH_WIN}}"
|
||||
BAT_CONTENT="${BAT_CONTENT//__DATA_DIR__/${GRID_DIR}}"
|
||||
BAT_CONTENT="${BAT_CONTENT//__CONFIG__/${CONFIG_PATH}}"
|
||||
BAT_CONTENT="${BAT_CONTENT//__RESULT__/${RESULT_DIR}}"
|
||||
BAT_CONTENT="${BAT_CONTENT//__SERVER__/${DCTS_SERVER_URL}}"
|
||||
BAT_CONTENT="${BAT_CONTENT//__WORKFLOW__/${WORKFLOW_NAME}}"
|
||||
|
||||
# 经 SSH stdin 管道写入远端 run_import.bat(UTF-8 with BOM 由 chcp 65001 处理中文)
|
||||
# 本地写临时 .bat(CRLF 行尾 + UTF-8 with BOM),再用 scp 上传——
|
||||
# 绕开 “ssh 管道 + powershell $input 自动变量 + stdin 双重消费” 导致的空文件问题。
|
||||
BAT_REMOTE_PATH="${REMOTE_SRC_DIR}/run_import.bat"
|
||||
echo -e "\n${CYAN}=== 在远端生成 run_import.bat ===${NC}"
|
||||
printf '%s\r\n' "$BAT_CONTENT" | ssh -p "${REMOTE_PORT}" ${SSH_OPTS} "${REMOTE_USER}@${REMOTE_IP}" \
|
||||
"powershell -NoProfile -Command \"\$input = [Console]::In.ReadToEnd(); [System.IO.File]::WriteAllText('${BAT_REMOTE_PATH}', \$input, [System.Text.Encoding]::UTF8)\"" \
|
||||
&& echo -e "${GREEN}[√] 已生成: ${BAT_REMOTE_PATH}${NC}" \
|
||||
|| { echo -e "${RED}[✗] 生成 run_import.bat 失败${NC}"; exit 1; }
|
||||
# 用固定可预测的临时路径,便于在成功/失败两条出口都显式清理(脚本头部已有 SSH ControlMaster 的 EXIT trap,
|
||||
# 这里不再叠加 trap 以免覆盖它)。
|
||||
TMP_BAT="${TMPDIR:-/tmp}/dcts_run_import.$$.bat"
|
||||
# 关键:必须写成 UTF-8 **带 BOM**(EF BB BF)。
|
||||
# 原因:chcp 65001 只改控制台显示编码,不改 cmd.exe 解析 .bat 文件的编码。无 BOM 时 cmd 按系统 ANSI(中文
|
||||
# Windows 为 GBK)逐字节解析 UTF-8 文件,中文多字节序列错位会导致 echo/if 解析错乱——
|
||||
# 报 "'数据目录' is not recognized as an internal or external command" 且 if 块边界被破坏,
|
||||
# 使 import_results.exe 根本不执行。带 BOM 后现代 cmd(Win10 1903+)会按 UTF-8 正确解析整个文件。
|
||||
# CRLF 行尾:命令替换 $(...) 会吞掉末尾换行,直接 printf '%s\r\n' 只在末尾加一个 \r,
|
||||
# 故先按 LF 展开再用 sed 给每行补 \r,确保每行都是 CRLF。
|
||||
{
|
||||
printf '\xEF\xBB\xBF' # UTF-8 BOM
|
||||
printf '%s\n' "$BAT_CONTENT" | sed 's/$/\r/'
|
||||
} > "$TMP_BAT"
|
||||
echo -e "\n${CYAN}=== 上传 run_import.bat 到远端 ===${NC}"
|
||||
if scp -P "${REMOTE_PORT}" ${SSH_OPTS} "$TMP_BAT" "${REMOTE_USER}@${REMOTE_IP}:${BAT_REMOTE_PATH}"; then
|
||||
echo -e "${GREEN}[√] 已生成: ${BAT_REMOTE_PATH}${NC}"
|
||||
else
|
||||
echo -e "${RED}[✗] 上传 run_import.bat 失败${NC}"
|
||||
rm -f "$TMP_BAT"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$TMP_BAT"
|
||||
|
||||
# =============================================================================
|
||||
# 输出最终提示
|
||||
@@ -256,16 +310,17 @@ echo ""
|
||||
echo -e "${CYAN}在 Windows 机 ${REMOTE_IP} 上启动导入(任选其一):${NC}"
|
||||
echo ""
|
||||
echo -e " 方式1(推荐):双击运行 ${YELLOW}${WORK_DIR_WIN}\\run_import.bat${NC}"
|
||||
echo -e " 会交互提示输入 Admin Token(回车则只迁移 result 不入库)"
|
||||
echo -e " 会逐项交互提示:旧数据目录 / workflow 配置 / 结果目录 / 服务端 / 工作流名 / Token"
|
||||
echo -e " 每项直接回车即采用 [方括号] 内的默认值;Token 空回车则只迁移 result 不入库"
|
||||
echo ""
|
||||
echo -e " 方式2(cmd): cd /d ${WORK_DIR_WIN} 然后 run_import.bat"
|
||||
echo ""
|
||||
echo -e "${YELLOW}参数(已内置在 .bat 里,无需手填):${NC}"
|
||||
echo -e " -d E:/fmq/grid 旧版 Python run_grid.py 的 results 根目录"
|
||||
echo -e "${YELLOW}参数默认值(双击时可逐项覆盖):${NC}"
|
||||
echo -e " -d ${GRID_DIR} 旧版 Python run_grid.py 的 results 根目录"
|
||||
echo -e " -c ${CONFIG_PATH}"
|
||||
echo -e " -r ${RESULT_DIR} 结果写入目标(node 容器卷映射处)"
|
||||
echo -e " -s ${DCTS_SERVER_URL} 服务端地址(来自 profile)"
|
||||
echo -e " -w sdB_cno 工作流名(必须等于将来要启动的工作流名)"
|
||||
echo -e " -r ${RESULT_DIR} 结果写入目标(node 容器卷映射处)"
|
||||
echo -e " -s ${DCTS_SERVER_URL} 服务端地址(来自 profile)"
|
||||
echo -e " -w ${WORKFLOW_NAME} 工作流名(必须等于将来要启动的工作流名)"
|
||||
echo ""
|
||||
echo -e "${YELLOW}⚠️ 注意:${NC}"
|
||||
echo -e " - -w sdB_cno 必须匹配将来在 Dashboard 创建/启动的工作流名,否则 converged 标记被隔离看不到、导致重算"
|
||||
|
||||
Reference in New Issue
Block a user