feat: 加固运行期可靠性并补齐调度与观测接口
- 运行句柄按 run_id 归属,修复取消后重跑时闸门被旧运行删除的竞态 - 调度循环改用取消令牌退出,启动时对账遗留运行,校验 rows_affected - 三类任务统一参与调度,新增 /api/v1/stats 与 /api/v1/schedule - SQLite 启用 WAL 与 busy_timeout,日志清理跳过活跃文件,单行上限 256 KiB - 前端缓存管理令牌并在 403 时重试一次,仪表盘改用统计接口
This commit is contained in:
@@ -79,7 +79,7 @@ pub fn prepare_script_environment(
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
);
|
||||
environment.insert("PYTHONUNBUFFERED".to_owned(), "1".to_owned());
|
||||
apply_stdio_defaults(&mut environment);
|
||||
return Ok(PreparedPython {
|
||||
interpreter: python,
|
||||
virtual_env: Some(venv.clone()),
|
||||
@@ -93,7 +93,7 @@ pub fn prepare_script_environment(
|
||||
});
|
||||
}
|
||||
|
||||
environment.insert("PYTHONUNBUFFERED".to_owned(), "1".to_owned());
|
||||
apply_stdio_defaults(&mut environment);
|
||||
Ok(PreparedPython {
|
||||
interpreter: interpreter_path,
|
||||
virtual_env: None,
|
||||
@@ -102,6 +102,17 @@ pub fn prepare_script_environment(
|
||||
})
|
||||
}
|
||||
|
||||
/// Python 的标准流默认跟随系统代码页(简体中文为 GBK),一旦打印 emoji 或生僻字符
|
||||
/// 就会 `UnicodeEncodeError` 直接失败。这里默认改用 UTF-8,日志侧本来就优先按 UTF-8 解码。
|
||||
/// 用户在任务里显式设置的 `PYTHONIOENCODING` 仍然优先。
|
||||
fn apply_stdio_defaults(environment: &mut BTreeMap<String, String>) {
|
||||
// 实时日志依赖标准流不缓冲,这一项始终强制。
|
||||
environment.insert("PYTHONUNBUFFERED".to_owned(), "1".to_owned());
|
||||
environment
|
||||
.entry("PYTHONIOENCODING".to_owned())
|
||||
.or_insert_with(|| "utf-8".to_owned());
|
||||
}
|
||||
|
||||
/// 虚拟环境的来源,用于在界面上说明"为什么"关联到了这个环境。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -630,6 +641,48 @@ mod tests {
|
||||
assert!(prepared.remove_environment.contains(&"VIRTUAL_ENV_PROMPT"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn injects_utf8_stdio_for_python_tasks() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
|
||||
let prepared = prepare_script_environment(
|
||||
temp.path().to_str().unwrap(),
|
||||
"python.exe",
|
||||
None,
|
||||
&BTreeMap::new(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
prepared.environment.get("PYTHONIOENCODING"),
|
||||
Some(&"utf-8".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
prepared.environment.get("PYTHONUNBUFFERED"),
|
||||
Some(&"1".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_user_pythonioencoding_override() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut user_env = BTreeMap::new();
|
||||
user_env.insert("PYTHONIOENCODING".to_owned(), "gbk".to_owned());
|
||||
|
||||
let prepared = prepare_script_environment(
|
||||
temp.path().to_str().unwrap(),
|
||||
"python.exe",
|
||||
None,
|
||||
&user_env,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
prepared.environment.get("PYTHONIOENCODING"),
|
||||
Some(&"gbk".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
fn probe(working_dir: &str, interpreter: &str, venv: Option<&str>) -> VenvProbeResponse {
|
||||
probe_venv(&VenvProbeRequest {
|
||||
working_dir: working_dir.to_owned(),
|
||||
|
||||
Reference in New Issue
Block a user