1use crate::{
8 ownership::*, Admission, Crdt, EventLog, GCounter, GCounterDelta, OrSet, OrSetDelta, Record,
9 RecordId, WireDecode, WireEncode, WireError, WireSchema,
10};
11use alloc::{format, string::String, vec::Vec};
12use std::{
13 fs::{self, File, OpenOptions, TryLockError},
14 io::{self, Read, Seek, SeekFrom, Write},
15 path::{Path, PathBuf},
16};
17
18#[derive(Debug)]
44#[non_exhaustive]
45pub enum LocalError {
46 Refused,
47 Exhausted,
48 PeerWriterAhead,
50 StoreExists,
52 RecoveryRequired,
54 UnsupportedFormat {
56 file: &'static str,
57 version: u8,
58 },
59 Configuration,
60 CounterWidth(crate::CoordinateError),
62 InvalidRecord(WireError),
63 History(WireError),
64 InvalidHistory,
65 Io(io::Error),
66 AncestorSync {
68 path: PathBuf,
69 source: io::Error,
70 },
71 RecordLimitExceeded {
75 max_records: usize,
76 default_limit: bool,
77 },
78}
79
80impl core::fmt::Display for LocalError {
81 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82 match self {
83 Self::Refused => {
84 f.write_str("local writer operation refused by ownership or lease checks")
85 }
86 Self::Exhausted => {
87 f.write_str("local writer sequence, generation, or token allocation exhausted")
88 }
89 Self::PeerWriterAhead => f.write_str("a peer returned a record for this writer above its durable high-water; local writes on this open replica are stopped; reopening clears this stop and an old-identity restore is detected only when a peer returns such a record; open a restored store with a new writer identity"),
90 Self::StoreExists => f.write_str("local store already exists for this writer; restart it, or create with a new root or writer ID"),
91 Self::RecoveryRequired => f.write_str("local store requires recovery: its writer fence, journal header, rollback counter or migration files are damaged or do not agree"),
92 Self::UnsupportedFormat { file, version } => write!(f, "unsupported {file} version {version}; highest version this build reads is 1; use a newer SafeMesh"),
93 Self::CounterWidth(error) => error.fmt(f),
94 Self::Configuration => f.write_str("invalid or mismatched local writer configuration"),
95 Self::InvalidRecord(error) => error.fmt(f),
96 Self::History(error) => write!(f, "local history wire validation failed: {error}"),
97 Self::InvalidHistory => {
98 f.write_str("local history failed replay or sequence validation")
99 }
100 Self::AncestorSync { path, source } => write!(f, "AncestorSync: cannot sync store ancestor {}: {source}", path.display()),
101 Self::Io(error) => write!(f, "local store I/O failed: {error}"),
102 Self::RecordLimitExceeded { max_records, default_limit } => {
103 write!(f, "local history exceeds restart record budget: RecordLimitExceeded: {max_records}")?;
104 if *default_limit { f.write_str("; pass DecodeLimits::UNLIMITED to reopen") } else { Ok(()) }
105 }
106 }
107 }
108}
109
110impl core::error::Error for LocalError {
111 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
112 match self {
113 Self::InvalidRecord(error) | Self::History(error) => Some(error),
114 Self::Io(error) | Self::AncestorSync { source: error, .. } => Some(error),
115 Self::CounterWidth(error) => Some(error),
116 _ => None,
117 }
118 }
119}
120impl From<io::Error> for LocalError {
121 fn from(e: io::Error) -> Self {
122 Self::Io(e)
123 }
124}
125
126const FENCE_MAGIC: &[u8; 8] = b"SMFENCE1";
127const TRANSACTION_MAGIC: &[u8; 8] = b"SMTRANS1";
128
129fn fence_fields(bytes: &[u8]) -> Result<usize, LocalError> {
130 if bytes.len() == 24 {
131 return Ok(0);
132 }
133 if bytes.len() >= 9 && &bytes[..8] == FENCE_MAGIC {
134 if bytes[8] > 1 {
135 return Err(LocalError::UnsupportedFormat {
136 file: "fence",
137 version: bytes[8],
138 });
139 }
140 if bytes[8] == 1 && bytes.len() == 33 {
141 return Ok(9);
142 }
143 }
144 Err(LocalError::RecoveryRequired)
145}
146
147fn transaction_fields(bytes: &[u8]) -> Result<usize, LocalError> {
150 if bytes.len() >= 25 && matches!(bytes[24], 0x02 | 0x03) {
151 return Ok(0);
152 }
153 if bytes.len() >= 9 && &bytes[..8] == TRANSACTION_MAGIC {
154 if bytes[8] > 1 {
155 return Err(LocalError::UnsupportedFormat {
156 file: "transaction",
157 version: bytes[8],
158 });
159 }
160 if bytes[8] == 1 && bytes.len() >= 34 {
161 return Ok(9);
162 }
163 }
164 if bytes.len() < 24 {
165 return Err(LocalError::RecoveryRequired);
166 }
167 Ok(0)
168}
169
170#[derive(Clone, Copy, Debug)]
172pub struct WriteTicket(u64);
173
174pub struct LocalReplica<C: Crdt> {
177 config: WriterConfig,
178 fence: File,
179 held: bool,
180 generation: u64,
181 state: C,
182 log: EventLog<C::Delta>,
183 last_sequence: u64,
184 peer_writer_ahead: bool,
185}
186
187struct PendingInsertion<'a, D> {
191 log: &'a mut EventLog<D>,
192 id: RecordId,
193 len: usize,
194 replica_count: Option<usize>,
195 shape_bound: bool,
196 version: Option<crate::VersionVector>,
197}
198impl<'a, D> PendingInsertion<'a, D> {
199 fn new(log: &'a mut EventLog<D>, id: RecordId) -> Self {
200 Self {
201 id,
202 len: log.records.len(),
203 replica_count: log.replica_count,
204 shape_bound: log.shape_bound,
205 version: Some(log.version.clone()),
206 log,
207 }
208 }
209 fn accept(mut self) {
210 self.version = None;
211 }
212}
213impl<D> Drop for PendingInsertion<'_, D> {
214 fn drop(&mut self) {
215 if let Some(version) = self.version.take() {
216 self.log.seen.remove(&self.id);
217 self.log.records.truncate(self.len);
218 self.log.replica_count = self.replica_count;
219 self.log.shape_bound = self.shape_bound;
220 self.log.version = version;
221 }
222 }
223}
224
225impl<C: Crdt> Drop for LocalReplica<C> {
226 fn drop(&mut self) {
227 let _ = self.fence.unlock();
230 }
231}
232
233fn prepare_durable_root(root: &Path, config: WriterConfig) -> Result<(), LocalError> {
237 if root
238 .join(format!("writer-{}.fence", config.writer))
239 .try_exists()?
240 {
241 return Ok(());
242 }
243 create_durable_root(root)
244}
245fn create_durable_root(root: &Path) -> Result<(), LocalError> {
246 create_durable_root_with(root, |parent| File::open(parent)?.sync_all())
247}
248
249fn create_durable_root_with(
250 root: &Path,
251 sync_parent: impl FnMut(&Path) -> io::Result<()>,
252) -> Result<(), LocalError> {
253 create_durable_root_using(root, |directory| fs::create_dir(directory), sync_parent)
254}
255
256fn create_durable_root_using(
257 root: &Path,
258 mut create: impl FnMut(&Path) -> io::Result<()>,
259 mut sync_parent: impl FnMut(&Path) -> io::Result<()>,
260) -> Result<(), LocalError> {
261 let root = std::path::absolute(root)?;
262 let mut missing = Vec::new();
263 let mut ancestor = root.as_path();
264 loop {
265 match fs::metadata(ancestor) {
266 Ok(metadata) if metadata.is_dir() => break,
267 Ok(_) => return Err(io::Error::from(io::ErrorKind::NotADirectory).into()),
268 Err(error) if error.kind() == io::ErrorKind::NotFound => {
269 missing.push(ancestor.to_path_buf());
270 ancestor = ancestor
271 .parent()
272 .ok_or_else(|| io::Error::other("no ancestor"))?;
273 }
274 Err(error) => return Err(error.into()),
275 }
276 }
277 for directory in missing.iter().rev() {
278 match create(directory) {
279 Ok(()) => {}
280 Err(error)
282 if error.kind() == io::ErrorKind::AlreadyExists
283 && fs::metadata(directory)?.is_dir() => {}
284 Err(error) => return Err(error.into()),
285 }
286 }
287 let resolved = root.canonicalize()?;
288 let mut parents: Vec<_> = resolved
289 .ancestors()
290 .skip(1)
291 .map(Path::to_path_buf)
292 .collect();
293 for parent in root.ancestors().skip(1) {
296 let parent = parent
297 .canonicalize()
298 .map_err(|source| LocalError::AncestorSync {
299 path: parent.to_path_buf(),
300 source,
301 })?;
302 if !parents.contains(&parent) {
303 parents.push(parent);
304 }
305 }
306 parents.sort_by_key(|parent| core::cmp::Reverse(parent.components().count()));
307 for parent in parents {
308 sync_parent(&parent).map_err(|source| LocalError::AncestorSync {
309 path: parent,
310 source,
311 })?;
312 }
313 Ok(())
314}
315
316impl<C: Crdt> LocalReplica<C>
317where
318 C::Delta: OwnedDelta + Clone + PartialEq,
319{
320 fn fresh(root: &Path, config: WriterConfig, state: C) -> Result<Self, LocalError> {
321 config.validate().map_err(|_| LocalError::Configuration)?;
322 prepare_durable_root(root, config)?;
323 let mut fence = OpenOptions::new()
324 .read(true)
325 .write(true)
326 .create(true)
327 .truncate(false)
328 .open(root.join(format!("writer-{}.fence", config.writer)))?;
329 let held = match fence.try_lock() {
330 Ok(()) => true,
331 Err(TryLockError::WouldBlock) => false,
332 Err(TryLockError::Error(e)) => return Err(e.into()),
333 };
334 let mut bytes = Vec::new();
335 fence.read_to_end(&mut bytes)?;
336 let generation = if bytes.is_empty() && held {
337 fence.write_all(&config.writers.to_le_bytes())?;
340 fence.write_all(&config.writer.to_le_bytes())?;
341 fence.write_all(&1u64.to_le_bytes())?;
342 fence.sync_all()?;
343 1
344 } else {
345 let at = fence_fields(&bytes)?;
346 let word = |i| u64::from_le_bytes(bytes[at + i..at + i + 8].try_into().unwrap());
347 if word(0) != config.writers || word(8) != config.writer {
348 return Err(LocalError::Configuration);
349 }
350 if held {
351 return Err(LocalError::StoreExists);
352 }
353 word(16)
354 };
355 Ok(Self {
356 config,
357 fence,
358 held,
359 generation,
360 log: EventLog::for_crdt(&state),
361 state,
362 last_sequence: 0,
363 peer_writer_ahead: false,
364 })
365 }
366
367 pub fn state(&self) -> &C {
368 &self.state
369 }
370 pub fn log(&self) -> &EventLog<C::Delta> {
371 &self.log
372 }
373 pub fn ticket(&self) -> WriteTicket {
374 WriteTicket(self.generation)
375 }
376 pub fn allocation_bytes(&self) -> Vec<u8> {
378 [
379 self.config.writers.to_le_bytes(),
380 self.config.writer.to_le_bytes(),
381 self.last_sequence.to_le_bytes(),
382 ]
383 .concat()
384 }
385 fn context(&self, ticket: WriteTicket, local: bool) -> WriteContext {
386 WriteContext {
387 config: self.config,
388 held: self.held,
389 generation: ticket.0,
390 current_generation: self.generation,
391 local,
392 }
393 }
394
395 pub fn renew(&mut self, ticket: WriteTicket) -> Result<WriteTicket, LocalError> {
398 if refuses(
399 self.context(ticket, true),
400 RecordId {
401 replica: self.config.writer,
402 sequence: 1,
403 },
404 OwnedPayload::Remove,
405 ) {
406 return Err(LocalError::Refused);
407 }
408 let next = next_sequence(self.generation).ok_or(LocalError::Exhausted)?;
409 self.held = false;
410 let mut bytes = Vec::new();
411 self.fence.seek(SeekFrom::Start(0))?;
412 self.fence.read_to_end(&mut bytes)?;
413 let at = fence_fields(&bytes)?;
414 self.fence.seek(SeekFrom::Start((at + 16) as u64))?;
415 self.fence.write_all(&next.to_le_bytes())?;
416 self.fence.sync_all()?;
417 self.generation = next;
418 self.held = true;
419 Ok(self.ticket())
420 }
421
422 pub fn receive(
426 &mut self,
427 ticket: WriteTicket,
428 record: Record<C::Delta>,
429 ) -> Result<Admission, LocalError> {
430 self.admit(ticket, record, false)
431 }
432 fn admit(
433 &mut self,
434 ticket: WriteTicket,
435 record: Record<C::Delta>,
436 local: bool,
437 ) -> Result<Admission, LocalError> {
438 self.admit_committed(ticket, record, local, |_, _| Ok(()), |_| Ok(()))
439 }
440 fn admit_committed(
441 &mut self,
442 ticket: WriteTicket,
443 record: Record<C::Delta>,
444 local: bool,
445 commit: impl FnOnce(&EventLog<C::Delta>, u64) -> Result<(), LocalError>,
446 save_alarm: impl FnOnce(&EventLog<C::Delta>) -> Result<(), LocalError>,
447 ) -> Result<Admission, LocalError> {
448 if local && self.peer_writer_ahead {
449 return Err(LocalError::PeerWriterAhead);
450 }
451 if !local
454 && record.id.sequence == 0
455 && self.held
456 && ticket.0 == self.generation
457 && self.generation > 0
458 && record.id.replica < self.config.writers
459 {
460 if let Err(
461 error @ (WireError::ZeroSequenceAdd { .. } | WireError::ZeroSequenceRemove { .. }),
462 ) = self.state.validate_record(record.id, &record.delta)
463 {
464 return Err(LocalError::InvalidRecord(error));
465 }
466 }
467 if refuses(
468 self.context(ticket, local),
469 record.id,
470 record.delta.owned_payload(),
471 ) {
472 return Err(LocalError::Refused);
473 }
474 if !local
477 && record.id.replica == self.config.writer
478 && record.id.sequence > self.last_sequence
479 {
480 self.peer_writer_ahead = true;
481 return Err(LocalError::PeerWriterAhead);
482 }
483 let outcome = self.log.admission(&self.state, &record);
484 if outcome != Admission::Accepted {
485 let prior = self.log.collisions.len();
486 self.log.raise_on_collision(outcome, record);
487 if self.log.collisions.len() != prior {
488 self.held = false;
489 save_alarm(&self.log)?;
490 self.held = true;
491 }
492 return Ok(outcome);
493 }
494 let candidate = PendingInsertion::new(&mut self.log, record.id);
495 let outcome = candidate.log.insert_record(&self.state, record.clone());
496 let sequence = if local {
497 record.id.sequence
498 } else {
499 self.last_sequence
500 };
501 self.held = false;
504 commit(candidate.log, sequence)?;
505 self.state.apply_delta(record.delta);
506 candidate.accept();
507 self.last_sequence = sequence;
508 self.held = true;
509 Ok(outcome)
510 }
511 fn replay_validated(&mut self, record: Record<C::Delta>) -> Result<(), LocalError> {
514 if refuses(
515 self.context(self.ticket(), false),
516 record.id,
517 record.delta.owned_payload(),
518 ) {
519 return Err(LocalError::Refused);
520 }
521 let id = record.id;
522 let outcome = self
523 .log
524 .admit_with(&mut self.state, record, |state, delta| {
525 state.apply_delta(delta.clone())
526 });
527 if outcome != Admission::Accepted {
528 return Err(LocalError::InvalidHistory);
529 }
530 if id.replica == self.config.writer {
531 self.last_sequence = self.last_sequence.max(id.sequence);
532 }
533 Ok(())
534 }
535
536 pub fn append(
539 &mut self,
540 ticket: WriteTicket,
541 delta: C::Delta,
542 ) -> Result<Record<C::Delta>, LocalError> {
543 if self.peer_writer_ahead {
544 return Err(LocalError::PeerWriterAhead);
545 }
546 let sequence = next_sequence(self.last_sequence).ok_or(LocalError::Exhausted)?;
547 let record = Record {
548 id: RecordId {
549 replica: self.config.writer,
550 sequence,
551 },
552 delta,
553 };
554 match self.admit(ticket, record.clone(), true)? {
555 Admission::Accepted => Ok(record),
556 _ => Err(LocalError::Refused),
557 }
558 }
559}
560
561impl LocalReplica<GCounter> {
562 pub fn counter(root: &Path, config: WriterConfig) -> Result<Self, LocalError> {
563 config.validate().map_err(|_| LocalError::Configuration)?;
564 let n = usize::try_from(config.writers).map_err(|_| LocalError::Configuration)?;
565 Self::fresh(
566 root,
567 config,
568 GCounter::try_new(n).map_err(LocalError::CounterWidth)?,
569 )
570 }
571 pub fn bump(
572 &mut self,
573 ticket: WriteTicket,
574 tally: u64,
575 ) -> Result<Record<GCounterDelta>, LocalError> {
576 self.append(
577 ticket,
578 GCounterDelta {
579 replica: self.config.writer as usize,
580 tally,
581 },
582 )
583 }
584}
585impl LocalReplica<OrSet<String, u64>> {
586 pub fn utf8_set(root: &Path, config: WriterConfig) -> Result<Self, LocalError> {
587 Self::fresh(root, config, OrSet::new())
588 }
589 pub fn add(
590 &mut self,
591 ticket: WriteTicket,
592 element: String,
593 ) -> Result<Record<OrSetDelta<String, u64>>, LocalError> {
594 if self.peer_writer_ahead {
595 return Err(LocalError::PeerWriterAhead);
596 }
597 let sequence = next_sequence(self.last_sequence).ok_or(LocalError::Exhausted)?;
598 let token = allocate_token(self.config.writers, self.config.writer, sequence)
599 .ok_or(LocalError::Exhausted)?;
600 self.append(ticket, OrSetDelta::Add { element, token })
601 }
602 pub fn remove(
603 &mut self,
604 ticket: WriteTicket,
605 element: &String,
606 ) -> Result<Record<OrSetDelta<String, u64>>, LocalError> {
607 self.append(
608 ticket,
609 OrSetDelta::Remove {
610 tokens: self.state.observed_tokens(element).into_iter().collect(),
611 },
612 )
613 }
614}
615
616#[path = "persistence.rs"]
617mod persistence;
618
619#[path = "journal.rs"]
620mod journal;
621#[path = "rollback.rs"]
622mod rollback;
623
624#[derive(Debug, PartialEq, Eq)]
632pub struct CommittedTransaction {
633 pub config: WriterConfig,
634 pub last_sequence: u64,
635 pub log_bytes: Vec<u8>,
636}
637impl CommittedTransaction {
638 pub fn read(root: &Path, config: WriterConfig) -> Result<Self, LocalError> {
639 config.validate().map_err(|_| LocalError::Configuration)?;
640 if store_format(root, config.writer)? == Format::Journal {
641 let bytes = fs::read(journal::path(root, config.writer))?;
642 let parsed = journal::parse(&bytes, config)?;
643 let records: Vec<&[u8]> = parsed.entries.iter().map(|&(_, record)| record).collect();
644 return Ok(Self {
645 config,
646 last_sequence: parsed
647 .entries
648 .last()
649 .map_or(parsed.base_sequence, |&(sequence, _)| sequence),
650 log_bytes: journal::assemble(parsed.base_frame, &records)
651 .map_err(LocalError::History)?,
652 });
653 }
654 legacy_transaction(root, config)
655 }
656}
657fn transaction_path(root: &Path, config: WriterConfig) -> PathBuf {
658 root.join(format!("writer-{}.transaction", config.writer))
659}
660
661#[derive(Clone, Copy, Debug, PartialEq, Eq)]
662enum Format {
663 Transaction,
666 Journal,
668}
669
670fn exists(path: &Path) -> Result<bool, LocalError> {
671 match fs::metadata(path) {
672 Ok(_) => Ok(true),
673 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
674 Err(e) => Err(e.into()),
675 }
676}
677
678fn store_format(root: &Path, writer: u64) -> Result<Format, LocalError> {
682 let transaction = exists(&root.join(format!("writer-{writer}.transaction")))?;
683 match (transaction, exists(&journal::path(root, writer))?) {
684 (true, true) => Err(LocalError::RecoveryRequired),
685 (false, true) => Ok(Format::Journal),
686 _ => Ok(Format::Transaction),
687 }
688}
689
690fn encode_error(error: WireError) -> LocalError {
691 LocalError::Io(io::Error::other(format!("{error:?}")))
692}
693
694fn read_prefix(path: &Path, len: u64) -> io::Result<Vec<u8>> {
696 let mut bytes = Vec::new();
697 File::open(path)?.take(len).read_to_end(&mut bytes)?;
698 Ok(bytes)
699}
700
701fn declared_records<D: WireSchema>(
708 path: &Path,
709 config: WriterConfig,
710) -> Result<Option<usize>, LocalError> {
711 let bytes = read_prefix(path, (33 + frame_prefix::<D>()) as u64)?;
712 let at = transaction_fields(&bytes)?;
713 let mut cursor = crate::WireCursor::new(&bytes[at..]);
714 let mut parse = || -> Result<Option<usize>, WireError> {
715 if cursor.read_u64()? != config.writers || cursor.read_u64()? != config.writer {
716 return Ok(None);
717 }
718 cursor.read_u64()?;
719 declared_frame_records::<D>(&mut cursor)
720 };
721 Ok(parse().unwrap_or(None))
722}
723
724fn frame_prefix<D: WireSchema>() -> usize {
726 1 + 8 + 4 + 4 + D::wire_schema().len() + 1 + 8 + 4
727}
728
729fn declared_frame_records<D: WireSchema>(
730 cursor: &mut crate::WireCursor<'_>,
731) -> Result<Option<usize>, WireError> {
732 if cursor.read_u8()? != crate::codec::TAG_EVENT_LOG {
733 return Ok(None);
734 }
735 let body = cursor.read_u32()?;
736 if cursor.read_u32()? != !body || cursor.read_u32()? != u32::MAX {
737 return Ok(None);
738 }
739 let schema_len = cursor.read_len()?;
740 if cursor.read_exact(schema_len)? != D::wire_schema().as_ref() {
741 return Ok(None);
742 }
743 match cursor.read_u8()? {
744 0 if !D::REQUIRES_ARITY => {}
745 1 => {
746 cursor.read_u64()?;
747 }
748 _ => return Ok(None),
749 }
750 cursor.read_len().map(Some)
751}
752
753fn declared_journal_records<D: WireSchema>(
757 root: &Path,
758 config: WriterConfig,
759 max_records: usize,
760) -> Result<Option<usize>, LocalError> {
761 let file = File::open(journal::path(root, config.writer))?;
762 let file_len = file.metadata()?.len();
763 let mut reader = io::BufReader::new(file);
764 let mut header = [0u8; journal::HEADER];
765 if reader.read_exact(&mut header).is_err() {
766 return Ok(None);
767 }
768 let Ok((_, base_len)) = journal::check_header(&header, config) else {
769 return Ok(None);
770 };
771 let prefix_len = frame_prefix::<D>().min(base_len);
772 let mut prefix = alloc::vec![0u8; prefix_len];
773 if reader.read_exact(&mut prefix).is_err() {
774 return Ok(None);
775 }
776 let Ok(Some(base)) = declared_frame_records::<D>(&mut crate::WireCursor::new(&prefix)) else {
777 return Ok(None);
778 };
779 if base > max_records {
780 return Ok(Some(base));
781 }
782 reader.seek_relative((base_len - prefix_len) as i64)?;
783 let entries = journal::count_entries(
784 &mut reader,
785 (journal::HEADER + base_len) as u64,
786 file_len,
787 max_records - base + 1,
788 )?;
789 Ok(Some(base + entries))
790}
791
792pub const SUPPORTED_MAX_RECORDS: usize = crate::DecodeLimits::DEFAULT_MAX_RECORDS;
799
800pub struct DurableReplica<C: Crdt> {
812 inner: LocalReplica<C>,
813 store: Store,
814 root: PathBuf,
815 torn_tail: Option<TornTail>,
816 rollback: Option<rollback::Counter>,
817}
818
819enum Store {
820 Transaction(PathBuf),
821 Journal {
824 file: File,
825 len: u64,
826 },
827}
828
829#[derive(Clone, Copy, Debug, PartialEq, Eq)]
836#[non_exhaustive]
837pub struct TornTail {
838 pub offset: u64,
840 pub discarded_bytes: u64,
842}
843
844struct RestartFence(Option<File>);
848
849impl RestartFence {
850 fn file(&mut self) -> &mut File {
851 self.0.as_mut().unwrap()
852 }
853
854 fn into_file(mut self) -> File {
855 self.0.take().unwrap()
856 }
857}
858
859impl Drop for RestartFence {
860 fn drop(&mut self) {
861 if let Some(file) = &self.0 {
862 let _ = file.unlock();
863 }
864 }
865}
866
867#[cfg(test)]
868std::thread_local! {
869 static RETAIN_RESTART_FENCE: std::cell::RefCell<Option<Option<File>>> = const { std::cell::RefCell::new(None) };
870}
871
872impl<C: Crdt> DurableReplica<C>
873where
874 C::Delta: OwnedDelta + Clone + PartialEq + WireEncode + WireSchema,
875{
876 fn fresh(root: &Path, config: WriterConfig, state: C) -> Result<Self, LocalError> {
877 prepare_durable_root(root, config)?;
878 let root = root.canonicalize()?;
879 let path = journal::path(&root, config.writer);
881 if exists(&transaction_path(&root, config))? || exists(&path)? {
882 return Err(LocalError::StoreExists);
883 }
884 rollback::Counter::ensure_absent(&root, config)?;
887 let mut inner = LocalReplica::fresh(&root, config, state)?;
888 if !inner.held {
889 return Err(LocalError::Refused);
890 }
891 inner.held = false;
892 let header = journal::header(config, 0, &inner.log.to_wire_bytes().map_err(encode_error)?)?;
894 persistence::replace(&path, &header)?;
895 let file = OpenOptions::new().read(true).write(true).open(&path)?;
896 let rollback = rollback::Counter::create(&root, config)?;
897 inner.held = true;
898 Ok(Self {
899 inner,
900 root,
901 store: Store::Journal {
902 file,
903 len: header.len() as u64,
904 },
905 torn_tail: None,
906 rollback: Some(rollback),
907 })
908 }
909 fn commit(
910 path: &Path,
911 config: WriterConfig,
912 log: &EventLog<C::Delta>,
913 sequence: u64,
914 ) -> Result<(), LocalError> {
915 let mut bytes = [
916 config.writers.to_le_bytes(),
917 config.writer.to_le_bytes(),
918 sequence.to_le_bytes(),
919 ]
920 .concat();
921 bytes.extend(log.to_wire_bytes().map_err(encode_error)?);
922 persistence::replace(path, &bytes)?;
923 Ok(())
924 }
925 fn append_entry(
928 file: &File,
929 len: &mut u64,
930 log: &EventLog<C::Delta>,
931 sequence: u64,
932 ) -> Result<(), LocalError> {
933 use std::os::unix::fs::FileExt;
934 let record = log.records().last().ok_or(LocalError::InvalidHistory)?;
935 let bytes = journal::entry(sequence, &record.to_wire_bytes().map_err(encode_error)?)?;
936 persistence::checkpoint(11)?;
937 #[cfg(test)]
939 if persistence::FAULT.with(|fault| fault.get().0) == 12 {
940 file.write_all_at(&bytes[..bytes.len() / 2], *len)?;
941 }
942 persistence::checkpoint(12)?;
943 file.write_all_at(&bytes, *len)?;
944 persistence::checkpoint(13)?;
945 file.sync_data()?;
947 persistence::checkpoint(14)?;
948 *len += bytes.len() as u64;
949 Ok(())
950 }
951 pub fn torn_tail(&self) -> Option<TornTail> {
955 self.torn_tail
956 }
957 pub fn state(&self) -> &C {
958 self.inner.state()
959 }
960 pub fn log(&self) -> &EventLog<C::Delta> {
961 self.inner.log()
962 }
963 pub fn allocation_bytes(&self) -> Vec<u8> {
964 self.inner.allocation_bytes()
965 }
966 pub fn ticket(&self) -> WriteTicket {
967 self.inner.ticket()
968 }
969 pub fn renew(&mut self, ticket: WriteTicket) -> Result<WriteTicket, LocalError> {
970 self.inner.renew(ticket)
971 }
972 fn admit(
973 &mut self,
974 ticket: WriteTicket,
975 record: Record<C::Delta>,
976 local: bool,
977 ) -> Result<Admission, LocalError> {
978 let store = &mut self.store;
979 let rollback = &mut self.rollback;
980 let config = self.inner.config;
981 let alarm_path = self.root.join(format!("writer-{}.alarms", config.writer));
982 let outcome = self.inner.admit_committed(
983 ticket,
984 record,
985 local,
986 |log, sequence| {
987 if local {
988 if let Some(counter) = rollback {
989 counter.advance(sequence)?;
990 }
991 }
992 match store {
993 Store::Transaction(path) => Self::commit(path, config, log, sequence),
994 Store::Journal { file, len } => Self::append_entry(file, len, log, sequence),
995 }
996 },
997 |log| {
998 let bytes = log
999 .collision_report_bytes()
1000 .map_err(encode_error)?
1001 .ok_or(LocalError::InvalidHistory)?;
1002 persistence::replace(&alarm_path, &bytes)?;
1003 Ok(())
1004 },
1005 )?;
1006 #[cfg(test)]
1007 persistence::checkpoint(7)?;
1008 Ok(outcome)
1009 }
1010 pub fn receive(
1011 &mut self,
1012 ticket: WriteTicket,
1013 record: Record<C::Delta>,
1014 ) -> Result<Admission, LocalError> {
1015 self.admit(ticket, record, false)
1016 }
1017 pub fn append(
1018 &mut self,
1019 ticket: WriteTicket,
1020 delta: C::Delta,
1021 ) -> Result<Record<C::Delta>, LocalError> {
1022 if self.inner.peer_writer_ahead {
1023 return Err(LocalError::PeerWriterAhead);
1024 }
1025 let sequence = next_sequence(self.inner.last_sequence).ok_or(LocalError::Exhausted)?;
1026 let record = Record {
1027 id: RecordId {
1028 replica: self.inner.config.writer,
1029 sequence,
1030 },
1031 delta,
1032 };
1033 match self.admit(ticket, record.clone(), true)? {
1034 Admission::Accepted => Ok(record),
1035 _ => Err(LocalError::Refused),
1036 }
1037 }
1038
1039 pub fn append_batch(
1043 &mut self,
1044 ticket: WriteTicket,
1045 deltas: impl IntoIterator<Item = C::Delta>,
1046 ) -> Result<Vec<Record<C::Delta>>, LocalError>
1047 where
1048 C: Clone,
1049 {
1050 let deltas: Vec<_> = deltas.into_iter().collect();
1051 if deltas.is_empty() {
1052 return Ok(Vec::new());
1053 }
1054 if self.inner.peer_writer_ahead {
1055 return Err(LocalError::PeerWriterAhead);
1056 }
1057 let count = u64::try_from(deltas.len()).map_err(|_| LocalError::Exhausted)?;
1058 let last = self
1059 .inner
1060 .last_sequence
1061 .checked_add(count)
1062 .ok_or(LocalError::Exhausted)?;
1063 let first = self.inner.last_sequence + 1;
1064 let mut state = self.inner.state.clone();
1065 let mut log = self.inner.log.clone();
1066 let mut records = Vec::with_capacity(deltas.len());
1067 let mut entries = Vec::with_capacity(deltas.len());
1068 for (index, delta) in deltas.into_iter().enumerate() {
1069 let sequence = first + index as u64;
1070 let record = Record {
1071 id: RecordId {
1072 replica: self.inner.config.writer,
1073 sequence,
1074 },
1075 delta,
1076 };
1077 if refuses(
1078 self.inner.context(ticket, true),
1079 record.id,
1080 record.delta.owned_payload(),
1081 ) {
1082 return Err(LocalError::Refused);
1083 }
1084 if log.insert_record(&state, record.clone()) != Admission::Accepted {
1085 return Err(LocalError::Refused);
1086 }
1087 state.apply_delta(record.delta.clone());
1088 if matches!(self.store, Store::Journal { .. }) {
1089 entries.push(journal::entry(
1090 sequence,
1091 &record.to_wire_bytes().map_err(encode_error)?,
1092 )?);
1093 }
1094 records.push(record);
1095 }
1096 self.inner.held = false;
1098 if let Some(counter) = &mut self.rollback {
1099 counter.advance_batch(first, last)?;
1100 }
1101 match &mut self.store {
1102 Store::Transaction(path) => Self::commit(path, self.inner.config, &log, last)?,
1103 Store::Journal { file, len } => {
1104 use std::os::unix::fs::FileExt;
1105 let mut offset = *len;
1106 for (index, bytes) in entries.iter().enumerate() {
1107 file.write_all_at(bytes, offset)?;
1108 offset += bytes.len() as u64;
1109 if index == 0 {
1110 persistence::checkpoint(21)?;
1111 }
1112 }
1113 persistence::checkpoint(13)?;
1114 file.sync_data()?;
1115 persistence::checkpoint(14)?;
1116 *len = offset;
1117 }
1118 }
1119 self.inner.state = state;
1120 self.inner.log = log;
1121 self.inner.last_sequence = last;
1122 self.inner.held = true;
1123 Ok(records)
1124 }
1125}
1126impl<C: Crdt> DurableReplica<C>
1127where
1128 C::Delta: OwnedDelta + Clone + PartialEq + WireEncode + WireDecode + WireSchema,
1129{
1130 fn restart(root: &Path, config: WriterConfig, state: C) -> Result<Self, LocalError> {
1131 Self::restart_with_limit_origin(root, config, state, crate::DecodeLimits::default(), true)
1132 }
1133
1134 fn restart_with_limits(
1136 root: &Path,
1137 config: WriterConfig,
1138 state: C,
1139 limits: crate::DecodeLimits,
1140 ) -> Result<Self, LocalError> {
1141 Self::restart_with_limit_origin(root, config, state, limits, false)
1142 }
1143
1144 fn restart_with_limit_origin(
1145 root: &Path,
1146 config: WriterConfig,
1147 state: C,
1148 limits: crate::DecodeLimits,
1149 default_limit: bool,
1150 ) -> Result<Self, LocalError> {
1151 config.validate().map_err(|_| LocalError::Configuration)?;
1152 let root = root.canonicalize()?;
1153 let fence = Self::lock_restart_fence(&root, config.writer)?;
1154 Self::restart_locked(&root, fence, config, state, limits, default_limit)
1155 }
1156
1157 fn lock_restart_fence(root: &Path, writer: u64) -> Result<RestartFence, LocalError> {
1158 let fence = OpenOptions::new()
1160 .read(true)
1161 .write(true)
1162 .open(root.join(format!("writer-{writer}.fence")))?;
1163 match fence.try_lock() {
1164 Ok(()) => {}
1165 Err(TryLockError::WouldBlock) => return Err(LocalError::Refused),
1166 Err(TryLockError::Error(e)) => return Err(e.into()),
1167 }
1168 #[cfg(test)]
1169 RETAIN_RESTART_FENCE.with(|slot| {
1170 if let Some(duplicate) = slot.borrow_mut().as_mut() {
1171 *duplicate = Some(fence.try_clone()?);
1172 }
1173 Ok::<(), io::Error>(())
1174 })?;
1175 Ok(RestartFence(Some(fence)))
1176 }
1177
1178 fn restart_locked(
1179 root: &Path,
1180 mut fence: RestartFence,
1181 config: WriterConfig,
1182 state: C,
1183 limits: crate::DecodeLimits,
1184 default_limit: bool,
1185 ) -> Result<Self, LocalError> {
1186 let mut bytes = Vec::new();
1187 fence.file().read_to_end(&mut bytes)?;
1188 let at = fence_fields(&bytes)?;
1189 let word = |i| u64::from_le_bytes(bytes[at + i..at + i + 8].try_into().unwrap());
1190 if word(0) != config.writers || word(8) != config.writer {
1191 return Err(LocalError::Configuration);
1192 }
1193 let generation = word(16);
1194 if generation == 0 {
1195 return Err(LocalError::RecoveryRequired);
1196 }
1197 let max_records = limits.max_records;
1198 let format = store_format(root, config.writer)?;
1199 if let Some(max_records) = max_records {
1202 let declared = match format {
1203 Format::Transaction => {
1204 declared_records::<C::Delta>(&transaction_path(root, config), config)?
1205 }
1206 Format::Journal => declared_journal_records::<C::Delta>(root, config, max_records)?,
1207 };
1208 if declared.is_some_and(|records| records > max_records) {
1209 return Err(LocalError::RecordLimitExceeded {
1210 max_records,
1211 default_limit,
1212 });
1213 }
1214 }
1215 let decode_error = |error| match error {
1216 crate::DecodeError::Wire(error) => LocalError::History(error),
1217 crate::DecodeError::RecordLimitExceeded { max_records }
1218 | crate::DecodeError::DefaultRecordLimitExceeded { max_records } => {
1219 LocalError::RecordLimitExceeded {
1220 max_records,
1221 default_limit,
1222 }
1223 }
1224 };
1225 let max_elements = |stored: usize| {
1231 limits.max_collection_elements.unwrap_or_else(|| {
1232 stored.max(crate::CollectionLimits::WIRE_DEFAULT.max_elements.unwrap())
1233 })
1234 };
1235 let (base, base_sequence, entries, store, torn_tail) = match format {
1237 Format::Transaction => {
1238 let transaction = CommittedTransaction::read(root, config)?;
1239 let log = EventLog::<C::Delta>::from_wire_bytes_for_with_limits(
1240 &transaction.log_bytes,
1241 &state,
1242 crate::DecodeLimits {
1243 max_records,
1244 max_collection_elements: Some(max_elements(transaction.log_bytes.len())),
1245 },
1246 )
1247 .map_err(decode_error)?;
1248 let store = Store::Transaction(transaction_path(root, config));
1249 (log, transaction.last_sequence, Vec::new(), store, None)
1250 }
1251 Format::Journal => {
1252 let mut file = OpenOptions::new()
1253 .read(true)
1254 .write(true)
1255 .open(journal::path(root, config.writer))?;
1256 let mut bytes = Vec::new();
1257 file.read_to_end(&mut bytes)?;
1258 let parsed = journal::parse(&bytes, config)?;
1259 let max_elements = max_elements(bytes.len());
1260 let log = EventLog::<C::Delta>::from_wire_bytes_for_with_limits(
1261 parsed.base_frame,
1262 &state,
1263 crate::DecodeLimits {
1264 max_records,
1265 max_collection_elements: Some(max_elements),
1266 },
1267 )
1268 .map_err(decode_error)?;
1269 if let Some(max_records) = max_records {
1270 if log.records().len() + parsed.entries.len() > max_records {
1271 return Err(LocalError::RecordLimitExceeded {
1272 max_records,
1273 default_limit,
1274 });
1275 }
1276 }
1277 let entries = parsed
1278 .entries
1279 .iter()
1280 .map(|&(sequence, bytes)| {
1281 Record::<C::Delta>::from_wire_bytes_with_collection_limits(
1282 bytes,
1283 crate::CollectionLimits {
1284 max_elements: Some(max_elements),
1285 },
1286 )
1287 .map(|record| (sequence, record))
1288 .map_err(LocalError::History)
1289 })
1290 .collect::<Result<Vec<_>, _>>()?;
1291 let torn_tail = parsed.tail.map(|tail| TornTail {
1292 offset: tail.offset,
1293 discarded_bytes: tail.bytes,
1294 });
1295 let store = Store::Journal {
1296 file,
1297 len: parsed.valid_len,
1298 };
1299 (log, parsed.base_sequence, entries, store, torn_tail)
1300 }
1301 };
1302 let mut inner = LocalReplica {
1303 config,
1304 fence: fence.into_file(),
1305 held: true,
1306 generation,
1307 log: EventLog::for_crdt(&state),
1308 state,
1309 last_sequence: 0,
1310 peer_writer_ahead: false,
1311 };
1312 for record in base.records() {
1315 inner.replay_validated(record.clone())?;
1316 }
1317 if inner.last_sequence != base_sequence {
1318 return Err(LocalError::InvalidHistory);
1319 }
1320 drop(base);
1321 for (sequence, record) in entries {
1322 inner.replay_validated(record)?;
1323 if inner.last_sequence != sequence {
1324 return Err(LocalError::InvalidHistory);
1325 }
1326 }
1327 let rollback = rollback::Counter::open(root, config, inner.last_sequence)?;
1328 let alarm_path = root.join(format!("writer-{}.alarms", config.writer));
1331 match fs::read(alarm_path) {
1332 Ok(bytes) => {
1333 let report =
1334 crate::CollisionReport::<C::Delta>::from_wire_bytes_with_limits(&bytes, limits)
1335 .map_err(decode_error)?;
1336 for (id, verdict) in inner.log.merge_collision_report(report) {
1337 if verdict != crate::CollisionVerdict::Recorded {
1338 let _ = id;
1339 return Err(LocalError::InvalidHistory);
1340 }
1341 }
1342 }
1343 Err(error) if error.kind() == io::ErrorKind::NotFound => {}
1344 Err(error) => return Err(error.into()),
1345 }
1346 if let (Some(tail), Store::Journal { file, .. }) = (torn_tail, &store) {
1349 file.set_len(tail.offset)?;
1350 file.sync_all()?;
1351 }
1352 inner.renew(inner.ticket())?;
1354 Ok(Self {
1355 inner,
1356 root: root.to_path_buf(),
1357 store,
1358 torn_tail,
1359 rollback,
1360 })
1361 }
1362
1363 fn migrate(root: &Path, config: WriterConfig, state: C) -> Result<Self, LocalError> {
1368 config.validate().map_err(|_| LocalError::Configuration)?;
1369 let root = root.canonicalize()?;
1370 let fence = Self::lock_restart_fence(&root, config.writer)?;
1371 let transaction = transaction_path(&root, config);
1372 let path = journal::path(&root, config.writer);
1373 if exists(&transaction)? && exists(&path)? {
1374 let committed = legacy_transaction(&root, config)?;
1377 let bytes = fs::read(&path)?;
1378 let parsed = journal::parse(&bytes, config)?;
1379 let limits = crate::DecodeLimits {
1380 max_records: crate::DecodeLimits::UNLIMITED.max_records,
1382 max_collection_elements: Some(bytes.len().max(4096)),
1383 };
1384 let decode = |frame| {
1385 EventLog::<C::Delta>::from_wire_bytes_for_with_limits(frame, &state, limits)
1386 .map_err(|_| LocalError::RecoveryRequired)
1387 };
1388 if !parsed.entries.is_empty()
1389 || parsed.tail.is_some()
1390 || parsed.base_sequence != committed.last_sequence
1391 || decode(parsed.base_frame)? != decode(&committed.log_bytes)?
1392 {
1393 return Err(LocalError::RecoveryRequired);
1394 }
1395 fs::remove_file(&transaction)?;
1396 File::open(&root)?.sync_all()?;
1397 }
1398 let mut replica =
1399 Self::restart_locked(&root, fence, config, state, Default::default(), true)?;
1400 if let Store::Transaction(_) = replica.store {
1401 let header = journal::header(
1402 config,
1403 replica.inner.last_sequence,
1404 &replica.inner.log.to_wire_bytes().map_err(encode_error)?,
1405 )?;
1406 persistence::replace(&path, &header)?;
1407 persistence::checkpoint(15)?;
1408 fs::remove_file(&transaction)?;
1409 File::open(&root)?.sync_all()?;
1410 let file = OpenOptions::new().read(true).write(true).open(&path)?;
1411 replica.store = Store::Journal {
1412 file,
1413 len: header.len() as u64,
1414 };
1415 }
1416 Ok(replica)
1417 }
1418}
1419
1420fn legacy_transaction(
1421 root: &Path,
1422 config: WriterConfig,
1423) -> Result<CommittedTransaction, LocalError> {
1424 let bytes = fs::read(transaction_path(root, config))?;
1425 let at = transaction_fields(&bytes)?;
1426 if bytes.len() < at + 24 {
1427 return Err(LocalError::RecoveryRequired);
1428 }
1429 let word = |i| u64::from_le_bytes(bytes[at + i..at + i + 8].try_into().unwrap());
1430 if word(0) != config.writers || word(8) != config.writer {
1431 return Err(LocalError::Configuration);
1432 }
1433 Ok(CommittedTransaction {
1434 config,
1435 last_sequence: word(16),
1436 log_bytes: bytes[at + 24..].to_vec(),
1437 })
1438}
1439
1440impl DurableReplica<GCounter> {
1441 pub fn restart_counter(root: &Path, config: WriterConfig) -> Result<Self, LocalError> {
1444 config.validate().map_err(|_| LocalError::Configuration)?;
1445 let n = usize::try_from(config.writers).map_err(|_| LocalError::Configuration)?;
1446 Self::restart(
1447 root,
1448 config,
1449 GCounter::try_new(n).map_err(LocalError::CounterWidth)?,
1450 )
1451 }
1452 pub fn restart_counter_with_limits(
1458 root: &Path,
1459 config: WriterConfig,
1460 limits: crate::DecodeLimits,
1461 ) -> Result<Self, LocalError> {
1462 config.validate().map_err(|_| LocalError::Configuration)?;
1463 let n = usize::try_from(config.writers).map_err(|_| LocalError::Configuration)?;
1464 Self::restart_with_limits(
1465 root,
1466 config,
1467 GCounter::try_new(n).map_err(LocalError::CounterWidth)?,
1468 limits,
1469 )
1470 }
1471 pub fn restart_counter_from_store(root: &Path, writer: u64) -> Result<Self, LocalError> {
1474 Self::restart_counter_from_store_with_origin(
1475 root,
1476 writer,
1477 crate::DecodeLimits::default(),
1478 true,
1479 )
1480 }
1481 pub fn restart_counter_from_store_with_limits(
1484 root: &Path,
1485 writer: u64,
1486 limits: crate::DecodeLimits,
1487 ) -> Result<Self, LocalError> {
1488 Self::restart_counter_from_store_with_origin(root, writer, limits, false)
1489 }
1490
1491 fn restart_counter_from_store_with_origin(
1492 root: &Path,
1493 writer: u64,
1494 limits: crate::DecodeLimits,
1495 default_limit: bool,
1496 ) -> Result<Self, LocalError> {
1497 let root = root.canonicalize()?;
1498 let fence = Self::lock_restart_fence(&root, writer)?;
1499 let (bytes, at) = match store_format(&root, writer)? {
1502 Format::Transaction => {
1503 let bytes = read_prefix(&root.join(format!("writer-{writer}.transaction")), 34)?;
1504 let at = transaction_fields(&bytes)?;
1505 (bytes, at)
1506 }
1507 Format::Journal => {
1508 let bytes = read_prefix(&journal::path(&root, writer), journal::HEADER as u64)?;
1509 journal::check_magic(&bytes)?;
1510 if bytes.len() < journal::HEADER {
1511 return Err(LocalError::RecoveryRequired);
1512 }
1513 (bytes, 8)
1514 }
1515 };
1516 if bytes.len() < at + 24 {
1517 return Err(LocalError::RecoveryRequired);
1518 }
1519 let writers = u64::from_le_bytes(bytes[at..at + 8].try_into().unwrap());
1520 let stored_writer = u64::from_le_bytes(bytes[at + 8..at + 16].try_into().unwrap());
1521 WriterConfig {
1522 writers,
1523 writer: stored_writer,
1524 }
1525 .validate()
1526 .map_err(|_| LocalError::Configuration)?;
1527 let config = WriterConfig { writers, writer };
1528 if stored_writer != writer {
1529 return Err(LocalError::Configuration);
1530 }
1531 let n = usize::try_from(writers).map_err(|_| LocalError::Configuration)?;
1532 Self::restart_locked(
1533 &root,
1534 fence,
1535 config,
1536 GCounter::try_new(n).map_err(LocalError::CounterWidth)?,
1537 limits,
1538 default_limit,
1539 )
1540 }
1541 pub fn migrate_counter_to_append_log(
1549 root: &Path,
1550 config: WriterConfig,
1551 ) -> Result<Self, LocalError> {
1552 config.validate().map_err(|_| LocalError::Configuration)?;
1553 let n = usize::try_from(config.writers).map_err(|_| LocalError::Configuration)?;
1554 Self::migrate(root, config, GCounter::new(n))
1555 }
1556 pub fn counter(root: &Path, config: WriterConfig) -> Result<Self, LocalError> {
1557 config.validate().map_err(|_| LocalError::Configuration)?;
1558 let n = usize::try_from(config.writers).map_err(|_| LocalError::Configuration)?;
1559 Self::fresh(
1560 root,
1561 config,
1562 GCounter::try_new(n).map_err(LocalError::CounterWidth)?,
1563 )
1564 }
1565 pub fn bump(
1566 &mut self,
1567 ticket: WriteTicket,
1568 tally: u64,
1569 ) -> Result<Record<GCounterDelta>, LocalError> {
1570 self.append(
1571 ticket,
1572 GCounterDelta {
1573 replica: self.inner.config.writer as usize,
1574 tally,
1575 },
1576 )
1577 }
1578 pub fn bump_batch(
1581 &mut self,
1582 ticket: WriteTicket,
1583 tallies: impl IntoIterator<Item = u64>,
1584 ) -> Result<Vec<Record<GCounterDelta>>, LocalError> {
1585 let replica = self.inner.config.writer as usize;
1586 self.append_batch(
1587 ticket,
1588 tallies
1589 .into_iter()
1590 .map(|tally| GCounterDelta { replica, tally }),
1591 )
1592 }
1593}
1594impl DurableReplica<OrSet<String, u64>> {
1595 pub fn restart_utf8_set(root: &Path, config: WriterConfig) -> Result<Self, LocalError> {
1598 Self::restart(root, config, OrSet::new())
1599 }
1600 pub fn restart_utf8_set_with_limits(
1607 root: &Path,
1608 config: WriterConfig,
1609 limits: crate::DecodeLimits,
1610 ) -> Result<Self, LocalError> {
1611 Self::restart_with_limits(root, config, OrSet::new(), limits)
1612 }
1613 pub fn restart_utf8_set_with_max_collection_elements(
1616 root: &Path,
1617 config: WriterConfig,
1618 max_elements: usize,
1619 ) -> Result<Self, LocalError> {
1620 Self::restart_with_limits(
1621 root,
1622 config,
1623 OrSet::new(),
1624 crate::DecodeLimits {
1625 max_records: crate::DecodeLimits::default().max_records,
1626 max_collection_elements: Some(max_elements),
1627 },
1628 )
1629 }
1630 pub fn utf8_set(root: &Path, config: WriterConfig) -> Result<Self, LocalError> {
1631 Self::fresh(root, config, OrSet::new())
1632 }
1633 pub fn migrate_utf8_set_to_append_log(
1639 root: &Path,
1640 config: WriterConfig,
1641 ) -> Result<Self, LocalError> {
1642 Self::migrate(root, config, OrSet::new())
1643 }
1644 pub fn add(
1645 &mut self,
1646 ticket: WriteTicket,
1647 element: String,
1648 ) -> Result<Record<OrSetDelta<String, u64>>, LocalError> {
1649 if self.inner.peer_writer_ahead {
1650 return Err(LocalError::PeerWriterAhead);
1651 }
1652 let sequence = next_sequence(self.inner.last_sequence).ok_or(LocalError::Exhausted)?;
1653 let token = allocate_token(
1654 self.inner.config.writers,
1655 self.inner.config.writer,
1656 sequence,
1657 )
1658 .ok_or(LocalError::Exhausted)?;
1659 self.append(ticket, OrSetDelta::Add { element, token })
1660 }
1661 pub fn remove(
1662 &mut self,
1663 ticket: WriteTicket,
1664 element: &String,
1665 ) -> Result<Record<OrSetDelta<String, u64>>, LocalError> {
1666 self.append(
1667 ticket,
1668 OrSetDelta::Remove {
1669 tokens: self
1670 .inner
1671 .state
1672 .observed_tokens(element)
1673 .into_iter()
1674 .collect(),
1675 },
1676 )
1677 }
1678}
1679
1680#[cfg(test)]
1681#[path = "durable_tests.rs"]
1682mod durable_tests;