summaryrefslogtreecommitdiffstats
path: root/src/api
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-20 06:10:48 +0200
committertv <tv@krebsco.de>2026-10-20 06:12:35 +0200
commit55acf6f276efa952da7bb6f8e0377791549f3ea1 (patch)
tree9541e63791da54f11c621110e2e6060d6a0e0c9b /src/api
parent786f172e7fec1d09cc43fff6b96730132fbfd048 (diff)
serve stale reads during rejoinHEADmaster
Diffstat (limited to 'src/api')
-rw-r--r--src/api/store.rs40
1 files changed, 28 insertions, 12 deletions
diff --git a/src/api/store.rs b/src/api/store.rs
index de5ae9b..e991005 100644
--- a/src/api/store.rs
+++ b/src/api/store.rs
@@ -17,7 +17,7 @@ use tokio::sync::broadcast;
use tracing::{debug, error, instrument};
use super::{parse_key_path, response_for_value, store_status};
-use crate::state::AppState;
+use crate::state::{AppState, ReadSource};
use crate::store::{self, StoreError};
use crate::sync::paths_related;
@@ -62,8 +62,9 @@ pub(crate) async fn get_store_root(
let accept = headers.get(ACCEPT).cloned();
let want_conflicts = query.conflicts;
- state
- .render(
+ let (response, source) = state
+ .render_read(
+ true,
move |doc| {
if want_conflicts {
store::read_path_with_conflicts(doc, &Vec::new())
@@ -82,7 +83,8 @@ pub(crate) async fn get_store_root(
}
},
)
- .await
+ .await?;
+ mark_source(response, source)
}
#[utoipa::path(
@@ -98,7 +100,7 @@ pub(crate) async fn get_store_root(
(status = 400, description = "Invalid store path"),
(status = 404, description = "Value does not exist"),
(status = 500, description = "Failed to read or serialize the value"),
- (status = 503, description = "Node is not ready")
+ (status = 503, description = "Node is not ready (and not rejoining with a stale view)")
)
)]
pub(crate) async fn get_store_entry(
@@ -107,17 +109,14 @@ pub(crate) async fn get_store_entry(
headers: HeaderMap,
Query(query): Query<ReadQuery>,
) -> Result<Response, StatusCode> {
- if !state.is_ready().await {
- return Err(StatusCode::SERVICE_UNAVAILABLE);
- }
-
let path = parse_key_path(&key)?;
let accept = headers.get(ACCEPT).cloned();
let want_conflicts = query.conflicts;
- state
- .render(
+ let (response, source) = state
+ .render_read(
+ false,
move |doc| {
if want_conflicts {
store::read_path_with_conflicts(doc, &path)
@@ -136,7 +135,24 @@ pub(crate) async fn get_store_entry(
}
},
)
- .await
+ .await?;
+ mark_source(response, source)
+}
+
+/// Header on reads served from the pre-rejoin document; its value is that epoch.
+const STALE_HEADER: &str = "x-continuum-stale";
+
+fn mark_source(
+ response: Result<Response, StatusCode>,
+ source: ReadSource,
+) -> Result<Response, StatusCode> {
+ let mut response = response?;
+ if let ReadSource::Stale(epoch) = source {
+ if let Ok(value) = HeaderValue::from_str(&epoch.to_string()) {
+ response.headers_mut().insert(STALE_HEADER, value);
+ }
+ }
+ Ok(response)
}
#[utoipa::path(