coven_storage/remote/
rotation.rs1use super::*;
2
3pub struct PendingRotation(std::sync::RwLock<Option<RotationGate>>);
4
5#[derive(Debug, thiserror::Error)]
6pub enum RotationStateError {
7 #[error("rotation gate transition failed: {0}")]
8 Gate(#[from] coven_protocol::objects::RotationGateError),
9 #[error("rotation state lock is poisoned")]
10 LockPoisoned,
11}
12
13pub trait CloudSyncRotationStateAccess: Send + Sync {
14 fn mark_candidate(
15 &self,
16 generation: u64,
17 mutation: ObjectHash,
18 ) -> Result<(), RotationStateError>;
19 fn mark_committed_mutation(
20 &self,
21 generation: u64,
22 mutation: ObjectHash,
23 ) -> Result<(), RotationStateError>;
24 fn gate(&self) -> Option<RotationGate>;
25 fn install_durable_gate(&self, gate: Option<RotationGate>);
26 fn check(&self, live_generation: Option<u64>) -> Result<(), RotationPending>;
27}
28
29impl Default for PendingRotation {
30 fn default() -> Self {
31 Self(std::sync::RwLock::new(None))
32 }
33}
34
35impl PendingRotation {
36 pub fn none() -> Self {
37 Self::default()
38 }
39
40 pub fn mark_candidate(
41 &self,
42 generation: u64,
43 mutation: coven_protocol::store_commit::ObjectHash,
44 ) -> Result<(), RotationStateError> {
45 let mut recorded = self
46 .0
47 .write()
48 .map_err(|_| RotationStateError::LockPoisoned)?;
49 *recorded = Some(RotationGate::with_candidate(
50 recorded.clone(),
51 generation,
52 mutation,
53 )?);
54 Ok(())
55 }
56
57 pub fn mark_committed_mutation(
58 &self,
59 generation: u64,
60 mutation: coven_protocol::store_commit::ObjectHash,
61 ) -> Result<(), RotationStateError> {
62 let mut recorded = self
63 .0
64 .write()
65 .map_err(|_| RotationStateError::LockPoisoned)?;
66 *recorded = Some(RotationGate::commit_candidate(
67 recorded.clone(),
68 generation,
69 mutation,
70 )?);
71 Ok(())
72 }
73
74 pub fn gate(&self) -> Option<RotationGate> {
75 self.0.read().unwrap().clone()
76 }
77
78 pub fn install_durable_gate(&self, gate: Option<RotationGate>) {
79 *self.0.write().unwrap() = gate;
80 }
81
82 pub fn check(&self, live_generation: Option<u64>) -> Result<(), RotationPending> {
86 let Some(live_generation) = live_generation else {
87 return Ok(());
88 };
89 if let Some(gate) = self.gate() {
90 return Err(RotationPending {
91 state: gate.pending_state(),
92 live_generation,
93 });
94 }
95 Ok(())
96 }
97
98 #[cfg(any(test, feature = "test-utils"))]
104 pub fn mark_committed(&self, generation: u64) -> Result<(), RotationStateError> {
105 let mut recorded = self
106 .0
107 .write()
108 .map_err(|_| RotationStateError::LockPoisoned)?;
109 *recorded = Some(RotationGate::merge_peer_commit(
110 recorded.clone(),
111 generation,
112 )?);
113 Ok(())
114 }
115
116 #[cfg(any(test, feature = "test-utils"))]
119 pub fn pending_generation(&self) -> Option<u64> {
120 self.0
121 .read()
122 .unwrap()
123 .as_ref()
124 .map(|gate| gate.generation().get())
125 }
126}
127
128impl CloudSyncRotationStateAccess for PendingRotation {
129 fn mark_candidate(
130 &self,
131 generation: u64,
132 mutation: ObjectHash,
133 ) -> Result<(), RotationStateError> {
134 PendingRotation::mark_candidate(self, generation, mutation)
135 }
136
137 fn mark_committed_mutation(
138 &self,
139 generation: u64,
140 mutation: ObjectHash,
141 ) -> Result<(), RotationStateError> {
142 PendingRotation::mark_committed_mutation(self, generation, mutation)
143 }
144
145 fn gate(&self) -> Option<RotationGate> {
146 PendingRotation::gate(self)
147 }
148
149 fn install_durable_gate(&self, gate: Option<RotationGate>) {
150 PendingRotation::install_durable_gate(self, gate);
151 }
152
153 fn check(&self, live_generation: Option<u64>) -> Result<(), RotationPending> {
154 PendingRotation::check(self, live_generation)
155 }
156}