1mod config;
15mod files;
16mod format;
17mod log;
18mod reads;
19mod sessions;
20
21pub use config::JsonStorageSettings;
24
25use std::{
29 num::NonZeroUsize,
30 sync::{Arc, atomic::Ordering},
31};
32
33use tokio::sync::Mutex;
35
36use crate::{
38 query::{FileSelection, ObjectSelection, RecordSelection, SchemaSelection, StreamSelection},
39 *,
40};
41use files::Files;
42use log::{Log, Row};
43
44pub struct JsonStorage {
53 settings: JsonStorageSettings,
54 history: StoreHistory,
55 owner: Arc<Mutex<Option<Log>>>,
56 files: Arc<Files>,
57}
58
59impl 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
653fn task(error: tokio::task::JoinError) -> Error {
658 Error::backend("JSON storage task", BackendFailure::Permanent, error)
659}
660
661