收敛平台会话 epoch 重装语义,避免原生计数与渲染层漂移
- GUI owner 重装与清除改为按 authority 快照重定基准,保持原生计数与渲染层认知一致 - 注释写明 epoch 交接的授权前提,以及写入单调性由渲染层 reserve 与 durable session revision 保证 - 同主体换凭据的重装保持身份代次,在途 operation 的冻结会话继续有效 - 补充 epoch 重装与换主体失效的回归用例,替换原先错误假设原生计数器单调的用例
This commit is contained in:
@@ -445,33 +445,14 @@ pub(crate) fn replace_platform_session_for_gui_owner(
|
||||
let mut current = platform_session()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
// 新的 GUI authority epoch 可以替换任意旧会话,但两个计数器只能前进:把写入下限
|
||||
// 重置到请求值会让旧 epoch 的迟到写入重新变成“更新”。同一主体的重装(续期后的
|
||||
// Runner 重挂、同一 epoch 的重新对账)保持身份代次,只更新凭据。
|
||||
let same_identity = current.snapshot.as_ref().is_some_and(|current_snapshot| {
|
||||
current_snapshot.user_id == snapshot.user_id
|
||||
&& current_snapshot.api_base_url == snapshot.api_base_url
|
||||
});
|
||||
let next_identity_generation = if same_identity {
|
||||
current
|
||||
.identity_generation
|
||||
.max(snapshot.identity_generation)
|
||||
} else {
|
||||
current
|
||||
.identity_generation
|
||||
.saturating_add(1)
|
||||
.max(snapshot.identity_generation)
|
||||
};
|
||||
let next_revision = current.revision.saturating_add(1).max(snapshot.revision);
|
||||
current.revision = next_revision;
|
||||
current.identity_generation = next_identity_generation;
|
||||
current.snapshot = Some(PlatformSessionSnapshot {
|
||||
user_id: snapshot.user_id,
|
||||
access_token: snapshot.access_token,
|
||||
api_base_url: snapshot.api_base_url,
|
||||
identity_generation: next_identity_generation,
|
||||
revision: next_revision,
|
||||
});
|
||||
// 这条路径是 GUI authority epoch 的重定性入口:只有 durable claim 的 epoch + session
|
||||
// revision 与登记完全一致时才会走到这里,新 epoch 可以替换旧进程留下的任意计数器。
|
||||
// 因此按调用方快照重定基准,让原生计数与渲染层认知严格一致;Runner 同 epoch 的幂等
|
||||
// 重挂仍走 install_platform_session_checked 的精确相等校验。写入的持续单调性由渲染层
|
||||
// reserve(max(本地 + 1, 原生下限 + 1))和 durable session revision 保证。
|
||||
current.revision = snapshot.revision;
|
||||
current.identity_generation = snapshot.identity_generation;
|
||||
current.snapshot = Some(snapshot);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -544,12 +525,9 @@ pub(crate) fn clear_platform_session_for_gui_owner(identity_generation: u64, rev
|
||||
let mut current = platform_session()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
// 与 replace 同一口径:清除也只能前进,不能让新 epoch 把下限归零。
|
||||
current.revision = current.revision.saturating_add(1).max(revision);
|
||||
current.identity_generation = current
|
||||
.identity_generation
|
||||
.saturating_add(1)
|
||||
.max(identity_generation);
|
||||
// 与 replace 同一口径:epoch 交接按调用方快照重定基准,避免原生计数与渲染层认知漂移。
|
||||
current.revision = revision;
|
||||
current.identity_generation = identity_generation;
|
||||
current.snapshot = None;
|
||||
}
|
||||
|
||||
@@ -849,7 +827,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gui_owner_replacement_keeps_counters_monotonic_and_same_subject_identity() {
|
||||
fn gui_owner_replacement_rebases_to_the_authority_and_only_subject_change_fences() {
|
||||
let _session = clear_test_platform_session();
|
||||
replace_platform_session_for_gui_owner("gui-owner-a", "token-a", TEST_ORIGIN, 5, 5)
|
||||
.expect("install gui owner A");
|
||||
@@ -857,37 +835,30 @@ mod tests {
|
||||
assert_eq!(installed.identity_generation, 5);
|
||||
assert_eq!(installed.revision, 5);
|
||||
|
||||
// 同一主体只换凭据:身份代次保持,写入 revision 前进。
|
||||
// 同一主体只换凭据(续期后 Runner 重挂走的就是这条 replace 路径):身份代次保持、
|
||||
// 写入 revision 前进,在途 operation 的冻结会话仍然有效。
|
||||
replace_platform_session_for_gui_owner("gui-owner-a", "token-a2", TEST_ORIGIN, 5, 6)
|
||||
.expect("refresh gui owner A credential");
|
||||
let refreshed = current_platform_session().expect("gui owner A refreshed session");
|
||||
assert_eq!(refreshed.identity_generation, 5);
|
||||
assert_eq!(refreshed.revision, 6);
|
||||
validate_frozen_platform_session(&installed)
|
||||
.expect("same-subject credential replacement keeps the frozen session valid");
|
||||
|
||||
// 迟到的旧 epoch 写入不能把写入下限拉回去。
|
||||
replace_platform_session_for_gui_owner("gui-owner-a", "token-a", TEST_ORIGIN, 5, 4)
|
||||
.expect("stale gui owner write");
|
||||
let after_stale =
|
||||
current_platform_session().expect("gui owner A session after stale write");
|
||||
assert_eq!(
|
||||
after_stale.identity_generation,
|
||||
refreshed.identity_generation
|
||||
);
|
||||
assert!(after_stale.revision > refreshed.revision);
|
||||
|
||||
// 换主体必须推进身份代次,使旧身份的在途 operation 失效。
|
||||
replace_platform_session_for_gui_owner("gui-owner-b", "token-b", TEST_ORIGIN, 5, 5)
|
||||
// 换主体必须推进身份代次,旧身份的在途 operation 失败关闭。
|
||||
replace_platform_session_for_gui_owner("gui-owner-b", "token-b", TEST_ORIGIN, 6, 7)
|
||||
.expect("switch gui owner");
|
||||
let switched = current_platform_session().expect("gui owner B session");
|
||||
assert_eq!(switched.user_id, "gui-owner-b");
|
||||
assert!(switched.identity_generation > after_stale.identity_generation);
|
||||
assert!(validate_frozen_platform_session(&after_stale).is_err());
|
||||
assert_eq!(switched.identity_generation, 6);
|
||||
assert!(validate_frozen_platform_session(&installed).is_err());
|
||||
assert!(validate_frozen_platform_session(&refreshed).is_err());
|
||||
|
||||
// 清除同样只能前进,不能把下限归零。
|
||||
clear_platform_session_for_gui_owner(0, 0);
|
||||
// epoch 交接后的清除同样按调用方快照重定基准,让原生计数与渲染层认知一致。
|
||||
clear_platform_session_for_gui_owner(7, 8);
|
||||
let cleared = current_platform_session_write_state();
|
||||
assert!(cleared.revision > switched.revision);
|
||||
assert!(cleared.identity_generation > switched.identity_generation);
|
||||
assert_eq!(cleared.revision, 8);
|
||||
assert_eq!(cleared.identity_generation, 7);
|
||||
assert!(current_platform_session().is_none());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user