laminar_core/cluster/control/controller/
barrier.rs1use super::*;
4
5impl ClusterController {
6 pub async fn announce_barrier(&self, ann: &BarrierAnnouncement) -> Result<(), String> {
11 if !self.process_lease_is_live() {
12 return Err("stable node process lease is no longer live".into());
13 }
14 self.barrier.announce(ann).await
15 }
16
17 #[cfg(feature = "cluster")]
22 pub async fn announce_prepare_barrier(
23 &self,
24 ann: &BarrierAnnouncement,
25 quorum_window: Duration,
26 ) -> Result<(), String> {
27 if !self.process_lease_is_live() {
28 return Err("stable node process lease is no longer live".into());
29 }
30 self.barrier.announce_prepare(ann, quorum_window).await
31 }
32
33 #[cfg(feature = "cluster")]
40 pub async fn announce_prepare_barrier_until(
41 &self,
42 ann: &BarrierAnnouncement,
43 attempt_deadline: tokio::time::Instant,
44 retry_window: Duration,
45 ) -> Result<(), String> {
46 if !self.process_lease_is_live() {
47 return Err("stable node process lease is no longer live".into());
48 }
49 self.barrier
50 .announce_prepare_until(ann, attempt_deadline, retry_window)
51 .await
52 }
53
54 pub async fn observe_barrier_matching<F>(
60 &self,
61 mut predicate: F,
62 ) -> Result<Option<BarrierAnnouncement>, String>
63 where
64 F: FnMut(&BarrierAnnouncement) -> bool,
65 {
66 let Some(leader) = self.current_leader() else {
67 return Ok(None);
68 };
69 let Some(announcement) = self.barrier.observe_hint(leader).await? else {
70 return Ok(None);
71 };
72 if !predicate(&announcement) {
73 return Ok(None);
74 }
75 #[cfg(feature = "cluster")]
76 self.barrier.validate_observed(&announcement).await?;
77 Ok(Some(announcement))
78 }
79
80 #[cfg(feature = "cluster")]
85 pub async fn observe_checkpoint_prepare(
86 &self,
87 ) -> Result<Option<CheckpointPrepareObservation>, String> {
88 self.observe_checkpoint_prepare_until(CHECKPOINT_PREPARE_OBSERVATION_TIMEOUT)
89 .await
90 }
91
92 #[cfg(feature = "cluster")]
103 pub async fn observe_checkpoint_prepare_until(
104 &self,
105 checkpoint_timeout: Duration,
106 ) -> Result<Option<CheckpointPrepareObservation>, String> {
107 if checkpoint_timeout.is_zero() {
108 return Err("checkpoint Prepare observation timeout must be greater than zero".into());
109 }
110 let Some(leader) = self.current_leader() else {
111 return Ok(None);
112 };
113 let observation_started = tokio::time::Instant::now();
114 let observation_deadline = observation_started
115 .checked_add(checkpoint_timeout)
116 .ok_or_else(|| "checkpoint Prepare observation deadline overflowed".to_string())?;
117 if tokio::time::Instant::now() >= observation_deadline {
118 return Err("checkpoint Prepare observation deadline already elapsed".into());
119 }
120 let Some(announcement) =
121 tokio::time::timeout_at(observation_deadline, self.barrier.observe_hint(leader))
122 .await
123 .map_err(|_| "checkpoint Prepare hint observation timed out".to_string())??
124 else {
125 return Ok(None);
126 };
127 if announcement.phase != super::super::Phase::Prepare {
128 return Ok(None);
129 }
130 let attempt = crate::checkpoint::CheckpointAttempt::new(
131 announcement.epoch,
132 announcement.checkpoint_id,
133 );
134 if self
135 .barrier
136 .prepare_settlement_covers(attempt.checkpoint_id)
137 {
138 return Ok(None);
139 }
140 let received_at = self
141 .barrier
142 .prepare_received_at_or_insert(&announcement, observation_started.into_std())
143 .ok_or_else(|| "checkpoint Prepare lost its exact observation clock".to_string())?;
144 let attempt_deadline = tokio::time::Instant::from_std(received_at)
145 .checked_add(checkpoint_timeout)
146 .ok_or_else(|| "checkpoint Prepare attempt deadline overflowed".to_string())?;
147 if tokio::time::Instant::now() >= attempt_deadline {
148 return self
149 .ignore_durably_settled_prepare(
150 attempt,
151 observation_deadline,
152 "checkpoint Prepare attempt deadline elapsed before authority validation",
153 )
154 .await;
155 }
156 let validation = tokio::time::timeout_at(
157 attempt_deadline,
158 self.barrier.validate_checkpoint_prepare(&announcement),
159 )
160 .await;
161 let validation_error = match validation {
162 Ok(Ok(())) => None,
163 Ok(Err(error)) => Some(error),
164 Err(_) => Some("checkpoint Prepare authority validation timed out".to_string()),
165 };
166 if let Some(error) = validation_error {
167 return self
168 .ignore_durably_settled_prepare(attempt, observation_deadline, &error)
169 .await;
170 }
171 let proof = announcement
172 .leader_proof
173 .as_ref()
174 .filter(|proof| proof.is_canonical())
175 .ok_or_else(|| "leader Prepare omitted its canonical authority proof".to_string())?;
176 let assignment_error = match announcement.assignment_fence.as_ref() {
177 None => Some(
178 "[LDB-6055] leader Prepare omitted its canonical assignment certificate"
179 .to_string(),
180 ),
181 Some(fence) if !fence.is_canonical() => Some(
182 "[LDB-6055] leader Prepare carried a non-canonical assignment certificate"
183 .to_string(),
184 ),
185 Some(fence) => self
186 .checkpoint_assignment_fence(fence.assignment_version)
187 .and_then(|certified| {
188 self.checkpoint_assignment_fence_after_authority_validation(certified, proof)
189 })
190 .map_or_else(
191 || {
192 Some(format!(
193 "[LDB-6055] follower cannot certify leader Prepare assignment {}",
194 fence.assignment_version
195 ))
196 },
197 |certified| {
198 (certified != *fence).then(|| {
199 format!(
200 "[LDB-6055] follower assignment differs from leader Prepare assignment {}",
201 fence.assignment_version
202 )
203 })
204 },
205 ),
206 };
207 Ok(Some(match assignment_error {
208 Some(error) => CheckpointPrepareObservation::AssignmentRejected {
209 announcement,
210 error,
211 },
212 None => CheckpointPrepareObservation::AssignmentReady(announcement),
213 }))
214 }
215
216 #[cfg(feature = "cluster")]
217 async fn ignore_durably_settled_prepare(
218 &self,
219 attempt: crate::checkpoint::CheckpointAttempt,
220 deadline: tokio::time::Instant,
221 validation_error: &str,
222 ) -> Result<Option<CheckpointPrepareObservation>, String> {
223 if self
224 .barrier
225 .prepare_settlement_covers(attempt.checkpoint_id)
226 {
227 return Ok(None);
228 }
229 if tokio::time::Instant::now() >= deadline {
230 return Err(validation_error.to_owned());
231 }
232 let authority = self.checkpoint_authority().map_err(|error| {
233 format!(
234 "{validation_error}; checkpoint Prepare settlement authority is unavailable: {error}"
235 )
236 })?;
237 let settlement =
238 tokio::time::timeout_at(deadline, authority.cluster_attempt_settlement(attempt))
239 .await
240 .map_err(|_| {
241 format!("{validation_error}; checkpoint Prepare settlement audit timed out")
242 })?
243 .map_err(|error| {
244 format!(
245 "{validation_error}; checkpoint Prepare settlement audit failed: {error}"
246 )
247 })?;
248 let Some(settlement) = settlement else {
249 return Err(validation_error.to_owned());
250 };
251 let settled =
252 crate::checkpoint::CheckpointAttempt::new(settlement.epoch, settlement.checkpoint_id);
253 match settled.relation_to(attempt) {
254 crate::checkpoint::CheckpointAttemptRelation::Exact
255 | crate::checkpoint::CheckpointAttemptRelation::Newer => {
256 self.barrier
257 .record_prepare_settlement(settled.checkpoint_id);
258 Ok(None)
259 }
260 crate::checkpoint::CheckpointAttemptRelation::Older
261 | crate::checkpoint::CheckpointAttemptRelation::Conflict => Err(format!(
262 "{validation_error}; checkpoint Prepare settlement {settled:?} does not close {attempt:?}"
263 )),
264 }
265 }
266
267 #[cfg(feature = "cluster")]
270 #[must_use]
271 pub fn checkpoint_announcement_watch(
272 &self,
273 ) -> Option<watch::Receiver<Option<BarrierAnnouncement>>> {
274 self.barrier.announcement_watch()
275 }
276
277 #[must_use]
279 pub fn checkpoint_prepare_received_at(
280 &self,
281 prepare: &BarrierAnnouncement,
282 ) -> Option<std::time::Instant> {
283 self.barrier.prepare_received_at(prepare)
284 }
285
286 pub async fn ack_barrier(&self, ack: &BarrierAck) -> Result<(), String> {
291 if !self.process_lease_is_live() {
292 return Err("stable node process lease is no longer live".into());
293 }
294 self.barrier.ack(ack).await
295 }
296}