1#[path = "file_cache.rs"]
4mod file_cache;
5#[path = "file_cache_retry.rs"]
6mod file_cache_retry;
7#[path = "file_device_only.rs"]
8mod file_device_only;
9#[path = "file_error.rs"]
10mod file_error;
11#[path = "file_failure.rs"]
12mod file_failure;
13#[path = "file_keep.rs"]
14mod file_keep;
15#[path = "file_read.rs"]
16mod file_read;
17#[path = "file_status.rs"]
18mod file_status;
19#[path = "file_upload.rs"]
20mod file_upload;
21#[path = "file_upload_abandoned.rs"]
22mod file_upload_abandoned;
23#[path = "file_upload_connections.rs"]
24mod file_upload_connections;
25#[path = "file_upload_failure.rs"]
26mod file_upload_failure;
27#[path = "file_upload_pool.rs"]
28mod file_upload_pool;
29#[path = "file_upload_queue.rs"]
30mod file_upload_queue;
31#[path = "file_upload_retry.rs"]
32mod file_upload_retry;
33#[path = "file_upload_settings.rs"]
34mod file_upload_settings;
35use crate::SyncError;
36pub use coven_database::PinProgress;
37use coven_database::{DbError, FileDatabase, Mode, ReadOnly, Writer};
38use coven_foundation::locks::{lock_unpoisoned, write_unpoisoned};
39use coven_foundation::{files::StoreDir, id_source::IdSourceRef};
40use coven_storage::Storage;
41pub use file_device_only::{DeviceOnlyError, DeviceOnlyStep};
42pub use file_error::FileReadError;
43pub use file_read::{FileRangeStream, FileStream, ReadProgress};
44pub use file_status::FileStatus;
45pub(crate) use file_upload_connections::SpareConnection;
46pub use file_upload_pool::DrainOutcome;
47pub use file_upload_queue::{QueuedUpload, UploadPhase, UploadProgress, UploadQueue};
48pub use file_upload_settings::UploadSettings;
49use std::{
50 collections::{BTreeMap, BTreeSet},
51 sync::{
52 atomic::{AtomicBool, AtomicUsize, Ordering},
53 Arc, Mutex,
54 },
55};
56use tokio::sync::{watch, Notify};
57
58use coven_storage::TransferBudget;
59pub use coven_storage::TransferLimits;
60
61pub trait FilesMode: Mode {
65 type Uploads: Send + Sync + 'static;
67}
68impl FilesMode for Writer {
69 type Uploads = UploadSettings;
70}
71impl FilesMode for ReadOnly {
72 type Uploads = ();
73}
74
75pub struct Files<M: FilesMode = Writer> {
81 inner: Arc<FilesInner<M>>,
82 worker: Arc<FileWorker>,
83}
84impl<M: FilesMode> Clone for Files<M> {
86 fn clone(&self) -> Self {
87 Self { inner: self.inner.clone(), worker: self.worker.clone() }
88 }
89}
90type FileReadLocks = BTreeMap<
92 (String, coven_foundation::id_source::ObjectId),
93 std::sync::Weak<tokio::sync::Mutex<()>>,
94>;
95struct FilesInner<M: FilesMode = Writer> {
96 database: FileDatabase<M>,
97 directory: StoreDir,
98 storage: std::sync::RwLock<Option<Arc<dyn Storage>>>,
99 send: Arc<crate::SequenceState>,
100 waits: Arc<crate::RetryWaits>,
101 connection_waits: Mutex<BTreeSet<coven_database::RetrySubject>>,
104 ids: IdSourceRef,
105 clock: coven_foundation::clock::ClockRef,
107 uploads: M::Uploads,
110 unmetered_only: AtomicBool,
114 network: coven_foundation::network_watch::NetworkView,
116 hold_wake: Notify,
119 #[cfg(test)]
121 pub(crate) held_downloads: tokio::sync::watch::Sender<usize>,
122 #[cfg(test)]
125 pub(crate) shared_reads: tokio::sync::watch::Sender<usize>,
126 upload_wake: Arc<Notify>,
128 upload_parks: crate::Parks,
131 drain: tokio::sync::Mutex<()>,
133 app_retries: Mutex<Vec<file_upload_pool::AppRetry>>,
135 worker_ended: std::sync::OnceLock<file_upload_pool::WorkerEnd>,
139 changing: AtomicUsize,
142 cache: tokio::sync::RwLock<()>,
143 reads: Mutex<FileReadLocks>,
144 transfers: Arc<TransferBudget>,
145 pins: tokio::sync::Mutex<()>,
146 closed: AtomicBool,
147 #[cfg(test)]
151 blocked_end_gate: Mutex<Option<Arc<dyn Fn() + Send + Sync>>>,
152 #[cfg(test)]
157 device_only_gate: Mutex<Option<file_device_only::DeviceOnlyGate>>,
158}
159struct FileWorker {
160 stop: watch::Sender<bool>,
161 task: tokio::sync::Mutex<Option<coven_foundation::task::OwnedTask>>,
164}
165impl Drop for FileWorker {
166 fn drop(&mut self) {
167 self.stop.send_replace(true);
168 }
169}
170impl Files<ReadOnly> {
171 pub fn read_only(
174 database: FileDatabase<ReadOnly>,
175 directory: StoreDir,
176 storage: Option<Arc<dyn Storage>>,
177 send: Arc<crate::SequenceState>,
178 waits: Arc<crate::RetryWaits>,
179 ids: IdSourceRef,
180 transfers: Arc<TransferBudget>,
181 clock: coven_foundation::clock::ClockRef,
182 network: coven_foundation::network_watch::NetworkView,
183 ) -> Self {
184 let inner = Arc::new(FilesInner::new(
185 database,
186 directory,
187 storage,
188 send,
189 waits,
190 ids,
191 transfers,
192 clock,
193 network,
194 (),
195 ));
196 let worker =
197 FileWorker { stop: watch::channel(false).0, task: tokio::sync::Mutex::new(None) };
198 Self { inner, worker: Arc::new(worker) }
199 }
200}
201impl<M: FilesMode> FilesInner<M> {
202 fn new(
203 database: FileDatabase<M>,
204 directory: StoreDir,
205 storage: Option<Arc<dyn Storage>>,
206 send: Arc<crate::SequenceState>,
207 waits: Arc<crate::RetryWaits>,
208 ids: IdSourceRef,
209 transfers: Arc<TransferBudget>,
210 clock: coven_foundation::clock::ClockRef,
211 network: coven_foundation::network_watch::NetworkView,
212 uploads: M::Uploads,
213 ) -> Self {
214 Self {
215 database,
216 directory,
217 storage: std::sync::RwLock::new(storage),
218 send,
219 waits,
220 connection_waits: Mutex::new(BTreeSet::new()),
221 ids,
222 clock,
223 uploads,
224 unmetered_only: AtomicBool::new(false),
225 network,
226 hold_wake: Notify::new(),
227 #[cfg(test)]
228 held_downloads: tokio::sync::watch::channel(0).0,
229 #[cfg(test)]
230 shared_reads: tokio::sync::watch::channel(0).0,
231 upload_wake: Arc::new(Notify::new()),
232 upload_parks: crate::Parks::new(),
233 drain: tokio::sync::Mutex::new(()),
234 app_retries: Mutex::new(Vec::new()),
235 worker_ended: std::sync::OnceLock::new(),
236 changing: AtomicUsize::new(0),
237 cache: tokio::sync::RwLock::new(()),
238 reads: Mutex::new(BTreeMap::new()),
239 transfers,
240 pins: tokio::sync::Mutex::new(()),
241 closed: AtomicBool::new(false),
242 #[cfg(test)]
243 blocked_end_gate: Mutex::new(None),
244 #[cfg(test)]
245 device_only_gate: Mutex::new(None),
246 }
247 }
248}
249impl<M: FilesMode> Files<M> {
250 pub async fn close(&self) {
255 self.inner.closed.store(true, Ordering::Release);
256 self.inner.hold_wake.notify_waiters();
258 self.worker.stop.send_replace(true);
259 if let Some(task) = self.worker.task.lock().await.take() {
260 task.ended().await;
261 }
262 let _drain = self.inner.drain.lock().await;
263 let _pins = self.inner.pins.lock().await;
264 let _cache = self.inner.cache.write().await;
265 lock_unpoisoned(&self.inner.app_retries).clear();
266 write_unpoisoned(&self.inner.storage).take();
267 }
268}
269impl Files {
270 #[cfg(any(test, feature = "test-utils"))]
273 pub fn uploads_parked_from_now(
274 &self,
275 ) -> impl std::future::Future<Output = ()> + Send + 'static {
276 self.inner.upload_parks.parked_from_now()
277 }
278
279 pub fn new(
282 database: FileDatabase,
283 directory: StoreDir,
284 storage: Option<Arc<dyn Storage>>,
285 send: Arc<crate::SequenceState>,
286 waits: Arc<crate::RetryWaits>,
287 ids: IdSourceRef,
288 transfers: Arc<TransferBudget>,
289 clock: coven_foundation::clock::ClockRef,
290 network: coven_foundation::network_watch::NetworkView,
291 ) -> Self {
292 let uploads_changes = database.queued_work_changes();
293 let (stop, _) = watch::channel(false);
294 let inner = Arc::new(FilesInner::new(
295 database,
296 directory,
297 storage,
298 send,
299 waits,
300 ids,
301 transfers,
302 clock,
303 network,
304 UploadSettings::new(),
305 ));
306 let owner = Arc::downgrade(&inner);
310 let task = coven_foundation::task::spawn_owned(
311 "the upload worker",
312 inner.database.bugs().clone(),
313 upload_worker(Arc::downgrade(&inner), stop.subscribe(), uploads_changes),
314 |bug| async move {
315 if let Some(owner) = owner.upgrade() {
316 if let Err(error) = owner.waits.sync_halted(bug.clone()).await {
319 let error = crate::SyncError::from(error);
320 if !coven_foundation::task::report_carried(
321 owner.database.bugs().as_ref(),
322 &error,
323 ) {
324 tracing::debug!(
325 "halting sync after the upload worker's panic failed: {error}"
326 );
327 }
328 }
329 owner.worker_ended(file_upload_pool::WorkerEnd::Panicked(bug));
330 }
331 },
332 );
333 let worker = FileWorker { stop, task: tokio::sync::Mutex::new(Some(task)) };
334 Self { inner, worker: Arc::new(worker) }
335 }
336 pub fn transfer_limits(&self) -> TransferLimits {
338 self.inner.transfers.limits()
339 }
340 pub fn set_transfer_limits(&self, limits: TransferLimits) {
344 self.inner.transfers.set_limits(limits);
345 self.inner.upload_wake.notify_one();
346 }
347 pub async fn retry_uploads_now(&self) -> Result<DrainOutcome, SyncError> {
353 self.inner.ask_retry()?.answered().await
356 }
357 pub fn set_uploads_paused(
365 &self,
366 sql: &coven_database::SqlContext<'_, '_>,
367 paused: bool,
368 ) -> Result<(), DbError> {
369 self.inner.check_open()?;
370 sql.set_uploads_paused(paused)?;
371 let inner = self.inner.clone();
372 sql.after_commit(move || {
373 let change = inner.hold_change_blocking();
376 inner.set_paused(paused);
377 drop(change);
378 if !paused {
379 inner.upload_wake.notify_one();
380 }
381 });
382 Ok(())
383 }
384 pub fn set_unmetered_only(
391 &self,
392 sql: &coven_database::SqlContext<'_, '_>,
393 unmetered_only: bool,
394 ) -> Result<(), DbError> {
395 self.inner.check_open()?;
396 sql.set_unmetered_only(unmetered_only)?;
397 let inner = self.inner.clone();
398 sql.after_commit(move || {
399 let change = inner.hold_change_blocking();
400 inner.unmetered_only.store(unmetered_only, Ordering::Release);
401 drop(change);
402 if !unmetered_only {
403 inner.upload_wake.notify_one();
404 inner.hold_wake.notify_waiters();
405 }
406 });
407 Ok(())
408 }
409
410 pub async fn set_storage(
414 &self,
415 storage: Option<Arc<dyn Storage>>,
416 ready: impl std::future::Future<Output = Result<(), SyncError>>,
417 ) -> Result<(), SyncError> {
418 let _changing = ConnectionChange::start(&self.inner.changing, &self.inner.upload_wake);
419 let _drain = self.inner.drain.lock().await;
420 let _pins = self.inner.pins.lock().await;
421 let _cache = self.inner.cache.write().await;
422 self.inner.check_open()?;
423 ready.await?;
424 *write_unpoisoned(&self.inner.storage) = storage;
425 lock_unpoisoned(&self.inner.connection_waits).clear();
426 Ok(())
427 }
428}
429
430struct ConnectionChange<'a> {
434 changing: &'a AtomicUsize,
435 wake: &'a Notify,
436}
437impl<'a> ConnectionChange<'a> {
438 fn start(changing: &'a AtomicUsize, wake: &'a Notify) -> Self {
439 changing.fetch_add(1, Ordering::AcqRel);
440 wake.notify_one();
441 Self { changing, wake }
442 }
443}
444impl Drop for ConnectionChange<'_> {
445 fn drop(&mut self) {
446 self.changing.fetch_sub(1, Ordering::AcqRel);
447 self.wake.notify_one();
448 }
449}
450
451async fn upload_worker(
457 owner: std::sync::Weak<FilesInner>,
458 stopping: watch::Receiver<bool>,
459 changes: coven_database::DatabaseChanges,
460) {
461 let ended = serve_uploads(owner.clone(), stopping, changes).await;
462 if let (Some(ended), Some(owner)) = (ended, owner.upgrade()) {
465 owner.worker_ended(file_upload_pool::WorkerEnd::of(ended));
466 }
467}
468
469async fn serve_uploads(
473 owner: std::sync::Weak<FilesInner>,
474 mut stopping: watch::Receiver<bool>,
475 mut changes: coven_database::DatabaseChanges,
476) -> Option<SyncError> {
477 let first = owner.upgrade()?;
478 let parks = first.upload_parks.clone();
479 let _running = parks.running();
480 let wake = first.upload_wake.clone();
481 wake.notify_one();
483 let mut sending = first.send.watch();
484 let mut syncing = first.waits.syncing();
485 let mut reports = first.waits.network_reports();
486 let mut network = first.network.clone();
488 drop(first);
489 let mut retry: crate::retry_waits::Wake = Box::pin(std::future::pending());
491 loop {
492 tokio::select! {
493 biased;
494 never = parks.polling() => match never {},
495 _ = stopping.changed() => return None,
496 result = changes.next() => if let Err(error) = result {
497 if !matches!(error, DbError::StoreClosed) {
498 tracing::error!(?error, "file commit observation failed");
499 }
500 return Some(error.into());
501 },
502 () = wake.notified() => {},
503 result = sending.changed() => if result.is_err() { return Some(DbError::StoreClosed.into()) },
506 result = syncing.changed() => if result.is_err() { return Some(DbError::StoreClosed.into()) },
508 result = reports.changed() => if result.is_err() { return Some(DbError::StoreClosed.into()) },
509 () = network.changed() => {},
510 () = &mut retry => {},
511 never = parks.parked() => match never {},
512 }
513 let owner = owner.upgrade()?;
514 let bugs = owner.database.bugs();
515 let run = async {
516 match owner.drain_uploads().await {
517 Ok(_) => (),
518 Err(SyncError::NoStorage) => {
519 tracing::debug!("file uploads waiting for storage");
520 }
521 Err(SyncError::Storage(error))
522 if error.failure() == coven_storage::StorageFailure::CatchUpRequired =>
523 {
524 tracing::debug!("file uploads waiting for a completed catch-up");
525 }
526 Err(SyncError::Database(DbError::StoreClosed)) => (),
527 Err(error) => {
528 if !coven_foundation::task::report_carried(bugs.as_ref(), &error) {
529 tracing::error!(?error, "upload queue failed");
530 }
531 }
532 }
533 match owner.keep_pinned().await {
536 Ok(()) | Err(SyncError::Database(DbError::StoreClosed)) => (),
537 Err(error) => {
538 if !coven_foundation::task::report_carried(bugs.as_ref(), &error) {
539 tracing::error!(?error, "keeping files here failed");
540 }
541 }
542 }
543 };
544 tokio::select! { biased; _ = stopping.changed() => return None, () = run => {} }
545 retry = match owner.database.bound_uploads().await {
548 Ok(bound) => {
549 owner.next_upload_retry(&bound).unwrap_or_else(|| Box::pin(std::future::pending()))
550 }
551 Err(DbError::StoreClosed) => return Some(DbError::StoreClosed.into()),
552 Err(error) => {
555 if !coven_foundation::task::report_carried(owner.database.bugs().as_ref(), &error) {
556 tracing::error!(?error, "reading the bound uploads failed");
557 }
558 Box::pin(std::future::pending())
559 }
560 };
561 }
562}
563
564impl<M: FilesMode> FilesInner<M> {
565 fn connection_due(&self, subject: coven_database::RetrySubject) -> bool {
566 !lock_unpoisoned(&self.connection_waits).contains(&subject)
567 }
568
569 fn wait_for_connection(&self, subject: coven_database::RetrySubject) {
570 lock_unpoisoned(&self.connection_waits).insert(subject);
571 }
572
573 fn forget_connection_wait(&self, subject: coven_database::RetrySubject) {
574 lock_unpoisoned(&self.connection_waits).remove(&subject);
575 }
576
577 fn retain_connection_waits(&self, keep: impl Fn(&coven_database::RetrySubject) -> bool) {
579 lock_unpoisoned(&self.connection_waits).retain(|subject| keep(subject));
580 }
581
582 pub(crate) async fn lock_file(
585 &self,
586 file: &coven_database::FileRef,
587 location: &coven_database::FileLocation,
588 ) -> tokio::sync::OwnedMutexGuard<()> {
589 let lock = {
590 let mut reads = lock_unpoisoned(&self.reads);
591 reads.retain(|_, lock| lock.strong_count() != 0);
592 let key = (file.namespace().to_owned(), location.object());
593 match reads.get(&key).and_then(std::sync::Weak::upgrade) {
594 Some(lock) => {
595 #[cfg(test)]
596 self.shared_reads.send_modify(|shared| *shared += 1);
597 lock
598 }
599 None => {
600 let lock = Arc::new(tokio::sync::Mutex::new(()));
601 reads.insert(key, Arc::downgrade(&lock));
602 lock
603 }
604 }
605 };
606 lock.lock_owned().await
607 }
608
609 pub(crate) fn check_open(&self) -> Result<(), DbError> {
610 if self.closed.load(Ordering::Acquire) {
611 Err(DbError::StoreClosed)
612 } else {
613 Ok(())
614 }
615 }
616 pub(crate) fn metered_holds(&self, bound: bool) -> bool {
620 self.unmetered_only.load(Ordering::Acquire)
621 && self.network.current().metered
622 && held_on_metered(bound)
623 }
624 pub(crate) fn downloads_held(&self) -> bool {
626 self.metered_holds(false)
627 }
628 pub(crate) async fn until_downloads_unheld(&self) -> Result<(), DbError> {
632 let mut network = self.network.clone();
633 loop {
634 let woken = self.hold_wake.notified();
635 tokio::pin!(woken);
636 woken.as_mut().enable();
637 self.check_open()?;
638 network.read();
639 if !self.downloads_held() {
640 return Ok(());
641 }
642 #[cfg(test)]
643 let _held = HeldDownload::new(&self.held_downloads);
644 tokio::select! {
645 () = woken => {}
646 () = network.changed() => {}
647 }
648 }
649 }
650 pub(crate) async fn trim(&self, namespace: &str) {
651 if let Err(error) = self.database.trim_cache(namespace).await {
652 tracing::error!(?error, namespace, "cache eviction failed");
655 }
656 }
657}
658impl FilesInner {
659 pub(crate) async fn read_unmetered_only(&self) -> Result<(), DbError> {
664 let _change = self.lock_hold_change().await;
665 let unmetered_only = self.database.unmetered_only().await?;
666 self.unmetered_only.store(unmetered_only, Ordering::Release);
667 Ok(())
668 }
669}
670#[cfg(test)]
672struct HeldDownload<'a>(&'a tokio::sync::watch::Sender<usize>);
673
674#[cfg(test)]
675impl<'a> HeldDownload<'a> {
676 fn new(held: &'a tokio::sync::watch::Sender<usize>) -> Self {
677 held.send_modify(|held| *held += 1);
678 Self(held)
679 }
680}
681
682#[cfg(test)]
683impl Drop for HeldDownload<'_> {
684 fn drop(&mut self) {
685 self.0.send_modify(|held| *held -= 1);
686 }
687}
688
689#[derive(Clone, Copy, Debug, PartialEq, Eq)]
691pub(crate) enum TrailingHold {
692 Paused,
694 MemberKeysChanging,
698 Metered,
701}
702
703pub(crate) fn held_on_metered(bound: bool) -> bool {
708 !bound
709}
710
711#[cfg(test)]
712#[path = "files_tests.rs"]
713mod tests;