diff --git a/safekeeper/src/handler.rs b/safekeeper/src/handler.rs
index 60df5dd372..99f0e90711 100644
--- a/safekeeper/src/handler.rs
+++ b/safekeeper/src/handler.rs
@@ -218,7 +218,7 @@ impl SafekeeperPostgresHandler {
/// Handle IDENTIFY_SYSTEM replication command
///
fn handle_identify_system(&mut self, pgb: &mut PostgresBackend) -> Result<(), QueryError> {
- let tli = GlobalTimelines::get(self.ttid)?;
+ let tli = GlobalTimelines::get(self.ttid).map_err(|e| QueryError::Other(e.into()))?;
let lsn = if self.is_walproposer_recovery() {
// walproposer should get all local WAL until flush_lsn
diff --git a/safekeeper/src/http/openapi_spec.yaml b/safekeeper/src/http/openapi_spec.yaml
index da225f244b..51ce7589a0 100644
--- a/safekeeper/src/http/openapi_spec.yaml
+++ b/safekeeper/src/http/openapi_spec.yaml
@@ -119,6 +119,12 @@ paths:
$ref: "#/components/responses/ForbiddenError"
default:
$ref: "#/components/responses/GenericError"
+ "404":
+ description: Timeline not found
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NotFoundError"
delete:
tags:
diff --git a/safekeeper/src/http/routes.rs b/safekeeper/src/http/routes.rs
index ced9599b36..b157fcb076 100644
--- a/safekeeper/src/http/routes.rs
+++ b/safekeeper/src/http/routes.rs
@@ -1,6 +1,5 @@
use hyper::{Body, Request, Response, StatusCode, Uri};
-use anyhow::Context;
use once_cell::sync::Lazy;
use postgres_ffi::WAL_SEGMENT_SIZE;
use safekeeper_api::models::SkTimelineInfo;
@@ -112,12 +111,7 @@ async fn timeline_status_handler(request: Request
) -> Result) -> Result Result<(), QueryError> {
let _enter = info_span!("WAL sender", ttid = %spg.ttid).entered();
- let tli = GlobalTimelines::get(spg.ttid)?;
+ let tli = GlobalTimelines::get(spg.ttid).map_err(|e| QueryError::Other(e.into()))?;
// spawn the background thread which receives HotStandbyFeedback messages.
let bg_timeline = Arc::clone(&tli);
diff --git a/safekeeper/src/timeline.rs b/safekeeper/src/timeline.rs
index 7479741774..98c565cde4 100644
--- a/safekeeper/src/timeline.rs
+++ b/safekeeper/src/timeline.rs
@@ -1,7 +1,7 @@
//! This module implements Timeline lifecycle management and has all neccessary code
//! to glue together SafeKeeper and all other background services.
-use anyhow::{bail, Result};
+use anyhow::{anyhow, bail, Result};
use parking_lot::{Mutex, MutexGuard};
use postgres_ffi::XLogSegNo;
use pq_proto::ReplicationFeedback;
@@ -13,6 +13,7 @@ use tokio::{
time::Instant,
};
use tracing::*;
+use utils::http::error::ApiError;
use utils::{
id::{NodeId, TenantTimelineId},
lsn::Lsn,
@@ -356,6 +357,18 @@ pub enum TimelineError {
UninitialinzedPgVersion(TenantTimelineId),
}
+// Convert to HTTP API error.
+impl From for ApiError {
+ fn from(te: TimelineError) -> ApiError {
+ match te {
+ TimelineError::NotFound(ttid) => {
+ ApiError::NotFound(anyhow!("timeline {} not found", ttid))
+ }
+ _ => ApiError::InternalServerError(anyhow!("{}", te)),
+ }
+ }
+}
+
/// Timeline struct manages lifecycle (creation, deletion, restore) of a safekeeper timeline.
/// It also holds SharedState and provides mutually exclusive access to it.
pub struct Timeline {
diff --git a/safekeeper/src/timelines_global_map.rs b/safekeeper/src/timelines_global_map.rs
index baef17ffa8..c99ca0a51a 100644
--- a/safekeeper/src/timelines_global_map.rs
+++ b/safekeeper/src/timelines_global_map.rs
@@ -5,7 +5,7 @@
use crate::safekeeper::ServerInfo;
use crate::timeline::{Timeline, TimelineError};
use crate::SafeKeeperConf;
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{bail, Context, Result};
use once_cell::sync::Lazy;
use serde::Serialize;
use std::collections::HashMap;
@@ -50,11 +50,11 @@ impl GlobalTimelinesState {
}
/// Get timeline from the map. Returns error if timeline doesn't exist.
- fn get(&self, ttid: &TenantTimelineId) -> Result> {
+ fn get(&self, ttid: &TenantTimelineId) -> Result, TimelineError> {
self.timelines
.get(ttid)
.cloned()
- .ok_or_else(|| anyhow!(TimelineError::NotFound(*ttid)))
+ .ok_or(TimelineError::NotFound(*ttid))
}
}
@@ -240,17 +240,17 @@ impl GlobalTimelines {
/// Get a timeline from the global map. If it's not present, it doesn't exist on disk,
/// or was corrupted and couldn't be loaded on startup. Returned timeline is always valid,
/// i.e. loaded in memory and not cancelled.
- pub fn get(ttid: TenantTimelineId) -> Result> {
+ pub fn get(ttid: TenantTimelineId) -> Result, TimelineError> {
let res = TIMELINES_STATE.lock().unwrap().get(&ttid);
match res {
Ok(tli) => {
if tli.is_cancelled() {
- anyhow::bail!(TimelineError::Cancelled(ttid));
+ return Err(TimelineError::Cancelled(ttid));
}
Ok(tli)
}
- Err(e) => Err(e),
+ _ => res,
}
}