use std::path::{Path, PathBuf};
use anyhow::Context as _;
use crate::datastore::write::{self, Durability};
use crate::datastore::{Datastore, schema};
pub(crate) const FILE_NAME: &str = "tombstone.json";
#[derive(serde::Serialize, serde::Deserialize)]
pub(crate) struct Tombstone {
pub(crate) generation_id: String,
pub(crate) recorded_at_unix: u64,
}
pub(crate) fn path(output_root: &Path) -> PathBuf {
output_root.join(FILE_NAME)
}
/// a tombstone left behind means an activation never finished its swap.
pub(crate) fn present(output_root: &Path) -> bool {
path(output_root).is_file()
}
/// write this before the serving pointer moves, or a crash can leave
/// unflushed bytes with no tombstone.
pub(crate) fn write(output_root: &Path, generation_id: &str) -> anyhow::Result<()> {
Datastore::at(output_root)
.record::<schema::ActivationTombstone>(&())
.put(&Tombstone {
generation_id: generation_id.to_string(),
recorded_at_unix: crate::publish::unix_now(),
})
}
pub(crate) fn clear(output_root: &Path) -> anyhow::Result<()> {
Datastore::at(output_root)
.record::<schema::ActivationTombstone>(&())
.remove()?;
write::sync_directory(output_root, Durability::Fsync)
.context("fsync output root after tombstone clear")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_present_clear_roundtrip() {
let root = tempfile::tempdir().unwrap();
assert!(!present(root.path()));
write(root.path(), &"a".repeat(64)).unwrap();
assert!(present(root.path()));
let stored = Datastore::at(root.path())
.record::<schema::ActivationTombstone>(&())
.read()
.unwrap()
.unwrap();
assert_eq!(stored.generation_id, "a".repeat(64));
clear(root.path()).unwrap();
assert!(!present(root.path()));
clear(root.path()).unwrap();
}
#[test]
fn a_tombstone_this_release_cannot_read_is_not_ignored() {
let root = tempfile::tempdir().unwrap();
std::fs::write(path(root.path()), b"{\"version\": 9}").unwrap();
assert!(
present(root.path()),
"the barrier is the file, not its bytes"
);
assert!(
Datastore::at(root.path())
.record::<schema::ActivationTombstone>(&())
.read()
.is_err()
);
}
}