LCOV - code coverage report
Current view: top level - pageserver/src - page_service.rs (source / functions) Coverage Total Hit
Test: 52d9d4a58355424a48c56cb9ba9670a073f618b9.info Lines: 33.5 % 847 284
Test Date: 2024-11-21 08:31:22 Functions: 8.8 % 91 8

            Line data    Source code
       1              : //! The Page Service listens for client connections and serves their GetPage@LSN
       2              : //! requests.
       3              : 
       4              : use anyhow::{bail, Context};
       5              : use async_compression::tokio::write::GzipEncoder;
       6              : use bytes::Buf;
       7              : use futures::FutureExt;
       8              : use itertools::Itertools;
       9              : use once_cell::sync::OnceCell;
      10              : use pageserver_api::models::{self, TenantState};
      11              : use pageserver_api::models::{
      12              :     PagestreamBeMessage, PagestreamDbSizeRequest, PagestreamDbSizeResponse,
      13              :     PagestreamErrorResponse, PagestreamExistsRequest, PagestreamExistsResponse,
      14              :     PagestreamFeMessage, PagestreamGetPageRequest, PagestreamGetSlruSegmentRequest,
      15              :     PagestreamGetSlruSegmentResponse, PagestreamNblocksRequest, PagestreamNblocksResponse,
      16              :     PagestreamProtocolVersion,
      17              : };
      18              : use pageserver_api::shard::TenantShardId;
      19              : use postgres_backend::{is_expected_io_error, AuthType, PostgresBackend, QueryError};
      20              : use pq_proto::framed::ConnectionError;
      21              : use pq_proto::FeStartupPacket;
      22              : use pq_proto::{BeMessage, FeMessage, RowDescriptor};
      23              : use std::borrow::Cow;
      24              : use std::io;
      25              : use std::str;
      26              : use std::str::FromStr;
      27              : use std::sync::Arc;
      28              : use std::time::SystemTime;
      29              : use std::time::{Duration, Instant};
      30              : use tokio::io::{AsyncRead, AsyncWrite};
      31              : use tokio::io::{AsyncWriteExt, BufWriter};
      32              : use tokio::task::JoinHandle;
      33              : use tokio_util::sync::CancellationToken;
      34              : use tracing::*;
      35              : use utils::{
      36              :     auth::{Claims, Scope, SwappableJwtAuth},
      37              :     id::{TenantId, TimelineId},
      38              :     lsn::Lsn,
      39              :     simple_rcu::RcuReadGuard,
      40              : };
      41              : 
      42              : use crate::auth::check_permission;
      43              : use crate::basebackup;
      44              : use crate::basebackup::BasebackupError;
      45              : use crate::config::PageServerConf;
      46              : use crate::context::{DownloadBehavior, RequestContext};
      47              : use crate::metrics::{self};
      48              : use crate::metrics::{ComputeCommandKind, COMPUTE_COMMANDS_COUNTERS, LIVE_CONNECTIONS};
      49              : use crate::pgdatadir_mapping::Version;
      50              : use crate::span::debug_assert_current_span_has_tenant_and_timeline_id;
      51              : use crate::span::debug_assert_current_span_has_tenant_and_timeline_id_no_shard_id;
      52              : use crate::task_mgr::TaskKind;
      53              : use crate::task_mgr::{self, COMPUTE_REQUEST_RUNTIME};
      54              : use crate::tenant::mgr::ShardSelector;
      55              : use crate::tenant::mgr::TenantManager;
      56              : use crate::tenant::mgr::{GetActiveTenantError, GetTenantError, ShardResolveResult};
      57              : use crate::tenant::timeline::{self, WaitLsnError};
      58              : use crate::tenant::GetTimelineError;
      59              : use crate::tenant::PageReconstructError;
      60              : use crate::tenant::Timeline;
      61              : use pageserver_api::key::rel_block_to_key;
      62              : use pageserver_api::reltag::{BlockNumber, RelTag, SlruKind};
      63              : use postgres_ffi::pg_constants::DEFAULTTABLESPACE_OID;
      64              : use postgres_ffi::BLCKSZ;
      65              : 
      66              : /// How long we may wait for a [`crate::tenant::mgr::TenantSlot::InProgress`]` and/or a [`crate::tenant::Tenant`] which
      67              : /// is not yet in state [`TenantState::Active`].
      68              : ///
      69              : /// NB: this is a different value than [`crate::http::routes::ACTIVE_TENANT_TIMEOUT`].
      70              : const ACTIVE_TENANT_TIMEOUT: Duration = Duration::from_millis(30000);
      71              : 
      72              : ///////////////////////////////////////////////////////////////////////////////
      73              : 
      74              : pub struct Listener {
      75              :     cancel: CancellationToken,
      76              :     /// Cancel the listener task through `listen_cancel` to shut down the listener
      77              :     /// and get a handle on the existing connections.
      78              :     task: JoinHandle<Connections>,
      79              : }
      80              : 
      81              : pub struct Connections {
      82              :     cancel: CancellationToken,
      83              :     tasks: tokio::task::JoinSet<ConnectionHandlerResult>,
      84              : }
      85              : 
      86            0 : pub fn spawn(
      87            0 :     conf: &'static PageServerConf,
      88            0 :     tenant_manager: Arc<TenantManager>,
      89            0 :     pg_auth: Option<Arc<SwappableJwtAuth>>,
      90            0 :     tcp_listener: tokio::net::TcpListener,
      91            0 : ) -> Listener {
      92            0 :     let cancel = CancellationToken::new();
      93            0 :     let libpq_ctx = RequestContext::todo_child(
      94            0 :         TaskKind::LibpqEndpointListener,
      95            0 :         // listener task shouldn't need to download anything. (We will
      96            0 :         // create a separate sub-contexts for each connection, with their
      97            0 :         // own download behavior. This context is used only to listen and
      98            0 :         // accept connections.)
      99            0 :         DownloadBehavior::Error,
     100            0 :     );
     101            0 :     let task = COMPUTE_REQUEST_RUNTIME.spawn(task_mgr::exit_on_panic_or_error(
     102            0 :         "libpq listener",
     103            0 :         libpq_listener_main(
     104            0 :             tenant_manager,
     105            0 :             pg_auth,
     106            0 :             tcp_listener,
     107            0 :             conf.pg_auth_type,
     108            0 :             conf.server_side_batch_timeout,
     109            0 :             libpq_ctx,
     110            0 :             cancel.clone(),
     111            0 :         )
     112            0 :         .map(anyhow::Ok),
     113            0 :     ));
     114            0 : 
     115            0 :     Listener { cancel, task }
     116            0 : }
     117              : 
     118              : impl Listener {
     119            0 :     pub async fn stop_accepting(self) -> Connections {
     120            0 :         self.cancel.cancel();
     121            0 :         self.task
     122            0 :             .await
     123            0 :             .expect("unreachable: we wrap the listener task in task_mgr::exit_on_panic_or_error")
     124            0 :     }
     125              : }
     126              : impl Connections {
     127            0 :     pub(crate) async fn shutdown(self) {
     128            0 :         let Self { cancel, mut tasks } = self;
     129            0 :         cancel.cancel();
     130            0 :         while let Some(res) = tasks.join_next().await {
     131            0 :             Self::handle_connection_completion(res);
     132            0 :         }
     133            0 :     }
     134              : 
     135            0 :     fn handle_connection_completion(res: Result<anyhow::Result<()>, tokio::task::JoinError>) {
     136            0 :         match res {
     137            0 :             Ok(Ok(())) => {}
     138            0 :             Ok(Err(e)) => error!("error in page_service connection task: {:?}", e),
     139            0 :             Err(e) => error!("page_service connection task panicked: {:?}", e),
     140              :         }
     141            0 :     }
     142              : }
     143              : 
     144              : ///
     145              : /// Main loop of the page service.
     146              : ///
     147              : /// Listens for connections, and launches a new handler task for each.
     148              : ///
     149              : /// Returns Ok(()) upon cancellation via `cancel`, returning the set of
     150              : /// open connections.
     151              : ///
     152            0 : pub async fn libpq_listener_main(
     153            0 :     tenant_manager: Arc<TenantManager>,
     154            0 :     auth: Option<Arc<SwappableJwtAuth>>,
     155            0 :     listener: tokio::net::TcpListener,
     156            0 :     auth_type: AuthType,
     157            0 :     server_side_batch_timeout: Option<Duration>,
     158            0 :     listener_ctx: RequestContext,
     159            0 :     listener_cancel: CancellationToken,
     160            0 : ) -> Connections {
     161            0 :     let connections_cancel = CancellationToken::new();
     162            0 :     let mut connection_handler_tasks = tokio::task::JoinSet::default();
     163              : 
     164              :     loop {
     165            0 :         let accepted = tokio::select! {
     166              :             biased;
     167            0 :             _ = listener_cancel.cancelled() => break,
     168            0 :             next = connection_handler_tasks.join_next(), if !connection_handler_tasks.is_empty() => {
     169            0 :                 let res = next.expect("we dont poll while empty");
     170            0 :                 Connections::handle_connection_completion(res);
     171            0 :                 continue;
     172              :             }
     173            0 :             accepted = listener.accept() => accepted,
     174            0 :         };
     175            0 : 
     176            0 :         match accepted {
     177            0 :             Ok((socket, peer_addr)) => {
     178            0 :                 // Connection established. Spawn a new task to handle it.
     179            0 :                 debug!("accepted connection from {}", peer_addr);
     180            0 :                 let local_auth = auth.clone();
     181            0 :                 let connection_ctx = listener_ctx
     182            0 :                     .detached_child(TaskKind::PageRequestHandler, DownloadBehavior::Download);
     183            0 :                 connection_handler_tasks.spawn(page_service_conn_main(
     184            0 :                     tenant_manager.clone(),
     185            0 :                     local_auth,
     186            0 :                     socket,
     187            0 :                     auth_type,
     188            0 :                     server_side_batch_timeout,
     189            0 :                     connection_ctx,
     190            0 :                     connections_cancel.child_token(),
     191            0 :                 ));
     192              :             }
     193            0 :             Err(err) => {
     194            0 :                 // accept() failed. Log the error, and loop back to retry on next connection.
     195            0 :                 error!("accept() failed: {:?}", err);
     196              :             }
     197              :         }
     198              :     }
     199              : 
     200            0 :     debug!("page_service listener loop terminated");
     201              : 
     202            0 :     Connections {
     203            0 :         cancel: connections_cancel,
     204            0 :         tasks: connection_handler_tasks,
     205            0 :     }
     206            0 : }
     207              : 
     208              : type ConnectionHandlerResult = anyhow::Result<()>;
     209              : 
     210            0 : #[instrument(skip_all, fields(peer_addr))]
     211              : async fn page_service_conn_main(
     212              :     tenant_manager: Arc<TenantManager>,
     213              :     auth: Option<Arc<SwappableJwtAuth>>,
     214              :     socket: tokio::net::TcpStream,
     215              :     auth_type: AuthType,
     216              :     server_side_batch_timeout: Option<Duration>,
     217              :     connection_ctx: RequestContext,
     218              :     cancel: CancellationToken,
     219              : ) -> ConnectionHandlerResult {
     220              :     let _guard = LIVE_CONNECTIONS
     221              :         .with_label_values(&["page_service"])
     222              :         .guard();
     223              : 
     224              :     socket
     225              :         .set_nodelay(true)
     226              :         .context("could not set TCP_NODELAY")?;
     227              : 
     228              :     let peer_addr = socket.peer_addr().context("get peer address")?;
     229              :     tracing::Span::current().record("peer_addr", field::display(peer_addr));
     230              : 
     231              :     // setup read timeout of 10 minutes. the timeout is rather arbitrary for requirements:
     232              :     // - long enough for most valid compute connections
     233              :     // - less than infinite to stop us from "leaking" connections to long-gone computes
     234              :     //
     235              :     // no write timeout is used, because the kernel is assumed to error writes after some time.
     236              :     let mut socket = tokio_io_timeout::TimeoutReader::new(socket);
     237              : 
     238              :     let default_timeout_ms = 10 * 60 * 1000; // 10 minutes by default
     239            0 :     let socket_timeout_ms = (|| {
     240            0 :         fail::fail_point!("simulated-bad-compute-connection", |avg_timeout_ms| {
     241              :             // Exponential distribution for simulating
     242              :             // poor network conditions, expect about avg_timeout_ms to be around 15
     243              :             // in tests
     244            0 :             if let Some(avg_timeout_ms) = avg_timeout_ms {
     245            0 :                 let avg = avg_timeout_ms.parse::<i64>().unwrap() as f32;
     246            0 :                 let u = rand::random::<f32>();
     247            0 :                 ((1.0 - u).ln() / (-avg)) as u64
     248              :             } else {
     249            0 :                 default_timeout_ms
     250              :             }
     251            0 :         });
     252            0 :         default_timeout_ms
     253              :     })();
     254              : 
     255              :     // A timeout here does not mean the client died, it can happen if it's just idle for
     256              :     // a while: we will tear down this PageServerHandler and instantiate a new one if/when
     257              :     // they reconnect.
     258              :     socket.set_timeout(Some(std::time::Duration::from_millis(socket_timeout_ms)));
     259              :     let socket = std::pin::pin!(socket);
     260              : 
     261              :     fail::fail_point!("ps::connection-start::pre-login");
     262              : 
     263              :     // XXX: pgbackend.run() should take the connection_ctx,
     264              :     // and create a child per-query context when it invokes process_query.
     265              :     // But it's in a shared crate, so, we store connection_ctx inside PageServerHandler
     266              :     // and create the per-query context in process_query ourselves.
     267              :     let mut conn_handler = PageServerHandler::new(
     268              :         tenant_manager,
     269              :         auth,
     270              :         server_side_batch_timeout,
     271              :         connection_ctx,
     272              :         cancel.clone(),
     273              :     );
     274              :     let pgbackend = PostgresBackend::new_from_io(socket, peer_addr, auth_type, None)?;
     275              : 
     276              :     match pgbackend.run(&mut conn_handler, &cancel).await {
     277              :         Ok(()) => {
     278              :             // we've been requested to shut down
     279              :             Ok(())
     280              :         }
     281              :         Err(QueryError::Disconnected(ConnectionError::Io(io_error))) => {
     282              :             if is_expected_io_error(&io_error) {
     283              :                 info!("Postgres client disconnected ({io_error})");
     284              :                 Ok(())
     285              :             } else {
     286              :                 let tenant_id = conn_handler.timeline_handles.tenant_id();
     287              :                 Err(io_error).context(format!(
     288              :                     "Postgres connection error for tenant_id={:?} client at peer_addr={}",
     289              :                     tenant_id, peer_addr
     290              :                 ))
     291              :             }
     292              :         }
     293              :         other => {
     294              :             let tenant_id = conn_handler.timeline_handles.tenant_id();
     295              :             other.context(format!(
     296              :                 "Postgres query error for tenant_id={:?} client peer_addr={}",
     297              :                 tenant_id, peer_addr
     298              :             ))
     299              :         }
     300              :     }
     301              : }
     302              : 
     303              : struct PageServerHandler {
     304              :     auth: Option<Arc<SwappableJwtAuth>>,
     305              :     claims: Option<Claims>,
     306              : 
     307              :     /// The context created for the lifetime of the connection
     308              :     /// services by this PageServerHandler.
     309              :     /// For each query received over the connection,
     310              :     /// `process_query` creates a child context from this one.
     311              :     connection_ctx: RequestContext,
     312              : 
     313              :     cancel: CancellationToken,
     314              : 
     315              :     timeline_handles: TimelineHandles,
     316              : 
     317              :     /// Messages queued up for the next processing batch
     318              :     next_batch: Option<BatchedFeMessage>,
     319              : 
     320              :     /// See [`PageServerConf::server_side_batch_timeout`]
     321              :     server_side_batch_timeout: Option<Duration>,
     322              : }
     323              : 
     324              : struct TimelineHandles {
     325              :     wrapper: TenantManagerWrapper,
     326              :     /// Note on size: the typical size of this map is 1.  The largest size we expect
     327              :     /// to see is the number of shards divided by the number of pageservers (typically < 2),
     328              :     /// or the ratio used when splitting shards (i.e. how many children created from one)
     329              :     /// parent shard, where a "large" number might be ~8.
     330              :     handles: timeline::handle::Cache<TenantManagerTypes>,
     331              : }
     332              : 
     333              : impl TimelineHandles {
     334            0 :     fn new(tenant_manager: Arc<TenantManager>) -> Self {
     335            0 :         Self {
     336            0 :             wrapper: TenantManagerWrapper {
     337            0 :                 tenant_manager,
     338            0 :                 tenant_id: OnceCell::new(),
     339            0 :             },
     340            0 :             handles: Default::default(),
     341            0 :         }
     342            0 :     }
     343            0 :     async fn get(
     344            0 :         &mut self,
     345            0 :         tenant_id: TenantId,
     346            0 :         timeline_id: TimelineId,
     347            0 :         shard_selector: ShardSelector,
     348            0 :     ) -> Result<timeline::handle::Handle<TenantManagerTypes>, GetActiveTimelineError> {
     349            0 :         if *self.wrapper.tenant_id.get_or_init(|| tenant_id) != tenant_id {
     350            0 :             return Err(GetActiveTimelineError::Tenant(
     351            0 :                 GetActiveTenantError::SwitchedTenant,
     352            0 :             ));
     353            0 :         }
     354            0 :         self.handles
     355            0 :             .get(timeline_id, shard_selector, &self.wrapper)
     356            0 :             .await
     357            0 :             .map_err(|e| match e {
     358            0 :                 timeline::handle::GetError::TenantManager(e) => e,
     359              :                 timeline::handle::GetError::TimelineGateClosed => {
     360            0 :                     trace!("timeline gate closed");
     361            0 :                     GetActiveTimelineError::Timeline(GetTimelineError::ShuttingDown)
     362              :                 }
     363              :                 timeline::handle::GetError::PerTimelineStateShutDown => {
     364            0 :                     trace!("per-timeline state shut down");
     365            0 :                     GetActiveTimelineError::Timeline(GetTimelineError::ShuttingDown)
     366              :                 }
     367            0 :             })
     368            0 :     }
     369              : 
     370            0 :     fn tenant_id(&self) -> Option<TenantId> {
     371            0 :         self.wrapper.tenant_id.get().copied()
     372            0 :     }
     373              : }
     374              : 
     375              : pub(crate) struct TenantManagerWrapper {
     376              :     tenant_manager: Arc<TenantManager>,
     377              :     // We do not support switching tenant_id on a connection at this point.
     378              :     // We can can add support for this later if needed without changing
     379              :     // the protocol.
     380              :     tenant_id: once_cell::sync::OnceCell<TenantId>,
     381              : }
     382              : 
     383              : #[derive(Debug)]
     384              : pub(crate) struct TenantManagerTypes;
     385              : 
     386              : impl timeline::handle::Types for TenantManagerTypes {
     387              :     type TenantManagerError = GetActiveTimelineError;
     388              :     type TenantManager = TenantManagerWrapper;
     389              :     type Timeline = Arc<Timeline>;
     390              : }
     391              : 
     392              : impl timeline::handle::ArcTimeline<TenantManagerTypes> for Arc<Timeline> {
     393            0 :     fn gate(&self) -> &utils::sync::gate::Gate {
     394            0 :         &self.gate
     395            0 :     }
     396              : 
     397            0 :     fn shard_timeline_id(&self) -> timeline::handle::ShardTimelineId {
     398            0 :         Timeline::shard_timeline_id(self)
     399            0 :     }
     400              : 
     401            0 :     fn per_timeline_state(&self) -> &timeline::handle::PerTimelineState<TenantManagerTypes> {
     402            0 :         &self.handles
     403            0 :     }
     404              : 
     405            0 :     fn get_shard_identity(&self) -> &pageserver_api::shard::ShardIdentity {
     406            0 :         Timeline::get_shard_identity(self)
     407            0 :     }
     408              : }
     409              : 
     410              : impl timeline::handle::TenantManager<TenantManagerTypes> for TenantManagerWrapper {
     411            0 :     async fn resolve(
     412            0 :         &self,
     413            0 :         timeline_id: TimelineId,
     414            0 :         shard_selector: ShardSelector,
     415            0 :     ) -> Result<Arc<Timeline>, GetActiveTimelineError> {
     416            0 :         let tenant_id = self.tenant_id.get().expect("we set this in get()");
     417            0 :         let timeout = ACTIVE_TENANT_TIMEOUT;
     418            0 :         let wait_start = Instant::now();
     419            0 :         let deadline = wait_start + timeout;
     420            0 :         let tenant_shard = loop {
     421            0 :             let resolved = self
     422            0 :                 .tenant_manager
     423            0 :                 .resolve_attached_shard(tenant_id, shard_selector);
     424            0 :             match resolved {
     425            0 :                 ShardResolveResult::Found(tenant_shard) => break tenant_shard,
     426              :                 ShardResolveResult::NotFound => {
     427            0 :                     return Err(GetActiveTimelineError::Tenant(
     428            0 :                         GetActiveTenantError::NotFound(GetTenantError::NotFound(*tenant_id)),
     429            0 :                     ));
     430              :                 }
     431            0 :                 ShardResolveResult::InProgress(barrier) => {
     432            0 :                     // We can't authoritatively answer right now: wait for InProgress state
     433            0 :                     // to end, then try again
     434            0 :                     tokio::select! {
     435            0 :                         _  = barrier.wait() => {
     436            0 :                             // The barrier completed: proceed around the loop to try looking up again
     437            0 :                         },
     438            0 :                         _ = tokio::time::sleep(deadline.duration_since(Instant::now())) => {
     439            0 :                             return Err(GetActiveTimelineError::Tenant(GetActiveTenantError::WaitForActiveTimeout {
     440            0 :                                 latest_state: None,
     441            0 :                                 wait_time: timeout,
     442            0 :                             }));
     443              :                         }
     444              :                     }
     445              :                 }
     446              :             };
     447              :         };
     448              : 
     449            0 :         tracing::debug!("Waiting for tenant to enter active state...");
     450            0 :         tenant_shard
     451            0 :             .wait_to_become_active(deadline.duration_since(Instant::now()))
     452            0 :             .await
     453            0 :             .map_err(GetActiveTimelineError::Tenant)?;
     454              : 
     455            0 :         let timeline = tenant_shard
     456            0 :             .get_timeline(timeline_id, true)
     457            0 :             .map_err(GetActiveTimelineError::Timeline)?;
     458            0 :         set_tracing_field_shard_id(&timeline);
     459            0 :         Ok(timeline)
     460            0 :     }
     461              : }
     462              : 
     463            0 : #[derive(thiserror::Error, Debug)]
     464              : enum PageStreamError {
     465              :     /// We encountered an error that should prompt the client to reconnect:
     466              :     /// in practice this means we drop the connection without sending a response.
     467              :     #[error("Reconnect required: {0}")]
     468              :     Reconnect(Cow<'static, str>),
     469              : 
     470              :     /// We were instructed to shutdown while processing the query
     471              :     #[error("Shutting down")]
     472              :     Shutdown,
     473              : 
     474              :     /// Something went wrong reading a page: this likely indicates a pageserver bug
     475              :     #[error("Read error")]
     476              :     Read(#[source] PageReconstructError),
     477              : 
     478              :     /// Ran out of time waiting for an LSN
     479              :     #[error("LSN timeout: {0}")]
     480              :     LsnTimeout(WaitLsnError),
     481              : 
     482              :     /// The entity required to serve the request (tenant or timeline) is not found,
     483              :     /// or is not found in a suitable state to serve a request.
     484              :     #[error("Not found: {0}")]
     485              :     NotFound(Cow<'static, str>),
     486              : 
     487              :     /// Request asked for something that doesn't make sense, like an invalid LSN
     488              :     #[error("Bad request: {0}")]
     489              :     BadRequest(Cow<'static, str>),
     490              : }
     491              : 
     492              : impl From<PageReconstructError> for PageStreamError {
     493            0 :     fn from(value: PageReconstructError) -> Self {
     494            0 :         match value {
     495            0 :             PageReconstructError::Cancelled => Self::Shutdown,
     496            0 :             e => Self::Read(e),
     497              :         }
     498            0 :     }
     499              : }
     500              : 
     501              : impl From<GetActiveTimelineError> for PageStreamError {
     502            0 :     fn from(value: GetActiveTimelineError) -> Self {
     503            0 :         match value {
     504              :             GetActiveTimelineError::Tenant(GetActiveTenantError::Cancelled)
     505              :             | GetActiveTimelineError::Tenant(GetActiveTenantError::WillNotBecomeActive(
     506              :                 TenantState::Stopping { .. },
     507              :             ))
     508            0 :             | GetActiveTimelineError::Timeline(GetTimelineError::ShuttingDown) => Self::Shutdown,
     509            0 :             GetActiveTimelineError::Tenant(e) => Self::NotFound(format!("{e}").into()),
     510            0 :             GetActiveTimelineError::Timeline(e) => Self::NotFound(format!("{e}").into()),
     511              :         }
     512            0 :     }
     513              : }
     514              : 
     515              : impl From<WaitLsnError> for PageStreamError {
     516            0 :     fn from(value: WaitLsnError) -> Self {
     517            0 :         match value {
     518            0 :             e @ WaitLsnError::Timeout(_) => Self::LsnTimeout(e),
     519            0 :             WaitLsnError::Shutdown => Self::Shutdown,
     520            0 :             e @ WaitLsnError::BadState { .. } => Self::Reconnect(format!("{e}").into()),
     521              :         }
     522            0 :     }
     523              : }
     524              : 
     525              : impl From<WaitLsnError> for QueryError {
     526            0 :     fn from(value: WaitLsnError) -> Self {
     527            0 :         match value {
     528            0 :             e @ WaitLsnError::Timeout(_) => Self::Other(anyhow::Error::new(e)),
     529            0 :             WaitLsnError::Shutdown => Self::Shutdown,
     530            0 :             WaitLsnError::BadState { .. } => Self::Reconnect,
     531              :         }
     532            0 :     }
     533              : }
     534              : 
     535              : enum BatchedFeMessage {
     536              :     Exists {
     537              :         span: Span,
     538              :         req: models::PagestreamExistsRequest,
     539              :     },
     540              :     Nblocks {
     541              :         span: Span,
     542              :         req: models::PagestreamNblocksRequest,
     543              :     },
     544              :     GetPage {
     545              :         span: Span,
     546              :         shard: timeline::handle::Handle<TenantManagerTypes>,
     547              :         effective_request_lsn: Lsn,
     548              :         pages: smallvec::SmallVec<[(RelTag, BlockNumber); 1]>,
     549              :     },
     550              :     DbSize {
     551              :         span: Span,
     552              :         req: models::PagestreamDbSizeRequest,
     553              :     },
     554              :     GetSlruSegment {
     555              :         span: Span,
     556              :         req: models::PagestreamGetSlruSegmentRequest,
     557              :     },
     558              :     RespondError {
     559              :         span: Span,
     560              :         error: PageStreamError,
     561              :     },
     562              : }
     563              : 
     564              : enum BatchOrEof {
     565              :     /// In the common case, this has one entry.
     566              :     /// At most, it has two entries: the first is the leftover batch, the second is an error.
     567              :     Batch(smallvec::SmallVec<[BatchedFeMessage; 1]>),
     568              :     Eof,
     569              : }
     570              : 
     571              : impl PageServerHandler {
     572            0 :     pub fn new(
     573            0 :         tenant_manager: Arc<TenantManager>,
     574            0 :         auth: Option<Arc<SwappableJwtAuth>>,
     575            0 :         server_side_batch_timeout: Option<Duration>,
     576            0 :         connection_ctx: RequestContext,
     577            0 :         cancel: CancellationToken,
     578            0 :     ) -> Self {
     579            0 :         PageServerHandler {
     580            0 :             auth,
     581            0 :             claims: None,
     582            0 :             connection_ctx,
     583            0 :             timeline_handles: TimelineHandles::new(tenant_manager),
     584            0 :             cancel,
     585            0 :             next_batch: None,
     586            0 :             server_side_batch_timeout,
     587            0 :         }
     588            0 :     }
     589              : 
     590              :     /// This function always respects cancellation of any timeline in `[Self::shard_timelines]`.  Pass in
     591              :     /// a cancellation token at the next scope up (such as a tenant cancellation token) to ensure we respect
     592              :     /// cancellation if there aren't any timelines in the cache.
     593              :     ///
     594              :     /// If calling from a function that doesn't use the `[Self::shard_timelines]` cache, then pass in the
     595              :     /// timeline cancellation token.
     596            0 :     async fn flush_cancellable<IO>(
     597            0 :         &self,
     598            0 :         pgb: &mut PostgresBackend<IO>,
     599            0 :         cancel: &CancellationToken,
     600            0 :     ) -> Result<(), QueryError>
     601            0 :     where
     602            0 :         IO: AsyncRead + AsyncWrite + Send + Sync + Unpin,
     603            0 :     {
     604            0 :         tokio::select!(
     605            0 :             flush_r = pgb.flush() => {
     606            0 :                 Ok(flush_r?)
     607              :             },
     608            0 :             _ = cancel.cancelled() => {
     609            0 :                 Err(QueryError::Shutdown)
     610              :             }
     611              :         )
     612            0 :     }
     613              : 
     614            0 :     async fn read_batch_from_connection<IO>(
     615            0 :         &mut self,
     616            0 :         pgb: &mut PostgresBackend<IO>,
     617            0 :         tenant_id: &TenantId,
     618            0 :         timeline_id: &TimelineId,
     619            0 :         ctx: &RequestContext,
     620            0 :     ) -> Result<Option<BatchOrEof>, QueryError>
     621            0 :     where
     622            0 :         IO: AsyncRead + AsyncWrite + Send + Sync + Unpin,
     623            0 :     {
     624            0 :         let mut batch = self.next_batch.take();
     625            0 :         let mut batch_started_at: Option<std::time::Instant> = None;
     626              : 
     627            0 :         let next_batch: Option<BatchedFeMessage> = loop {
     628            0 :             let sleep_fut = match (self.server_side_batch_timeout, batch_started_at) {
     629            0 :                 (Some(batch_timeout), Some(started_at)) => futures::future::Either::Left(
     630            0 :                     tokio::time::sleep_until((started_at + batch_timeout).into()),
     631            0 :                 ),
     632            0 :                 _ => futures::future::Either::Right(futures::future::pending()),
     633              :             };
     634              : 
     635            0 :             let msg = tokio::select! {
     636              :                 biased;
     637            0 :                 _ = self.cancel.cancelled() => {
     638            0 :                     return Err(QueryError::Shutdown)
     639              :                 }
     640            0 :                 msg = pgb.read_message() => {
     641            0 :                     msg
     642              :                 }
     643            0 :                 _ = sleep_fut => {
     644            0 :                     assert!(batch.is_some());
     645            0 :                     break None;
     646              :                 }
     647              :             };
     648            0 :             let copy_data_bytes = match msg? {
     649            0 :                 Some(FeMessage::CopyData(bytes)) => bytes,
     650              :                 Some(FeMessage::Terminate) => {
     651            0 :                     return Ok(Some(BatchOrEof::Eof));
     652              :                 }
     653            0 :                 Some(m) => {
     654            0 :                     return Err(QueryError::Other(anyhow::anyhow!(
     655            0 :                         "unexpected message: {m:?} during COPY"
     656            0 :                     )));
     657              :                 }
     658              :                 None => {
     659            0 :                     return Ok(Some(BatchOrEof::Eof));
     660              :                 } // client disconnected
     661              :             };
     662            0 :             trace!("query: {copy_data_bytes:?}");
     663            0 :             fail::fail_point!("ps::handle-pagerequest-message");
     664              : 
     665              :             // parse request
     666            0 :             let neon_fe_msg = PagestreamFeMessage::parse(&mut copy_data_bytes.reader())?;
     667              : 
     668            0 :             let this_msg = match neon_fe_msg {
     669            0 :                 PagestreamFeMessage::Exists(req) => BatchedFeMessage::Exists {
     670            0 :                     span: tracing::info_span!("handle_get_rel_exists_request", rel = %req.rel, req_lsn = %req.request_lsn),
     671            0 :                     req,
     672              :                 },
     673            0 :                 PagestreamFeMessage::Nblocks(req) => BatchedFeMessage::Nblocks {
     674            0 :                     span: tracing::info_span!("handle_get_nblocks_request", rel = %req.rel, req_lsn = %req.request_lsn),
     675            0 :                     req,
     676              :                 },
     677            0 :                 PagestreamFeMessage::DbSize(req) => BatchedFeMessage::DbSize {
     678            0 :                     span: tracing::info_span!("handle_db_size_request", dbnode = %req.dbnode, req_lsn = %req.request_lsn),
     679            0 :                     req,
     680              :                 },
     681            0 :                 PagestreamFeMessage::GetSlruSegment(req) => BatchedFeMessage::GetSlruSegment {
     682            0 :                     span: tracing::info_span!("handle_get_slru_segment_request", kind = %req.kind, segno = %req.segno, req_lsn = %req.request_lsn),
     683            0 :                     req,
     684              :                 },
     685              :                 PagestreamFeMessage::GetPage(PagestreamGetPageRequest {
     686            0 :                     request_lsn,
     687            0 :                     not_modified_since,
     688            0 :                     rel,
     689            0 :                     blkno,
     690              :                 }) => {
     691              :                     // shard_id is filled in by the handler
     692            0 :                     let span = tracing::info_span!(
     693            0 :                         "handle_get_page_at_lsn_request_batched",
     694            0 :                         %tenant_id, %timeline_id, shard_id = tracing::field::Empty, req_lsn = %request_lsn,
     695            0 :                         batch_size = tracing::field::Empty, batch_id = tracing::field::Empty
     696            0 :                     );
     697              : 
     698              :                     macro_rules! current_batch_and_error {
     699              :                         ($error:expr) => {{
     700              :                             let error = BatchedFeMessage::RespondError {
     701              :                                 span,
     702              :                                 error: $error,
     703              :                             };
     704              :                             let batch_and_error = match batch {
     705              :                                 Some(b) => smallvec::smallvec![b, error],
     706              :                                 None => smallvec::smallvec![error],
     707              :                             };
     708              :                             Ok(Some(BatchOrEof::Batch(batch_and_error)))
     709              :                         }};
     710              :                     }
     711              : 
     712            0 :                     let key = rel_block_to_key(rel, blkno);
     713            0 :                     let shard = match self
     714            0 :                         .timeline_handles
     715            0 :                         .get(*tenant_id, *timeline_id, ShardSelector::Page(key))
     716            0 :                         .instrument(span.clone())
     717            0 :                         .await
     718              :                     {
     719            0 :                         Ok(tl) => tl,
     720              :                         Err(GetActiveTimelineError::Tenant(GetActiveTenantError::NotFound(_))) => {
     721              :                             // We already know this tenant exists in general, because we resolved it at
     722              :                             // start of connection.  Getting a NotFound here indicates that the shard containing
     723              :                             // the requested page is not present on this node: the client's knowledge of shard->pageserver
     724              :                             // mapping is out of date.
     725              :                             //
     726              :                             // Closing the connection by returning ``::Reconnect` has the side effect of rate-limiting above message, via
     727              :                             // client's reconnect backoff, as well as hopefully prompting the client to load its updated configuration
     728              :                             // and talk to a different pageserver.
     729            0 :                             return current_batch_and_error!(PageStreamError::Reconnect(
     730            0 :                                 "getpage@lsn request routed to wrong shard".into()
     731            0 :                             ));
     732              :                         }
     733            0 :                         Err(e) => {
     734            0 :                             return current_batch_and_error!(e.into());
     735              :                         }
     736              :                     };
     737            0 :                     let effective_request_lsn = match Self::wait_or_get_last_lsn(
     738            0 :                         &shard,
     739            0 :                         request_lsn,
     740            0 :                         not_modified_since,
     741            0 :                         &shard.get_latest_gc_cutoff_lsn(),
     742            0 :                         ctx,
     743            0 :                     )
     744              :                     // TODO: if we actually need to wait for lsn here, it delays the entire batch which doesn't need to wait
     745            0 :                     .await
     746              :                     {
     747            0 :                         Ok(lsn) => lsn,
     748            0 :                         Err(e) => {
     749            0 :                             return current_batch_and_error!(e);
     750              :                         }
     751              :                     };
     752              :                     BatchedFeMessage::GetPage {
     753            0 :                         span,
     754            0 :                         shard,
     755            0 :                         effective_request_lsn,
     756            0 :                         pages: smallvec::smallvec![(rel, blkno)],
     757              :                     }
     758              :                 }
     759              :             };
     760              : 
     761            0 :             let batch_timeout = match self.server_side_batch_timeout {
     762            0 :                 Some(value) => value,
     763              :                 None => {
     764              :                     // Batching is not enabled - stop on the first message.
     765            0 :                     return Ok(Some(BatchOrEof::Batch(smallvec::smallvec![this_msg])));
     766              :                 }
     767              :             };
     768              : 
     769              :             // check if we can batch
     770            0 :             match (&mut batch, this_msg) {
     771            0 :                 (None, this_msg) => {
     772            0 :                     batch = Some(this_msg);
     773            0 :                 }
     774              :                 (
     775              :                     Some(BatchedFeMessage::GetPage {
     776              :                         span: _,
     777            0 :                         shard: accum_shard,
     778            0 :                         pages: accum_pages,
     779            0 :                         effective_request_lsn: accum_lsn,
     780              :                     }),
     781              :                     BatchedFeMessage::GetPage {
     782              :                         span: _,
     783            0 :                         shard: this_shard,
     784            0 :                         pages: this_pages,
     785            0 :                         effective_request_lsn: this_lsn,
     786              :                     },
     787            0 :                 ) if async {
     788            0 :                     assert_eq!(this_pages.len(), 1);
     789            0 :                     if accum_pages.len() >= Timeline::MAX_GET_VECTORED_KEYS as usize {
     790            0 :                         assert_eq!(accum_pages.len(), Timeline::MAX_GET_VECTORED_KEYS as usize);
     791            0 :                         return false;
     792            0 :                     }
     793            0 :                     if (accum_shard.tenant_shard_id, accum_shard.timeline_id)
     794            0 :                         != (this_shard.tenant_shard_id, this_shard.timeline_id)
     795              :                     {
     796              :                         // TODO: we _could_ batch & execute each shard seperately (and in parallel).
     797              :                         // But the current logic for keeping responses in order does not support that.
     798            0 :                         return false;
     799            0 :                     }
     800            0 :                     // the vectored get currently only supports a single LSN, so, bounce as soon
     801            0 :                     // as the effective request_lsn changes
     802            0 :                     if *accum_lsn != this_lsn {
     803            0 :                         return false;
     804            0 :                     }
     805            0 :                     true
     806            0 :                 }
     807            0 :                 .await =>
     808            0 :                 {
     809            0 :                     // ok to batch
     810            0 :                     accum_pages.extend(this_pages);
     811            0 :                 }
     812            0 :                 (Some(_), this_msg) => {
     813            0 :                     // by default, don't continue batching
     814            0 :                     break Some(this_msg);
     815              :                 }
     816              :             }
     817              : 
     818              :             // batching impl piece
     819            0 :             let started_at = batch_started_at.get_or_insert_with(Instant::now);
     820            0 :             if started_at.elapsed() > batch_timeout {
     821            0 :                 break None;
     822            0 :             }
     823              :         };
     824              : 
     825            0 :         self.next_batch = next_batch;
     826            0 :         Ok(batch.map(|b| BatchOrEof::Batch(smallvec::smallvec![b])))
     827            0 :     }
     828              : 
     829              :     /// Pagestream sub-protocol handler.
     830              :     ///
     831              :     /// It is a simple request-response protocol inside a COPYBOTH session.
     832              :     ///
     833              :     /// # Coding Discipline
     834              :     ///
     835              :     /// Coding discipline within this function: all interaction with the `pgb` connection
     836              :     /// needs to be sensitive to connection shutdown, currently signalled via [`Self::cancel`].
     837              :     /// This is so that we can shutdown page_service quickly.
     838            0 :     #[instrument(skip_all)]
     839              :     async fn handle_pagerequests<IO>(
     840              :         &mut self,
     841              :         pgb: &mut PostgresBackend<IO>,
     842              :         tenant_id: TenantId,
     843              :         timeline_id: TimelineId,
     844              :         _protocol_version: PagestreamProtocolVersion,
     845              :         ctx: RequestContext,
     846              :     ) -> Result<(), QueryError>
     847              :     where
     848              :         IO: AsyncRead + AsyncWrite + Send + Sync + Unpin,
     849              :     {
     850              :         debug_assert_current_span_has_tenant_and_timeline_id_no_shard_id();
     851              : 
     852              :         // switch client to COPYBOTH
     853              :         pgb.write_message_noflush(&BeMessage::CopyBothResponse)?;
     854              :         tokio::select! {
     855              :             biased;
     856              :             _ = self.cancel.cancelled() => {
     857              :                 return Err(QueryError::Shutdown)
     858              :             }
     859              :             res = pgb.flush() => {
     860              :                 res?;
     861              :             }
     862              :         }
     863              : 
     864              :         // If [`PageServerHandler`] is reused for multiple pagestreams,
     865              :         // then make sure to not process requests from the previous ones.
     866              :         self.next_batch = None;
     867              : 
     868              :         loop {
     869              :             let maybe_batched = self
     870              :                 .read_batch_from_connection(pgb, &tenant_id, &timeline_id, &ctx)
     871              :                 .await?;
     872              :             let batched = match maybe_batched {
     873              :                 Some(BatchOrEof::Batch(b)) => b,
     874              :                 Some(BatchOrEof::Eof) => {
     875              :                     break;
     876              :                 }
     877              :                 None => {
     878              :                     continue;
     879              :                 }
     880              :             };
     881              : 
     882              :             for batch in batched {
     883              :                 // invoke handler function
     884              :                 let (handler_results, span): (
     885              :                     Vec<Result<PagestreamBeMessage, PageStreamError>>,
     886              :                     _,
     887              :                 ) = match batch {
     888              :                     BatchedFeMessage::Exists { span, req } => {
     889              :                         fail::fail_point!("ps::handle-pagerequest-message::exists");
     890              :                         (
     891              :                             vec![
     892              :                                 self.handle_get_rel_exists_request(
     893              :                                     tenant_id,
     894              :                                     timeline_id,
     895              :                                     &req,
     896              :                                     &ctx,
     897              :                                 )
     898              :                                 .instrument(span.clone())
     899              :                                 .await,
     900              :                             ],
     901              :                             span,
     902              :                         )
     903              :                     }
     904              :                     BatchedFeMessage::Nblocks { span, req } => {
     905              :                         fail::fail_point!("ps::handle-pagerequest-message::nblocks");
     906              :                         (
     907              :                             vec![
     908              :                                 self.handle_get_nblocks_request(tenant_id, timeline_id, &req, &ctx)
     909              :                                     .instrument(span.clone())
     910              :                                     .await,
     911              :                             ],
     912              :                             span,
     913              :                         )
     914              :                     }
     915              :                     BatchedFeMessage::GetPage {
     916              :                         span,
     917              :                         shard,
     918              :                         effective_request_lsn,
     919              :                         pages,
     920              :                     } => {
     921              :                         fail::fail_point!("ps::handle-pagerequest-message::getpage");
     922              :                         (
     923              :                             {
     924              :                                 let npages = pages.len();
     925              :                                 let res = self
     926              :                                     .handle_get_page_at_lsn_request_batched(
     927              :                                         &shard,
     928              :                                         effective_request_lsn,
     929              :                                         pages,
     930              :                                         &ctx,
     931              :                                     )
     932              :                                     .instrument(span.clone())
     933              :                                     .await;
     934              :                                 assert_eq!(res.len(), npages);
     935              :                                 res
     936              :                             },
     937              :                             span,
     938              :                         )
     939              :                     }
     940              :                     BatchedFeMessage::DbSize { span, req } => {
     941              :                         fail::fail_point!("ps::handle-pagerequest-message::dbsize");
     942              :                         (
     943              :                             vec![
     944              :                                 self.handle_db_size_request(tenant_id, timeline_id, &req, &ctx)
     945              :                                     .instrument(span.clone())
     946              :                                     .await,
     947              :                             ],
     948              :                             span,
     949              :                         )
     950              :                     }
     951              :                     BatchedFeMessage::GetSlruSegment { span, req } => {
     952              :                         fail::fail_point!("ps::handle-pagerequest-message::slrusegment");
     953              :                         (
     954              :                             vec![
     955              :                                 self.handle_get_slru_segment_request(
     956              :                                     tenant_id,
     957              :                                     timeline_id,
     958              :                                     &req,
     959              :                                     &ctx,
     960              :                                 )
     961              :                                 .instrument(span.clone())
     962              :                                 .await,
     963              :                             ],
     964              :                             span,
     965              :                         )
     966              :                     }
     967              :                     BatchedFeMessage::RespondError { span, error } => {
     968              :                         // We've already decided to respond with an error, so we don't need to
     969              :                         // call the handler.
     970              :                         (vec![Err(error)], span)
     971              :                     }
     972              :                 };
     973              : 
     974              :                 // Map handler result to protocol behavior.
     975              :                 // Some handler errors cause exit from pagestream protocol.
     976              :                 // Other handler errors are sent back as an error message and we stay in pagestream protocol.
     977              :                 for handler_result in handler_results {
     978              :                     let response_msg = match handler_result {
     979              :                         Err(e) => match &e {
     980              :                             PageStreamError::Shutdown => {
     981              :                                 // If we fail to fulfil a request during shutdown, which may be _because_ of
     982              :                                 // shutdown, then do not send the error to the client.  Instead just drop the
     983              :                                 // connection.
     984            0 :                                 span.in_scope(|| info!("dropping connection due to shutdown"));
     985              :                                 return Err(QueryError::Shutdown);
     986              :                             }
     987              :                             PageStreamError::Reconnect(reason) => {
     988            0 :                                 span.in_scope(|| info!("handler requested reconnect: {reason}"));
     989              :                                 return Err(QueryError::Reconnect);
     990              :                             }
     991              :                             PageStreamError::Read(_)
     992              :                             | PageStreamError::LsnTimeout(_)
     993              :                             | PageStreamError::NotFound(_)
     994              :                             | PageStreamError::BadRequest(_) => {
     995              :                                 // print the all details to the log with {:#}, but for the client the
     996              :                                 // error message is enough.  Do not log if shutting down, as the anyhow::Error
     997              :                                 // here includes cancellation which is not an error.
     998              :                                 let full = utils::error::report_compact_sources(&e);
     999            0 :                                 span.in_scope(|| {
    1000            0 :                                     error!("error reading relation or page version: {full:#}")
    1001            0 :                                 });
    1002              :                                 PagestreamBeMessage::Error(PagestreamErrorResponse {
    1003              :                                     message: e.to_string(),
    1004              :                                 })
    1005              :                             }
    1006              :                         },
    1007              :                         Ok(response_msg) => response_msg,
    1008              :                     };
    1009              : 
    1010              :                     // marshal & transmit response message
    1011              :                     pgb.write_message_noflush(&BeMessage::CopyData(&response_msg.serialize()))?;
    1012              :                 }
    1013              :                 tokio::select! {
    1014              :                     biased;
    1015              :                     _ = self.cancel.cancelled() => {
    1016              :                         // We were requested to shut down.
    1017              :                         info!("shutdown request received in page handler");
    1018              :                         return Err(QueryError::Shutdown)
    1019              :                     }
    1020              :                     res = pgb.flush() => {
    1021              :                         res?;
    1022              :                     }
    1023              :                 }
    1024              :             }
    1025              :         }
    1026              :         Ok(())
    1027              :     }
    1028              : 
    1029              :     /// Helper function to handle the LSN from client request.
    1030              :     ///
    1031              :     /// Each GetPage (and Exists and Nblocks) request includes information about
    1032              :     /// which version of the page is being requested. The primary compute node
    1033              :     /// will always request the latest page version, by setting 'request_lsn' to
    1034              :     /// the last inserted or flushed WAL position, while a standby will request
    1035              :     /// a version at the LSN that it's currently caught up to.
    1036              :     ///
    1037              :     /// In either case, if the page server hasn't received the WAL up to the
    1038              :     /// requested LSN yet, we will wait for it to arrive. The return value is
    1039              :     /// the LSN that should be used to look up the page versions.
    1040              :     ///
    1041              :     /// In addition to the request LSN, each request carries another LSN,
    1042              :     /// 'not_modified_since', which is a hint to the pageserver that the client
    1043              :     /// knows that the page has not been modified between 'not_modified_since'
    1044              :     /// and the request LSN. This allows skipping the wait, as long as the WAL
    1045              :     /// up to 'not_modified_since' has arrived. If the client doesn't have any
    1046              :     /// information about when the page was modified, it will use
    1047              :     /// not_modified_since == lsn. If the client lies and sends a too low
    1048              :     /// not_modified_hint such that there are in fact later page versions, the
    1049              :     /// behavior is undefined: the pageserver may return any of the page versions
    1050              :     /// or an error.
    1051            0 :     async fn wait_or_get_last_lsn(
    1052            0 :         timeline: &Timeline,
    1053            0 :         request_lsn: Lsn,
    1054            0 :         not_modified_since: Lsn,
    1055            0 :         latest_gc_cutoff_lsn: &RcuReadGuard<Lsn>,
    1056            0 :         ctx: &RequestContext,
    1057            0 :     ) -> Result<Lsn, PageStreamError> {
    1058            0 :         let last_record_lsn = timeline.get_last_record_lsn();
    1059            0 : 
    1060            0 :         // Sanity check the request
    1061            0 :         if request_lsn < not_modified_since {
    1062            0 :             return Err(PageStreamError::BadRequest(
    1063            0 :                 format!(
    1064            0 :                     "invalid request with request LSN {} and not_modified_since {}",
    1065            0 :                     request_lsn, not_modified_since,
    1066            0 :                 )
    1067            0 :                 .into(),
    1068            0 :             ));
    1069            0 :         }
    1070            0 : 
    1071            0 :         if request_lsn < **latest_gc_cutoff_lsn {
    1072            0 :             let gc_info = &timeline.gc_info.read().unwrap();
    1073            0 :             if !gc_info.leases.contains_key(&request_lsn) {
    1074              :                 // The requested LSN is below gc cutoff and is not guarded by a lease.
    1075              : 
    1076              :                 // Check explicitly for INVALID just to get a less scary error message if the
    1077              :                 // request is obviously bogus
    1078            0 :                 return Err(if request_lsn == Lsn::INVALID {
    1079            0 :                     PageStreamError::BadRequest("invalid LSN(0) in request".into())
    1080              :                 } else {
    1081            0 :                     PageStreamError::BadRequest(format!(
    1082            0 :                         "tried to request a page version that was garbage collected. requested at {} gc cutoff {}",
    1083            0 :                         request_lsn, **latest_gc_cutoff_lsn
    1084            0 :                     ).into())
    1085              :                 });
    1086            0 :             }
    1087            0 :         }
    1088              : 
    1089              :         // Wait for WAL up to 'not_modified_since' to arrive, if necessary
    1090            0 :         if not_modified_since > last_record_lsn {
    1091            0 :             timeline
    1092            0 :                 .wait_lsn(
    1093            0 :                     not_modified_since,
    1094            0 :                     crate::tenant::timeline::WaitLsnWaiter::PageService,
    1095            0 :                     ctx,
    1096            0 :                 )
    1097            0 :                 .await?;
    1098              :             // Since we waited for 'not_modified_since' to arrive, that is now the last
    1099              :             // record LSN. (Or close enough for our purposes; the last-record LSN can
    1100              :             // advance immediately after we return anyway)
    1101            0 :             Ok(not_modified_since)
    1102              :         } else {
    1103              :             // It might be better to use max(not_modified_since, latest_gc_cutoff_lsn)
    1104              :             // here instead. That would give the same result, since we know that there
    1105              :             // haven't been any modifications since 'not_modified_since'. Using an older
    1106              :             // LSN might be faster, because that could allow skipping recent layers when
    1107              :             // finding the page. However, we have historically used 'last_record_lsn', so
    1108              :             // stick to that for now.
    1109            0 :             Ok(std::cmp::min(last_record_lsn, request_lsn))
    1110              :         }
    1111            0 :     }
    1112              : 
    1113              :     /// Handles the lsn lease request.
    1114              :     /// If a lease cannot be obtained, the client will receive NULL.
    1115            0 :     #[instrument(skip_all, fields(shard_id, %lsn))]
    1116              :     async fn handle_make_lsn_lease<IO>(
    1117              :         &mut self,
    1118              :         pgb: &mut PostgresBackend<IO>,
    1119              :         tenant_shard_id: TenantShardId,
    1120              :         timeline_id: TimelineId,
    1121              :         lsn: Lsn,
    1122              :         ctx: &RequestContext,
    1123              :     ) -> Result<(), QueryError>
    1124              :     where
    1125              :         IO: AsyncRead + AsyncWrite + Send + Sync + Unpin,
    1126              :     {
    1127              :         let timeline = self
    1128              :             .timeline_handles
    1129              :             .get(
    1130              :                 tenant_shard_id.tenant_id,
    1131              :                 timeline_id,
    1132              :                 ShardSelector::Known(tenant_shard_id.to_index()),
    1133              :             )
    1134              :             .await?;
    1135              :         set_tracing_field_shard_id(&timeline);
    1136              : 
    1137              :         let lease = timeline
    1138              :             .renew_lsn_lease(lsn, timeline.get_lsn_lease_length(), ctx)
    1139            0 :             .inspect_err(|e| {
    1140            0 :                 warn!("{e}");
    1141            0 :             })
    1142              :             .ok();
    1143            0 :         let valid_until_str = lease.map(|l| {
    1144            0 :             l.valid_until
    1145            0 :                 .duration_since(SystemTime::UNIX_EPOCH)
    1146            0 :                 .expect("valid_until is earlier than UNIX_EPOCH")
    1147            0 :                 .as_millis()
    1148            0 :                 .to_string()
    1149            0 :         });
    1150            0 :         let bytes = valid_until_str.as_ref().map(|x| x.as_bytes());
    1151              : 
    1152              :         pgb.write_message_noflush(&BeMessage::RowDescription(&[RowDescriptor::text_col(
    1153              :             b"valid_until",
    1154              :         )]))?
    1155              :         .write_message_noflush(&BeMessage::DataRow(&[bytes]))?;
    1156              : 
    1157              :         Ok(())
    1158              :     }
    1159              : 
    1160            0 :     #[instrument(skip_all, fields(shard_id))]
    1161              :     async fn handle_get_rel_exists_request(
    1162              :         &mut self,
    1163              :         tenant_id: TenantId,
    1164              :         timeline_id: TimelineId,
    1165              :         req: &PagestreamExistsRequest,
    1166              :         ctx: &RequestContext,
    1167              :     ) -> Result<PagestreamBeMessage, PageStreamError> {
    1168              :         let timeline = self
    1169              :             .timeline_handles
    1170              :             .get(tenant_id, timeline_id, ShardSelector::Zero)
    1171              :             .await?;
    1172              :         let _timer = timeline
    1173              :             .query_metrics
    1174              :             .start_timer(metrics::SmgrQueryType::GetRelExists, ctx);
    1175              : 
    1176              :         let latest_gc_cutoff_lsn = timeline.get_latest_gc_cutoff_lsn();
    1177              :         let lsn = Self::wait_or_get_last_lsn(
    1178              :             &timeline,
    1179              :             req.request_lsn,
    1180              :             req.not_modified_since,
    1181              :             &latest_gc_cutoff_lsn,
    1182              :             ctx,
    1183              :         )
    1184              :         .await?;
    1185              : 
    1186              :         let exists = timeline
    1187              :             .get_rel_exists(req.rel, Version::Lsn(lsn), ctx)
    1188              :             .await?;
    1189              : 
    1190              :         Ok(PagestreamBeMessage::Exists(PagestreamExistsResponse {
    1191              :             exists,
    1192              :         }))
    1193              :     }
    1194              : 
    1195            0 :     #[instrument(skip_all, fields(shard_id))]
    1196              :     async fn handle_get_nblocks_request(
    1197              :         &mut self,
    1198              :         tenant_id: TenantId,
    1199              :         timeline_id: TimelineId,
    1200              :         req: &PagestreamNblocksRequest,
    1201              :         ctx: &RequestContext,
    1202              :     ) -> Result<PagestreamBeMessage, PageStreamError> {
    1203              :         let timeline = self
    1204              :             .timeline_handles
    1205              :             .get(tenant_id, timeline_id, ShardSelector::Zero)
    1206              :             .await?;
    1207              : 
    1208              :         let _timer = timeline
    1209              :             .query_metrics
    1210              :             .start_timer(metrics::SmgrQueryType::GetRelSize, ctx);
    1211              : 
    1212              :         let latest_gc_cutoff_lsn = timeline.get_latest_gc_cutoff_lsn();
    1213              :         let lsn = Self::wait_or_get_last_lsn(
    1214              :             &timeline,
    1215              :             req.request_lsn,
    1216              :             req.not_modified_since,
    1217              :             &latest_gc_cutoff_lsn,
    1218              :             ctx,
    1219              :         )
    1220              :         .await?;
    1221              : 
    1222              :         let n_blocks = timeline
    1223              :             .get_rel_size(req.rel, Version::Lsn(lsn), ctx)
    1224              :             .await?;
    1225              : 
    1226              :         Ok(PagestreamBeMessage::Nblocks(PagestreamNblocksResponse {
    1227              :             n_blocks,
    1228              :         }))
    1229              :     }
    1230              : 
    1231            0 :     #[instrument(skip_all, fields(shard_id))]
    1232              :     async fn handle_db_size_request(
    1233              :         &mut self,
    1234              :         tenant_id: TenantId,
    1235              :         timeline_id: TimelineId,
    1236              :         req: &PagestreamDbSizeRequest,
    1237              :         ctx: &RequestContext,
    1238              :     ) -> Result<PagestreamBeMessage, PageStreamError> {
    1239              :         let timeline = self
    1240              :             .timeline_handles
    1241              :             .get(tenant_id, timeline_id, ShardSelector::Zero)
    1242              :             .await?;
    1243              : 
    1244              :         let _timer = timeline
    1245              :             .query_metrics
    1246              :             .start_timer(metrics::SmgrQueryType::GetDbSize, ctx);
    1247              : 
    1248              :         let latest_gc_cutoff_lsn = timeline.get_latest_gc_cutoff_lsn();
    1249              :         let lsn = Self::wait_or_get_last_lsn(
    1250              :             &timeline,
    1251              :             req.request_lsn,
    1252              :             req.not_modified_since,
    1253              :             &latest_gc_cutoff_lsn,
    1254              :             ctx,
    1255              :         )
    1256              :         .await?;
    1257              : 
    1258              :         let total_blocks = timeline
    1259              :             .get_db_size(DEFAULTTABLESPACE_OID, req.dbnode, Version::Lsn(lsn), ctx)
    1260              :             .await?;
    1261              :         let db_size = total_blocks as i64 * BLCKSZ as i64;
    1262              : 
    1263              :         Ok(PagestreamBeMessage::DbSize(PagestreamDbSizeResponse {
    1264              :             db_size,
    1265              :         }))
    1266              :     }
    1267              : 
    1268            0 :     #[instrument(skip_all)]
    1269              :     async fn handle_get_page_at_lsn_request_batched(
    1270              :         &mut self,
    1271              :         timeline: &Timeline,
    1272              :         effective_lsn: Lsn,
    1273              :         pages: smallvec::SmallVec<[(RelTag, BlockNumber); 1]>,
    1274              :         ctx: &RequestContext,
    1275              :     ) -> Vec<Result<PagestreamBeMessage, PageStreamError>> {
    1276              :         debug_assert_current_span_has_tenant_and_timeline_id();
    1277              :         let _timer = timeline.query_metrics.start_timer_many(
    1278              :             metrics::SmgrQueryType::GetPageAtLsn,
    1279              :             pages.len(),
    1280              :             ctx,
    1281              :         );
    1282              : 
    1283              :         let pages = timeline
    1284              :             .get_rel_page_at_lsn_batched(pages, effective_lsn, ctx)
    1285              :             .await;
    1286              : 
    1287            0 :         Vec::from_iter(pages.into_iter().map(|page| {
    1288            0 :             page.map(|page| {
    1289            0 :                 PagestreamBeMessage::GetPage(models::PagestreamGetPageResponse { page })
    1290            0 :             })
    1291            0 :             .map_err(PageStreamError::from)
    1292            0 :         }))
    1293              :     }
    1294              : 
    1295            0 :     #[instrument(skip_all, fields(shard_id))]
    1296              :     async fn handle_get_slru_segment_request(
    1297              :         &mut self,
    1298              :         tenant_id: TenantId,
    1299              :         timeline_id: TimelineId,
    1300              :         req: &PagestreamGetSlruSegmentRequest,
    1301              :         ctx: &RequestContext,
    1302              :     ) -> Result<PagestreamBeMessage, PageStreamError> {
    1303              :         let timeline = self
    1304              :             .timeline_handles
    1305              :             .get(tenant_id, timeline_id, ShardSelector::Zero)
    1306              :             .await?;
    1307              : 
    1308              :         let _timer = timeline
    1309              :             .query_metrics
    1310              :             .start_timer(metrics::SmgrQueryType::GetSlruSegment, ctx);
    1311              : 
    1312              :         let latest_gc_cutoff_lsn = timeline.get_latest_gc_cutoff_lsn();
    1313              :         let lsn = Self::wait_or_get_last_lsn(
    1314              :             &timeline,
    1315              :             req.request_lsn,
    1316              :             req.not_modified_since,
    1317              :             &latest_gc_cutoff_lsn,
    1318              :             ctx,
    1319              :         )
    1320              :         .await?;
    1321              : 
    1322              :         let kind = SlruKind::from_repr(req.kind)
    1323              :             .ok_or(PageStreamError::BadRequest("invalid SLRU kind".into()))?;
    1324              :         let segment = timeline.get_slru_segment(kind, req.segno, lsn, ctx).await?;
    1325              : 
    1326              :         Ok(PagestreamBeMessage::GetSlruSegment(
    1327              :             PagestreamGetSlruSegmentResponse { segment },
    1328              :         ))
    1329              :     }
    1330              : 
    1331              :     /// Note on "fullbackup":
    1332              :     /// Full basebackups should only be used for debugging purposes.
    1333              :     /// Originally, it was introduced to enable breaking storage format changes,
    1334              :     /// but that is not applicable anymore.
    1335              :     ///
    1336              :     /// # Coding Discipline
    1337              :     ///
    1338              :     /// Coding discipline within this function: all interaction with the `pgb` connection
    1339              :     /// needs to be sensitive to connection shutdown, currently signalled via [`Self::cancel`].
    1340              :     /// This is so that we can shutdown page_service quickly.
    1341              :     ///
    1342              :     /// TODO: wrap the pgb that we pass to the basebackup handler so that it's sensitive
    1343              :     /// to connection cancellation.
    1344              :     #[allow(clippy::too_many_arguments)]
    1345            0 :     #[instrument(skip_all, fields(shard_id, ?lsn, ?prev_lsn, %full_backup))]
    1346              :     async fn handle_basebackup_request<IO>(
    1347              :         &mut self,
    1348              :         pgb: &mut PostgresBackend<IO>,
    1349              :         tenant_id: TenantId,
    1350              :         timeline_id: TimelineId,
    1351              :         lsn: Option<Lsn>,
    1352              :         prev_lsn: Option<Lsn>,
    1353              :         full_backup: bool,
    1354              :         gzip: bool,
    1355              :         replica: bool,
    1356              :         ctx: &RequestContext,
    1357              :     ) -> Result<(), QueryError>
    1358              :     where
    1359              :         IO: AsyncRead + AsyncWrite + Send + Sync + Unpin,
    1360              :     {
    1361            0 :         fn map_basebackup_error(err: BasebackupError) -> QueryError {
    1362            0 :             match err {
    1363            0 :                 BasebackupError::Client(e) => QueryError::Disconnected(ConnectionError::Io(e)),
    1364            0 :                 BasebackupError::Server(e) => QueryError::Other(e),
    1365              :             }
    1366            0 :         }
    1367              : 
    1368              :         let started = std::time::Instant::now();
    1369              : 
    1370              :         let timeline = self
    1371              :             .timeline_handles
    1372              :             .get(tenant_id, timeline_id, ShardSelector::Zero)
    1373              :             .await?;
    1374              : 
    1375              :         let latest_gc_cutoff_lsn = timeline.get_latest_gc_cutoff_lsn();
    1376              :         if let Some(lsn) = lsn {
    1377              :             // Backup was requested at a particular LSN. Wait for it to arrive.
    1378              :             info!("waiting for {}", lsn);
    1379              :             timeline
    1380              :                 .wait_lsn(
    1381              :                     lsn,
    1382              :                     crate::tenant::timeline::WaitLsnWaiter::PageService,
    1383              :                     ctx,
    1384              :                 )
    1385              :                 .await?;
    1386              :             timeline
    1387              :                 .check_lsn_is_in_scope(lsn, &latest_gc_cutoff_lsn)
    1388              :                 .context("invalid basebackup lsn")?;
    1389              :         }
    1390              : 
    1391              :         let lsn_awaited_after = started.elapsed();
    1392              : 
    1393              :         // switch client to COPYOUT
    1394              :         pgb.write_message_noflush(&BeMessage::CopyOutResponse)
    1395              :             .map_err(QueryError::Disconnected)?;
    1396              :         self.flush_cancellable(pgb, &self.cancel).await?;
    1397              : 
    1398              :         // Send a tarball of the latest layer on the timeline. Compress if not
    1399              :         // fullbackup. TODO Compress in that case too (tests need to be updated)
    1400              :         if full_backup {
    1401              :             let mut writer = pgb.copyout_writer();
    1402              :             basebackup::send_basebackup_tarball(
    1403              :                 &mut writer,
    1404              :                 &timeline,
    1405              :                 lsn,
    1406              :                 prev_lsn,
    1407              :                 full_backup,
    1408              :                 replica,
    1409              :                 ctx,
    1410              :             )
    1411              :             .await
    1412              :             .map_err(map_basebackup_error)?;
    1413              :         } else {
    1414              :             let mut writer = BufWriter::new(pgb.copyout_writer());
    1415              :             if gzip {
    1416              :                 let mut encoder = GzipEncoder::with_quality(
    1417              :                     &mut writer,
    1418              :                     // NOTE using fast compression because it's on the critical path
    1419              :                     //      for compute startup. For an empty database, we get
    1420              :                     //      <100KB with this method. The Level::Best compression method
    1421              :                     //      gives us <20KB, but maybe we should add basebackup caching
    1422              :                     //      on compute shutdown first.
    1423              :                     async_compression::Level::Fastest,
    1424              :                 );
    1425              :                 basebackup::send_basebackup_tarball(
    1426              :                     &mut encoder,
    1427              :                     &timeline,
    1428              :                     lsn,
    1429              :                     prev_lsn,
    1430              :                     full_backup,
    1431              :                     replica,
    1432              :                     ctx,
    1433              :                 )
    1434              :                 .await
    1435              :                 .map_err(map_basebackup_error)?;
    1436              :                 // shutdown the encoder to ensure the gzip footer is written
    1437              :                 encoder
    1438              :                     .shutdown()
    1439              :                     .await
    1440            0 :                     .map_err(|e| QueryError::Disconnected(ConnectionError::Io(e)))?;
    1441              :             } else {
    1442              :                 basebackup::send_basebackup_tarball(
    1443              :                     &mut writer,
    1444              :                     &timeline,
    1445              :                     lsn,
    1446              :                     prev_lsn,
    1447              :                     full_backup,
    1448              :                     replica,
    1449              :                     ctx,
    1450              :                 )
    1451              :                 .await
    1452              :                 .map_err(map_basebackup_error)?;
    1453              :             }
    1454              :             writer
    1455              :                 .flush()
    1456              :                 .await
    1457            0 :                 .map_err(|e| map_basebackup_error(BasebackupError::Client(e)))?;
    1458              :         }
    1459              : 
    1460              :         pgb.write_message_noflush(&BeMessage::CopyDone)
    1461              :             .map_err(QueryError::Disconnected)?;
    1462              :         self.flush_cancellable(pgb, &timeline.cancel).await?;
    1463              : 
    1464              :         let basebackup_after = started
    1465              :             .elapsed()
    1466              :             .checked_sub(lsn_awaited_after)
    1467              :             .unwrap_or(Duration::ZERO);
    1468              : 
    1469              :         info!(
    1470              :             lsn_await_millis = lsn_awaited_after.as_millis(),
    1471              :             basebackup_millis = basebackup_after.as_millis(),
    1472              :             "basebackup complete"
    1473              :         );
    1474              : 
    1475              :         Ok(())
    1476              :     }
    1477              : 
    1478              :     // when accessing management api supply None as an argument
    1479              :     // when using to authorize tenant pass corresponding tenant id
    1480            0 :     fn check_permission(&self, tenant_id: Option<TenantId>) -> Result<(), QueryError> {
    1481            0 :         if self.auth.is_none() {
    1482              :             // auth is set to Trust, nothing to check so just return ok
    1483            0 :             return Ok(());
    1484            0 :         }
    1485            0 :         // auth is some, just checked above, when auth is some
    1486            0 :         // then claims are always present because of checks during connection init
    1487            0 :         // so this expect won't trigger
    1488            0 :         let claims = self
    1489            0 :             .claims
    1490            0 :             .as_ref()
    1491            0 :             .expect("claims presence already checked");
    1492            0 :         check_permission(claims, tenant_id).map_err(|e| QueryError::Unauthorized(e.0))
    1493            0 :     }
    1494              : }
    1495              : 
    1496              : /// `basebackup tenant timeline [lsn] [--gzip] [--replica]`
    1497              : #[derive(Debug, Clone, Eq, PartialEq)]
    1498              : struct BaseBackupCmd {
    1499              :     tenant_id: TenantId,
    1500              :     timeline_id: TimelineId,
    1501              :     lsn: Option<Lsn>,
    1502              :     gzip: bool,
    1503              :     replica: bool,
    1504              : }
    1505              : 
    1506              : /// `fullbackup tenant timeline [lsn] [prev_lsn]`
    1507              : #[derive(Debug, Clone, Eq, PartialEq)]
    1508              : struct FullBackupCmd {
    1509              :     tenant_id: TenantId,
    1510              :     timeline_id: TimelineId,
    1511              :     lsn: Option<Lsn>,
    1512              :     prev_lsn: Option<Lsn>,
    1513              : }
    1514              : 
    1515              : /// `pagestream_v2 tenant timeline`
    1516              : #[derive(Debug, Clone, Eq, PartialEq)]
    1517              : struct PageStreamCmd {
    1518              :     tenant_id: TenantId,
    1519              :     timeline_id: TimelineId,
    1520              : }
    1521              : 
    1522              : /// `lease lsn tenant timeline lsn`
    1523              : #[derive(Debug, Clone, Eq, PartialEq)]
    1524              : struct LeaseLsnCmd {
    1525              :     tenant_shard_id: TenantShardId,
    1526              :     timeline_id: TimelineId,
    1527              :     lsn: Lsn,
    1528              : }
    1529              : 
    1530              : #[derive(Debug, Clone, Eq, PartialEq)]
    1531              : enum PageServiceCmd {
    1532              :     Set,
    1533              :     PageStream(PageStreamCmd),
    1534              :     BaseBackup(BaseBackupCmd),
    1535              :     FullBackup(FullBackupCmd),
    1536              :     LeaseLsn(LeaseLsnCmd),
    1537              : }
    1538              : 
    1539              : impl PageStreamCmd {
    1540            6 :     fn parse(query: &str) -> anyhow::Result<Self> {
    1541            6 :         let parameters = query.split_whitespace().collect_vec();
    1542            6 :         if parameters.len() != 2 {
    1543            2 :             bail!(
    1544            2 :                 "invalid number of parameters for pagestream command: {}",
    1545            2 :                 query
    1546            2 :             );
    1547            4 :         }
    1548            4 :         let tenant_id = TenantId::from_str(parameters[0])
    1549            4 :             .with_context(|| format!("Failed to parse tenant id from {}", parameters[0]))?;
    1550            2 :         let timeline_id = TimelineId::from_str(parameters[1])
    1551            2 :             .with_context(|| format!("Failed to parse timeline id from {}", parameters[1]))?;
    1552            2 :         Ok(Self {
    1553            2 :             tenant_id,
    1554            2 :             timeline_id,
    1555            2 :         })
    1556            6 :     }
    1557              : }
    1558              : 
    1559              : impl FullBackupCmd {
    1560            4 :     fn parse(query: &str) -> anyhow::Result<Self> {
    1561            4 :         let parameters = query.split_whitespace().collect_vec();
    1562            4 :         if parameters.len() < 2 || parameters.len() > 4 {
    1563            0 :             bail!(
    1564            0 :                 "invalid number of parameters for basebackup command: {}",
    1565            0 :                 query
    1566            0 :             );
    1567            4 :         }
    1568            4 :         let tenant_id = TenantId::from_str(parameters[0])
    1569            4 :             .with_context(|| format!("Failed to parse tenant id from {}", parameters[0]))?;
    1570            4 :         let timeline_id = TimelineId::from_str(parameters[1])
    1571            4 :             .with_context(|| format!("Failed to parse timeline id from {}", parameters[1]))?;
    1572              :         // The caller is responsible for providing correct lsn and prev_lsn.
    1573            4 :         let lsn = if let Some(lsn_str) = parameters.get(2) {
    1574              :             Some(
    1575            2 :                 Lsn::from_str(lsn_str)
    1576            2 :                     .with_context(|| format!("Failed to parse Lsn from {lsn_str}"))?,
    1577              :             )
    1578              :         } else {
    1579            2 :             None
    1580              :         };
    1581            4 :         let prev_lsn = if let Some(prev_lsn_str) = parameters.get(3) {
    1582              :             Some(
    1583            2 :                 Lsn::from_str(prev_lsn_str)
    1584            2 :                     .with_context(|| format!("Failed to parse Lsn from {prev_lsn_str}"))?,
    1585              :             )
    1586              :         } else {
    1587            2 :             None
    1588              :         };
    1589            4 :         Ok(Self {
    1590            4 :             tenant_id,
    1591            4 :             timeline_id,
    1592            4 :             lsn,
    1593            4 :             prev_lsn,
    1594            4 :         })
    1595            4 :     }
    1596              : }
    1597              : 
    1598              : impl BaseBackupCmd {
    1599           18 :     fn parse(query: &str) -> anyhow::Result<Self> {
    1600           18 :         let parameters = query.split_whitespace().collect_vec();
    1601           18 :         if parameters.len() < 2 {
    1602            0 :             bail!(
    1603            0 :                 "invalid number of parameters for basebackup command: {}",
    1604            0 :                 query
    1605            0 :             );
    1606           18 :         }
    1607           18 :         let tenant_id = TenantId::from_str(parameters[0])
    1608           18 :             .with_context(|| format!("Failed to parse tenant id from {}", parameters[0]))?;
    1609           18 :         let timeline_id = TimelineId::from_str(parameters[1])
    1610           18 :             .with_context(|| format!("Failed to parse timeline id from {}", parameters[1]))?;
    1611              :         let lsn;
    1612              :         let flags_parse_from;
    1613           18 :         if let Some(maybe_lsn) = parameters.get(2) {
    1614           16 :             if *maybe_lsn == "latest" {
    1615            2 :                 lsn = None;
    1616            2 :                 flags_parse_from = 3;
    1617           14 :             } else if maybe_lsn.starts_with("--") {
    1618           10 :                 lsn = None;
    1619           10 :                 flags_parse_from = 2;
    1620           10 :             } else {
    1621              :                 lsn = Some(
    1622            4 :                     Lsn::from_str(maybe_lsn)
    1623            4 :                         .with_context(|| format!("Failed to parse lsn from {maybe_lsn}"))?,
    1624              :                 );
    1625            4 :                 flags_parse_from = 3;
    1626              :             }
    1627            2 :         } else {
    1628            2 :             lsn = None;
    1629            2 :             flags_parse_from = 2;
    1630            2 :         }
    1631              : 
    1632           18 :         let mut gzip = false;
    1633           18 :         let mut replica = false;
    1634              : 
    1635           22 :         for &param in &parameters[flags_parse_from..] {
    1636           22 :             match param {
    1637           22 :                 "--gzip" => {
    1638           14 :                     if gzip {
    1639            2 :                         bail!("duplicate parameter for basebackup command: {param}")
    1640           12 :                     }
    1641           12 :                     gzip = true
    1642              :                 }
    1643            8 :                 "--replica" => {
    1644            4 :                     if replica {
    1645            0 :                         bail!("duplicate parameter for basebackup command: {param}")
    1646            4 :                     }
    1647            4 :                     replica = true
    1648              :                 }
    1649            4 :                 _ => bail!("invalid parameter for basebackup command: {param}"),
    1650              :             }
    1651              :         }
    1652           12 :         Ok(Self {
    1653           12 :             tenant_id,
    1654           12 :             timeline_id,
    1655           12 :             lsn,
    1656           12 :             gzip,
    1657           12 :             replica,
    1658           12 :         })
    1659           18 :     }
    1660              : }
    1661              : 
    1662              : impl LeaseLsnCmd {
    1663            4 :     fn parse(query: &str) -> anyhow::Result<Self> {
    1664            4 :         let parameters = query.split_whitespace().collect_vec();
    1665            4 :         if parameters.len() != 3 {
    1666            0 :             bail!(
    1667            0 :                 "invalid number of parameters for lease lsn command: {}",
    1668            0 :                 query
    1669            0 :             );
    1670            4 :         }
    1671            4 :         let tenant_shard_id = TenantShardId::from_str(parameters[0])
    1672            4 :             .with_context(|| format!("Failed to parse tenant id from {}", parameters[0]))?;
    1673            4 :         let timeline_id = TimelineId::from_str(parameters[1])
    1674            4 :             .with_context(|| format!("Failed to parse timeline id from {}", parameters[1]))?;
    1675            4 :         let lsn = Lsn::from_str(parameters[2])
    1676            4 :             .with_context(|| format!("Failed to parse lsn from {}", parameters[2]))?;
    1677            4 :         Ok(Self {
    1678            4 :             tenant_shard_id,
    1679            4 :             timeline_id,
    1680            4 :             lsn,
    1681            4 :         })
    1682            4 :     }
    1683              : }
    1684              : 
    1685              : impl PageServiceCmd {
    1686           42 :     fn parse(query: &str) -> anyhow::Result<Self> {
    1687           42 :         let query = query.trim();
    1688           42 :         let Some((cmd, other)) = query.split_once(' ') else {
    1689            4 :             bail!("cannot parse query: {query}")
    1690              :         };
    1691           38 :         match cmd.to_ascii_lowercase().as_str() {
    1692           38 :             "pagestream_v2" => Ok(Self::PageStream(PageStreamCmd::parse(other)?)),
    1693           32 :             "basebackup" => Ok(Self::BaseBackup(BaseBackupCmd::parse(other)?)),
    1694           14 :             "fullbackup" => Ok(Self::FullBackup(FullBackupCmd::parse(other)?)),
    1695           10 :             "lease" => {
    1696            6 :                 let Some((cmd2, other)) = other.split_once(' ') else {
    1697            0 :                     bail!("invalid lease command: {cmd}");
    1698              :                 };
    1699            6 :                 let cmd2 = cmd2.to_ascii_lowercase();
    1700            6 :                 if cmd2 == "lsn" {
    1701            4 :                     Ok(Self::LeaseLsn(LeaseLsnCmd::parse(other)?))
    1702              :                 } else {
    1703            2 :                     bail!("invalid lease command: {cmd}");
    1704              :                 }
    1705              :             }
    1706            4 :             "set" => Ok(Self::Set),
    1707            0 :             _ => Err(anyhow::anyhow!("unsupported command {cmd} in {query}")),
    1708              :         }
    1709           42 :     }
    1710              : }
    1711              : 
    1712              : impl<IO> postgres_backend::Handler<IO> for PageServerHandler
    1713              : where
    1714              :     IO: AsyncRead + AsyncWrite + Send + Sync + Unpin,
    1715              : {
    1716            0 :     fn check_auth_jwt(
    1717            0 :         &mut self,
    1718            0 :         _pgb: &mut PostgresBackend<IO>,
    1719            0 :         jwt_response: &[u8],
    1720            0 :     ) -> Result<(), QueryError> {
    1721              :         // this unwrap is never triggered, because check_auth_jwt only called when auth_type is NeonJWT
    1722              :         // which requires auth to be present
    1723            0 :         let data = self
    1724            0 :             .auth
    1725            0 :             .as_ref()
    1726            0 :             .unwrap()
    1727            0 :             .decode(str::from_utf8(jwt_response).context("jwt response is not UTF-8")?)
    1728            0 :             .map_err(|e| QueryError::Unauthorized(e.0))?;
    1729              : 
    1730            0 :         if matches!(data.claims.scope, Scope::Tenant) && data.claims.tenant_id.is_none() {
    1731            0 :             return Err(QueryError::Unauthorized(
    1732            0 :                 "jwt token scope is Tenant, but tenant id is missing".into(),
    1733            0 :             ));
    1734            0 :         }
    1735            0 : 
    1736            0 :         debug!(
    1737            0 :             "jwt scope check succeeded for scope: {:#?} by tenant id: {:?}",
    1738              :             data.claims.scope, data.claims.tenant_id,
    1739              :         );
    1740              : 
    1741            0 :         self.claims = Some(data.claims);
    1742            0 :         Ok(())
    1743            0 :     }
    1744              : 
    1745            0 :     fn startup(
    1746            0 :         &mut self,
    1747            0 :         _pgb: &mut PostgresBackend<IO>,
    1748            0 :         _sm: &FeStartupPacket,
    1749            0 :     ) -> Result<(), QueryError> {
    1750            0 :         fail::fail_point!("ps::connection-start::startup-packet");
    1751            0 :         Ok(())
    1752            0 :     }
    1753              : 
    1754            0 :     #[instrument(skip_all, fields(tenant_id, timeline_id))]
    1755              :     async fn process_query(
    1756              :         &mut self,
    1757              :         pgb: &mut PostgresBackend<IO>,
    1758              :         query_string: &str,
    1759              :     ) -> Result<(), QueryError> {
    1760            0 :         fail::fail_point!("simulated-bad-compute-connection", |_| {
    1761            0 :             info!("Hit failpoint for bad connection");
    1762            0 :             Err(QueryError::SimulatedConnectionError)
    1763            0 :         });
    1764              : 
    1765              :         fail::fail_point!("ps::connection-start::process-query");
    1766              : 
    1767              :         let ctx = self.connection_ctx.attached_child();
    1768              :         debug!("process query {query_string}");
    1769              :         let query = PageServiceCmd::parse(query_string)?;
    1770              :         match query {
    1771              :             PageServiceCmd::PageStream(PageStreamCmd {
    1772              :                 tenant_id,
    1773              :                 timeline_id,
    1774              :             }) => {
    1775              :                 tracing::Span::current()
    1776              :                     .record("tenant_id", field::display(tenant_id))
    1777              :                     .record("timeline_id", field::display(timeline_id));
    1778              : 
    1779              :                 self.check_permission(Some(tenant_id))?;
    1780              : 
    1781              :                 COMPUTE_COMMANDS_COUNTERS
    1782              :                     .for_command(ComputeCommandKind::PageStreamV2)
    1783              :                     .inc();
    1784              : 
    1785              :                 self.handle_pagerequests(
    1786              :                     pgb,
    1787              :                     tenant_id,
    1788              :                     timeline_id,
    1789              :                     PagestreamProtocolVersion::V2,
    1790              :                     ctx,
    1791              :                 )
    1792              :                 .await?;
    1793              :             }
    1794              :             PageServiceCmd::BaseBackup(BaseBackupCmd {
    1795              :                 tenant_id,
    1796              :                 timeline_id,
    1797              :                 lsn,
    1798              :                 gzip,
    1799              :                 replica,
    1800              :             }) => {
    1801              :                 tracing::Span::current()
    1802              :                     .record("tenant_id", field::display(tenant_id))
    1803              :                     .record("timeline_id", field::display(timeline_id));
    1804              : 
    1805              :                 self.check_permission(Some(tenant_id))?;
    1806              : 
    1807              :                 COMPUTE_COMMANDS_COUNTERS
    1808              :                     .for_command(ComputeCommandKind::Basebackup)
    1809              :                     .inc();
    1810              :                 let metric_recording = metrics::BASEBACKUP_QUERY_TIME.start_recording(&ctx);
    1811            0 :                 let res = async {
    1812            0 :                     self.handle_basebackup_request(
    1813            0 :                         pgb,
    1814            0 :                         tenant_id,
    1815            0 :                         timeline_id,
    1816            0 :                         lsn,
    1817            0 :                         None,
    1818            0 :                         false,
    1819            0 :                         gzip,
    1820            0 :                         replica,
    1821            0 :                         &ctx,
    1822            0 :                     )
    1823            0 :                     .await?;
    1824            0 :                     pgb.write_message_noflush(&BeMessage::CommandComplete(b"SELECT 1"))?;
    1825            0 :                     Result::<(), QueryError>::Ok(())
    1826            0 :                 }
    1827              :                 .await;
    1828              :                 metric_recording.observe(&res);
    1829              :                 res?;
    1830              :             }
    1831              :             // same as basebackup, but result includes relational data as well
    1832              :             PageServiceCmd::FullBackup(FullBackupCmd {
    1833              :                 tenant_id,
    1834              :                 timeline_id,
    1835              :                 lsn,
    1836              :                 prev_lsn,
    1837              :             }) => {
    1838              :                 tracing::Span::current()
    1839              :                     .record("tenant_id", field::display(tenant_id))
    1840              :                     .record("timeline_id", field::display(timeline_id));
    1841              : 
    1842              :                 self.check_permission(Some(tenant_id))?;
    1843              : 
    1844              :                 COMPUTE_COMMANDS_COUNTERS
    1845              :                     .for_command(ComputeCommandKind::Fullbackup)
    1846              :                     .inc();
    1847              : 
    1848              :                 // Check that the timeline exists
    1849              :                 self.handle_basebackup_request(
    1850              :                     pgb,
    1851              :                     tenant_id,
    1852              :                     timeline_id,
    1853              :                     lsn,
    1854              :                     prev_lsn,
    1855              :                     true,
    1856              :                     false,
    1857              :                     false,
    1858              :                     &ctx,
    1859              :                 )
    1860              :                 .await?;
    1861              :                 pgb.write_message_noflush(&BeMessage::CommandComplete(b"SELECT 1"))?;
    1862              :             }
    1863              :             PageServiceCmd::Set => {
    1864              :                 // important because psycopg2 executes "SET datestyle TO 'ISO'"
    1865              :                 // on connect
    1866              :                 pgb.write_message_noflush(&BeMessage::CommandComplete(b"SELECT 1"))?;
    1867              :             }
    1868              :             PageServiceCmd::LeaseLsn(LeaseLsnCmd {
    1869              :                 tenant_shard_id,
    1870              :                 timeline_id,
    1871              :                 lsn,
    1872              :             }) => {
    1873              :                 tracing::Span::current()
    1874              :                     .record("tenant_id", field::display(tenant_shard_id))
    1875              :                     .record("timeline_id", field::display(timeline_id));
    1876              : 
    1877              :                 self.check_permission(Some(tenant_shard_id.tenant_id))?;
    1878              : 
    1879              :                 COMPUTE_COMMANDS_COUNTERS
    1880              :                     .for_command(ComputeCommandKind::LeaseLsn)
    1881              :                     .inc();
    1882              : 
    1883              :                 match self
    1884              :                     .handle_make_lsn_lease(pgb, tenant_shard_id, timeline_id, lsn, &ctx)
    1885              :                     .await
    1886              :                 {
    1887              :                     Ok(()) => {
    1888              :                         pgb.write_message_noflush(&BeMessage::CommandComplete(b"SELECT 1"))?
    1889              :                     }
    1890              :                     Err(e) => {
    1891              :                         error!("error obtaining lsn lease for {lsn}: {e:?}");
    1892              :                         pgb.write_message_noflush(&BeMessage::ErrorResponse(
    1893              :                             &e.to_string(),
    1894              :                             Some(e.pg_error_code()),
    1895              :                         ))?
    1896              :                     }
    1897              :                 };
    1898              :             }
    1899              :         }
    1900              : 
    1901              :         Ok(())
    1902              :     }
    1903              : }
    1904              : 
    1905              : impl From<GetActiveTenantError> for QueryError {
    1906            0 :     fn from(e: GetActiveTenantError) -> Self {
    1907            0 :         match e {
    1908            0 :             GetActiveTenantError::WaitForActiveTimeout { .. } => QueryError::Disconnected(
    1909            0 :                 ConnectionError::Io(io::Error::new(io::ErrorKind::TimedOut, e.to_string())),
    1910            0 :             ),
    1911              :             GetActiveTenantError::Cancelled
    1912              :             | GetActiveTenantError::WillNotBecomeActive(TenantState::Stopping { .. }) => {
    1913            0 :                 QueryError::Shutdown
    1914              :             }
    1915            0 :             e @ GetActiveTenantError::NotFound(_) => QueryError::NotFound(format!("{e}").into()),
    1916            0 :             e => QueryError::Other(anyhow::anyhow!(e)),
    1917              :         }
    1918            0 :     }
    1919              : }
    1920              : 
    1921            0 : #[derive(Debug, thiserror::Error)]
    1922              : pub(crate) enum GetActiveTimelineError {
    1923              :     #[error(transparent)]
    1924              :     Tenant(GetActiveTenantError),
    1925              :     #[error(transparent)]
    1926              :     Timeline(#[from] GetTimelineError),
    1927              : }
    1928              : 
    1929              : impl From<GetActiveTimelineError> for QueryError {
    1930            0 :     fn from(e: GetActiveTimelineError) -> Self {
    1931            0 :         match e {
    1932            0 :             GetActiveTimelineError::Tenant(GetActiveTenantError::Cancelled) => QueryError::Shutdown,
    1933            0 :             GetActiveTimelineError::Tenant(e) => e.into(),
    1934            0 :             GetActiveTimelineError::Timeline(e) => QueryError::NotFound(format!("{e}").into()),
    1935              :         }
    1936            0 :     }
    1937              : }
    1938              : 
    1939            0 : fn set_tracing_field_shard_id(timeline: &Timeline) {
    1940            0 :     debug_assert_current_span_has_tenant_and_timeline_id_no_shard_id();
    1941            0 :     tracing::Span::current().record(
    1942            0 :         "shard_id",
    1943            0 :         tracing::field::display(timeline.tenant_shard_id.shard_slug()),
    1944            0 :     );
    1945            0 :     debug_assert_current_span_has_tenant_and_timeline_id();
    1946            0 : }
    1947              : 
    1948              : struct WaitedForLsn(Lsn);
    1949              : impl From<WaitedForLsn> for Lsn {
    1950            0 :     fn from(WaitedForLsn(lsn): WaitedForLsn) -> Self {
    1951            0 :         lsn
    1952            0 :     }
    1953              : }
    1954              : 
    1955              : #[cfg(test)]
    1956              : mod tests {
    1957              :     use utils::shard::ShardCount;
    1958              : 
    1959              :     use super::*;
    1960              : 
    1961              :     #[test]
    1962            2 :     fn pageservice_cmd_parse() {
    1963            2 :         let tenant_id = TenantId::generate();
    1964            2 :         let timeline_id = TimelineId::generate();
    1965            2 :         let cmd =
    1966            2 :             PageServiceCmd::parse(&format!("pagestream_v2 {tenant_id} {timeline_id}")).unwrap();
    1967            2 :         assert_eq!(
    1968            2 :             cmd,
    1969            2 :             PageServiceCmd::PageStream(PageStreamCmd {
    1970            2 :                 tenant_id,
    1971            2 :                 timeline_id
    1972            2 :             })
    1973            2 :         );
    1974            2 :         let cmd = PageServiceCmd::parse(&format!("basebackup {tenant_id} {timeline_id}")).unwrap();
    1975            2 :         assert_eq!(
    1976            2 :             cmd,
    1977            2 :             PageServiceCmd::BaseBackup(BaseBackupCmd {
    1978            2 :                 tenant_id,
    1979            2 :                 timeline_id,
    1980            2 :                 lsn: None,
    1981            2 :                 gzip: false,
    1982            2 :                 replica: false
    1983            2 :             })
    1984            2 :         );
    1985            2 :         let cmd =
    1986            2 :             PageServiceCmd::parse(&format!("basebackup {tenant_id} {timeline_id} --gzip")).unwrap();
    1987            2 :         assert_eq!(
    1988            2 :             cmd,
    1989            2 :             PageServiceCmd::BaseBackup(BaseBackupCmd {
    1990            2 :                 tenant_id,
    1991            2 :                 timeline_id,
    1992            2 :                 lsn: None,
    1993            2 :                 gzip: true,
    1994            2 :                 replica: false
    1995            2 :             })
    1996            2 :         );
    1997            2 :         let cmd =
    1998            2 :             PageServiceCmd::parse(&format!("basebackup {tenant_id} {timeline_id} latest")).unwrap();
    1999            2 :         assert_eq!(
    2000            2 :             cmd,
    2001            2 :             PageServiceCmd::BaseBackup(BaseBackupCmd {
    2002            2 :                 tenant_id,
    2003            2 :                 timeline_id,
    2004            2 :                 lsn: None,
    2005            2 :                 gzip: false,
    2006            2 :                 replica: false
    2007            2 :             })
    2008            2 :         );
    2009            2 :         let cmd = PageServiceCmd::parse(&format!("basebackup {tenant_id} {timeline_id} 0/16ABCDE"))
    2010            2 :             .unwrap();
    2011            2 :         assert_eq!(
    2012            2 :             cmd,
    2013            2 :             PageServiceCmd::BaseBackup(BaseBackupCmd {
    2014            2 :                 tenant_id,
    2015            2 :                 timeline_id,
    2016            2 :                 lsn: Some(Lsn::from_str("0/16ABCDE").unwrap()),
    2017            2 :                 gzip: false,
    2018            2 :                 replica: false
    2019            2 :             })
    2020            2 :         );
    2021            2 :         let cmd = PageServiceCmd::parse(&format!(
    2022            2 :             "basebackup {tenant_id} {timeline_id} --replica --gzip"
    2023            2 :         ))
    2024            2 :         .unwrap();
    2025            2 :         assert_eq!(
    2026            2 :             cmd,
    2027            2 :             PageServiceCmd::BaseBackup(BaseBackupCmd {
    2028            2 :                 tenant_id,
    2029            2 :                 timeline_id,
    2030            2 :                 lsn: None,
    2031            2 :                 gzip: true,
    2032            2 :                 replica: true
    2033            2 :             })
    2034            2 :         );
    2035            2 :         let cmd = PageServiceCmd::parse(&format!(
    2036            2 :             "basebackup {tenant_id} {timeline_id} 0/16ABCDE --replica --gzip"
    2037            2 :         ))
    2038            2 :         .unwrap();
    2039            2 :         assert_eq!(
    2040            2 :             cmd,
    2041            2 :             PageServiceCmd::BaseBackup(BaseBackupCmd {
    2042            2 :                 tenant_id,
    2043            2 :                 timeline_id,
    2044            2 :                 lsn: Some(Lsn::from_str("0/16ABCDE").unwrap()),
    2045            2 :                 gzip: true,
    2046            2 :                 replica: true
    2047            2 :             })
    2048            2 :         );
    2049            2 :         let cmd = PageServiceCmd::parse(&format!("fullbackup {tenant_id} {timeline_id}")).unwrap();
    2050            2 :         assert_eq!(
    2051            2 :             cmd,
    2052            2 :             PageServiceCmd::FullBackup(FullBackupCmd {
    2053            2 :                 tenant_id,
    2054            2 :                 timeline_id,
    2055            2 :                 lsn: None,
    2056            2 :                 prev_lsn: None
    2057            2 :             })
    2058            2 :         );
    2059            2 :         let cmd = PageServiceCmd::parse(&format!(
    2060            2 :             "fullbackup {tenant_id} {timeline_id} 0/16ABCDE 0/16ABCDF"
    2061            2 :         ))
    2062            2 :         .unwrap();
    2063            2 :         assert_eq!(
    2064            2 :             cmd,
    2065            2 :             PageServiceCmd::FullBackup(FullBackupCmd {
    2066            2 :                 tenant_id,
    2067            2 :                 timeline_id,
    2068            2 :                 lsn: Some(Lsn::from_str("0/16ABCDE").unwrap()),
    2069            2 :                 prev_lsn: Some(Lsn::from_str("0/16ABCDF").unwrap()),
    2070            2 :             })
    2071            2 :         );
    2072            2 :         let tenant_shard_id = TenantShardId::unsharded(tenant_id);
    2073            2 :         let cmd = PageServiceCmd::parse(&format!(
    2074            2 :             "lease lsn {tenant_shard_id} {timeline_id} 0/16ABCDE"
    2075            2 :         ))
    2076            2 :         .unwrap();
    2077            2 :         assert_eq!(
    2078            2 :             cmd,
    2079            2 :             PageServiceCmd::LeaseLsn(LeaseLsnCmd {
    2080            2 :                 tenant_shard_id,
    2081            2 :                 timeline_id,
    2082            2 :                 lsn: Lsn::from_str("0/16ABCDE").unwrap(),
    2083            2 :             })
    2084            2 :         );
    2085            2 :         let tenant_shard_id = TenantShardId::split(&tenant_shard_id, ShardCount(8))[1];
    2086            2 :         let cmd = PageServiceCmd::parse(&format!(
    2087            2 :             "lease lsn {tenant_shard_id} {timeline_id} 0/16ABCDE"
    2088            2 :         ))
    2089            2 :         .unwrap();
    2090            2 :         assert_eq!(
    2091            2 :             cmd,
    2092            2 :             PageServiceCmd::LeaseLsn(LeaseLsnCmd {
    2093            2 :                 tenant_shard_id,
    2094            2 :                 timeline_id,
    2095            2 :                 lsn: Lsn::from_str("0/16ABCDE").unwrap(),
    2096            2 :             })
    2097            2 :         );
    2098            2 :         let cmd = PageServiceCmd::parse("set a = b").unwrap();
    2099            2 :         assert_eq!(cmd, PageServiceCmd::Set);
    2100            2 :         let cmd = PageServiceCmd::parse("SET foo").unwrap();
    2101            2 :         assert_eq!(cmd, PageServiceCmd::Set);
    2102            2 :     }
    2103              : 
    2104              :     #[test]
    2105            2 :     fn pageservice_cmd_err_handling() {
    2106            2 :         let tenant_id = TenantId::generate();
    2107            2 :         let timeline_id = TimelineId::generate();
    2108            2 :         let cmd = PageServiceCmd::parse("unknown_command");
    2109            2 :         assert!(cmd.is_err());
    2110            2 :         let cmd = PageServiceCmd::parse("pagestream_v2");
    2111            2 :         assert!(cmd.is_err());
    2112            2 :         let cmd = PageServiceCmd::parse(&format!("pagestream_v2 {tenant_id}xxx"));
    2113            2 :         assert!(cmd.is_err());
    2114            2 :         let cmd = PageServiceCmd::parse(&format!("pagestream_v2 {tenant_id}xxx {timeline_id}xxx"));
    2115            2 :         assert!(cmd.is_err());
    2116            2 :         let cmd = PageServiceCmd::parse(&format!(
    2117            2 :             "basebackup {tenant_id} {timeline_id} --gzip --gzip"
    2118            2 :         ));
    2119            2 :         assert!(cmd.is_err());
    2120            2 :         let cmd = PageServiceCmd::parse(&format!(
    2121            2 :             "basebackup {tenant_id} {timeline_id} --gzip --unknown"
    2122            2 :         ));
    2123            2 :         assert!(cmd.is_err());
    2124            2 :         let cmd = PageServiceCmd::parse(&format!(
    2125            2 :             "basebackup {tenant_id} {timeline_id} --gzip 0/16ABCDE"
    2126            2 :         ));
    2127            2 :         assert!(cmd.is_err());
    2128            2 :         let cmd = PageServiceCmd::parse(&format!("lease {tenant_id} {timeline_id} gzip 0/16ABCDE"));
    2129            2 :         assert!(cmd.is_err());
    2130            2 :     }
    2131              : }
        

Generated by: LCOV version 2.1-beta