1use coven_foundation::locks::{lock_unpoisoned, read_unpoisoned, write_unpoisoned};
6use coven_foundation::task::{blocking_completing, CarriesBug};
7use std::collections::BTreeSet;
8use std::sync::{Arc, Mutex, RwLock};
9
10#[path = "file_database.rs"]
11pub(crate) mod file_database;
12
13#[path = "database_local_bytes.rs"]
14pub(crate) mod local_bytes;
15
16#[path = "database_mode.rs"]
17mod mode;
18pub use mode::{Mode, ReadOnly, Writer};
19
20#[path = "file_staging.rs"]
21mod file_staging;
22pub use file_staging::StagedFiles;
23#[path = "read_pool.rs"]
24mod read_pool;
25use read_pool::ReadPool;
26
27#[path = "input_database.rs"]
28pub(crate) mod input_database;
29
30#[path = "database_operations.rs"]
31mod operations;
32#[path = "database_provider_access.rs"]
33mod provider_access;
34
35#[path = "database_file_retention.rs"]
36pub(crate) mod file_retention;
37#[path = "database_snapshots.rs"]
38mod snapshots;
39
40#[cfg(any(test, feature = "test-utils"))]
41#[path = "database_operations_tests.rs"]
42pub(crate) mod operation_tests;
43
44use coven_foundation::clock::{ClockRef, SystemClock};
45use coven_foundation::files::{StoreDir, StoreLock};
46use coven_foundation::id_source::FileId;
47use coven_foundation::id_source::{DeviceId, IdSourceRef, UuidIds};
48
49use crate::authorization::SqlAuthorization;
50use crate::observation::CommitObserver;
51use crate::read::RunningRead;
52use crate::sqlite::DatabaseConnection;
53use crate::{DbError, Migration, MigrationOutcome, SyncedTable};
54use crate::{LiveQuery, LostValue, Read, ReconfigurableLiveQuery, SqlReadContext};
55
56#[path = "database_builder.rs"]
57mod builder;
58#[path = "database_store.rs"]
59mod store;
60
61#[path = "database_call.rs"]
62mod call;
63#[path = "database_retry.rs"]
64mod retry;
65#[path = "database_sync.rs"]
66mod sync;
67pub use builder::{
68 closed_sync_facts, discarded_operations, discarded_store_name, read_store_name, DatabaseBuilder,
69};
70pub(crate) use call::{finish_blocking, process, start_call};
71
72pub struct Database<M: Mode = Writer> {
104 observer: CommitObserver,
107 bugs: coven_foundation::task::BugSinkRef,
109 inner: Arc<RwLock<Option<M::Open>>>,
113 closing: Arc<coven_foundation::files::StoreClosingGuard>,
116 writer: M::WriterParts,
118}
119
120impl<M: Mode> Clone for Database<M> {
122 fn clone(&self) -> Self {
123 Self {
124 observer: self.observer.clone(),
125 bugs: self.bugs.clone(),
126 inner: self.inner.clone(),
127 closing: self.closing.clone(),
128 writer: self.writer.clone(),
129 }
130 }
131}
132
133#[derive(Clone)]
135pub struct WriterParts {
136 transfers: crate::transfers_in_flight::TransfersInFlight,
138 network: crate::network_facts::NetworkFacts,
141 file_tasks: Arc<tokio::sync::RwLock<()>>,
142 local_bytes_changes: Arc<tokio::sync::watch::Sender<u64>>,
145 #[cfg(test)]
148 close_waits: Arc<Mutex<Option<tokio::sync::oneshot::Sender<()>>>>,
149 #[cfg(test)]
152 abandoned_stagings: Arc<Mutex<Vec<tests::hooks::StagingRemoval>>>,
153}
154
155pub struct DatabaseInner {
157 checkpoints: Vec<crate::wal_checkpoints::WalCheckpointer>,
160 statistics: crate::upkeep::UpkeepThread<crate::planner_statistics::PlannerStatistics>,
163 inputs: input_database::InputDatabase,
164 access: DatabaseAccess<StoreLock>,
165 migrations: Vec<MigrationOutcome>,
166 raise_operation: Option<Box<crate::migration::MigrationOperation>>,
171 clock: ClockRef,
172 ids: IdSourceRef,
173 staging: Arc<crate::reserved_names::ReservedNames>,
180 copying: Arc<Mutex<BTreeSet<String>>>,
183 #[cfg(test)]
186 removal_gate: Mutex<Option<Arc<dyn Fn() + Send + Sync>>>,
187 #[cfg(test)]
190 flush_hook: Mutex<Option<coven_foundation::files::FlushHook>>,
191 #[cfg(any(test, feature = "test-utils"))]
194 retry_end_failures: std::sync::atomic::AtomicUsize,
195 #[cfg(any(test, feature = "test-utils"))]
198 applied_slot_failures: std::sync::atomic::AtomicUsize,
199}
200
201pub struct DatabaseAccess<L> {
205 writer: Arc<Mutex<DatabaseConnection>>,
208 readers: ReadPool,
209 observer: CommitObserver,
211 write_schema: crate::write_schema::WriteSchema,
212 directory: StoreDir,
213 device: DeviceId,
214 lock: L,
215 #[cfg(test)]
219 local_open_gate: Mutex<Option<Box<dyn FnOnce() + Send>>>,
220}
221
222impl<L> DatabaseAccess<L> {
223 fn call<R, E: From<DbError> + CarriesBug>(
231 &self,
232 run: impl FnOnce() -> Result<R, E>,
233 ) -> Result<R, E> {
234 if let Some(error) = self.observer.error() {
235 return Err(error.into());
236 }
237 let (result, own) = self.observer.calling(run);
238 if let Some(bug) = own {
239 let carried = result.as_ref().err().and_then(CarriesBug::bug);
240 if let Some(other) = carried.filter(|other| **other != bug) {
241 self.observer.report(other);
242 }
243 return Err(DbError::Bug(bug).into());
244 }
245 match (result, self.observer.error()) {
246 (Ok(_), Some(error)) => Err(error.into()),
247 (result, _) => result,
248 }
249 }
250
251 fn with_committed<R, E: From<DbError> + CarriesBug>(
254 &self,
255 run: impl FnOnce(&DatabaseConnection) -> Result<R, E>,
256 ) -> Result<R, E> {
257 self.call(|| {
258 self.readers
259 .acquire_reader()
260 .with_reader(|reader| reader.read_transaction(|| run(reader)))
261 })
262 }
263
264 fn with_writer<R, E: From<DbError>>(
265 &self,
266 run: impl FnOnce(&mut DatabaseConnection) -> Result<R, E>,
267 ) -> Result<R, E> {
268 with_locked(&self.writer, |writer| {
269 #[cfg(any(test, feature = "test-utils"))]
270 writer.record_work(|work| work.writer_holds += 1);
271 if let Some(error) = self.observer.error() {
272 return Err(error.into());
273 }
274 #[cfg(any(test, feature = "test-utils"))]
275 let run = |writer| coven_foundation::test_work::writing(|| run(writer));
276 run(writer)
277 })
278 }
279
280 fn remove_released(&self, chosen: crate::file_removals::Chosen) -> Vec<DbError> {
284 let released = match chosen {
285 Ok(released) => released,
286 Err(error) => return vec![error],
287 };
288 let (removed, mut failures) = crate::file_removals::remove_bytes(&self.directory, released);
289 if !removed.is_empty() {
290 let forgotten = self.with_writer(|writer| {
291 writer.transaction(|db| crate::file_removals::forget(db, &removed))?;
292 #[cfg(any(test, feature = "test-utils"))]
293 writer.checkpoint(crate::test_utils::WriteCheckpoint::Finished);
294 Ok::<_, DbError>(())
295 });
296 if let Err(error) = forgotten {
297 failures.push(error);
298 }
299 }
300 failures
301 }
302
303 fn open_local_file(
313 &self,
314 reference: &crate::FileRef,
315 ) -> Result<Option<crate::LocalFileStream>, crate::LocalFileError> {
316 let file = reference.id();
317 let source = || {
318 self.readers
319 .acquire_reader()
320 .read_snapshot(|sql| sql.device_only_source(self.device, file))
321 };
322 let Some(found) = source()? else {
323 return Ok(None);
324 };
325 #[cfg(test)]
326 if let Some(gate) = lock_unpoisoned(&self.local_open_gate).take() {
327 gate();
328 }
329 match crate::file_ref::open_source(
330 &self.directory,
331 &file.to_string(),
332 reference.size(),
333 found,
334 ) {
335 Ok(stream) => Ok(Some(stream)),
336 Err(error) if error.judges_bytes() && source()?.is_none() => Ok(None),
337 Err(error) => Err(error),
338 }
339 }
340
341 fn file_ref(&self, file: FileId) -> Result<crate::FileRef, DbError> {
342 self.readers.acquire_reader().read_snapshot(|sql| sql.file_ref(file))
343 }
344
345 fn user_file(&self, file: FileId) -> Result<Option<crate::UserFile>, DbError> {
346 self.readers.acquire_reader().read_snapshot(|sql| sql.user_file(file))
347 }
348
349 fn read<R, E: From<DbError>>(
350 &self,
351 read: impl FnOnce(SqlReadContext<'_>) -> Result<R, E>,
352 ) -> Result<R, E> {
353 self.readers.acquire_reader().read_snapshot(read)
354 }
355
356 fn schema_version(&self) -> Result<u32, DbError> {
357 self.readers.acquire_reader().schema_version()
358 }
359
360 fn sqlite_settings(&self) -> Result<crate::SqliteSettingsInEffect, DbError> {
362 self.readers.acquire_reader().with_reader(DatabaseConnection::sqlite_settings)
363 }
364
365 fn close(self) -> Result<(), DbError> {
366 self.observer.close();
367 let mut failures = self.readers.close();
368 match Arc::try_unwrap(self.writer) {
370 Ok(writer) => {
371 if let Err(error) = coven_foundation::locks::into_unpoisoned(writer).close() {
372 failures.push(error);
373 }
374 }
375 Err(_held) => failures.push(DbError::Invariant {
376 what: "a writer closes after its checkpointer stopped",
377 }),
378 }
379 drop(self.lock);
380 if failures.is_empty() {
381 Ok(())
382 } else {
383 Err(DbError::Closing { failures })
384 }
385 }
386}
387
388pub(crate) fn with_locked<T, R>(connection: &Mutex<T>, run: impl FnOnce(&mut T) -> R) -> R {
393 run(&mut lock_unpoisoned(connection))
394}
395
396impl DatabaseInner {
397 fn call<R, E: From<DbError> + CarriesBug>(
398 &self,
399 run: impl FnOnce(&Self) -> Result<R, E>,
400 ) -> Result<R, E> {
401 self.access.call(|| run(self))
402 }
403
404 fn release_file_reservations(&self, names: &[crate::file_removals::OwnedFile]) {
406 self.staging.release(names);
407 }
408
409 fn with_files<R, E: crate::WriteFailure>(
415 &self,
416 operation: &'static str,
417 run: impl FnOnce(&mut DatabaseConnection, &crate::file_write::WriteFiles<'_>) -> Result<R, E>,
418 ) -> Result<R, E> {
419 let (result, chosen, committed) = self.access.with_writer(|writer| {
420 let files = crate::file_write::WriteFiles::new(
421 &self.access.write_schema,
422 self.access.device,
423 self.ids.as_ref(),
424 &self.staging,
425 &self.copying,
426 );
427 let result = coven_foundation::task::caught_sync(operation, || run(writer, &files))
428 .unwrap_or_else(|bug| Err(E::from(DbError::Bug(bug))));
429 let usable = !matches!(self.access.observer.error(), Some(DbError::RollbackFailed));
432 let chosen = usable.then(|| released(writer, self));
433 Ok::<_, E>((result, chosen, files.take_committed()))
434 })?;
435 let result = match chosen {
436 Some(chosen) => self.finish_removals(result, chosen),
437 None => result,
438 };
439 if result.is_ok() {
441 committed.into_iter().for_each(|effect| effect());
442 }
443 result
444 }
445
446 fn remove_chosen(&self, chosen: crate::file_removals::Chosen) -> Vec<DbError> {
449 #[cfg(test)]
450 {
451 let gate = lock_unpoisoned(&self.removal_gate).clone();
452 if let Some(gate) = gate {
453 gate();
454 }
455 }
456 self.access.remove_released(chosen)
457 }
458
459 fn finish_removals<R, E: crate::WriteFailure>(
462 &self,
463 result: Result<R, E>,
464 chosen: crate::file_removals::Chosen,
465 ) -> Result<R, E> {
466 crate::write_failure::after_cleanup(result, self.remove_chosen(chosen))
467 }
468}
469
470pub(crate) fn release_copies(copying: &Mutex<BTreeSet<String>>, paths: &[String]) {
473 let mut held = lock_unpoisoned(copying);
474 for path in paths {
475 held.remove(path);
476 }
477}
478
479fn released(writer: &DatabaseConnection, inner: &DatabaseInner) -> crate::file_removals::Chosen {
482 inner.staging.with_names(|active| {
483 let copying = lock_unpoisoned(&inner.copying);
484 crate::file_removals::choose(writer, active, ©ing)
485 })
486}
487
488impl<M: Mode> Database<M> {
489 pub fn bugs(&self) -> &coven_foundation::task::BugSinkRef {
491 &self.bugs
492 }
493
494 async fn call<R, E>(
497 &self,
498 run: impl FnOnce(&M::Open) -> Result<R, E> + Send + 'static,
499 ) -> Result<R, E>
500 where
501 R: Send + 'static,
502 E: From<DbError> + CarriesBug + Send + 'static,
503 {
504 finish_blocking(
505 start_call("database call", &self.bugs, self.inner.clone(), move |open| {
506 M::access(open).call(|| run(open))
507 })
508 .await,
509 )
510 }
511
512 pub async fn open_local_file(
516 &self,
517 reference: &crate::FileRef,
518 ) -> Result<Option<crate::LocalFileStream>, crate::LocalFileError> {
519 let reference = reference.clone();
520 self.call(move |open| M::access(open).open_local_file(&reference)).await
521 }
522
523 pub async fn file_ref(&self, file: FileId) -> Result<crate::FileRef, DbError> {
526 self.call(move |open| M::access(open).file_ref(file)).await
527 }
528
529 pub async fn user_file(&self, file: FileId) -> Result<Option<crate::UserFile>, DbError> {
531 self.call(move |open| M::access(open).user_file(file)).await
532 }
533
534 pub fn read<F, R, E>(&self, read: F) -> Read<'_, F, M>
538 where
539 F: FnOnce(SqlReadContext<'_>) -> Result<R, E> + Send + 'static,
540 R: Send + 'static,
541 E: From<DbError> + CarriesBug + Send + 'static,
542 {
543 Read::new(self, read)
544 }
545
546 pub async fn lost_values(&self) -> Result<Vec<LostValue>, DbError> {
548 self.read(|sql| sql.lost_values()).await
549 }
550
551 pub(crate) fn start_read<F, R, E>(&self, read: F) -> RunningRead<R, E>
552 where
553 F: FnOnce(SqlReadContext<'_>) -> Result<R, E> + Send + 'static,
554 R: Send + 'static,
555 E: From<DbError> + CarriesBug + Send + 'static,
556 {
557 RunningRead::new(start_call("read", &self.bugs, self.inner.clone(), move |open| {
558 let access = M::access(open);
559 access.call(|| access.read(read))
560 }))
561 }
562
563 pub async fn schema_version(&self) -> Result<u32, DbError> {
565 self.call(|open| M::access(open).schema_version()).await
566 }
567
568 pub async fn sqlite_settings(&self) -> Result<crate::SqliteSettingsInEffect, DbError> {
570 self.call(|open| M::access(open).sqlite_settings()).await
571 }
572}
573
574impl Database {
575 pub fn begin_close(&self) {
578 self.closing.begin_close();
579 }
580 pub fn sync_changes(&self) -> crate::DatabaseChanges {
585 self.table_changes(&["_coven_uploads"], true, true)
586 }
587
588 pub fn started_operations(&self) -> crate::DatabaseChanges {
591 self.table_changes(&["_coven_operations"], true, true)
592 }
593
594 pub fn fail_expiry_wait(&self, error: coven_foundation::clock::WallTimerError) {
598 self.observer.fail(crate::observation::HandleFailure::WallTimer(error));
599 }
600
601 fn table_changes(
604 &self,
605 tables: &[&str],
606 first: bool,
607 inserted_only: bool,
608 ) -> crate::DatabaseChanges {
609 let reads: Vec<_> = tables
610 .iter()
611 .map(|table| crate::observation::TableRead {
612 table: (*table).into(),
613 columns: BTreeSet::new(),
614 scope: crate::row_scope::RowScope::All,
615 inserted_only,
616 })
617 .collect();
618 self.observer.changes(reads, first)
619 }
620
621 pub fn attempt_commits(&self, attempt: crate::CommitScope) -> crate::AttemptCommits {
625 self.observer.attempt(attempt)
626 }
627
628 pub fn watch_attempt(
633 &self,
634 attempt: crate::AttemptCommits,
635 rows: &[crate::PrerequisiteRows],
636 ) -> crate::DatabaseChanges {
637 self.observer.watch_attempt(attempt, rows)
638 }
639
640 pub fn uploads_in_flight(&self) -> crate::InFlight {
643 self.writer.transfers.uploads()
644 }
645
646 pub fn downloads_in_flight(&self) -> crate::InFlight {
650 self.writer.transfers.downloads()
651 }
652
653 pub fn record_network(&self, network: coven_foundation::network_watch::NetworkState) {
657 self.writer.network.set(network);
658 }
659
660 pub fn subscribe<F, R, E>(&self, query: F) -> LiveQuery<R, E>
663 where
664 F: Fn(SqlReadContext<'_>) -> Result<R, E> + Send + Sync + 'static,
665 R: Send + 'static,
666 E: From<DbError> + CarriesBug + Send + 'static,
667 {
668 LiveQuery::new(self.subscribe_reconfigurable((), move |(), sql| query(sql)))
669 }
670
671 pub fn subscribe_reconfigurable<Q, F, R, E>(
673 &self,
674 initial_request: Q,
675 query: F,
676 ) -> ReconfigurableLiveQuery<Q, R, E>
677 where
678 Q: Clone + PartialEq + Send + Sync + 'static,
679 F: Fn(&Q, SqlReadContext<'_>) -> Result<R, E> + Send + Sync + 'static,
680 R: Send + 'static,
681 E: From<DbError> + CarriesBug + Send + 'static,
682 {
683 let commits = self.observer.subscribe();
684 let database = Arc::downgrade(&self.inner);
687 let query = Arc::new(query);
688 let completion = commits.clone();
689 let bugs = self.bugs.clone();
690 ReconfigurableLiveQuery::new(
691 initial_request,
692 Arc::new(move |request| {
693 let database = database.clone();
694 let query = query.clone();
695 let completion = completion.clone();
696 let bugs = bugs.clone();
697 Box::pin(async move {
698 let database = database.upgrade().ok_or(DbError::StoreClosed)?;
699 finish_blocking(
700 start_call("live query", &bugs, database, move |inner| {
701 inner.call(|inner| {
702 inner.access.readers.acquire_reader().live_read(
703 &mut |reads, lookups| completion.finish_judging(reads, lookups),
704 move |sql| query(&request, sql),
705 )
706 })
707 })
708 .await,
709 )
710 })
711 }),
712 commits,
713 self.bugs.clone(),
714 )
715 }
716
717 pub fn subscribe_lost_values<E>(&self) -> LiveQuery<Vec<LostValue>, E>
719 where
720 E: From<DbError> + coven_foundation::task::CarriesBug + Send + 'static,
721 {
722 self.subscribe(|sql| sql.lost_values().map_err(E::from))
723 }
724
725 pub async fn write<F, R>(&self, sql: F) -> Result<R, DbError>
729 where
730 F: FnOnce(crate::SqlContext<'_, '_>) -> Result<R, DbError> + Send + 'static,
731 R: Send + 'static,
732 {
733 self.write_as(sql).await
734 }
735
736 pub async fn write_as<F, R, E>(&self, sql: F) -> Result<R, E>
739 where
740 F: FnOnce(crate::SqlContext<'_, '_>) -> Result<R, E> + Send + 'static,
741 R: Send + 'static,
742 E: crate::WriteFailure + Send + 'static,
743 {
744 self.call(move |inner| {
745 inner.with_files("write", |writer, files| {
746 let result = coven_foundation::task::caught_sync("write", || {
747 writer.local_write(
748 &inner.access.write_schema,
749 inner.access.device,
750 inner.clock.now(),
751 files,
752 |files| sql(crate::SqlContext::new(files)),
753 )
754 });
755 inner.release_file_reservations(&files.take_brought());
759 release_copies(&inner.copying, &files.take_held_copies());
760 #[cfg(any(test, feature = "test-utils"))]
761 writer.checkpoint(crate::test_utils::WriteCheckpoint::StagingReleased);
762 result.unwrap_or_else(|bug| Err(E::from(DbError::Bug(bug))))
763 })
764 })
765 .await
766 }
767
768 pub async fn stage_files<F, T, E>(&self, stage: F) -> Result<StagedFiles<T>, E>
775 where
776 F: FnOnce(&mut crate::WriteBatch<'_>) -> Result<T, E> + Send + 'static,
777 T: Send + 'static,
778 E: crate::WriteFailure + Send + 'static,
779 {
780 let lease = self.writer.file_tasks.clone().read_owned().await;
781 let database = self.inner.clone();
782 let (staging, value) = finish_blocking(
783 blocking_completing(
784 "stage files",
785 self.bugs.clone(),
786 crate::commit_scope::bind(move || {
787 file_staging::FileStaging::new(database, lease, stage)
788 }),
789 )
790 .await,
791 )?;
792 let (staging, flushed) =
793 staging.write().await.map_err(|failed| E::from(DbError::from(failed)))?;
794 match flushed {
795 Ok(_flushed) => Ok(staging.into_staged(value)),
796 Err(error) => finish_blocking(
800 blocking_completing(
801 "discard staged files",
802 self.bugs.clone(),
803 crate::commit_scope::bind(move || {
804 Err::<std::convert::Infallible, E>(staging.discard::<E>(error))
805 }),
806 )
807 .await,
808 )
809 .map(|never| match never {}),
810 }
811 }
812
813 #[cfg(any(test, feature = "test-utils"))]
816 pub async fn staged_write<F, S, T, R, E>(&self, stage: F, write: S) -> Result<R, E>
817 where
818 F: FnOnce(&mut crate::WriteBatch<'_>) -> Result<T, E> + Send + 'static,
819 S: FnOnce(crate::SqlContext<'_, '_>, T) -> Result<R, E> + Send + 'static,
820 T: Send + 'static,
821 R: Send + 'static,
822 E: crate::WriteFailure + Send + 'static,
823 {
824 let staged = self.stage_files(stage).await?;
825 self.write_as(move |sql| {
826 let value = sql.put_staged(staged)?;
827 write(sql, value)
828 })
829 .await
830 }
831
832 pub async fn write_bulk<F, R, E>(&self, sql: F) -> Result<R, E>
842 where
843 F: FnOnce(crate::BulkSqlContext<'_>) -> Result<R, E> + Send + 'static,
844 R: Send + 'static,
845 E: crate::WriteFailure + Send + 'static,
846 {
847 self.call(move |inner| inner.access.with_writer(|writer| writer.bulk_write(sql))).await
848 }
849
850 pub async fn checkpoint(
852 &self,
853 mode: crate::CheckpointMode,
854 ) -> Result<crate::WalCheckpoint, DbError> {
855 self.call(move |inner| inner.access.with_writer(|writer| writer.wal_checkpoint(mode))).await
856 }
857
858 pub async fn incremental_vacuum(&self, pages: crate::IncrementalVacuum) -> Result<(), DbError> {
861 self.call(move |inner| inner.access.with_writer(|writer| writer.incremental_vacuum(pages)))
862 .await
863 }
864
865 pub async fn release_memory(&self) -> Result<(), DbError> {
872 self.call(|inner| {
873 inner.access.readers.acquire_reader().with_reader(|reader| Ok(reader.release_memory()?))
874 })
875 .await
876 }
877
878 pub fn applied_migrations(&self) -> Result<Vec<MigrationOutcome>, DbError> {
880 let inner = read_unpoisoned(&self.inner);
881 let inner = inner.as_ref().ok_or(DbError::StoreClosed)?;
882 if let Some(error) = inner.access.observer.error() {
883 return Err(error);
884 }
885 Ok(inner.migrations.clone())
886 }
887
888 pub async fn close(&self) -> Result<(), DbError> {
891 self.closing.begin_close();
892 let database = self.clone();
893 let bugs = self.bugs.clone();
896 coven_foundation::task::spawn_completing("close the store", self.bugs.clone(), async move {
897 let file_tasks = database.writer.file_tasks.clone().write_owned().await;
898 finish_blocking(
899 blocking_completing(
900 "close the store",
901 bugs,
902 crate::commit_scope::bind(move || {
903 let _file_tasks = file_tasks;
904 #[cfg(test)]
905 database.tell_close_wait();
906 let mut slot = write_unpoisoned(&database.inner);
907 let Some(inner) = slot.take() else {
908 return Err(DbError::StoreClosed);
909 };
910 let mut failures: Vec<DbError> = inner
911 .checkpoints
912 .into_iter()
913 .filter_map(|checkpoints| checkpoints.stop().err())
914 .collect();
915 failures.extend(inner.statistics.stop().err());
916 failures.extend(inner.inputs.close());
917 if inner.access.observer.error().is_none() {
921 if let Err(error) = lock_unpoisoned(&inner.access.writer).optimize() {
922 failures.push(error);
923 }
924 }
925 match inner.access.close() {
926 Err(DbError::Closing { failures: application }) => {
927 failures.extend(application)
928 }
929 Err(error) => failures.push(error),
930 Ok(()) => (),
931 }
932 if failures.is_empty() {
933 Ok(())
934 } else {
935 Err(DbError::Closing { failures })
936 }
937 }),
938 )
939 .await,
940 )
941 })
942 .await
943 .map_err(DbError::from)?
944 }
945}
946
947impl Database<ReadOnly> {
948 pub fn begin_close(&self) {
953 self.observer.close();
954 self.closing.begin_close();
955 }
956
957 pub async fn close(&self) -> Result<(), DbError> {
959 let handle = self.clone();
960 finish_blocking(
961 blocking_completing("close the store", self.bugs.clone(), move || {
962 let mut slot = write_unpoisoned(&handle.inner);
963 slot.take().ok_or(DbError::StoreClosed)?.close()
964 })
965 .await,
966 )
967 }
968}
969
970#[cfg(test)]
971#[path = "database_tests.rs"]
972mod tests;