foundationdb/recipes/ranked_register/mod.rs
1// Copyright 2024 foundationdb-rs developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! # Ranked Register for FoundationDB
9//!
10//! A shared memory abstraction that encapsulates Paxos ballots, based on
11//! Chockler & Malkhi's "Active Disk Paxos with infinitely many processes"
12//! (PODC 2002). A ranked register is a mutable register with conflict detection
13//! via ranks, supporting unbounded processes with finite storage.
14//!
15//! ## Operations
16//!
17//! | Operation | Who | Effect |
18//! |-----------|-----|--------|
19//! | [`read(rank)`](RankedRegister::read) | Leader | Updates max_read_rank (installs fence), returns current value |
20//! | [`write(rank, value)`](RankedRegister::write) | Leader | Commits only if rank is high enough |
21//! | [`value()`](RankedRegister::value) | Followers | Plain read, no fence installed |
22//!
23//! ## Composing with Leader Election
24//!
25//! The ranked register is designed to work with the leader election recipe.
26//! The leader election's ballot serves as the rank for register operations,
27//! providing automatic fencing against stale leaders.
28//!
29//! ```rust,no_run
30//! # fn example() {
31//! use foundationdb::recipes::leader_election::LeaderElection;
32//! use foundationdb::recipes::ranked_register::{RankedRegister, Rank};
33//! use foundationdb::tuple::Subspace;
34//!
35//! let election = LeaderElection::new(Subspace::all().subspace(&"my-election"));
36//! let register = RankedRegister::new(Subspace::all().subspace(&"my-state"));
37//!
38//! // In the leader's main loop:
39//! // db.run(|txn, _| async move {
40//! // let result = election.run_election_cycle(&txn, process_id, priority, now).await?;
41//! // match result {
42//! // ElectionResult::Leader(state) => {
43//! // let rank = Rank::from(state.ballot);
44//! // // Read current state (installs fence at this ballot)
45//! // let current = register.read(&txn, rank).await?;
46//! // // Mutate and write back
47//! // register.write(&txn, rank, b"new_value").await?;
48//! // }
49//! // ElectionResult::Follower(_) => {
50//! // // Safe read — doesn't interfere with leader's writes
51//! // let current = register.value(&txn).await?;
52//! // }
53//! // }
54//! // Ok(())
55//! // }).await?;
56//! # }
57//! ```
58//!
59//! ### Why This Works
60//!
61//! - Leader election's ballot is monotonically increasing
62//! - A deposed leader has a lower ballot than the new leader
63//! - `read(rank)` installs a fence at the ballot value
64//! - Any write with a lower rank is automatically rejected
65//! - `value()` is safe for followers — it never installs a fence
66
67mod algorithm;
68mod errors;
69mod keys;
70mod types;
71
72pub use errors::{RankedRegisterError, Result};
73pub use types::{Rank, ReadResult, RegisterState, WriteResult};
74
75use crate::{tuple::Subspace, Transaction};
76use std::ops::Deref;
77
78/// A ranked register backed by FoundationDB
79///
80/// Provides a mutable register with conflict detection via ranks.
81/// No initialization is needed — an absent key represents the bottom state
82/// (zero ranks, no value).
83///
84/// # Thread Safety
85///
86/// `RankedRegister` is [`Clone`], [`Send`], and [`Sync`]. It holds only a
87/// [`Subspace`] and can be safely shared across tasks.
88#[derive(Clone, Debug)]
89pub struct RankedRegister {
90 subspace: Subspace,
91}
92
93impl RankedRegister {
94 /// Create a new ranked register instance
95 ///
96 /// The subspace isolates this register from other data in the database.
97 /// No initialization step is required — the register starts in the
98 /// bottom state (zero ranks, no value) until the first write.
99 pub fn new(subspace: Subspace) -> Self {
100 Self { subspace }
101 }
102
103 /// Returns a reference to the underlying subspace
104 pub fn subspace(&self) -> &Subspace {
105 &self.subspace
106 }
107
108 /// Perform a ranked read
109 ///
110 /// Updates `max_read_rank` if the given rank is higher, installing a fence
111 /// that prevents lower-ranked writes. Returns the current write rank and value.
112 ///
113 /// Used by the leader before writing to ensure consistency.
114 pub async fn read<T>(&self, txn: &T, rank: Rank) -> Result<ReadResult>
115 where
116 T: Deref<Target = Transaction>,
117 {
118 algorithm::read(txn, &self.subspace, rank).await
119 }
120
121 /// Perform a ranked write
122 ///
123 /// Commits the value only if:
124 /// - `rank >= max_read_rank` (no higher fence)
125 /// - `rank > max_write_rank` (no equal-or-higher write)
126 ///
127 /// Returns [`WriteResult::Committed`] or [`WriteResult::Aborted`].
128 pub async fn write<T>(&self, txn: &T, rank: Rank, value: &[u8]) -> Result<WriteResult>
129 where
130 T: Deref<Target = Transaction>,
131 {
132 algorithm::write(txn, &self.subspace, rank, value).await
133 }
134
135 /// Read the current value without updating ranks
136 ///
137 /// Safe for followers and observers — does not install a fence,
138 /// so it won't interfere with the leader's writes.
139 pub async fn value<T>(&self, txn: &T) -> Result<Option<Vec<u8>>>
140 where
141 T: Deref<Target = Transaction>,
142 {
143 algorithm::value(txn, &self.subspace).await
144 }
145}