1use std::sync::{atomic::Ordering, Arc};
4
5use laminar_core::cluster::control::{
6 TopologyAdmissionPhase, TopologyAdmissionStatus, TopologyError, TopologyOperationId,
7 TopologyRestoreInput,
8};
9use uuid::Uuid;
10
11use super::{DbError, DbState, LaminarDB};
12
13#[derive(Clone)]
14pub(crate) struct InstalledTopologyRuntime {
15 pub(crate) input: TopologyRestoreInput,
16 pub(crate) runtime_id: Uuid,
17 pub(crate) shutdown: tokio_util::sync::CancellationToken,
18 pub(crate) released_sequence: Option<u64>,
19 pub(crate) recovery: Option<super::restore::TopologyRecoveryRuntime>,
20}
21
22#[derive(Clone, Copy)]
23enum ActivationAction {
24 Certify,
25 Release,
26 Apply,
27}
28
29impl LaminarDB {
30 pub(crate) async fn refresh_released_topology_assignment(
31 &self,
32 controller: &laminar_core::cluster::control::ClusterController,
33 fence: &laminar_core::checkpoint::CheckpointAssignmentFence,
34 revision: u64,
35 deadline: tokio::time::Instant,
36 ) -> Result<Option<crate::db::AssignmentAuthorityActivation>, DbError> {
37 let Some(binding) = self.installed_topology_runtime.lock().clone() else {
38 return Ok(None);
39 };
40 if self.live_topology_commit().await?.as_ref() != binding.input.operation().commit.as_ref()
42 {
43 return Ok(None);
44 }
45 if binding.recovery.is_some() {
46 if binding.input.assignment() != fence
47 || !self.recovered_topology_runtime_is_active(&binding).await?
48 {
49 return Err(TopologyError::Fenced.into());
50 }
51 let proof = controller
52 .audit_assignment_leader_authority(fence, None, deadline)
53 .await
54 .map_err(TopologyError::Conflict)?;
55 return Ok(Some(
56 self.open_assignment_intake_after_audit(
57 controller,
58 fence,
59 None,
60 proof.owner.node_id,
61 revision,
62 deadline,
63 )
64 .await?,
65 ));
66 }
67 if binding.released_sequence.is_none() {
68 return Ok(None);
69 }
70 let fresh = controller
71 .committed_topology_restore_input(binding.input.operation().operation_id)
72 .await?;
73 Self::require_runtime_release(
74 &binding,
75 &fresh,
76 binding.released_sequence.ok_or(TopologyError::Fenced)?,
77 )?;
78 self.ensure_topology_runtime_live(&fresh, None).await?;
79 if fresh.assignment() != fence
80 || !controller
81 .authorize_topology_release(&fresh, binding.runtime_id)
82 .await?
83 || binding.shutdown.is_cancelled()
84 || DbState::load(&self.state) != DbState::Running
85 || self.is_closed()
86 || self.pending_recovery_fault.load(Ordering::Acquire) != 0
87 || self.cluster_authority_revoked.load(Ordering::Acquire)
88 || self.terminal_pipeline_halt.load(Ordering::Acquire)
89 || self.durable_terminal_recovery_fence.load(Ordering::Acquire)
90 || self.coordinated_recovery_in_progress()
91 || self
92 .owned_source_tasks
93 .lock()
94 .iter()
95 .any(|source| !source.is_running())
96 || self
97 .owned_sink_handles
98 .lock()
99 .iter()
100 .any(|sink| !sink.is_ready())
101 {
102 return Err(TopologyError::Fenced.into());
103 }
104 let leader = fresh.current_leader().ok_or(TopologyError::Fenced)?;
105 Ok(Some(
106 self.open_assignment_intake_after_audit(
107 controller,
108 fence,
109 None,
110 leader.owner.node_id,
111 revision,
112 deadline,
113 )
114 .await?,
115 ))
116 }
117
118 pub async fn certify_installed_cluster_topology(
124 self: &Arc<Self>,
125 operation: TopologyOperationId,
126 ) -> Result<TopologyAdmissionStatus, DbError> {
127 self.run_owned_topology_activation(operation, ActivationAction::Certify)
128 .await
129 }
130
131 pub async fn release_installed_cluster_topology(
138 self: &Arc<Self>,
139 operation: TopologyOperationId,
140 ) -> Result<TopologyAdmissionStatus, DbError> {
141 self.run_owned_topology_activation(operation, ActivationAction::Release)
142 .await
143 }
144
145 pub async fn apply_cluster_topology_release(
151 self: &Arc<Self>,
152 operation: TopologyOperationId,
153 ) -> Result<TopologyAdmissionStatus, DbError> {
154 self.run_owned_topology_activation(operation, ActivationAction::Apply)
155 .await
156 }
157
158 async fn run_owned_topology_activation(
159 self: &Arc<Self>,
160 operation: TopologyOperationId,
161 action: ActivationAction,
162 ) -> Result<TopologyAdmissionStatus, DbError> {
163 let executor = self.control_runtime.handle()?;
164 let permit = Arc::clone(&self.topology_validation_lock)
165 .try_lock_owned()
166 .map_err(|_| TopologyError::Contended)?;
167 let owner = Arc::clone(self);
168 let (result_tx, result_rx) = tokio::sync::oneshot::channel();
169 let task = executor.spawn(async move {
170 let _permit = permit;
171 let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(45);
172 let result = tokio::time::timeout_at(
173 deadline,
174 owner.activate_installed_topology(operation, action, deadline),
175 )
176 .await
177 .map_err(|_| DbError::from(TopologyError::Contended))
178 .and_then(std::convert::identity);
179 let _ = result_tx.send(result);
180 });
181 drop(task);
184 result_rx.await.map_err(|_| DbError::Pipeline("owned topology activation task exited without its result; reread the operation and retry".into()))?
185 }
186
187 async fn activate_installed_topology(
188 &self,
189 operation: TopologyOperationId,
190 action: ActivationAction,
191 deadline: tokio::time::Instant,
192 ) -> Result<TopologyAdmissionStatus, DbError> {
193 let _topology = self.topology_ddl_lock.write().await;
196 let _lifecycle = self.lifecycle_lock.lock().await;
197 let _assignment = self.assignment_adoption_lock.lock().await;
198 let binding = self
199 .installed_topology_runtime
200 .lock()
201 .clone()
202 .ok_or(TopologyError::Fenced)?;
203 if binding.input.operation().operation_id != operation
204 || binding.shutdown.is_cancelled()
205 || DbState::load(&self.state) != DbState::Running
206 {
207 return Err(TopologyError::Fenced.into());
208 }
209 let controller = self
210 .cluster_controller
211 .lock()
212 .clone()
213 .ok_or(TopologyError::Fenced)?;
214 let revision = self.assignment_authority_revision.load(Ordering::Acquire);
215 let fresh = controller
216 .committed_topology_restore_input(operation)
217 .await?;
218 if !fresh.same_installed_generation(&binding.input) {
219 return Err(TopologyError::Fenced.into());
220 }
221 if let Some(sequence) = binding.released_sequence {
222 Self::require_runtime_release(&binding, &fresh, sequence)?;
223 if !controller
224 .authorize_topology_release(&fresh, binding.runtime_id)
225 .await?
226 {
227 return Err(TopologyError::Fenced.into());
228 }
229 self.ensure_released_topology_runtime(&binding, &controller)?;
230 self.ensure_topology_runtime_live(&fresh, None).await?;
231 return Ok(fresh.operation().clone());
232 }
233 self.validate_topology_installation(&fresh).await?;
234 self.ensure_topology_runtime_ready(&fresh).await?;
235 let sinks = self.owned_sink_handles.lock().clone();
236 for sink in &sinks {
237 sink.sync_until(deadline).await.map_err(|error| {
238 DbError::Connector(format!(
239 "topology readiness for sink '{}': {error}",
240 sink.name()
241 ))
242 })?;
243 }
244 let sender = self
245 .shuffle_sender
246 .lock()
247 .clone()
248 .ok_or(TopologyError::Fenced)?;
249 sender
250 .establish_assignment_mesh(fresh.assignment())
251 .await
252 .map_err(|error| {
253 TopologyError::Conflict(format!("target receiver mesh is not ready: {error}"))
254 })?;
255 self.ensure_topology_runtime_ready(&fresh).await?;
256 self.ensure_topology_runtime_held()?;
257 if !matches!(action, ActivationAction::Apply) {
258 controller
259 .certify_topology_installation(&fresh, binding.runtime_id)
260 .await?;
261 }
262 if matches!(action, ActivationAction::Certify) {
263 self.ensure_topology_runtime_ready(&fresh).await?;
264 return Ok(controller
265 .committed_topology_restore_input(operation)
266 .await?
267 .operation()
268 .clone());
269 }
270 let fresh = controller
271 .committed_topology_restore_input(operation)
272 .await?;
273 if matches!(action, ActivationAction::Release) {
274 controller.release_topology_target(&fresh).await?;
275 }
276 let released = controller
277 .committed_topology_restore_input(operation)
278 .await?;
279 let sequence = released
280 .operation()
281 .activation
282 .as_ref()
283 .and_then(|round| round.release.as_ref())
284 .map(|release| release.authority_sequence)
285 .ok_or_else(|| {
286 TopologyError::Conflict(
287 "the exact target runtime has no participant-complete Release".into(),
288 )
289 })?;
290 Self::require_runtime_release(&binding, &released, sequence)?;
291 self.ensure_topology_runtime_ready(&released).await?;
292 {
293 let mut coordinator = self.coordinator.lock().await;
294 let coordinator = coordinator.as_mut().ok_or(TopologyError::Fenced)?;
295 coordinator
296 .reconcile_sink_open_witness_until(deadline)
297 .await?;
298 coordinator
299 .ensure_assignment_sink_epoch_until(deadline)
300 .await?;
301 }
302 let after = controller
303 .committed_topology_restore_input(operation)
304 .await?;
305 Self::require_runtime_release(&binding, &after, sequence)?;
306 if !controller
307 .authorize_topology_release(&after, binding.runtime_id)
308 .await?
309 {
310 return Err(TopologyError::Fenced.into());
311 }
312 self.validate_topology_installation(&after).await?;
313 self.ensure_topology_runtime_ready(&after).await?;
314 {
315 let _transition = self.cluster_authority_transition.lock();
316 self.ensure_topology_runtime_held()?;
317 if binding.shutdown.is_cancelled()
318 || controller.is_recovering()
319 || controller.is_draining()
320 || controller.try_live_local_process_authority_identity().ok()
321 != Some(after.process())
322 || self.assignment_authority_revision.load(Ordering::Acquire) != revision
323 || self
324 .owned_sink_handles
325 .lock()
326 .iter()
327 .any(|sink| !sink.is_ready())
328 || self
329 .owned_source_tasks
330 .lock()
331 .iter()
332 .any(|source| !source.is_running())
333 {
334 return Err(TopologyError::Fenced.into());
335 }
336 let mut installed = self.installed_topology_runtime.lock();
337 let installed = installed
338 .as_mut()
339 .filter(|installed| {
340 installed.runtime_id == binding.runtime_id && !installed.shutdown.is_cancelled()
341 })
342 .ok_or(TopologyError::Fenced)?;
343 installed.released_sequence = Some(sequence);
344 self.topology_cut_hold.store(false, Ordering::Release);
345 self.source_gate.store(false, Ordering::SeqCst);
346 }
347 Ok(after.operation().clone())
348 }
349
350 fn require_runtime_release(
351 binding: &InstalledTopologyRuntime,
352 input: &TopologyRestoreInput,
353 sequence: u64,
354 ) -> Result<(), DbError> {
355 let round = input
356 .operation()
357 .activation
358 .as_ref()
359 .ok_or(TopologyError::Fenced)?;
360 if !input.same_installed_generation(&binding.input)
361 || input.operation().phase != TopologyAdmissionPhase::Active
362 || round.assignment != *input.assignment()
363 || round.processes != input.processes()
364 || !round.installation_complete()
365 || round
366 .release
367 .as_ref()
368 .is_none_or(|release| release.authority_sequence != sequence)
369 || !round.installations.iter().any(|receipt| {
370 receipt.process == input.process() && receipt.runtime_id == binding.runtime_id
371 })
372 {
373 return Err(TopologyError::Fenced.into());
374 }
375 Ok(())
376 }
377
378 fn ensure_released_topology_runtime(
379 &self,
380 binding: &InstalledTopologyRuntime,
381 controller: &laminar_core::cluster::control::ClusterController,
382 ) -> Result<(), DbError> {
383 if self.is_closed()
384 || binding.shutdown.is_cancelled()
385 || DbState::load(&self.state) != DbState::Running
386 || self.source_gate.load(Ordering::Acquire)
387 || self.topology_cut_hold.load(Ordering::Acquire)
388 || self.cluster_authority_revoked.load(Ordering::Acquire)
389 || self.terminal_pipeline_halt.load(Ordering::Acquire)
390 || self.durable_terminal_recovery_fence.load(Ordering::Acquire)
391 || self.pending_recovery_fault.load(Ordering::Acquire) != 0
392 || self.coordinated_recovery_in_progress()
393 || controller.is_recovering()
394 || controller.is_draining()
395 || controller.try_live_local_process_authority_identity().ok()
396 != Some(binding.input.process())
397 {
398 return Err(TopologyError::Fenced.into());
399 }
400 Ok(())
401 }
402}