Skip to main content

harmos_storage/json/
mod.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Bounded JSON Collection Storage And Native File Publication.
11
12/* === Modules === */
13
14mod config;
15mod files;
16mod format;
17mod log;
18mod reads;
19mod sessions;
20
21/* === Re-Exports === */
22
23pub use config::JsonStorageSettings;
24
25/* === Imports === */
26
27// Standard Library Imports
28use std::{
29    num::NonZeroUsize,
30    sync::{Arc, atomic::Ordering},
31};
32
33// Third Party Imports
34use tokio::sync::Mutex;
35
36// Local Imports
37use crate::{
38    query::{FileSelection, ObjectSelection, RecordSelection, SchemaSelection, StreamSelection},
39    *,
40};
41use files::Files;
42use log::{Log, Row};
43
44/* === Constants === */
45
46/* === Macros === */
47
48/* === Type Aliases === */
49
50/* === Structs === */
51
52pub struct JsonStorage {
53    settings: JsonStorageSettings,
54    history: StoreHistory,
55    owner: Arc<Mutex<Option<Log>>>,
56    files: Arc<Files>,
57}
58
59/* === Enums === */
60
61/* === Traits === */
62
63/* === Implementations === */
64
65impl JsonStorage {
66    pub fn new(settings: JsonStorageSettings, history: StoreHistory) -> Self {
67        let files = Files::new(settings.root.clone(), history.clone(), settings.max_entry_bytes.get());
68        Self {
69            settings,
70            history,
71            owner: Arc::new(Mutex::new(None)),
72            files: Arc::new(files),
73        }
74    }
75
76    pub fn into_backends(self, settings: state::StorageStateSettings) -> StorageBackends {
77        let state = state::file::FileBackend::new(self.settings.root.join("state"), self.history.clone(), settings);
78        StorageBackends {
79            primary: Arc::new(self),
80            state: Arc::new(state),
81        }
82    }
83
84    async fn call<T: Send + 'static>(&self, action: impl FnOnce(&mut Log) -> Result<T> + Send + 'static) -> Result<T> {
85        if self.files.closing.load(Ordering::Acquire) {
86            return Err(Error::Closing { domain: "JSON storage" });
87        }
88        let mut owner = tracing::Instrument::instrument(self.owner.clone().lock_owned(), tracing::trace_span!("storage.lock.wait")).await;
89        if self.files.closing.load(Ordering::Acquire) {
90            return Err(Error::Closing { domain: "JSON storage" });
91        }
92        {
93            let context = crate::execution::Context::current();
94            tokio::task::spawn_blocking(move || {
95                context.blocking(|| {
96                    let log = owner.as_mut().ok_or(Error::NotReady { domain: "JSON storage" })?;
97                    action(log)
98                })
99            })
100        }
101        .await
102        .map_err(task)?
103    }
104
105    async fn read<T: Send + 'static>(&self, action: impl FnOnce(&mut Log) -> Result<T> + Send + 'static) -> Result<T> {
106        self.call(action).await
107    }
108
109    async fn read_selected<T: Send + 'static, S: Clone + MemorySize + Send + Sync + 'static>(
110        &self, snapshot: &BackendSnapshot, selection: &S, action: impl FnOnce(&mut Log, &BackendSnapshot, &S) -> Result<T> + Send + 'static,
111    ) -> Result<T> {
112        let snapshot = snapshot.clone();
113        let selection = selection.clone();
114        self.read(move |log| action(log, &snapshot, &selection)).await
115    }
116
117    async fn file_read<T: Send + 'static>(
118        &self, file: StagedFile, action: impl FnOnce(&Files, &str) -> Result<T> + Send + 'static,
119    ) -> Result<T> {
120        self.file(file, action).await
121    }
122
123    async fn write<T: Row>(&self, batch: StorageBatch<T>) -> Result<StorageCursor> {
124        self.call(move |log| log.write(batch)).await
125    }
126
127    async fn file<T: Send + 'static>(
128        &self, file: StagedFile, action: impl FnOnce(&Files, &str) -> Result<T> + Send + 'static,
129    ) -> Result<T> {
130        self.history.check(&file.history)?;
131        self.files.call(&file.id, action).await
132    }
133}
134
135impl StorageBackend for JsonStorage {
136    fn history(&self) -> &StoreHistory {
137        &self.history
138    }
139    fn open(&self) -> StorageFuture<'_, StorageCursor> {
140        Box::pin(async move {
141            let context = crate::execution::Context::current();
142            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "open", outcome = "cancelled"));
143            context
144                .result(span, {
145                    async move {
146                        let mut owner =
147                            tracing::Instrument::instrument(self.owner.clone().lock_owned(), tracing::trace_span!("storage.lock.wait"))
148                                .await;
149                        let settings = self.settings.clone();
150                        let history = self.history.clone();
151                        let files = self.files.clone();
152                        let result = {
153                            let context = crate::execution::Context::current();
154                            tokio::task::spawn_blocking(move || {
155                                context.blocking(|| {
156                                    if owner.is_none() {
157                                        *owner = Some(Log::open(&settings, history)?);
158                                    }
159                                    let log = owner.as_ref().expect("opened JSON log");
160                                    *files.lease.lock().map_err(|_| format::corrupt("file lease is poisoned"))? =
161                                        Arc::downgrade(&log.lease);
162                                    files.closing.store(false, Ordering::Release);
163                                    Ok(log.cursor())
164                                })
165                            })
166                        }
167                        .await
168                        .map_err(task)??;
169                        Ok(result)
170                    }
171                })
172                .await
173        })
174    }
175    fn cursor_saved(&self) -> StorageFuture<'_, StorageCursor> {
176        Box::pin(async move {
177            let context = crate::execution::Context::current();
178            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "cursor_saved", outcome = "cancelled"));
179            context.result(span, self.read(|log| Ok(log.cursor()))).await
180        })
181    }
182    fn close(&self) -> StorageFuture<'_, ()> {
183        Box::pin(async move {
184            let context = crate::execution::Context::current();
185            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "close", outcome = "cancelled"));
186            context
187                .result(span, {
188                    async move {
189                        self.files.closing.store(true, Ordering::Release);
190                        let activity = tracing::Instrument::instrument(
191                            self.files.activity.clone().write_owned(),
192                            tracing::trace_span!("storage.lock.wait"),
193                        )
194                        .await;
195                        let mut owner =
196                            tracing::Instrument::instrument(self.owner.clone().lock_owned(), tracing::trace_span!("storage.lock.wait"))
197                                .await;
198                        {
199                            let context = crate::execution::Context::current();
200                            tokio::task::spawn_blocking(move || {
201                                context.blocking(|| {
202                                    let _activity = activity;
203                                    owner.take();
204                                    Ok(())
205                                })
206                            })
207                        }
208                        .await
209                        .map_err(task)?
210                    }
211                })
212                .await
213        })
214    }
215    fn write_schemas(&self, batch: StorageBatch<StorageSchema>) -> StorageFuture<'_, StorageCursor> {
216        Box::pin(async move {
217            let context = crate::execution::Context::current();
218            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "write_schemas", outcome = "cancelled"));
219            context.result(span, self.write(batch)).await
220        })
221    }
222    fn write_objects(&self, batch: StorageBatch<ObjectEntry>) -> StorageFuture<'_, StorageCursor> {
223        Box::pin(async move {
224            let context = crate::execution::Context::current();
225            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "write_objects", outcome = "cancelled"));
226            context.result(span, self.write(batch)).await
227        })
228    }
229    fn write_records(&self, batch: StorageBatch<StoredRecord>) -> StorageFuture<'_, StorageCursor> {
230        Box::pin(async move {
231            let context = crate::execution::Context::current();
232            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "write_records", outcome = "cancelled"));
233            context.result(span, self.write(batch)).await
234        })
235    }
236    fn write_stream(&self, batch: StorageBatch<StoredRow>) -> StorageFuture<'_, StorageCursor> {
237        Box::pin(async move {
238            let context = crate::execution::Context::current();
239            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "write_stream", outcome = "cancelled"));
240            context.result(span, self.write(batch)).await
241        })
242    }
243    fn write_files(&self, batch: StorageBatch<FileReference>) -> StorageFuture<'_, StorageCursor> {
244        Box::pin(async move {
245            let context = crate::execution::Context::current();
246            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "write_files", outcome = "cancelled"));
247            context
248                .result(span, {
249                    let files = self.files.clone();
250                    self.call(move |log| {
251                        for reference in batch.as_slice() {
252                            let production = format::load::<files::Production>(
253                                &files.metadata_path(&files::key(&reference.metadata.id)),
254                                files.maximum,
255                            )?
256                            .ok_or_else(|| format::invalid("unknown staged file"))?;
257                            if production.phase != FilePhase::Finished || production.finish.as_ref() != Some(reference) {
258                                return Err(format::conflict("file publication precedes durable finish"));
259                            }
260                        }
261                        log.write(batch)
262                    })
263                })
264                .await
265        })
266    }
267    fn open_snapshot<'a>(&'a self, at: &'a StorageCursor) -> StorageFuture<'a, BackendSnapshot> {
268        Box::pin(async move {
269            let context = crate::execution::Context::current();
270            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "open_snapshot", outcome = "cancelled"));
271            context
272                .result(span, {
273                    async move {
274                        let at = at.clone();
275                        self.read(move |log| log.snapshot(at)).await
276                    }
277                })
278                .await
279        })
280    }
281    fn release_snapshot<'a>(&'a self, snapshot: &'a BackendSnapshot) -> StorageFuture<'a, ()> {
282        Box::pin(async move {
283            let context = crate::execution::Context::current();
284            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "release_snapshot", outcome = "cancelled"));
285            context
286                .result(span, {
287                    let snapshot = snapshot.clone();
288                    self.read(move |log| {
289                        log.pin(&snapshot)?;
290                        log.snapshots.remove(&snapshot.token);
291                        log.readers.remove(&snapshot.token);
292                        Ok(())
293                    })
294                })
295                .await
296        })
297    }
298    fn read_schemas<'a>(
299        &'a self, snapshot: &'a BackendSnapshot, selection: &'a SchemaSelection, page: ScanPage,
300    ) -> StorageFuture<'a, Vec<StorageSchema>> {
301        Box::pin(async move {
302            let context = crate::execution::Context::current();
303            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_schemas", outcome = "cancelled"));
304            context
305                .result(span, {
306                    self.read_selected(snapshot, selection, move |log, snapshot, selection| {
307                        let generation = log.pin(snapshot)?;
308                        log.scan(snapshot.at.sequence, generation, page, |row: &StorageSchema| {
309                            Ok(selection.schema.as_ref().is_none_or(|key| key == &row.key))
310                        })
311                    })
312                })
313                .await
314        })
315    }
316    fn read_records<'a>(
317        &'a self, snapshot: &'a BackendSnapshot, selection: &'a RecordSelection, page: ScanPage,
318    ) -> StorageFuture<'a, Vec<StoredRecord>> {
319        Box::pin(async move {
320            let context = crate::execution::Context::current();
321            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_records", outcome = "cancelled"));
322            context
323                .result(span, {
324                    self.read_selected(snapshot, selection, move |log, snapshot, selection| {
325                        let generation = log.pin(snapshot)?;
326                        log.scan(snapshot.at.sequence, generation, page, |row: &StoredRecord| {
327                            Ok(crate::selection::Entry::matches(row, selection))
328                        })
329                    })
330                })
331                .await
332        })
333    }
334    fn read_stream<'a>(
335        &'a self, snapshot: &'a BackendSnapshot, selection: &'a StreamSelection, page: ScanPage,
336    ) -> StorageFuture<'a, Vec<StoredRow>> {
337        Box::pin(async move {
338            let context = crate::execution::Context::current();
339            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_stream", outcome = "cancelled"));
340            context
341                .result(span, {
342                    self.read_selected(snapshot, selection, move |log, snapshot, selection| {
343                        let generation = log.pin(snapshot)?;
344                        log.scan(snapshot.at.sequence, generation, page, |row: &StoredRow| {
345                            Ok(crate::selection::Entry::matches(row, selection))
346                        })
347                    })
348                })
349                .await
350        })
351    }
352    fn read_files<'a>(
353        &'a self, snapshot: &'a BackendSnapshot, selection: &'a FileSelection, page: ScanPage,
354    ) -> StorageFuture<'a, Vec<FileReference>> {
355        Box::pin(async move {
356            let context = crate::execution::Context::current();
357            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_files", outcome = "cancelled"));
358            context
359                .result(span, {
360                    self.read_selected(snapshot, selection, move |log, snapshot, selection| {
361                        let generation = log.pin(snapshot)?;
362                        log.scan(snapshot.at.sequence, generation, page, |row: &FileReference| {
363                            Ok(crate::selection::Entry::matches(row, selection))
364                        })
365                    })
366                })
367                .await
368        })
369    }
370    fn read_objects<'a>(
371        &'a self, snapshot: &'a BackendSnapshot, selection: &'a ObjectSelection, page: ScanPage,
372    ) -> StorageFuture<'a, Vec<ObjectEntry>> {
373        Box::pin(async move {
374            let context = crate::execution::Context::current();
375            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_objects", outcome = "cancelled"));
376            context
377                .result(span, {
378                    self.read_selected(snapshot, selection, move |log, snapshot, selection| log.objects(snapshot, selection, page))
379                })
380                .await
381        })
382    }
383    fn read_object<'a>(&'a self, snapshot: &'a BackendSnapshot, point: &'a ObjectPoint) -> StorageFuture<'a, Option<ObjectEntry>> {
384        Box::pin(async move {
385            let context = crate::execution::Context::current();
386            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_object", outcome = "cancelled"));
387            context.result(span, self.read_selected(snapshot, point, move |log, snapshot, point| log.object(snapshot, point))).await
388        })
389    }
390    fn read_file<'a>(
391        &'a self, snapshot: &'a BackendSnapshot, file: &'a FileReference, offset: u64, maximum: NonZeroUsize,
392    ) -> StorageFuture<'a, Vec<u8>> {
393        Box::pin(async move {
394            let context = crate::execution::Context::current();
395            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_file", outcome = "cancelled"));
396            context
397                .result(span, {
398                    async move {
399                        let files = Arc::clone(&self.files);
400                        self.read_selected(snapshot, file, move |log, snapshot, reference| {
401                            log.pin(snapshot)?;
402                            if log.readers.get(&snapshot.token).is_none_or(|reader| reader.reference != *reference) {
403                                if reference.position.sequence <= snapshot.at.sequence
404                                    && log.one::<FileReference>(reference.position.sequence)?.as_ref() != Some(reference)
405                                {
406                                    return Err(format::conflict("file differs from pinned saved view"));
407                                }
408                                let key = files::key(&reference.metadata.id);
409                                let reader = files.open_reader(&key, reference.clone())?;
410                                log.readers.insert(snapshot.token.clone(), reader);
411                            }
412                            log.readers.get_mut(&snapshot.token).expect("pinned file reader").read(offset, maximum)
413                        })
414                        .await
415                    }
416                })
417                .await
418        })
419    }
420
421    fn release_object_history<'a>(
422        &'a self, schema: &'a SchemaKey, item: &'a ObjectKey, through: &'a StorageCursor,
423    ) -> StorageFuture<'a, ()> {
424        Box::pin(async move {
425            let context = crate::execution::Context::current();
426            let span =
427                context.sync(|| tracing::debug_span!("storage.json.backend", method = "release_object_history", outcome = "cancelled"));
428            context
429                .result(span, {
430                    let schema = schema.clone();
431                    let item = item.clone();
432                    let through = through.clone();
433                    self.read(move |log| log.release(schema, item, through))
434                })
435                .await
436        })
437    }
438    fn prune_objects<'a>(
439        &'a self, through: &'a StorageCursor, maximum: NonZeroUsize, retention: fn(&SchemaKey) -> harmos_domain::Result<ObjectRetention>,
440    ) -> StorageFuture<'a, bool> {
441        Box::pin(async move {
442            let context = crate::execution::Context::current();
443            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "prune_objects", outcome = "cancelled"));
444            context
445                .result(span, {
446                    let through = through.clone();
447                    self.read(move |log| log.prune(&through, maximum, retention))
448                })
449                .await
450        })
451    }
452    fn create_file<'a>(&'a self, metadata: &'a FileMetadata) -> StorageFuture<'a, StagedFile> {
453        Box::pin(async move {
454            let context = crate::execution::Context::current();
455            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "create_file", outcome = "cancelled"));
456            context
457                .result(span, {
458                    let metadata = metadata.clone();
459                    let files = self.files.clone();
460                    let id = metadata.id.clone();
461                    async move {
462                        files
463                            .call(&id, move |files, key| {
464                                let result = files.create(key, metadata, false);
465                                Ok(result)
466                            })
467                            .await?
468                    }
469                })
470                .await
471        })
472    }
473    fn write_file<'a>(&'a self, staging: &'a StagedFile, offset: u64, bytes: FileChunk) -> StorageFuture<'a, ()> {
474        Box::pin(async move {
475            let context = crate::execution::Context::current();
476            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "write_file", outcome = "cancelled"));
477
478            context.result(span, self.file_read(staging.clone(), move |files, key| files.write(key, offset, bytes.as_slice()))).await
479        })
480    }
481    fn finish_file<'a>(
482        &'a self, staging: &'a StagedFile, position: &'a StorageCursor, length: u64, sha256: [u8; 32],
483    ) -> StorageFuture<'a, FileReference> {
484        Box::pin(async move {
485            let context = crate::execution::Context::current();
486            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "finish_file", outcome = "cancelled"));
487            context
488                .result(span, {
489                    let position = position.clone();
490                    self.file_read(staging.clone(), move |files, key| files.finish(key, position, length, sha256))
491                })
492                .await
493        })
494    }
495    fn abort_file<'a>(&'a self, staging: &'a StagedFile) -> StorageFuture<'a, ()> {
496        Box::pin(async move {
497            let context = crate::execution::Context::current();
498            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "abort_file", outcome = "cancelled"));
499
500            context.result(span, self.file_read(staging.clone(), move |files, key| files.abort(key, false, false))).await
501        })
502    }
503    fn supports_file_sessions(&self) -> bool {
504        true
505    }
506    fn create_production<'a>(&'a self, metadata: &'a FileMetadata) -> StorageFuture<'a, StagedFile> {
507        Box::pin(async move {
508            let context = crate::execution::Context::current();
509            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "create_production", outcome = "cancelled"));
510            context
511                .result(span, {
512                    let metadata = metadata.clone();
513                    let files = self.files.clone();
514                    let id = metadata.id.clone();
515                    async move {
516                        files
517                            .call(&id, move |files, key| {
518                                let result = files.create(key, metadata, true);
519                                Ok(result)
520                            })
521                            .await?
522                    }
523                })
524                .await
525        })
526    }
527    fn fence_production<'a>(&'a self, file: &'a StagedFile, expected: u64, next: u64) -> StorageFuture<'a, ()> {
528        Box::pin(async move {
529            let context = crate::execution::Context::current();
530            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "fence_production", outcome = "cancelled"));
531
532            context.result(span, self.file_read(file.clone(), move |files, key| files.fence(key, expected, next))).await
533        })
534    }
535    fn save_production(&self, checkpoint: StorageBatch<FileCheckpoint>) -> StorageFuture<'_, ()> {
536        Box::pin(async move {
537            let context = crate::execution::Context::current();
538            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "save_production", outcome = "cancelled"));
539            context
540                .result(span, {
541                    async move {
542                        let [value] = checkpoint.as_slice() else {
543                            return Err(format::invalid("checkpoint requires one value"));
544                        };
545                        let file = StagedFile {
546                            history: value.session.history.clone(),
547                            id: value.session.id.clone(),
548                        };
549                        self.file(file, move |files, key| files.checkpoint(key, &checkpoint.as_slice()[0])).await
550                    }
551                })
552                .await
553        })
554    }
555    fn recover_productions(&self, limits: ReadLimits) -> StorageFuture<'_, Vec<FileCheckpoint>> {
556        Box::pin(async move {
557            let context = crate::execution::Context::current();
558            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "recover_productions", outcome = "cancelled"));
559            context
560                .result(span, {
561                    let files = self.files.clone();
562                    self.read(move |_| files.recover(limits))
563                })
564                .await
565        })
566    }
567    fn file_progress<'a>(&'a self, file: &'a StagedFile) -> StorageFuture<'a, FileProgress> {
568        Box::pin(async move {
569            let context = crate::execution::Context::current();
570            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "file_progress", outcome = "cancelled"));
571            context.result(span, async move { self.file_read(file.clone(), move |files, key| files.progress(key)).await }).await
572        })
573    }
574
575    fn read_production<'a>(&'a self, file: &'a StagedFile, offset: u64, maximum: NonZeroUsize) -> StorageFuture<'a, Vec<u8>> {
576        Box::pin(async move {
577            let context = crate::execution::Context::current();
578            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_production", outcome = "cancelled"));
579            context
580                .result(span, {
581                    async move { self.file_read(file.clone(), move |files, key| files.read(key, offset, maximum, None)).await }
582                })
583                .await
584        })
585    }
586
587    fn abandon_file<'a>(&'a self, file: &'a StagedFile) -> StorageFuture<'a, ()> {
588        Box::pin(async move {
589            let context = crate::execution::Context::current();
590            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "abandon_file", outcome = "cancelled"));
591
592            context.result(span, self.file_read(file.clone(), move |files, key| files.abort(key, true, false))).await
593        })
594    }
595    fn supports_file_deletion(&self) -> bool {
596        true
597    }
598    fn delete_file<'a>(&'a self, file: &'a StagedFile) -> StorageFuture<'a, ()> {
599        Box::pin(async move {
600            let context = crate::execution::Context::current();
601            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "delete_file", outcome = "cancelled"));
602
603            context.result(span, self.file_read(file.clone(), move |files, key| files.abort(key, false, true))).await
604        })
605    }
606    fn supports_mutation_receipts(&self) -> bool {
607        true
608    }
609    fn save_mutations(&self, receipts: StorageBatch<MutationReceipt>) -> StorageFuture<'_, ()> {
610        Box::pin(async move {
611            let context = crate::execution::Context::current();
612            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "save_mutations", outcome = "cancelled"));
613            context
614                .result(span, {
615                    let files = self.files.clone();
616                    self.call(move |log| {
617                        for receipt in receipts.as_slice() {
618                            files.save_receipt(receipt, log.saved)?;
619                        }
620                        Ok(())
621                    })
622                })
623                .await
624        })
625    }
626    fn recover_mutations(&self, limits: ReadLimits) -> StorageFuture<'_, Vec<MutationReceipt>> {
627        Box::pin(async move {
628            let context = crate::execution::Context::current();
629            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "recover_mutations", outcome = "cancelled"));
630            context
631                .result(span, {
632                    let files = self.files.clone();
633                    self.read(move |_| files.receipts(limits))
634                })
635                .await
636        })
637    }
638    fn read_identities<'a>(
639        &'a self, snapshot: &'a BackendSnapshot, selection: &'a IdentitySelection, page: ScanPage,
640    ) -> StorageFuture<'a, Vec<StoredIdentity>> {
641        Box::pin(async move {
642            let context = crate::execution::Context::current();
643            let span = context.sync(|| tracing::debug_span!("storage.json.backend", method = "read_identities", outcome = "cancelled"));
644            context
645                .result(span, {
646                    self.read_selected(snapshot, selection, move |log, snapshot, selection| log.catalog(snapshot, selection, page))
647                })
648                .await
649        })
650    }
651}
652
653/* === Public Functions === */
654
655/* === Private Functions === */
656
657fn task(error: tokio::task::JoinError) -> Error {
658    Error::backend("JSON storage task", BackendFailure::Permanent, error)
659}
660
661/* === Tests === */