laminar_core/cluster/control/controller/
authority.rs1use super::*;
4
5impl ClusterController {
6 #[must_use]
8 pub fn instance_id(&self) -> NodeId {
9 self.instance_id
10 }
11
12 #[must_use]
14 pub fn kv(&self) -> &Arc<dyn ClusterKv> {
15 &self.kv
16 }
17
18 #[must_use]
23 pub fn current_leader(&self) -> Option<NodeId> {
24 let members = self.members_rx.borrow();
25 let participants = self.leadership_participants.read();
26 let unresponsive = self.unresponsive.lock();
27 let is_participant = |node: NodeId| {
28 participants
29 .as_ref()
30 .is_none_or(|(_, roster)| roster.binary_search(&node.0).is_ok())
31 };
32 let mut eligible: Vec<NodeId> = members
33 .iter()
34 .filter(|m| {
35 m.id != self.instance_id
36 && matches!(m.state, NodeState::Active)
37 && !unresponsive.contains_key(&m.id.0)
38 })
39 .map(|m| m.id)
40 .collect();
41 if self.leader_eligible.load(Ordering::SeqCst)
42 && self.process_lease_is_live()
43 && !unresponsive.contains_key(&self.instance_id.0)
44 {
45 eligible.push(self.instance_id);
46 }
47 let certified = eligible
48 .iter()
49 .copied()
50 .filter(|node| is_participant(*node))
51 .collect::<Vec<_>>();
52 if let Some(leader) = leader_of(&certified) {
53 return Some(leader);
54 }
55 #[cfg(feature = "cluster")]
56 if self.has_leader_lease_fencing() {
57 return leader_of(&eligible);
58 }
59 None
60 }
61
62 #[must_use]
65 pub fn is_gossip_leader(&self) -> bool {
66 self.current_leader() == Some(self.instance_id)
67 }
68
69 #[must_use]
72 pub fn is_leader(&self) -> bool {
73 if !self.process_lease_is_live() {
74 return false;
75 }
76 if !self.is_gossip_leader() {
77 return false;
78 }
79 #[cfg(feature = "cluster")]
80 if let Some(gate) = self.leader_lease.get() {
81 let lease = gate.lease.borrow();
82 let deadline = gate.deadline.borrow().clone();
83 return super::super::lease_grants_leadership(&lease, &gate.owner, &deadline);
84 }
85 true
86 }
87
88 #[must_use]
91 #[cfg(feature = "cluster")]
92 pub fn has_leader_lease_fencing(&self) -> bool {
93 self.leader_lease.get().is_some()
94 }
95
96 #[must_use]
103 #[cfg(feature = "cluster")]
104 pub fn leader_fencing_token(&self) -> Option<u64> {
105 self.capture_leader_proof().map(|proof| proof.fencing_token)
106 }
107
108 #[must_use]
114 #[cfg(feature = "cluster")]
115 pub fn capture_leader_proof(&self) -> Option<super::super::LeaderProof> {
116 if !self.process_lease_proof_is_live() || !self.is_gossip_leader() {
117 return None;
118 }
119 let gate = self.leader_lease.get()?;
120 let lease = gate.lease.borrow();
121 let deadline = gate.deadline.borrow().clone();
122 if !super::super::lease_grants_leadership(&lease, &gate.owner, &deadline) {
123 return None;
124 }
125 lease.as_ref().map(super::super::LeaderLease::proof)
126 }
127
128 #[must_use]
130 #[cfg(feature = "cluster")]
131 pub fn proof_is_live(&self, proof: &super::super::LeaderProof) -> bool {
132 if !self.process_lease_proof_is_live() || !self.is_gossip_leader() {
133 return false;
134 }
135 let Some(gate) = self.leader_lease.get() else {
136 return false;
137 };
138 let lease = gate.lease.borrow();
139 let deadline = gate.deadline.borrow().clone();
140 super::super::lease_grants_proof(&lease, &gate.owner, &deadline, proof)
141 }
142
143 #[must_use]
149 #[cfg(feature = "cluster")]
150 pub fn capture_catalog_bootstrap_proof(&self) -> Option<super::super::LeaderProof> {
151 if !self.process_lease_proof_is_live() || !self.is_leader_lease_candidate() {
152 return None;
153 }
154 let gate = self.leader_lease.get()?;
155 let lease = gate.lease.borrow();
156 let deadline = gate.deadline.borrow().clone();
157 if !super::super::lease_grants_leadership(&lease, &gate.owner, &deadline) {
158 return None;
159 }
160 lease.as_ref().map(super::super::LeaderLease::proof)
161 }
162
163 #[must_use]
165 #[cfg(feature = "cluster")]
166 pub fn catalog_bootstrap_proof_is_live(&self, proof: &super::super::LeaderProof) -> bool {
167 if !self.process_lease_proof_is_live() || !self.is_leader_lease_candidate() {
168 return false;
169 }
170 let Some(gate) = self.leader_lease.get() else {
171 return false;
172 };
173 let lease = gate.lease.borrow();
174 let deadline = gate.deadline.borrow().clone();
175 super::super::lease_grants_proof(&lease, &gate.owner, &deadline, proof)
176 }
177
178 #[must_use]
184 #[cfg(feature = "cluster")]
185 pub fn leader_grant_watch(&self) -> Option<watch::Receiver<Option<super::super::LeaderLease>>> {
186 self.leader_lease.get().map(|gate| gate.lease.clone())
187 }
188
189 #[cfg(feature = "cluster")]
190 pub(super) fn process_lease_proof_is_live(&self) -> bool {
191 self.process_lease_live.load(Ordering::Acquire)
192 && self
193 .process_lease_deadline
194 .get()
195 .is_some_and(|deadline| deadline.is_live())
196 }
197
198 #[cfg(feature = "cluster")]
207 pub fn set_leader_lease_watch(
208 &self,
209 lease: watch::Receiver<Option<super::super::LeaderLease>>,
210 owner: super::super::LeaderLeaseOwner,
211 deadline: Arc<super::super::LeaseDeadline>,
212 ) -> Result<(), String> {
213 let (_deadline_tx, deadline) = watch::channel(deadline);
214 self.set_leader_lease_runtime_watches(lease, owner, deadline)
215 }
216
217 #[cfg(feature = "cluster")]
226 pub fn set_leader_lease_runtime_watches(
227 &self,
228 lease: watch::Receiver<Option<super::super::LeaderLease>>,
229 owner: super::super::LeaderLeaseOwner,
230 deadline: watch::Receiver<Arc<super::super::LeaseDeadline>>,
231 ) -> Result<(), String> {
232 if owner.node != self.instance_id || owner.boot.is_nil() || owner.process_term == 0 {
233 return Err("leader lease owner does not match this process".into());
234 }
235 self.leader_lease
236 .set(LeaderLeaseGate {
237 lease,
238 owner,
239 deadline,
240 })
241 .map_err(|_| "leader lease fencing is already installed".into())
242 }
243
244 pub(super) fn notify_leader_eligibility_change(&self) {
245 let eligible = self.is_leader_lease_candidate();
246 self.leader_candidacy.send_if_modified(|published| {
247 let next = published
248 .transition(eligible)
249 .unwrap_or_else(super::super::LeaderCandidacy::terminal);
250 if next == *published {
251 false
252 } else {
253 *published = next;
254 true
255 }
256 });
257 }
258
259 pub(super) fn is_leader_lease_candidate(&self) -> bool {
260 if self.is_gossip_leader() {
261 return true;
262 }
263 !self.active.load(Ordering::Acquire)
264 && !self.is_draining()
265 && self.process_lease_is_live()
266 && !self
267 .members_rx
268 .borrow()
269 .iter()
270 .any(|member| matches!(member.state, NodeState::Active))
271 }
272
273 #[cfg(feature = "cluster")]
275 #[must_use]
276 pub fn leader_candidacy_watch(
277 self: &Arc<Self>,
278 ) -> watch::Receiver<super::super::LeaderCandidacy> {
279 let mut members = self.members_rx.clone();
280 members.borrow_and_update();
281 self.notify_leader_eligibility_change();
282 let candidacy = self.leader_candidacy.clone();
283 let candidate_rx = candidacy.subscribe();
284 let controller = Arc::downgrade(self);
285 tokio::spawn(async move {
286 loop {
287 tokio::select! {
288 biased;
289 () = candidacy.closed() => return,
290 changed = members.changed() => {
291 if changed.is_err() {
292 candidacy.send_replace(super::super::LeaderCandidacy::terminal());
293 return;
294 }
295 }
296 }
297 let Some(controller) = controller.upgrade() else {
298 return;
299 };
300 controller.notify_leader_eligibility_change();
301 }
302 });
303 candidate_rx
304 }
305
306 pub fn set_active(&self, active: bool) {
308 let _transition = self.process_authority_transition.lock();
309 let active = active && self.process_lease_is_live();
310 self.active.store(active, Ordering::SeqCst);
311 let eligible = active && !self.is_draining();
312 if self.leader_eligible.swap(eligible, Ordering::SeqCst) != eligible {
313 self.notify_leader_eligibility_change();
314 }
315 }
316
317 pub fn fence_process_lease(&self) {
319 let _transition = self.process_authority_transition.lock();
320 self.process_lease_live.store(false, Ordering::SeqCst);
321 if let Some(deadline) = self.process_lease_deadline.get() {
322 deadline.fence();
323 }
324 self.active.store(false, Ordering::SeqCst);
325 self.recovering.store(true, Ordering::SeqCst);
326 let roster_changed = self.leadership_participants.write().take().is_some();
327 if self.leader_eligible.swap(false, Ordering::SeqCst) || roster_changed {
328 self.notify_leader_eligibility_change();
329 }
330 self.checkpoint_assignment_fence.send_replace(None);
331 self.checkpoint_drain_transition.send_replace(None);
332 }
333
334 #[must_use]
336 pub fn process_lease_is_live(&self) -> bool {
337 self.process_lease_live.load(Ordering::Acquire)
338 && self
339 .process_lease_deadline
340 .get()
341 .is_none_or(|deadline| deadline.is_live())
342 }
343
344 pub(super) fn recovery_process_lease_is_live(&self) -> bool {
345 self.process_lease_live.load(Ordering::Acquire)
346 && self
347 .process_lease_deadline
348 .get()
349 .is_some_and(|deadline| deadline.is_live())
350 }
351
352 pub async fn wait_for_process_lease_loss(&self) {
354 if !self.process_lease_live.load(Ordering::Acquire) {
355 return;
356 }
357 let Some(deadline) = self.process_lease_deadline.get() else {
358 std::future::pending::<()>().await;
359 return;
360 };
361 deadline.wait_until_expired().await;
362 }
363
364 pub fn set_process_lease_deadline(
373 &self,
374 deadline: Arc<super::super::LeaseDeadline>,
375 ) -> Result<(), String> {
376 let _transition = self.process_authority_transition.lock();
377 if !self.process_lease_live.load(Ordering::Acquire) {
378 return Err("process lease authority is already terminally fenced".into());
379 }
380 if let Some(current) = self.process_lease_deadline.get() {
381 if !Arc::ptr_eq(current, &deadline) {
382 return Err("process lease deadline is already installed".into());
383 }
384 #[cfg(feature = "cluster")]
385 self.barrier.install_process_lease_deadline(deadline)?;
386 return Ok(());
387 }
388 #[cfg(feature = "cluster")]
389 self.barrier
390 .install_process_lease_deadline(Arc::clone(&deadline))?;
391 self.process_lease_deadline
392 .set(deadline)
393 .map_err(|_| "process lease deadline is already installed".to_string())
394 }
395
396 #[must_use]
398 pub fn process_lease_deadline(&self) -> Option<Arc<super::super::LeaseDeadline>> {
399 self.process_lease_deadline.get().cloned()
400 }
401
402 pub fn set_process_lease_authority(
407 &self,
408 authority: Arc<super::super::process_lease::ProcessLeaseAuthority>,
409 ) -> Result<(), String> {
410 self.process_lease_authority
411 .set(authority)
412 .map_err(|_| "process lease authority is already installed".to_string())
413 }
414
415 pub async fn fence_process_incarnation(
420 &self,
421 participant: CheckpointParticipant,
422 deadline: tokio::time::Instant,
423 ) -> Result<super::super::process_lease::ProcessLeaseFence, String> {
424 self.process_lease_authority
425 .get()
426 .ok_or_else(|| "process lease authority is not installed".to_string())?
427 .fence_incarnation(participant, deadline)
428 .await
429 .map_err(|error| error.to_string())
430 }
431
432 pub fn process_fencing_deadline(
438 &self,
439 io_budget: Duration,
440 ) -> Result<tokio::time::Instant, String> {
441 self.process_lease_authority
442 .get()
443 .ok_or_else(|| "process lease authority is not installed".to_string())?
444 .fencing_deadline(io_budget)
445 .map_err(|error| error.to_string())
446 }
447
448 pub async fn verify_current_process_incarnation(
453 &self,
454 participant: CheckpointParticipant,
455 deadline: tokio::time::Instant,
456 ) -> Result<bool, String> {
457 self.process_lease_authority
458 .get()
459 .ok_or_else(|| "process lease authority is not installed".to_string())?
460 .verify_current_participant(participant, deadline)
461 .await
462 .map_err(|error| error.to_string())
463 }
464
465 pub async fn verify_process_lease_fence(
470 &self,
471 fence: &super::super::process_lease::ProcessLeaseFence,
472 deadline: tokio::time::Instant,
473 ) -> Result<bool, String> {
474 self.process_lease_authority
475 .get()
476 .ok_or_else(|| "process lease authority is not installed".to_string())?
477 .verify_fence(fence, deadline)
478 .await
479 .map_err(|error| error.to_string())
480 }
481
482 pub async fn record_assignment_recovery_decision(
492 &self,
493 proof: &super::super::LeaderProof,
494 decision: super::super::AssignmentRecoveryDecision,
495 deadline: tokio::time::Instant,
496 ) -> Result<super::super::RecordAssignmentRecoveryDecisionResult, String> {
497 let process_authority = Arc::clone(
498 self.process_lease_authority
499 .get()
500 .ok_or_else(|| "process lease authority is not installed".to_string())?,
501 );
502 let checks = futures::stream::iter(decision.removed_process_fences.iter().cloned())
503 .map(|fence| {
504 let process_authority = Arc::clone(&process_authority);
505 async move { process_authority.verify_fence(&fence, deadline).await }
506 })
507 .buffer_unordered(CONTROL_ROSTER_IO_CONCURRENCY)
508 .collect::<Vec<_>>()
509 .await;
510 for check in checks {
511 match check {
512 Ok(true) => {}
513 Ok(false) => {
514 return Err("assignment recovery contains an unverified process fence".into());
515 }
516 Err(error) => {
517 return Err(format!(
518 "assignment recovery process fence verification failed: {error}"
519 ));
520 }
521 }
522 }
523 if tokio::time::Instant::now() >= deadline {
524 return Err("assignment recovery authority admission exceeded its deadline".into());
525 }
526 let authority = self
527 .checkpoint_authority()
528 .map_err(|error| error.to_string())?;
529 Box::pin(tokio::time::timeout_at(
530 deadline,
531 authority.record_assignment_recovery_decision(proof, decision),
532 ))
533 .await
534 .map_err(|_| "assignment recovery authority admission exceeded its deadline".to_string())?
535 .map_err(|error| error.to_string())
536 }
537}