mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 10:04:55 +08:00
fix(release): 修复发布一致性与并发边界
This commit is contained in:
@@ -393,12 +393,25 @@ pub fn read_cas_reuse_reference_manifest_at(
|
||||
|
||||
/// Decrements and removes CAS references recorded for a release.
|
||||
///
|
||||
/// The operation is resumable: after every successful decrement the remaining
|
||||
/// object IDs are atomically written back to the release-local manifest.
|
||||
/// Each decrement is committed together with a durable `(release, ordinal)`
|
||||
/// ownership record in CAS metadata. The release-local manifest remains a
|
||||
/// resumable progress cursor, so a crash before its rewrite cannot decrement
|
||||
/// the same ownership twice.
|
||||
pub fn release_cas_reuse_references(release_root: &Path, cas_root: &Path) -> Result<usize, String> {
|
||||
let Some(mut manifest) = read_cas_reuse_reference_manifest_at(release_root)? else {
|
||||
return Ok(0);
|
||||
};
|
||||
let release_id = release_root
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.filter(|name| !name.is_empty() && *name != "." && *name != "..")
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"无法从 release 路径确定 CAS ownership ID:{}",
|
||||
release_root.display()
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
let objects_root = cas_root.join("objects");
|
||||
let metadata_path = cas_root.join("metadata.sqlite");
|
||||
require_existing_directory(cas_root, "CAS 根目录")?;
|
||||
@@ -406,20 +419,24 @@ pub fn release_cas_reuse_references(release_root: &Path, cas_root: &Path) -> Res
|
||||
require_existing_file(cas_root, &metadata_path, "CAS 元数据库")?;
|
||||
let mut released = 0usize;
|
||||
while let Some(object_id) = manifest.object_ids.pop() {
|
||||
let ordinal = manifest.object_ids.len() as u64;
|
||||
let cas_root = cas_root.to_path_buf();
|
||||
let object_id_for_runtime = object_id.clone();
|
||||
let release_id_for_runtime = release_id.clone();
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|error| format!("创建 CAS 引用清理 runtime 失败:{error}"))?;
|
||||
runtime.block_on(async move {
|
||||
let did_release = runtime.block_on(async move {
|
||||
let cas = crate::FileSystemCasRepository::new(cas_root);
|
||||
cas.remove_reference(&object_id_for_runtime)
|
||||
cas.release_reference_once(&release_id_for_runtime, ordinal, &object_id_for_runtime)
|
||||
.await
|
||||
.map_err(|error| format!("减少 CAS release 引用失败 object={object_id}:{error}"))
|
||||
})?;
|
||||
write_cas_reuse_reference_manifest(release_root, &manifest)?;
|
||||
released += 1;
|
||||
if did_release {
|
||||
released += 1;
|
||||
}
|
||||
}
|
||||
let path = release_root.join(OFFICIAL_CAS_REUSE_REFERENCES_FILE);
|
||||
match fs::symlink_metadata(&path) {
|
||||
@@ -4656,6 +4673,26 @@ exit 22
|
||||
assert!(read_cas_reuse_reference_manifest_at(&out_dir)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
// 模拟 CAS 事务已提交但 release-local progress cursor 尚未写回;
|
||||
// 重试必须识别同一个 ownership pair,而不是再次递减。
|
||||
fs::write(
|
||||
out_dir.join(OFFICIAL_CAS_REUSE_REFERENCES_FILE),
|
||||
serde_json::to_vec(&OfficialCasReuseReferenceManifest {
|
||||
version: OFFICIAL_CAS_REUSE_REFERENCES_VERSION,
|
||||
object_ids: vec![object_id.clone()],
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
release_cas_reuse_references(&out_dir, &cas_root).unwrap(),
|
||||
0
|
||||
);
|
||||
assert_eq!(cas_reference_count(&cas_root, &object_id), 1);
|
||||
assert!(read_cas_reuse_reference_manifest_at(&out_dir)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user