精简项目写锁历史型注释
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 2m51s
Project CI / Native shell tests (pull_request) Successful in 18m39s

移除 Issue 编号和旧实现叙述

保留写锁等待与排障契约说明
This commit is contained in:
2026-09-11 11:10:57 +00:00
parent 811abdd375
commit 63f5a1d49e
2 changed files with 6 additions and 14 deletions
@@ -1473,9 +1473,7 @@ fn bridge_write_file(root: &Path, arguments: &Value) -> Value {
return Err("工具参数 content 不能包含 NUL".to_string());
}
reject_command_output_wrapper(content)?;
// Direct 写入原本用零等待取锁:任何重叠都在 24-42ms 内直接被判成"别人正在写",
// 而 `file.write / file.patch / file.delete` 等写入口用的是约 10 秒有界等待。
// 这是用户直接触发、失败即整轮无法落盘的项目写入通道,必须和其它写入口同语义:
// 这是用户直接触发、失败即整轮无法落盘的项目写入通道:
// 短暂重叠排队等成功,只有预算耗尽才报出带持锁方身份的错误。
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
@@ -1501,10 +1499,7 @@ fn bridge_write_file(root: &Path, arguments: &Value) -> Value {
}
/// 项目写锁的有界等待是同步轮询(2_000 × 5ms,最多约 10 秒)。handler 是 async
/// 直接在 handler 里走完整条写路径会占住一个 tokio worker:争用窗口内同一轮并行写多个
/// 文件时会有多个 worker 被占,而这条 bridge 与只读端点、UI 命令共享同一个 runtime,
/// 正是 Issue #318 现场"只读工具全部正常"这条诊断特征会被破坏的情形。
/// 因此整条写路径挪进阻塞线程池,等待语义与错误文案都不变。
/// 整条写路径必须放进阻塞线程池,避免同步等待占用 tokio worker。
async fn bridge_write_file_in_blocking_pool(root: PathBuf, arguments: Value) -> Value {
let task_root = root.clone();
match tokio::task::spawn_blocking(move || bridge_write_file(&task_root, &arguments)).await {
@@ -2742,8 +2737,7 @@ mod tests {
);
}
/// Issue #318 第 1 条验收:同一轮里并行的多个文件写必须排队成功,
/// 而不是互相报"项目正在被其他写操作占用"。
/// 同一轮里并行的多个文件写必须排队成功,而不是互相报"项目正在被其他写操作占用"。
#[test]
fn bridge_write_file_serializes_parallel_writes_in_one_round() {
let temporary = tempfile::tempdir().expect("create parallel direct write root");
@@ -2788,8 +2782,7 @@ mod tests {
}
}
/// Issue #318 第 1 条验收:App 自己另一条写通道正在写该项目时,
/// Direct 写入必须等待后成功,而不是在 24-42ms 内被判成"别人正在写"。
/// App 自己另一条写通道正在写该项目时,Direct 写入必须等待后成功。
#[test]
fn bridge_write_file_waits_for_a_short_same_process_project_writer() {
let temporary = tempfile::tempdir().expect("create contended direct write root");
@@ -2883,7 +2876,7 @@ mod tests {
);
}
/// Issue #318 第 3 条验收:权限拒绝不得被投影成"被其他写操作占用"。
/// 权限拒绝不得被投影成"被其他写操作占用"。
#[cfg(unix)]
#[test]
fn bridge_write_file_does_not_project_permission_denial_as_contention() {
@@ -1843,8 +1843,7 @@ fn acquire_game_creator_agent_runtime_project_write_lock_within(
};
if attempt + 1 == max_attempts {
// 等待预算耗尽才记一条:争用本身可能重试上千次,逐次记账会淹掉日志。
// 这条记录回答的正是 Issue #318 现场缺的问题——"谁在持锁、是不是自己人",
// 以及等满预算之后这到底是争用还是权限拒绝(`projection=`)。
// 这条记录保留持锁方身份和最终分类(`projection=`),便于排查等待耗尽。
// 单次试探(max_attempts == 1,例如 hydrate 的 try_acquire_*)根本没有等待:
// 既不写 `wait_exhausted`waitedMs≈0 会让"耗尽"这个词失去意义,而 hydrate
// 每次状态变化都会撞一次锁,会把它变成噪声),也不做终态改判。