use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use rocketmq_runtime::MetadataGeneration;
use rocketmq_runtime::MetadataIoCommitObservation;
use rocketmq_runtime::MetadataIoCommitOutcome;
use crate::AuthServiceError;
#[derive(Debug)]
pub(crate) enum SnapshotPersistence {
Durable {
generation: MetadataGeneration,
},
NotWritten(AuthServiceError),
Unconfirmed {
generation: MetadataGeneration,
error: AuthServiceError,
},
}
impl SnapshotPersistence {
pub(crate) fn may_have_written(&self) -> bool {
!matches!(self, Self::NotWritten(_))
}
pub(crate) fn into_error(self) -> Option<AuthServiceError> {
match self {
Self::Durable { .. } => None,
Self::NotWritten(error) | Self::Unconfirmed { error, .. } => Some(error),
}
}
}
pub(crate) fn classify_snapshot_persistence(observation: MetadataIoCommitObservation) -> SnapshotPersistence {
match observation {
MetadataIoCommitObservation::Settled {
outcome: MetadataIoCommitOutcome::Durable(generation),
..
} => SnapshotPersistence::Durable { generation },
MetadataIoCommitObservation::Settled {
outcome: MetadataIoCommitOutcome::FailedBeforeCommit(error),
..
} => SnapshotPersistence::NotWritten(AuthServiceError::metadata_io(error)),
MetadataIoCommitObservation::Settled {
generation,
outcome: MetadataIoCommitOutcome::CommitOutcomeUnknown(error),
} => SnapshotPersistence::Unconfirmed {
generation,
error: AuthServiceError::metadata_io(error),
},
MetadataIoCommitObservation::Unobserved { generation } => SnapshotPersistence::Unconfirmed {
generation,
error: AuthServiceError::metadata_io(rocketmq_runtime::RuntimeError::timed_out(
rocketmq_runtime::RuntimeOperation::WaitForDurableMetadata,
)),
},
MetadataIoCommitObservation::TargetConflict(_) => {
SnapshotPersistence::NotWritten(AuthServiceError::storage_conflict())
}
}
}
#[derive(Debug, Default)]
pub(crate) struct ReconciliationGeneration(AtomicU64);
impl ReconciliationGeneration {
pub(crate) fn record(&self, generation: MetadataGeneration) {
if generation.get() != 0 {
self.0.store(generation.get(), Ordering::Release);
}
}
pub(crate) fn release(&self, generation: MetadataGeneration) {
let recorded = self.0.load(Ordering::Acquire);
if recorded != 0 && generation.get() >= recorded {
self.0.store(0, Ordering::Release);
}
}
pub(crate) fn required(&self) -> bool {
self.0.load(Ordering::Acquire) != 0
}
pub(crate) fn generation(&self) -> Option<MetadataGeneration> {
match self.0.load(Ordering::Acquire) {
0 => None,
generation => Some(MetadataGeneration::new(generation)),
}
}
}
#[cfg(test)]
mod tests {
use std::io;
use super::*;
fn runtime_error() -> rocketmq_runtime::RuntimeError {
rocketmq_runtime::RuntimeError::io(
rocketmq_runtime::RuntimeOperation::MetadataSyncParent,
io::Error::other("injected persistence failure"),
)
}
#[test]
fn only_a_definite_pre_commit_failure_blocks_publication() {
let durable = classify_snapshot_persistence(MetadataIoCommitObservation::Settled {
generation: MetadataGeneration::new(3),
outcome: MetadataIoCommitOutcome::Durable(MetadataGeneration::new(3)),
});
assert!(durable.may_have_written());
assert!(durable.into_error().is_none());
let failed = classify_snapshot_persistence(MetadataIoCommitObservation::Settled {
generation: MetadataGeneration::new(3),
outcome: MetadataIoCommitOutcome::FailedBeforeCommit(runtime_error()),
});
assert!(!failed.may_have_written());
assert!(failed.into_error().is_some());
let unknown = classify_snapshot_persistence(MetadataIoCommitObservation::Settled {
generation: MetadataGeneration::new(3),
outcome: MetadataIoCommitOutcome::CommitOutcomeUnknown(runtime_error()),
});
assert!(unknown.may_have_written());
assert!(matches!(
unknown,
SnapshotPersistence::Unconfirmed {
generation,
..
} if generation == MetadataGeneration::new(3)
));
let unobserved = classify_snapshot_persistence(MetadataIoCommitObservation::Unobserved {
generation: MetadataGeneration::new(4),
});
assert!(unobserved.may_have_written());
assert!(matches!(unobserved, SnapshotPersistence::Unconfirmed { .. }));
}
#[test]
fn a_recorded_generation_is_released_by_a_newer_confirmed_one() {
let record = ReconciliationGeneration::default();
assert!(!record.required());
record.record(MetadataGeneration::new(5));
assert!(record.required());
assert_eq!(record.generation(), Some(MetadataGeneration::new(5)));
record.release(MetadataGeneration::new(4));
assert!(record.required());
record.release(MetadataGeneration::new(5));
assert!(!record.required());
assert_eq!(record.generation(), None);
}
}