diff options
Diffstat (limited to 'src/remake/quarantine.rs')
| -rw-r--r-- | src/remake/quarantine.rs | 39 |
1 files changed, 27 insertions, 12 deletions
diff --git a/src/remake/quarantine.rs b/src/remake/quarantine.rs index 5300f54..bb34d73 100644 --- a/src/remake/quarantine.rs +++ b/src/remake/quarantine.rs @@ -5,16 +5,16 @@ //! * `GET /admin/quarantine/{id}` shows the writes: every changed path with the value //! the old document's merged view holds there (`deleted` if none). //! * `GET /admin/quarantine/{id}/document` downloads the old document -//! (`AutoCommit::save()`), for inspection with other tools. +//! (`Document::save()`), for inspection with other tools. //! * `POST /admin/quarantine/{id}/replay` writes them into the current epoch anyway (as //! fresh writes, so they win over what is there) and removes the entry. //! * `DELETE /admin/quarantine/{id}` discards the entry. //! //! Quarantine lives in memory on the node that holds it. -use std::convert::Infallible; +use std::sync::Arc; -use automerge::AutoCommit; +use crate::document::Document; use axum::{ Json, extract::{Path, State}, @@ -33,7 +33,7 @@ use super::local::Quarantined; use crate::api::error_response; use crate::clock::UnixMillis; use crate::document::replica::Epoch; -use crate::document::store; +use crate::document::store::{self, StoreError}; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -69,7 +69,7 @@ pub(super) fn add<H: Host>( host: &H, epoch: Epoch, target: Epoch, - document: &AutoCommit, + document: &Document, quarantined: Vec<(Vec<String>, String)>, ) -> Option<String> { if quarantined.is_empty() { @@ -132,7 +132,7 @@ pub(crate) async fn get<H: Host>(State(host): State<H>, Path(id): Path<String>) } pub(crate) async fn document<H: Host>(State(host): State<H>, Path(id): Path<String>) -> Response { - let Some(mut document) = host + let Some(document) = host .remake() .with_quarantined(&id, |entry| entry.document.clone()) else { @@ -167,15 +167,30 @@ pub(crate) async fn replay<H: Host>(State(host): State<H>, Path(id): Path<String return not_found(); }; - let (stats, entry) = host - .commit_doc(move |doc| { + let entry = Arc::new(entry); + let replayed = { + let entry = Arc::clone(&entry); + host.commit_doc(move |doc| { // As they are: the operator decided they belong at their old paths. - let stats = - delta::replay_paths(&entry.document, doc, &entry.paths, &Translator::default()); - Ok::<_, Infallible>((stats, entry)) + Ok::<_, StoreError>(delta::replay_paths( + &entry.document, + doc, + &entry.paths, + &Translator::default(), + )) }) .await - .unwrap_or_else(|never| match never {}); + }; + // The commit's task held the only other reference, and it is done. + let entry = Arc::into_inner(entry).expect("the replay's reference is gone"); + let stats = match replayed { + Ok(stats) => stats, + Err(error) => { + // Rejoining since the check above: keep the entry for later. + host.remake().restore_quarantined(entry); + return error_response(StatusCode::SERVICE_UNAVAILABLE, &error.to_string(), None); + } + }; if stats.failed > 0 && stats.replayed == 0 { // Nothing went in; keep the entry for another look. |
