feat(artifacts): add fenced reconciliation recovery
This commit is contained in:
@@ -8,21 +8,21 @@ use crate::temp_scan::{list_names, valid_temp_name};
|
||||
use crate::{
|
||||
ArtifactError, ArtifactStore, ReconciliationMutation, ReconciliationNamespace,
|
||||
ReconciliationScan, ReconciliationScanStop,
|
||||
recovery::{FINAL_NAMESPACE, namespace_name, namespace_number},
|
||||
store::{
|
||||
QUARANTINE_DIR, RootLock, check_file, checkpointed, ensure_root_unchanged, fsync_fd,
|
||||
open_existing_dir, open_or_create_dir, rename_no_replace_at, stat_fd, unlinkat,
|
||||
},
|
||||
};
|
||||
|
||||
const FINAL_NAMESPACE: u8 = 0;
|
||||
const QUARANTINE_NAMESPACE: u8 = 1;
|
||||
|
||||
/// Opaque bounded-scan continuation, valid only for an unchanged namespace.
|
||||
/// Discard it after any put, quarantine, delete, or external mutation.
|
||||
pub struct ReconciliationCursor {
|
||||
root_dev: u64,
|
||||
root_ino: u64,
|
||||
namespace_start: u8,
|
||||
namespace: u8,
|
||||
namespace_end: u8,
|
||||
shard: u8,
|
||||
state: ReconciliationCursorState,
|
||||
}
|
||||
@@ -67,29 +67,17 @@ impl fmt::Debug for ReconciliationCursor {
|
||||
/// Opaque inode-bound evidence that exposes neither digest nor filesystem path.
|
||||
#[derive(Clone)]
|
||||
pub struct ReconciliationCandidate {
|
||||
root_dev: u64,
|
||||
root_ino: u64,
|
||||
namespace: ReconciliationNamespace,
|
||||
shard: String,
|
||||
shard_dev: u64,
|
||||
shard_ino: u64,
|
||||
name: String,
|
||||
dev: u64,
|
||||
ino: u64,
|
||||
modified_seconds: i64,
|
||||
modified_nanoseconds: i64,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ReconciliationCandidate {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("ReconciliationCandidate(..)")
|
||||
}
|
||||
}
|
||||
|
||||
impl ReconciliationCandidate {
|
||||
pub fn namespace(&self) -> ReconciliationNamespace {
|
||||
self.namespace
|
||||
}
|
||||
pub(crate) root_dev: u64,
|
||||
pub(crate) root_ino: u64,
|
||||
pub(crate) namespace: ReconciliationNamespace,
|
||||
pub(crate) shard: String,
|
||||
pub(crate) shard_dev: u64,
|
||||
pub(crate) shard_ino: u64,
|
||||
pub(crate) name: String,
|
||||
pub(crate) dev: u64,
|
||||
pub(crate) ino: u64,
|
||||
pub(crate) modified_seconds: i64,
|
||||
pub(crate) modified_nanoseconds: i64,
|
||||
}
|
||||
|
||||
/// Opaque single-use evidence for a stale temporary inode under the pinned root.
|
||||
@@ -113,25 +101,31 @@ pub struct TempScan {
|
||||
}
|
||||
|
||||
impl ArtifactStore {
|
||||
/// Streams entries without disclosing paths/digests, with opaque continuation.
|
||||
pub fn scan_reconciliation(
|
||||
pub(crate) fn scan_reconciliation_bounded(
|
||||
&self,
|
||||
start_namespace: ReconciliationNamespace,
|
||||
end_namespace: ReconciliationNamespace,
|
||||
continuation: Option<ReconciliationCursor>,
|
||||
scan_budget: usize,
|
||||
result_limit: usize,
|
||||
) -> Result<(ReconciliationScan, Vec<ReconciliationCandidate>), ArtifactError> {
|
||||
let root = self.root()?;
|
||||
let namespace = namespace_number(start_namespace);
|
||||
let namespace_end = namespace_number(end_namespace);
|
||||
let mut cursor = match continuation {
|
||||
Some(cursor) => {
|
||||
if cursor.root_dev != root.dev
|
||||
|| cursor.root_ino != root.ino
|
||||
|| cursor.namespace > QUARANTINE_NAMESPACE
|
||||
|| cursor.namespace_start != namespace
|
||||
|| cursor.namespace < namespace
|
||||
|| cursor.namespace > cursor.namespace_end
|
||||
|| cursor.namespace_end != namespace_end
|
||||
{
|
||||
return Err(ArtifactError::UnsafeRoot);
|
||||
}
|
||||
cursor
|
||||
}
|
||||
None => reconciliation_cursor(root.dev, root.ino),
|
||||
None => reconciliation_cursor(root.dev, root.ino, namespace, namespace_end),
|
||||
};
|
||||
let report = ReconciliationScan::empty(None);
|
||||
let _lock = match RootLock::shared(root) {
|
||||
@@ -177,7 +171,7 @@ impl ArtifactStore {
|
||||
}
|
||||
|
||||
let mut entries = Vec::new();
|
||||
while cursor.namespace <= QUARANTINE_NAMESPACE {
|
||||
while cursor.namespace <= cursor.namespace_end {
|
||||
if entries.len() == result_limit {
|
||||
return Ok(reconciliation_page(
|
||||
report,
|
||||
@@ -438,11 +432,18 @@ impl ArtifactStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn reconciliation_cursor(root_dev: u64, root_ino: u64) -> ReconciliationCursor {
|
||||
fn reconciliation_cursor(
|
||||
root_dev: u64,
|
||||
root_ino: u64,
|
||||
namespace: u8,
|
||||
namespace_end: u8,
|
||||
) -> ReconciliationCursor {
|
||||
ReconciliationCursor {
|
||||
root_dev,
|
||||
root_ino,
|
||||
namespace: FINAL_NAMESPACE,
|
||||
namespace_start: namespace,
|
||||
namespace,
|
||||
namespace_end,
|
||||
shard: 0,
|
||||
state: ReconciliationCursorState::OpenNamespace,
|
||||
}
|
||||
@@ -865,14 +866,6 @@ fn parse_dirent(buffer: &[u8], offset: usize) -> Result<(Vec<u8>, usize, i64), A
|
||||
Ok((raw_name[..end].to_vec(), after, cookie))
|
||||
}
|
||||
|
||||
fn namespace_name(namespace: u8) -> &'static [u8] {
|
||||
match namespace {
|
||||
FINAL_NAMESPACE => b"sha256",
|
||||
QUARANTINE_NAMESPACE => QUARANTINE_DIR,
|
||||
_ => unreachable!("validated reconciliation namespace"),
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_reconciliation_shard(cursor: &mut ReconciliationCursor, namespace: OwnedFd) {
|
||||
if cursor.shard == u8::MAX {
|
||||
advance_namespace(cursor);
|
||||
@@ -883,12 +876,16 @@ fn advance_reconciliation_shard(cursor: &mut ReconciliationCursor, namespace: Ow
|
||||
}
|
||||
|
||||
fn advance_namespace(cursor: &mut ReconciliationCursor) {
|
||||
cursor.namespace = cursor.namespace.saturating_add(1);
|
||||
cursor.namespace = if cursor.namespace >= cursor.namespace_end {
|
||||
cursor.namespace_end.saturating_add(1)
|
||||
} else {
|
||||
cursor.namespace + 1
|
||||
};
|
||||
cursor.shard = 0;
|
||||
cursor.state = ReconciliationCursorState::OpenNamespace;
|
||||
}
|
||||
|
||||
fn ensure_reconciliation_root(
|
||||
pub(crate) fn ensure_reconciliation_root(
|
||||
root: &crate::store::Root,
|
||||
candidate: &ReconciliationCandidate,
|
||||
) -> Result<(), ArtifactError> {
|
||||
@@ -904,7 +901,7 @@ fn ensure_reconciliation_root(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revalidate_candidate(
|
||||
pub(crate) fn revalidate_candidate(
|
||||
candidate: &ReconciliationCandidate,
|
||||
shard: i32,
|
||||
) -> Result<(), ArtifactError> {
|
||||
|
||||
Reference in New Issue
Block a user