1use 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 #[error("rotation candidate gate is absent during proven nonactivation")]
12 MissingCandidateDuringNonactivation,
13 #[error("rotation candidate gate is absent during candidate replacement")]
14 MissingCandidateDuringReplacement,
15}
16
17pub trait CloudSyncRotationStateAccess: Send + Sync {
18 fn mark_candidate(
19 &self,
20 generation: u64,
21 mutation: ObjectHash,
22 ) -> Result<(), RotationStateError>;
23 fn mark_committed_mutation(
24 &self,
25 generation: u64,
26 mutation: ObjectHash,
27 ) -> Result<(), RotationStateError>;
28 fn remove_candidate(
29 &self,
30 generation: u64,
31 mutation: ObjectHash,
32 ) -> Result<(), RotationStateError>;
33 fn replace_candidate_mutation(
34 &self,
35 generation: u64,
36 previous: ObjectHash,
37 replacement: ObjectHash,
38 ) -> Result<(), RotationStateError>;
39 fn gate(&self) -> Option<RotationGate>;
40 fn install_durable_gate(&self, gate: Option<RotationGate>);
41 fn check(&self, live_generation: Option<u64>) -> Result<(), RotationPending>;
42}
43
44impl Default for PendingRotation {
45 fn default() -> Self {
46 Self(std::sync::RwLock::new(None))
47 }
48}
49
50impl PendingRotation {
51 pub fn none() -> Self {
52 Self::default()
53 }
54
55 pub fn mark_candidate(
56 &self,
57 generation: u64,
58 mutation: coven_protocol::store_commit::ObjectHash,
59 ) -> Result<(), RotationStateError> {
60 let mut recorded = self
61 .0
62 .write()
63 .map_err(|_| RotationStateError::LockPoisoned)?;
64 *recorded = Some(RotationGate::with_candidate(
65 recorded.clone(),
66 generation,
67 mutation,
68 )?);
69 Ok(())
70 }
71
72 pub fn mark_committed_mutation(
73 &self,
74 generation: u64,
75 mutation: coven_protocol::store_commit::ObjectHash,
76 ) -> Result<(), RotationStateError> {
77 let mut recorded = self
78 .0
79 .write()
80 .map_err(|_| RotationStateError::LockPoisoned)?;
81 *recorded = Some(RotationGate::commit_candidate(
82 recorded.clone(),
83 generation,
84 mutation,
85 )?);
86 Ok(())
87 }
88
89 pub fn remove_candidate(
90 &self,
91 generation: u64,
92 mutation: coven_protocol::store_commit::ObjectHash,
93 ) -> Result<(), RotationStateError> {
94 let mut recorded = self
95 .0
96 .write()
97 .map_err(|_| RotationStateError::LockPoisoned)?;
98 let gate = recorded
99 .clone()
100 .ok_or(RotationStateError::MissingCandidateDuringNonactivation)?;
101 *recorded = gate.remove_candidate(generation, mutation)?;
102 Ok(())
103 }
104
105 pub fn replace_candidate_mutation(
106 &self,
107 generation: u64,
108 previous: coven_protocol::store_commit::ObjectHash,
109 replacement: coven_protocol::store_commit::ObjectHash,
110 ) -> Result<(), RotationStateError> {
111 let mut recorded = self
112 .0
113 .write()
114 .map_err(|_| RotationStateError::LockPoisoned)?;
115 let gate = recorded
116 .clone()
117 .ok_or(RotationStateError::MissingCandidateDuringReplacement)?;
118 *recorded = Some(gate.replace_candidate_mutation(generation, previous, replacement)?);
119 Ok(())
120 }
121
122 pub fn gate(&self) -> Option<RotationGate> {
123 self.0.read().unwrap().clone()
124 }
125
126 pub fn install_durable_gate(&self, gate: Option<RotationGate>) {
127 *self.0.write().unwrap() = gate;
128 }
129
130 pub fn check(&self, live_generation: Option<u64>) -> Result<(), RotationPending> {
134 let Some(live_generation) = live_generation else {
135 return Ok(());
136 };
137 if let Some(gate) = self.gate() {
138 return Err(RotationPending {
139 state: gate.pending_state(),
140 live_generation,
141 });
142 }
143 Ok(())
144 }
145
146 #[cfg(any(test, feature = "test-utils"))]
152 pub fn mark_committed(&self, generation: u64) -> Result<(), RotationStateError> {
153 let mut recorded = self
154 .0
155 .write()
156 .map_err(|_| RotationStateError::LockPoisoned)?;
157 *recorded = Some(RotationGate::merge_peer_commit(
158 recorded.clone(),
159 generation,
160 )?);
161 Ok(())
162 }
163
164 #[cfg(any(test, feature = "test-utils"))]
167 pub fn pending_generation(&self) -> Option<u64> {
168 self.0
169 .read()
170 .unwrap()
171 .as_ref()
172 .map(|gate| gate.generation().get())
173 }
174}
175
176impl CloudSyncRotationStateAccess for PendingRotation {
177 fn mark_candidate(
178 &self,
179 generation: u64,
180 mutation: ObjectHash,
181 ) -> Result<(), RotationStateError> {
182 PendingRotation::mark_candidate(self, generation, mutation)
183 }
184
185 fn mark_committed_mutation(
186 &self,
187 generation: u64,
188 mutation: ObjectHash,
189 ) -> Result<(), RotationStateError> {
190 PendingRotation::mark_committed_mutation(self, generation, mutation)
191 }
192
193 fn remove_candidate(
194 &self,
195 generation: u64,
196 mutation: ObjectHash,
197 ) -> Result<(), RotationStateError> {
198 PendingRotation::remove_candidate(self, generation, mutation)
199 }
200
201 fn replace_candidate_mutation(
202 &self,
203 generation: u64,
204 previous: ObjectHash,
205 replacement: ObjectHash,
206 ) -> Result<(), RotationStateError> {
207 PendingRotation::replace_candidate_mutation(self, generation, previous, replacement)
208 }
209
210 fn gate(&self) -> Option<RotationGate> {
211 PendingRotation::gate(self)
212 }
213
214 fn install_durable_gate(&self, gate: Option<RotationGate>) {
215 PendingRotation::install_durable_gate(self, gate);
216 }
217
218 fn check(&self, live_generation: Option<u64>) -> Result<(), RotationPending> {
219 PendingRotation::check(self, live_generation)
220 }
221}