laminar_db/db/topology/
transport.rs1use std::sync::atomic::Ordering;
5use std::sync::Arc;
6
7use laminar_core::cluster::control::{TopologyError, TopologyRestoreInput, TopologyVersion};
8use laminar_core::shuffle::ShuffleTopologyFence;
9
10use super::{DbError, DbState, LaminarDB, PreparedTopologyRestore};
11
12impl LaminarDB {
13 pub(crate) fn install_shuffle_assignment_fence(
16 &self,
17 fence: &laminar_core::checkpoint::CheckpointAssignmentFence,
18 ) -> Result<(), DbError> {
19 self.install_shuffle_assignment_certificate(fence)?;
20 self.bind_replayed_topology_fence().inspect_err(|_| {
21 self.invalidate_shuffle_assignment_fence();
22 })
23 }
24
25 pub(crate) fn bind_replayed_topology_fence(&self) -> Result<(), DbError> {
30 let Some(target) = self
31 .replayed_topology
32 .lock()
33 .and_then(|replayed| replayed.fence)
34 else {
35 return Ok(());
36 };
37 let sender = self.shuffle_sender.lock().clone();
38 let receiver = self.shuffle_receiver.lock().clone();
39 let (Some(sender), Some(receiver)) = (sender, receiver) else {
40 return Ok(());
41 };
42 if sender.topology_fence().is_some()
43 || sender.active_assignment_digest().is_none()
44 || receiver.active_assignment_digest().is_none()
45 {
46 return Ok(());
47 }
48 sender
49 .install_topology_fence_pair(&receiver, None, target)
50 .map_err(|error| TopologyError::Conflict(error.to_string()))?;
51 Ok(())
52 }
53
54 pub(crate) fn bind_shuffle_pipeline_identity(
58 &self,
59 identity: &laminar_core::checkpoint::PipelineIdentity,
60 ) -> Result<(), DbError> {
61 let sender = self.shuffle_sender.lock().clone();
62 let receiver = self.shuffle_receiver.lock().clone();
63 let (Some(sender), Some(receiver)) = (sender, receiver) else {
64 return Ok(());
65 };
66 sender
67 .install_pipeline_identity_pair(&receiver, identity)
68 .map_err(|error| DbError::Pipeline(format!("shuffle pipeline identity: {error}")))?;
69 Ok(())
70 }
71
72 pub(crate) fn shuffle_topology_fence(
75 &self,
76 sender: &laminar_core::shuffle::ShuffleSender,
77 ) -> Option<ShuffleTopologyFence> {
78 sender.topology_fence().or_else(|| {
79 self.replayed_topology
80 .lock()
81 .and_then(|replayed| replayed.fence)
82 })
83 }
84
85 pub async fn prepare_cluster_topology_transport(
98 &self,
99 image: &mut PreparedTopologyRestore,
100 ) -> Result<(), DbError> {
101 if !image.belongs_to(self) || !image.input.is_committed() {
102 return Err(TopologyError::Conflict(
103 "transport preparation requires this DB's committed private image".into(),
104 )
105 .into());
106 }
107 self.prepare_topology_transport_image(image).await
108 }
109
110 pub(crate) async fn prepare_coordinated_topology_transport(
111 &self,
112 image: &mut PreparedTopologyRestore,
113 ) -> Result<(), DbError> {
114 let start = image.recovery_start.as_ref().ok_or(TopologyError::Fenced)?;
115 if !image.belongs_to_recovery(self, start) || !image.is_committed() {
116 return Err(TopologyError::Fenced.into());
117 }
118 self.prepare_topology_transport_image(image).await
119 }
120
121 fn ensure_topology_transport_available(
122 &self,
123 image: &PreparedTopologyRestore,
124 ) -> Result<(), DbError> {
125 if let Some(start) = &image.recovery_start {
126 self.ensure_topology_recovery_stopped(start)
127 } else {
128 self.ensure_topology_restore_available(true)
129 }
130 }
131
132 async fn topology_transport_input(
133 &self,
134 image: &mut PreparedTopologyRestore,
135 ) -> Result<TopologyRestoreInput, DbError> {
136 let controller = self
137 .cluster_controller
138 .lock()
139 .clone()
140 .ok_or(TopologyError::Fenced)?;
141 if let Some(start) = &image.recovery_start {
142 self.ensure_topology_recovery_stopped(start)?;
143 if controller
144 .observe_recover_control()
145 .await
146 .map_err(|error| TopologyError::Conflict(error.to_string()))?
147 .as_ref()
148 != Some(start)
149 {
150 return Err(TopologyError::Fenced.into());
151 }
152 let fresh = controller
153 .topology_recovery_input(&start.round, super::recovery_runtime::start_epoch(start)?)
154 .await?;
155 if image
156 .recovery_input()
157 .is_none_or(|selected| !fresh.same_restore_requirements(selected))
158 {
159 return Err(TopologyError::Fenced.into());
160 }
161 Ok(fresh.migration().clone())
162 } else {
163 Ok(controller
164 .committed_topology_restore_input(image.input.operation().operation_id)
165 .await?)
166 }
167 }
168
169 async fn prepare_topology_transport_image(
170 &self,
171 image: &mut PreparedTopologyRestore,
172 ) -> Result<(), DbError> {
173 tokio::time::timeout(std::time::Duration::from_secs(45), async {
174 self.ensure_topology_transport_available(image)?;
175 let controller = self
176 .cluster_controller
177 .lock()
178 .clone()
179 .ok_or(TopologyError::Fenced)?;
180 let before = self.topology_transport_input(image).await?;
181 if !before.same_restore_requirements(&image.input) {
182 return Err(TopologyError::Fenced.into());
183 }
184 if DbState::load(&self.state) == DbState::ShuttingDown {
185 self.stop_pipeline_for_topology_retirement().await?;
186 }
187 drop(
188 super::restore::prepare_source_positions_at_cut(
189 &image.candidate,
190 &before,
191 image.recovery_input(),
192 )
193 .await?,
194 );
195 let _assignment = self.assignment_adoption_lock.lock().await;
196 let _execution = Arc::clone(&self.rotation_execution_fence)
197 .write_owned()
198 .await;
199 let fresh = self.topology_transport_input(image).await?;
200 if !fresh.same_restore_requirements(&before) {
201 return Err(TopologyError::Fenced.into());
202 }
203 let target = ShuffleTopologyFence::from_manifest(
204 image.target_version(),
205 &fresh.plan().target_manifest,
206 )
207 .map_err(|error| TopologyError::Invalid(error.to_string()))?;
208 let sender = self
209 .shuffle_sender
210 .lock()
211 .clone()
212 .ok_or(TopologyError::Fenced)?;
213 let receiver = self
214 .shuffle_receiver
215 .lock()
216 .clone()
217 .ok_or(TopologyError::Fenced)?;
218 let mut parent = if fresh.plan().expected_parent == TopologyVersion::LEGACY_BASELINE {
219 None
220 } else {
221 Some(
222 ShuffleTopologyFence::from_manifest(
223 fresh.plan().expected_parent,
224 &fresh.plan().parent_manifest,
225 )
226 .map_err(|error| TopologyError::Invalid(error.to_string()))?,
227 )
228 };
229 if DbState::load(&self.state) == DbState::Created && sender.topology_fence().is_none() {
230 parent = None;
231 }
232 if sender.local_id() != fresh.process().participant.node_id
233 || sender.incarnation() != fresh.process().participant.boot_incarnation
234 || sender.active_assignment_digest() != Some(fresh.assignment().digest())
235 || receiver.active_assignment_digest() != Some(fresh.assignment().digest())
236 {
237 return Err(TopologyError::Fenced.into());
238 }
239 {
241 let _transition = self.cluster_authority_transition.lock();
242 self.ensure_topology_transport_available(image)?;
243 if controller.try_live_local_process_authority_identity().ok()
244 != Some(fresh.process())
245 || controller.is_draining()
246 || self
247 .startup_attempt
248 .lock()
249 .as_ref()
250 .is_some_and(|attempt| !attempt.is_complete())
251 || self
252 .runtime_handle
253 .try_lock()
254 .map_err(|_| TopologyError::Fenced)?
255 .is_some()
256 || !self.owned_source_tasks.lock().is_empty()
257 || !self.owned_sink_handles.lock().is_empty()
258 || !self.owned_connector_task_fences.lock().is_empty()
259 {
260 return Err(TopologyError::Fenced.into());
261 }
262 image.graph.bind_cluster_topology_fence(target)?;
263 self.source_gate.store(true, Ordering::Release);
264 self.topology_cut_hold.store(true, Ordering::Release);
265 if let Some(start) = &image.recovery_start {
266 sender.install_topology_fence_pair_for_recovery(
267 &receiver,
268 parent,
269 target,
270 start.round.id.generation,
271 )
272 } else {
273 sender.install_topology_fence_pair(&receiver, parent, target)
274 }
275 .map_err(|error| TopologyError::Conflict(error.to_string()))?;
276 }
277 let after = self.topology_transport_input(image).await?;
278 if !after.same_restore_requirements(&fresh) {
279 return Err(TopologyError::Fenced.into());
280 }
281 self.ensure_topology_transport_available(image)?;
282 Ok(())
283 })
284 .await
285 .map_err(|_| TopologyError::Contended)?
286 }
287}