ic_backup/ops/persistence/attempt_journal/
mod.rs1use super::{
4 BackupLayoutGuard, JournalLock, JournalLockError, PersistenceError, create_json_durable,
5 read_json, write_json_durable,
6};
7use crate::model::attempt_journal::{
8 AttemptAuthorityRecord, AttemptJournalRecord, AttemptJournalRecordError,
9 MAX_ATTEMPT_JOURNAL_BYTES, MutationReceiptRequest, ObservationReceiptRequest,
10};
11use std::path::{Path, PathBuf};
12use thiserror::Error;
13
14#[derive(Debug)]
19pub struct AttemptJournalGuard<'a> {
20 layout: &'a BackupLayoutGuard,
21 _lock: JournalLock,
22 record: AttemptJournalRecord,
23 usable: bool,
24}
25
26impl<'a> AttemptJournalGuard<'a> {
27 pub fn create(
32 layout: &'a BackupLayoutGuard,
33 authority: AttemptAuthorityRecord,
34 ) -> Result<Self, AttemptJournalError> {
35 layout.check_root()?;
36 let path = journal_path(layout, &authority);
37 let lock = JournalLock::acquire(&path)?;
38 let record = AttemptJournalRecord::new(authority);
39 check_size(&record)?;
40 create_json_durable(&path, &record)?;
41 Ok(Self {
42 layout,
43 _lock: lock,
44 record,
45 usable: true,
46 })
47 }
48 pub fn open(
53 layout: &'a BackupLayoutGuard,
54 expected: &AttemptAuthorityRecord,
55 ) -> Result<Self, AttemptJournalError> {
56 layout.check_root()?;
57 let path = journal_path(layout, expected);
58 let lock = JournalLock::acquire(&path)?;
59 let record: AttemptJournalRecord = read_json(&path, MAX_ATTEMPT_JOURNAL_BYTES)?;
60 check_size(&record)?;
61 if record.authority() != expected {
62 return Err(AttemptJournalError::AuthorityMismatch);
63 }
64 Ok(Self {
65 layout,
66 _lock: lock,
67 record,
68 usable: true,
69 })
70 }
71 pub fn record(&self) -> Result<&AttemptJournalRecord, AttemptJournalError> {
76 self.check_usable()?;
77 Ok(&self.record)
78 }
79 #[must_use]
81 pub fn path(&self) -> PathBuf {
82 journal_path(self.layout, self.record.authority())
83 }
84 pub fn reserve_mutation(&mut self) -> Result<u32, AttemptJournalError> {
89 self.reserve_with(AttemptJournalRecord::reserve_mutation, write_json_durable)
90 }
91 pub fn reserve_observation(
97 &mut self,
98 mutation: u32,
99 request: &str,
100 ) -> Result<u32, AttemptJournalError> {
101 self.reserve_with(
102 |record| record.reserve_observation(mutation, request),
103 write_json_durable,
104 )
105 }
106 pub fn record_mutation(
111 &mut self,
112 receipt: MutationReceiptRequest,
113 ) -> Result<(), AttemptJournalError> {
114 self.reserve_with(|record| record.record_mutation(receipt), write_json_durable)
115 }
116 pub fn record_observation(
124 &mut self,
125 receipt: ObservationReceiptRequest,
126 ) -> Result<(), AttemptJournalError> {
127 self.reserve_with(
128 |record| record.record_observation(receipt),
129 write_json_durable,
130 )
131 }
132
133 fn reserve_with<T>(
134 &mut self,
135 transition: impl FnOnce(&mut AttemptJournalRecord) -> Result<T, AttemptJournalRecordError>,
136 write: impl FnOnce(&Path, &AttemptJournalRecord) -> Result<(), PersistenceError>,
137 ) -> Result<T, AttemptJournalError> {
138 self.check_usable()?;
139 let mut next = self.record.clone();
140 let result = transition(&mut next)?;
141 check_size(&next)?;
142 self.usable = false;
143 write(&self.path(), &next)?;
144 self.record = next;
145 self.usable = true;
146 Ok(result)
147 }
148 fn check_usable(&self) -> Result<(), AttemptJournalError> {
149 if !self.usable {
150 return Err(AttemptJournalError::IndeterminateWrite);
151 }
152 self.layout.check_root()?;
153 Ok(())
154 }
155}
156
157fn journal_path(layout: &BackupLayoutGuard, authority: &AttemptAuthorityRecord) -> PathBuf {
158 layout.root().join(format!(
159 "attempt-{}.json",
160 authority.binding().operation_sequence()
161 ))
162}
163fn check_size(record: &AttemptJournalRecord) -> Result<(), PersistenceError> {
164 super::json::check_json_size(record, MAX_ATTEMPT_JOURNAL_BYTES)
165}
166
167#[derive(Debug, Error)]
169pub enum AttemptJournalError {
170 #[error("attempt journal authority mismatch")]
172 AuthorityMismatch,
173 #[error("attempt journal write outcome indeterminate; reopen retained evidence")]
175 IndeterminateWrite,
176 #[error(transparent)]
178 Record(#[from] AttemptJournalRecordError),
179 #[error(transparent)]
181 Lock(#[from] JournalLockError),
182 #[error(transparent)]
184 Persistence(#[from] PersistenceError),
185}
186
187#[cfg(all(test, unix))]
188mod tests;