coven_replication/sync/sync_loop/
thread.rs1use futures_util::FutureExt;
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::{Arc, Condvar, Mutex};
4use std::time::Duration;
5
6use tracing::{debug, error, info};
7
8use super::{BlockedOperation, SyncCommand, SyncLoopFailure, SyncLoopHandleInner, SyncLoopStatus};
9use crate::sync::loop_policy::{self, LoopWait, SyncLoopReport, SyncLoopSuccess};
10use coven_foundation::stage_timing::StageTimings;
11
12struct RuntimeSlotState {
13 loop_thread: Option<SyncLoopThread>,
14 cancelled: bool,
15}
16
17struct RuntimeSlot {
18 state: Mutex<RuntimeSlotState>,
19 changed: Condvar,
20}
21
22impl RuntimeSlot {
23 fn new() -> Self {
24 Self {
25 state: Mutex::new(RuntimeSlotState {
26 loop_thread: None,
27 cancelled: false,
28 }),
29 changed: Condvar::new(),
30 }
31 }
32
33 fn install(&self, loop_thread: SyncLoopThread) {
34 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
35 state.loop_thread = Some(loop_thread);
36 self.changed.notify_one();
37 }
38
39 fn cancel(&self) {
40 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
41 state.cancelled = true;
42 self.changed.notify_one();
43 }
44
45 fn run(&self, runtime: tokio::runtime::Runtime) {
46 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
47 while state.loop_thread.is_none() && !state.cancelled {
48 state = self
49 .changed
50 .wait(state)
51 .unwrap_or_else(|error| error.into_inner());
52 }
53 let loop_thread = state.loop_thread.take();
54 drop(state);
55 if let Some(loop_thread) = loop_thread {
56 loop_thread.run(runtime);
57 }
58 }
59}
60
61pub struct PreparedSyncLoopRuntime {
68 slot: Arc<RuntimeSlot>,
69 thread_handle: Option<std::thread::JoinHandle<()>>,
70}
71
72impl PreparedSyncLoopRuntime {
73 pub(super) fn prepare() -> Result<Self, super::SyncLoopError> {
74 let slot = Arc::new(RuntimeSlot::new());
75 let thread_slot = Arc::clone(&slot);
76 let (ready_tx, ready_rx) = std::sync::mpsc::sync_channel(1);
77 let thread_handle = std::thread::Builder::new()
78 .name("coven-sync-loop".to_string())
79 .stack_size(8 * 1024 * 1024)
80 .spawn(move || {
81 let runtime = match tokio::runtime::Builder::new_current_thread()
82 .enable_all()
83 .build()
84 {
85 Ok(runtime) => runtime,
86 Err(error) => {
87 let _ = ready_tx.send(Err(Arc::new(error)));
88 return;
89 }
90 };
91 if ready_tx.send(Ok(())).is_err() {
92 return;
93 }
94 thread_slot.run(runtime);
95 })
96 .map_err(super::SyncLoopError::ThreadSpawn)?;
97
98 match ready_rx.recv() {
99 Ok(Ok(())) => Ok(Self {
100 slot,
101 thread_handle: Some(thread_handle),
102 }),
103 Ok(Err(error)) => {
104 let _ = thread_handle.join();
105 Err(super::SyncLoopError::Runtime(error))
106 }
107 Err(_) => {
108 let _ = thread_handle.join();
109 Err(super::SyncLoopError::ThreadPanicked)
110 }
111 }
112 }
113
114 pub(super) fn install(mut self, loop_thread: SyncLoopThread) -> std::thread::JoinHandle<()> {
115 self.slot.install(loop_thread);
116 self.thread_handle
117 .take()
118 .expect("prepared sync runtime owns its thread until installation")
119 }
120}
121
122impl Drop for PreparedSyncLoopRuntime {
123 fn drop(&mut self) {
124 let Some(thread_handle) = self.thread_handle.take() else {
125 return;
126 };
127 self.slot.cancel();
128 let _ = thread_handle.join();
129 }
130}
131
132pub(super) struct SyncLoopThread {
133 inner: Arc<SyncLoopHandleInner>,
134 trigger_rx: tokio::sync::mpsc::Receiver<()>,
135 command_rx: tokio::sync::mpsc::Receiver<SyncCommand>,
136 stop_rx: tokio::sync::watch::Receiver<bool>,
137 eager_cache_cancel_rx: tokio::sync::watch::Receiver<bool>,
138 activate_rx: tokio::sync::watch::Receiver<bool>,
139 status_tx: tokio::sync::watch::Sender<SyncLoopStatus>,
140 eager_cache_status_tx: tokio::sync::watch::Sender<crate::sync::store::EagerCacheFillStatus>,
141 running: Arc<AtomicBool>,
142}
143
144impl SyncLoopThread {
145 pub(super) fn new(
146 inner: Arc<SyncLoopHandleInner>,
147 trigger_rx: tokio::sync::mpsc::Receiver<()>,
148 command_rx: tokio::sync::mpsc::Receiver<SyncCommand>,
149 stop_rx: tokio::sync::watch::Receiver<bool>,
150 eager_cache_cancel_rx: tokio::sync::watch::Receiver<bool>,
151 activate_rx: tokio::sync::watch::Receiver<bool>,
152 status_tx: tokio::sync::watch::Sender<SyncLoopStatus>,
153 eager_cache_status_tx: tokio::sync::watch::Sender<crate::sync::store::EagerCacheFillStatus>,
154 running: Arc<AtomicBool>,
155 ) -> Self {
156 Self {
157 inner,
158 trigger_rx,
159 command_rx,
160 stop_rx,
161 eager_cache_cancel_rx,
162 activate_rx,
163 status_tx,
164 eager_cache_status_tx,
165 running,
166 }
167 }
168
169 fn run(mut self, runtime: tokio::runtime::Runtime) {
170 let _running_guard = RunningGuard {
171 running: Arc::clone(&self.running),
172 };
173 let status_tx = self.status_tx.clone();
174 if runtime
175 .block_on(std::panic::AssertUnwindSafe(self.run_loop()).catch_unwind())
176 .is_err()
177 {
178 let failure = SyncLoopFailure::Panicked;
179 error!("{failure}");
180 status_tx.send_replace(SyncLoopStatus::Failed { error: failure });
181 let cancelled = match self.eager_cache_status_tx.borrow().clone() {
182 crate::sync::store::EagerCacheFillStatus::Scanning => {
183 Some(crate::sync::store::EagerCacheFillStatus::Cancelled(
184 crate::sync::store::EagerCacheFillProgress::empty(),
185 ))
186 }
187 crate::sync::store::EagerCacheFillStatus::Downloading(progress) => Some(
188 crate::sync::store::EagerCacheFillStatus::Cancelled(progress),
189 ),
190 _ => None,
191 };
192 if let Some(cancelled) = cancelled {
193 self.eager_cache_status_tx.send_replace(cancelled);
194 }
195 }
196 }
197
198 async fn run_loop(&mut self) {
199 if !self.wait_for_activation().await {
200 return;
201 }
202
203 let eager_components = Arc::clone(&self.inner);
204 let mut eager_cancel = self.eager_cache_cancel_rx.clone();
205 let cycles_ended = Arc::new(tokio::sync::Notify::new());
208 let eager_cycles_ended = Arc::clone(&cycles_ended);
209 let eager_status = self.eager_cache_status_tx.clone();
210 let eager_fill = async move {
216 loop {
217 if let Err(error) = eager_components
218 .components
219 .fill_eager_cache(eager_cancel.clone(), &eager_status)
220 .await
221 {
222 error!(%error, "post-open eager cache fill failed");
223 return;
224 }
225 if *eager_cancel.borrow() {
226 return;
227 }
228 tokio::select! {
229 () = eager_components.components.eager_fill_wanted().notified() => {}
230 () = eager_cycles_ended.notified() => return,
231 changed = eager_cancel.changed() => {
232 if changed.is_err() || *eager_cancel.borrow() {
233 return;
234 }
235 }
236 }
237 }
238 };
239 tokio::pin!(eager_fill);
240 let cycles = self.run_cycles();
241 tokio::pin!(cycles);
242 tokio::select! {
243 () = &mut eager_fill => cycles.await,
244 () = &mut cycles => {
245 cycles_ended.notify_one();
248 eager_fill.await;
249 }
250 }
251 }
252
253 async fn run_cycles(&mut self) {
254 if !self.wait_for_first_cycle().await {
255 return;
256 }
257
258 let mut consecutive_failures = 0;
259 while self.running.load(Ordering::Acquire) && !*self.stop_rx.borrow() {
260 let mut timings = StageTimings::counting(
263 "sync loop iteration",
264 self.inner.components.provider_requests(),
265 );
266 self.status_tx.send_replace(SyncLoopStatus::CheckingStorage);
267 let reachable = timings
268 .stage("probe storage", self.inner.components.probe_storage())
269 .await;
270 let (decision, status) = match reachable {
271 Err(error) => {
272 let error = Arc::new(error);
273 let status = storage_check_failure_status(Arc::clone(&error));
274 let failure = SyncLoopFailure::Storage(error);
275 (
276 loop_policy::after_failure(failure, consecutive_failures, 300),
277 status,
278 )
279 }
280 Ok(_) => {
281 self.status_tx.send_replace(SyncLoopStatus::Publishing);
282 self.run_reachable_cycle(consecutive_failures, &mut timings)
283 .await
284 }
285 };
286 timings.report();
287 consecutive_failures = decision.consecutive_failures;
288 self.status_tx.send_replace(status);
289 if !self
290 .wait_for_next_cycle(decision.wait, consecutive_failures)
291 .await
292 {
293 break;
294 }
295 }
296 }
297
298 async fn wait_for_activation(&mut self) -> bool {
299 loop {
300 if *self.activate_rx.borrow() {
301 return true;
302 }
303 tokio::select! {
304 changed = self.activate_rx.changed() => {
305 if changed.is_err() {
306 return false;
307 }
308 }
309 changed = self.stop_rx.changed() => {
310 if changed.is_err() || *self.stop_rx.borrow() {
311 info!("Prepared sync loop stopped before activation");
312 return false;
313 }
314 }
315 }
316 }
317 }
318
319 async fn wait_for_first_cycle(&mut self) -> bool {
320 let startup_delay = tokio::time::sleep(Duration::from_secs(3));
321 tokio::pin!(startup_delay);
322 loop {
323 tokio::select! {
324 _ = &mut startup_delay => return true,
325 changed = self.stop_rx.changed() => {
326 if changed.is_err() || *self.stop_rx.borrow() {
327 info!("Sync loop stopped before first cycle");
328 return false;
329 }
330 }
331 message = self.trigger_rx.recv() => {
332 if message.is_none() {
333 info!("Sync trigger channel closed before first cycle");
334 return false;
335 }
336 return true;
337 }
338 command = self.command_rx.recv() => {
339 let Some(command) = command else {
340 info!("Sync command channel closed before first cycle");
341 return false;
342 };
343 self.inner.execute_command(command).await;
344 }
345 }
346 }
347 }
348
349 async fn run_reachable_cycle(
350 &self,
351 consecutive_failures: u32,
352 timings: &mut StageTimings,
353 ) -> (loop_policy::SyncLoopDecision, SyncLoopStatus) {
354 let (decision, cycle_went_offline) =
355 match timings.stage("cycle", self.inner.run_single_cycle()).await {
356 Ok(result) => (loop_policy::after_success(result), false),
357 Err(error) => {
358 let offline = error.is_offline();
359 let failure = SyncLoopFailure::Cycle(Arc::new(error));
360 (
361 loop_policy::after_failure(failure, consecutive_failures, 300),
362 offline,
363 )
364 }
365 };
366 let status = match &decision.report {
367 SyncLoopReport::Success(success) => {
368 match timings
369 .stage(
370 "read blocked operations",
371 self.inner.components.blocked_operations(),
372 )
373 .await
374 {
375 Ok(operations) => current_success_status(operations, success.clone()),
376 Err(error) => SyncLoopStatus::Failed {
377 error: SyncLoopFailure::BlockedOperations(Arc::new(error)),
378 },
379 }
380 }
381 SyncLoopReport::Failure(_) if cycle_went_offline => SyncLoopStatus::Offline,
382 SyncLoopReport::Failure(error) => SyncLoopStatus::Failed {
383 error: error.clone(),
384 },
385 };
386 (decision, status)
387 }
388
389 async fn wait_for_next_cycle(&mut self, wait: LoopWait, consecutive_failures: u32) -> bool {
390 let duration = match wait {
391 LoopWait::Immediate => Duration::ZERO,
392 LoopWait::Idle => Duration::from_secs(crate::sync::backoff::backoff_secs(0, 300)),
393 LoopWait::BackoffSecs(secs) => Duration::from_secs(secs),
394 };
395 if matches!(wait, LoopWait::BackoffSecs(_)) {
396 debug!("Backing off {duration:?} after {consecutive_failures} consecutive failure(s)");
397 }
398 tokio::select! {
399 _ = tokio::time::sleep(duration) => true,
400 changed = self.stop_rx.changed() => {
401 if changed.is_err() || *self.stop_rx.borrow() {
402 info!("Sync loop stop requested");
403 false
404 } else {
405 true
406 }
407 }
408 message = self.trigger_rx.recv() => {
409 if message.is_none() {
410 info!("Sync trigger channel closed, stopping sync loop");
411 false
412 } else {
413 true
414 }
415 }
416 command = self.command_rx.recv() => {
417 let Some(command) = command else {
418 info!("Sync command channel closed, stopping sync loop");
419 return false;
420 };
421 self.inner.execute_command(command).await;
422 true
423 }
424 }
425 }
426}
427
428pub(super) fn storage_check_failure_status(
429 error: Arc<coven_protocol::objects::StorageError>,
430) -> SyncLoopStatus {
431 if error.is_transport() {
432 SyncLoopStatus::Offline
433 } else {
434 SyncLoopStatus::Failed {
435 error: SyncLoopFailure::Storage(error),
436 }
437 }
438}
439
440pub(crate) fn current_success_status(
443 operations: Vec<BlockedOperation>,
444 success: SyncLoopSuccess,
445) -> SyncLoopStatus {
446 if operations.is_empty() {
447 SyncLoopStatus::Synchronized(success)
448 } else {
449 SyncLoopStatus::Blocked {
450 success,
451 operations,
452 }
453 }
454}
455
456struct RunningGuard {
457 running: Arc<AtomicBool>,
458}
459
460impl Drop for RunningGuard {
461 fn drop(&mut self) {
462 self.running.store(false, Ordering::Release);
463 }
464}