aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/quarantine.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/quarantine.rs')
-rw-r--r--src/remake/quarantine.rs39
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.