修复 AGC 壳 Rust shard-4 的两条偶发断言,并补齐 pitfalls 记录
- agent::thread_manager::tests::active_turn_changes_publish_one_notification_per_real_change 偶发 left 8/right 7:测试计数器 DIRECT_ACTIVE_TURNS_EVENT_TEST_COUNT 曾在 2026-10-01 按线程 作用域隔离,2026-10-02 退役 runtime_driver 把它搬进 agent/direct_events.rs 时降级回进程级 static AtomicU64,于是断言会取到宿主 tauri::async_runtime 后台回合在别的线程上的广播 (--test-threads=1 只串行测试线程)。改回 thread_local! Cell,并注明跨线程口径的覆盖取舍 - process_session::tests::process_session_graceful_terminate_keeps_wrapper_alive_for_target_cleanup 偶发 left "exited"/right "terminated":测试命令里 leader 打印 READY 后立刻 exit 0,同组后代 仍存活,trampoline 从 leader 被回收起开始 800ms 宽限;客户端只要晚于宽限才发出 terminate 就只能读到既成事实。改为 leader 用 wait 等后台子进程,让 terminate 必然落在会话仍 running 时 (trap / sleep 0.4 / marker / 断言均未改),并注明该用例的确定性来自 400ms < 800ms 的时间余量 - docs/project-memory/shared-memory/pitfalls.md:更新 graceful terminate 那条已过时的验证口径, 补三条 2026-10-04 条目(tracing interest 竞态、AGC 两条偶发的串台根因、SPA 深链前缀路由) - 本地实跑:修复前把计数器临时改回 AtomicU64 时同一并行口径 17/20 红;修复后并行 20 次全绿、 agent::thread_manager:: 连跑 5 次 72 passed;第 2 条用例是 #[cfg(target_os = "linux")], Windows 本机跑不到,已用真实 Linux 内核(WSL Alpine)验证命令形状(leader 活到 TERM、 同组后代完成 400ms 延迟清理 marker=done 0.41s、清理后组内零残留),CI 侧仍需跑 node apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs --shards=4 --shard-index=4 复核
This commit is contained in:
@@ -28,7 +28,22 @@ pub(crate) const DIRECT_ACTIVE_TURNS_CHANGED_EVENT: &str =
|
||||
"game-creator-direct-active-turns-changed";
|
||||
static DIRECT_ACTIVE_TURNS_EVENT_REVISION: AtomicU64 = AtomicU64::new(0);
|
||||
#[cfg(test)]
|
||||
static DIRECT_ACTIVE_TURNS_EVENT_TEST_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
thread_local! {
|
||||
/// 只统计**当前线程**发出的通知。`--test-threads=1` 只串行测试线程,宿主
|
||||
/// `tauri::async_runtime` 的后台回合仍在自己的工作线程上跑(放行任务随
|
||||
/// `TurnReservation::drop` 起整轮),并会在任意时刻广播「运行中的项目」变了。断言要观测的
|
||||
/// 是本测试自己触发的通知,不该被别的后台广播串台。
|
||||
///
|
||||
/// 这里必须留在测试线程作用域内:退役 `runtime_driver` 时这条口径曾被降级回进程级
|
||||
/// `AtomicU64`,于是 `agent::thread_manager::tests::active_turn_changes_publish_one_notification_per_real_change`
|
||||
/// 又回到偶发(同一片内前一个用例留下的后台回合广播进采样窗口)。
|
||||
///
|
||||
/// 代价:计数改成线程作用域后,**别的线程**上的重复 / 丢失通知不再被这条用例覆盖(那是
|
||||
/// 宿主后台回合的行为,本身就不该由单线程断言口径表达);要覆盖跨线程序,应另加用例
|
||||
/// 观察回合身份/序列号,而不是把计数器退回进程级。
|
||||
static DIRECT_ACTIVE_TURNS_EVENT_TEST_COUNT: std::cell::Cell<u64> =
|
||||
const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
const GAME_CREATOR_MANIFEST_INVALIDATION_RELAY_MAX_BYTES: u64 = 64 * 1024;
|
||||
const GAME_CREATOR_MANIFEST_INVALIDATION_EVENT_SINK_MAX: usize = 16;
|
||||
@@ -54,7 +69,7 @@ pub(crate) fn set_game_creator_agent_runtime_update_app_handle(app: tauri::AppHa
|
||||
pub(crate) fn emit_direct_active_turns_changed() {
|
||||
let revision = DIRECT_ACTIVE_TURNS_EVENT_REVISION.fetch_add(1, Ordering::AcqRel) + 1;
|
||||
#[cfg(test)]
|
||||
DIRECT_ACTIVE_TURNS_EVENT_TEST_COUNT.fetch_add(1, Ordering::AcqRel);
|
||||
let _ = DIRECT_ACTIVE_TURNS_EVENT_TEST_COUNT.try_with(|count| count.set(count.get() + 1));
|
||||
let Some(app) = GAME_CREATOR_AGENT_RUNTIME_UPDATE_APP_HANDLE.get() else {
|
||||
return;
|
||||
};
|
||||
@@ -66,7 +81,7 @@ pub(crate) fn emit_direct_active_turns_changed() {
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn direct_active_turns_event_test_count() -> u64 {
|
||||
DIRECT_ACTIVE_TURNS_EVENT_TEST_COUNT.load(Ordering::Acquire)
|
||||
DIRECT_ACTIVE_TURNS_EVENT_TEST_COUNT.with(std::cell::Cell::get)
|
||||
}
|
||||
|
||||
pub(crate) fn emit_direct_game_creator_progress(root: &Path, stage: &str, message: &str) {
|
||||
|
||||
@@ -630,12 +630,20 @@ fn process_session_graceful_terminate_keeps_wrapper_alive_for_target_cleanup() {
|
||||
let root = directory.path();
|
||||
init_local_game_project_at(root, "graceful-process-project", "Graceful Process Project")
|
||||
.expect("initialize project");
|
||||
// leader 输出 READY 后 **等** 同组后台子进程,不自己先退出。terminate 必须先送到 trampoline
|
||||
// 的 target group、再让组内后代把延迟清理跑完,这条断言才有确定性:leader 一旦先自然退出,
|
||||
// trampoline 的 800ms 宽限就开始计时,客户端只要在那之后才发出 terminate(CI 高负载下调度
|
||||
// 完全可能 >800ms),会话就会先以 `exited` 收口,客户端再 terminate 只能读到既成事实。
|
||||
// 后台子进程仍留在同一进程组里、仍靠 TERM 触发延迟 400ms 的 marker 清理,语义不变。
|
||||
// 注意:这条用例的确定性靠的是「trap 里 400ms 清理 < 800ms 宽限」的时间余量(实测 0.41s),
|
||||
// 不是真正的同步原语;若以后清理耗时逼近或超过宽限,应把 marker 拆成「收到 TERM 即时落盘 +
|
||||
// 延迟内容」两步,或让宽限可注入,而不是放宽断言。
|
||||
let spec = resolve_project_command_spec_at(
|
||||
root,
|
||||
"bash",
|
||||
&[
|
||||
"-lc".to_string(),
|
||||
"(trap 'sleep 0.4; printf done > graceful-marker.txt; exit 0' TERM; while :; do sleep 1; done) & printf 'READY\\n'; exit 0".to_string(),
|
||||
"(trap 'sleep 0.4; printf done > graceful-marker.txt; exit 0' TERM; while :; do sleep 1; done) & printf 'READY\\n'; wait".to_string(),
|
||||
],
|
||||
".",
|
||||
30,
|
||||
|
||||
Reference in New Issue
Block a user