1use super::Database;
4use crate::{
5 retry::{self, RetrySubject, RetryWait},
6 DbError,
7};
8
9impl Database {
10 pub async fn record_sync_retry<P: crate::PendingValue>(
13 &self,
14 connection: P::Subject,
15 record: Option<P>,
16 wait: Option<RetryWait>,
17 ) -> Result<(), DbError>
18 where
19 P::Subject: Send + 'static,
20 {
21 self.call(move |inner| {
22 inner.access.with_writer(|writer| {
23 writer.state_transaction(|db| {
24 if let Some(record) = record {
25 crate::pending::save(db, &record)?;
26 } else {
27 crate::pending::clear(db, &connection)?;
28 }
29 retry::set(db, RetrySubject::Sync, wait.as_ref())
30 })
31 })
32 })
33 .await
34 }
35
36 pub async fn clear_sync_retry<S: serde::Serialize + Send + 'static>(
38 &self,
39 connection: S,
40 ) -> Result<(), DbError> {
41 self.call(move |inner| {
42 let changed = inner.access.readers.acquire_reader().with_reader(|db| {
43 Ok::<_, DbError>(
44 retry::find(db, RetrySubject::Sync)?.is_some()
45 || crate::pending::recorded(db, &connection)?,
46 )
47 })?;
48 if !changed {
49 return Ok(());
50 }
51 inner.access.with_writer(|writer| {
52 writer.state_transaction(|db| {
53 retry::clear(db, RetrySubject::Sync)?;
54 crate::pending::clear(db, &connection)
55 })
56 })
57 })
58 .await
59 }
60
61 pub async fn record_operation_retry<P: crate::PendingValue>(
63 &self,
64 record: P,
65 wait: Option<RetryWait>,
66 ) -> Result<(), DbError> {
67 self.call(move |inner| {
68 let id = record
69 .operation()
70 .ok_or(DbError::Invariant { what: "an operation retry names its journal row" })?;
71 inner.access.with_writer(|writer| {
72 writer.state_transaction(|db| {
73 retry::save_failure(db, &record, RetrySubject::Operation(id), wait.as_ref())
74 })
75 })
76 })
77 .await
78 }
79
80 pub async fn record_operation_retry_at<P: crate::PendingValue>(
88 &self,
89 record: P,
90 wait: Option<RetryWait>,
91 step: u32,
92 ) -> Result<bool, DbError> {
93 self.call(move |inner| {
94 let id = record
95 .operation()
96 .ok_or(DbError::Invariant { what: "an operation retry names its journal row" })?;
97 inner.access.with_writer(|writer| {
98 writer.state_transaction(|db| {
99 if crate::operation::find(db, id)?.is_none_or(|row| row.last_step != step) {
100 return Ok(false);
101 }
102 retry::save_failure(db, &record, RetrySubject::Operation(id), wait.as_ref())
103 .map(|()| true)
104 })
105 })
106 })
107 .await
108 }
109
110 pub async fn retry_waits(&self) -> Result<Vec<(RetrySubject, RetryWait)>, DbError> {
112 self.call(move |inner| inner.access.readers.acquire_reader().with_reader(retry::read_all))
113 .await
114 }
115
116 pub async fn retry_wait(&self, subject: RetrySubject) -> Result<Option<RetryWait>, DbError> {
118 self.call(move |inner| {
119 inner.access.readers.acquire_reader().with_reader(|db| retry::find(db, subject))
120 })
121 .await
122 }
123
124 #[cfg(any(test, feature = "test-utils"))]
126 pub fn subscribe_retry_wait(
127 &self,
128 subject: RetrySubject,
129 ) -> crate::LiveQuery<Option<RetryWait>, crate::DbError> {
130 self.subscribe(move |sql| sql.retry_wait(subject))
131 }
132
133 pub async fn record_retry(
136 &self,
137 subject: RetrySubject,
138 wait: RetryWait,
139 ) -> Result<(), DbError> {
140 self.call(move |inner| {
141 inner
142 .access
143 .with_writer(|writer| writer.transaction(|db| retry::save(db, subject, &wait)))
144 })
145 .await
146 }
147
148 pub async fn end_retry_waits<P: crate::PendingValue>(
154 &self,
155 now: std::time::SystemTime,
156 select: impl FnOnce(
157 &[(RetrySubject, RetryWait)],
158 &[P],
159 &[crate::FileUpload],
160 &[crate::PinRequest],
161 ) -> Vec<RetrySubject>
162 + Send
163 + 'static,
164 ) -> Result<Vec<(RetrySubject, RetryWait)>, DbError> {
165 self.call(move |inner| {
166 #[cfg(any(test, feature = "test-utils"))]
167 let injected = inner
168 .retry_end_failures
169 .fetch_update(
170 std::sync::atomic::Ordering::SeqCst,
171 std::sync::atomic::Ordering::SeqCst,
172 |left| left.checked_sub(1),
173 )
174 .is_ok();
175 inner.access.with_writer(|writer| {
176 writer.transaction(|db| {
177 #[cfg(any(test, feature = "test-utils"))]
178 if injected {
179 return Err(DbError::Sqlite(rusqlite::Error::SqliteFailure(
180 rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_IOERR),
181 Some("injected retry end failure".into()),
182 )));
183 }
184 let waits = retry::read_all(db)?;
185 let pending = crate::pending::read::<P>(db)?;
186 let uploads = crate::file_queue::read(db)?;
187 let pins = crate::file_pins::read(db)?;
188 let mut ended = Vec::new();
189 for subject in select(&waits, &pending, &uploads, &pins) {
190 let Some((_, wait)) = waits.iter().find(|(held, _)| *held == subject)
191 else {
192 return Err(DbError::Invariant {
193 what: "an ended retry wait is persisted",
194 });
195 };
196 retry::end(db, subject, now)?;
197 ended.push((
198 subject,
199 RetryWait {
200 failures: wait.failures,
201 not_before: now,
202 wait: std::time::Duration::ZERO,
203 },
204 ));
205 }
206 Ok(ended)
207 })
208 })
209 })
210 .await
211 }
212
213 #[cfg(any(test, feature = "test-utils"))]
215 pub async fn fail_retry_ends(&self, count: usize) {
216 self.call(move |inner| {
217 inner.retry_end_failures.store(count, std::sync::atomic::Ordering::SeqCst);
218 Ok::<_, DbError>(())
219 })
220 .await
221 .expect("an open store arms its retry-end failures");
222 }
223
224 pub async fn clear_retry(&self, subject: RetrySubject) -> Result<(), DbError> {
226 self.call(move |inner| {
227 inner.access.with_writer(|writer| writer.transaction(|db| retry::clear(db, subject)))
228 })
229 .await
230 }
231}