宿主:用户按下的终止不再被记成通道失败
execution.rs:`fail_turn` 的判据从"只看 is_closed"改成"`is_closed` 或 `host_stop_requested` 都不算失败"。用户点「终止」时 `cancel_from_host` 先同步置位 `host_stop_requested`、再异步中断会话,`closed` 与阶段要等那个任务跑到才变;这段窗口里到达的 `TransportClosed` / `interrupted` 都是宿主自己收尾的结果,以前会被记成 `transport-failed`。判据收在 `fail_turn` 里,调用点不必各写一遍,将来新增收口路径也不会漏。不记失败事实照旧收束,原因仍写进报告。 mod.rs:`interrupted` 分支去掉现在重复的 `!host_stop_requested()` 检查(同一个事实只留一处判据)。 execution.rs 单测:新增"用户请求过终止 + 未 closed 时 fail_turn 不写失败事实、原因仍进报告";变异验证:撤掉新判据该用例变红。codex_app_server 过滤 101 passed。
This commit is contained in:
@@ -686,9 +686,15 @@ impl ExecutionAdapter {
|
||||
/// 事件通道关闭)、等待模型回执超时、app-server 单方面把这一轮判成中断。终态判定会读这份事实,
|
||||
/// 于是这些收场不会再被收尾阶段(`ExecutionPhase::Interrupted`)抹成一次没有原因的"已结束"。
|
||||
///
|
||||
/// **宿主自己关的连接不算失败。** 正常终态、用户主动停止、预算与交付收尾都会把连接关掉,回合
|
||||
/// 事件通道上看到的是同一个 `TransportClosed`;判据是 [`Self::is_closed`]——适配器先于连接置位
|
||||
/// 就说明这一轮是宿主在收束,只按既有口径中断收口(原因照样写进报告,便于核对)。
|
||||
/// **宿主自己收束的这一轮不算失败。** 正常终态、用户主动停止、预算与交付收尾都会把连接关掉,
|
||||
/// 回合事件通道上看到的是同一个 `TransportClosed`;判据有两条,都收在这里,调用点不必各写一遍:
|
||||
///
|
||||
/// - [`Self::is_closed`]:适配器先于连接置位,说明这一轮是宿主在收束;
|
||||
/// - [`Self::host_stop_requested`]:用户按过「终止」。`cancel_from_host` 先**同步**置位再异步
|
||||
/// 中断会话,`closed` 与阶段都要等那个任务跑到才变,所以"标志已置、阶段未变"的窗口里到达的
|
||||
/// 通道断开 / 中断都是宿主自己收尾的结果,不能记成 `transport-failed`。
|
||||
///
|
||||
/// 不记失败事实不等于不收束:原因照样写进报告(`interrupt` 会把它追加进去),便于核对。
|
||||
///
|
||||
/// **事实要落在适配器上,不能落在调用点的局部变量里。** 回合还开着的时候,看门狗会在同一个
|
||||
/// `inner.closed` 标志上把本轮收束掉(见 [`Self::start_watchdog`]),谁先谁后取决于调度,而终态
|
||||
@@ -698,7 +704,7 @@ impl ExecutionAdapter {
|
||||
/// 不得覆盖它。
|
||||
pub(super) async fn fail_turn(&self, failure: DirectTurnError) {
|
||||
let reason = failure.to_string();
|
||||
if !self.is_closed() {
|
||||
if !self.is_closed() && !self.host_stop_requested() {
|
||||
if let Ok(mut slot) = self.turn_failure.lock() {
|
||||
if slot.is_none() {
|
||||
*slot = Some(failure);
|
||||
@@ -1202,6 +1208,25 @@ mod tests {
|
||||
assert!(adapter.report().contains("模型本次执行结束"));
|
||||
}
|
||||
|
||||
/// 用户按下的「终止」不记失败事实:`cancel_from_host` 先同步置位 `host_stop_requested`、再异步
|
||||
/// 中断会话,这中间到达的通道断开 / 中断都是宿主自己收尾的结果,不能讲成 `transport-failed`。
|
||||
#[tokio::test]
|
||||
async fn user_requested_stop_is_not_recorded_as_a_failure() {
|
||||
let (_temp, adapter) = fixture();
|
||||
adapter.request_host_stop();
|
||||
|
||||
adapter
|
||||
.fail_turn(DirectTurnError::TransportClosed {
|
||||
diagnostic: "Codex app-server 已退出;exitStatus=signal: 9 (SIGKILL)".into(),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert!(adapter.turn_failure().is_none());
|
||||
assert!(adapter.host_stop_requested());
|
||||
// 不算失败不等于不用记:原因照样进报告,排障能看到现场。
|
||||
assert!(adapter.report().contains("SIGKILL"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn production_snapshot_identity_uses_canonical_digest_and_preserves_manifest_authority() {
|
||||
let (_temp, adapter) = fixture();
|
||||
|
||||
@@ -3958,10 +3958,11 @@ impl CodexAppServerConnection {
|
||||
}
|
||||
"interrupted" => {
|
||||
if let Some(adapter) = approval_adapter.as_ref() {
|
||||
// app-server 自己把这一轮判成中断,而宿主没有请求过终止(用户点
|
||||
// 「终止」会先置 `host_stop_requested`、并把阶段推成终态):这是异常
|
||||
// 收场,必须让界面看到原因,不能只是把回合静默收口。
|
||||
if !adapter.is_host_ending() && !adapter.host_stop_requested() {
|
||||
// app-server 自己把这一轮判成中断,而宿主没有在收束:这是异常
|
||||
// 收场,必须让界面看到原因,不能只是把回合静默收口。用户按过
|
||||
// 「终止」的情况由 `fail_turn` 自己判(`host_stop_requested`),
|
||||
// 不在这里再写一遍。
|
||||
if !adapter.is_host_ending() {
|
||||
adapter
|
||||
.fail_turn(DirectTurnError::TurnInterrupted {
|
||||
detail:
|
||||
|
||||
Reference in New Issue
Block a user