Rust volume: stamp ExpireAtSec into the live volume_info on .vif persist (#11813)

The worker fix reads the source's TTL destroy time through
ReadVolumeFileStatus, which returns the in-memory volume_info. Go's
SaveVolumeInfo stamps volumeInfo.ExpireAtSec before writing; Rust's
save_vif wrote it only to the serialized file, so a Rust source kept
reporting the stale creation-time value and the EC .vif would carry a
wrong expiry. Stamp the live volume_info first, mirroring Go and
save_volume_info.
This commit is contained in:
Chris Lu authored and GitHub committed 2026-10-18 04:29:48 -07:00
1 parent 8c55425d55
commit ab39e3a51b
2 files changed
+53 -3

No files matched your search

+49
View File
@@ -10408,6 +10408,55 @@ mod tests {
assert!(vif.expire_at_sec <= before + ttl.to_seconds() + 5);
}
/// The worker EC encode reads ExpireAtSec out of ReadVolumeFileStatus, so
/// persisting a readonly mark must stamp it into the live volume_info too,
/// not only into the written .vif. A creation-time stamp would sit a full
/// last-write interval early.
#[tokio::test]
async fn test_mark_readonly_persist_reports_expire_at_sec() {
let ttl = crate::storage::needle::ttl::TTL::read("3m").unwrap();
let (service, _tmp) = make_local_service_with_volume("ttl", Some(ttl));
// A later write must move the expiry: pin it ahead of the setup write
// so the stale creation-time stamp cannot collide on the same second.
let last_write = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
+ 60;
service
.state
.store
.write()
.unwrap()
.find_volume_mut(VolumeId(1))
.unwrap()
.1
.set_last_modified_ts_for_test(last_write);
service
.state
.store
.write()
.unwrap()
.find_volume_mut(VolumeId(1))
.unwrap()
.1
.set_read_only_persist(false, true)
.unwrap();
let status = service
.read_volume_file_status(Request::new(
volume_server_pb::ReadVolumeFileStatusRequest { volume_id: 1 },
))
.await
.unwrap()
.into_inner();
assert_eq!(
status.volume_info.unwrap().expire_at_sec,
last_write + ttl.to_seconds()
);
}
/// A re-encode must not mix artifacts from a previous run. Generate used to
/// go straight into the encode, truncating only the `.ecNN` files on the
/// encoding disk; a stale shard on a sibling disk survived and was later
+4 -3
View File
@@ -3969,13 +3969,14 @@ impl Volume {
self.no_write_can_delete && !self.has_remote_file() && !self.no_write_or_delete;
self.volume_info.read_only = self.no_write_or_delete || marked_can_delete;
self.volume_info.read_only_can_delete = marked_can_delete;
let mut vif = VifVolumeInfo::from_pb(&self.volume_info);
// Match Go's SaveVolumeInfo: compute ExpireAtSec from TTL
// Match Go's SaveVolumeInfo: stamp ExpireAtSec into the live volume_info
// so read_volume_file_status returns it, not only into the written file.
let expire_at_sec = self.expire_at_sec();
if expire_at_sec > 0 {
vif.expire_at_sec = expire_at_sec;
self.volume_info.expire_at_sec = expire_at_sec;
}
let vif = VifVolumeInfo::from_pb(&self.volume_info);
let content = serde_json::to_string_pretty(&vif)
.map_err(|e| VolumeError::Io(io::Error::other(e.to_string())))?;