宿主:并发拒单的两个身份改成回合身份
- Thread Manager 的 `accept_turn` 在并发冲突时回**已有的 `turn_id`**(调用方的 `clientTurnId`),不再回进程内的占用 token - `DirectTurnReservation::accept` 的拒单载荷改成 `existing`=已在跑那一轮的 `clientTurnId`、`incoming`=本次请求的 `clientTurnId`;同一轮重发时两者相等,"同一轮消息仍在处理中"那条文案才走得到 - 补 `accept_conflict_reports_client_turn_ids_not_reservation_tokens`(同一轮 / 另一轮两条分支都钉住)与 `accept_conflict_returns_the_existing_turn_id`(manager 侧只回回合身份) - 占用对象自己的 token 保持 UUID 不变(`complete_direct_thread_turn_if_reserved` 靠它配对),改的只有错误载荷
This commit is contained in:
@@ -200,8 +200,10 @@ impl DirectThreadManager {
|
||||
|
||||
/// 接单:同一个临界区里拒绝并发、登记占用、追加逻辑回合开始事件。
|
||||
///
|
||||
/// 返回 `Err(existing_token)` 表示这个 thread 已经有一条没收口的回合——此时不动队列,
|
||||
/// 由调用方把它投影成接单拒绝。
|
||||
/// 返回 `Err(existing_turn_id)` 表示这个 thread 已经有一条没收口的回合——此时不动队列,
|
||||
/// 由调用方把它投影成接单拒绝。回的是**回合身份**(调用方接单时给的 `turn_id`)而不是占用
|
||||
/// `token`:占用 token 只活在这个进程里,界面拿它匹配不了自己发出的那一轮,也没法判断
|
||||
/// "撞的是同一轮还是另一轮"。
|
||||
fn accept_turn(
|
||||
&mut self,
|
||||
thread_id: &str,
|
||||
@@ -213,7 +215,7 @@ impl DirectThreadManager {
|
||||
{
|
||||
let thread = self.threads.entry(thread_id.to_string()).or_default();
|
||||
if let Some(active) = thread.active_turn.as_ref() {
|
||||
return Err(active.token.clone());
|
||||
return Err(active.turn_id.clone());
|
||||
}
|
||||
thread.active_turn = Some(ActiveDirectTurn {
|
||||
token: token.to_string(),
|
||||
@@ -562,6 +564,9 @@ pub(crate) fn append_direct_thread_event(
|
||||
}
|
||||
|
||||
/// 接单:拒绝并发 + 登记占用 + 发逻辑回合开始事件(见 [`DirectThreadManager::accept_turn`])。
|
||||
/// `Err` 是这一轮**已有的回合身份**(`turn_id`,也就是调用方的 `clientTurnId`),不是占用 token:
|
||||
/// 调用方拿它投影成 `TurnAlreadyRunning` 的两个身份字段,界面按"撞的是同一轮还是另一轮"决定要
|
||||
/// 不要动当前回合。
|
||||
pub(crate) fn accept_direct_thread_turn(
|
||||
thread_id: &str,
|
||||
token: &str,
|
||||
@@ -1072,4 +1077,20 @@ mod tests {
|
||||
);
|
||||
assert!(snapshot_of(&manager, thread_id).is_none());
|
||||
}
|
||||
|
||||
/// 并发接单回给调用方的是**回合身份**(`turn_id`),不是占用 `token`:token 只活在这个进程
|
||||
/// 里,界面拿它匹配不了自己发出的那一轮。
|
||||
#[test]
|
||||
fn accept_conflict_returns_the_existing_turn_id() {
|
||||
let mut manager = DirectThreadManager::with_limits(100, 100_000);
|
||||
manager
|
||||
.accept_turn("thread-1", "token-1", "turn-1", None, FIXED_AT_MS)
|
||||
.expect("accept");
|
||||
|
||||
let conflict = manager
|
||||
.accept_turn("thread-1", "token-2", "turn-2", None, FIXED_AT_MS)
|
||||
.err();
|
||||
|
||||
assert_eq!(conflict.as_deref(), Some("turn-1"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ impl DirectTurnReservation {
|
||||
/// 失败表示这个 thread 已经有一条没收口的回合(并发接单),此时不改队列、不发事件。
|
||||
/// `client_turn_id` 是给界面看的回合身份(首页快照与进度回填按它匹配),与占用身份 `token`
|
||||
/// 是两件事:前者来自调用方,后者只活在这个进程里。
|
||||
///
|
||||
/// 拒单载荷里的两个身份都取**回合身份**:`existing` 是已在跑的那一轮的 `clientTurnId`
|
||||
/// (Thread Manager 回的就是它),`incoming` 是本次请求的 `clientTurnId`。同一轮重发时两者
|
||||
/// 相等,界面才走得到"同一轮消息仍在处理中"那条文案。
|
||||
pub(crate) fn accept(
|
||||
thread_id: &str,
|
||||
client_turn_id: &str,
|
||||
@@ -47,7 +51,7 @@ impl DirectTurnReservation {
|
||||
)
|
||||
.map_err(|existing| DirectTurnError::TurnAlreadyRunning {
|
||||
existing_invocation_id: existing,
|
||||
incoming_invocation_id: token.clone(),
|
||||
incoming_invocation_id: client_turn_id.to_string(),
|
||||
})?;
|
||||
Ok(Self {
|
||||
thread_id: thread_id.to_string(),
|
||||
@@ -152,6 +156,51 @@ mod tests {
|
||||
drop(reservation);
|
||||
}
|
||||
|
||||
/// 拒单载荷里的两个身份都是**回合身份**:撞的是同一轮时两者相等,界面才走得到"同一轮消息仍在
|
||||
/// 处理中";撞的是另一轮时两者不等,界面才敢提示"另一条回合在运行"。占用 token 只活在本进程,
|
||||
/// 一旦漏进载荷,这两个分支就都判不出来(UUID 永远不等于界面的 `clientTurnId`)。
|
||||
#[test]
|
||||
fn accept_conflict_reports_client_turn_ids_not_reservation_tokens() {
|
||||
let thread = unique_thread("same-turn-conflict");
|
||||
let reservation =
|
||||
DirectTurnReservation::accept(&thread, "turn-1", Some("u-1")).expect("accept");
|
||||
|
||||
let same_turn = match DirectTurnReservation::accept(&thread, "turn-1", Some("u-1")) {
|
||||
Ok(_) => panic!("同一 thread 的第二条回合必须被拒"),
|
||||
Err(error) => error,
|
||||
};
|
||||
let DirectTurnError::TurnAlreadyRunning {
|
||||
existing_invocation_id,
|
||||
incoming_invocation_id,
|
||||
} = &same_turn
|
||||
else {
|
||||
panic!("expected a concurrency rejection, got {same_turn:?}");
|
||||
};
|
||||
assert_eq!(existing_invocation_id, "turn-1");
|
||||
assert_eq!(incoming_invocation_id, "turn-1");
|
||||
assert!(
|
||||
same_turn.to_string().contains("同一轮消息仍在处理中"),
|
||||
"{same_turn}"
|
||||
);
|
||||
|
||||
let other_turn = match DirectTurnReservation::accept(&thread, "turn-2", Some("u-2")) {
|
||||
Ok(_) => panic!("同一 thread 的第二条回合必须被拒"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert!(matches!(
|
||||
&other_turn,
|
||||
DirectTurnError::TurnAlreadyRunning {
|
||||
existing_invocation_id,
|
||||
incoming_invocation_id,
|
||||
} if existing_invocation_id == "turn-1" && incoming_invocation_id == "turn-2"
|
||||
));
|
||||
assert!(
|
||||
other_turn.to_string().contains("另一条 Direct 客户端回合"),
|
||||
"{other_turn}"
|
||||
);
|
||||
drop(reservation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_without_a_terminal_writes_a_host_dropped_terminal() {
|
||||
let thread = unique_thread("drop");
|
||||
|
||||
Reference in New Issue
Block a user