LCOV - code coverage report
Current view: top level - pageserver/src/tenant/timeline - walreceiver.rs (source / functions) Coverage Total Hit
Test: 8ac049b474321fdc72ddcb56d7165153a1a900e8.info Lines: 82.0 % 111 91
Test Date: 2023-09-06 10:18:01 Functions: 69.7 % 33 23

            Line data    Source code
       1              : //! WAL receiver manages an open connection to safekeeper, to get the WAL it streams into.
       2              : //! To do so, a current implementation needs to do the following:
       3              : //!
       4              : //! * acknowledge the timelines that it needs to stream WAL into.
       5              : //! Pageserver is able to dynamically (un)load tenants on attach and detach,
       6              : //! hence WAL receiver needs to react on such events.
       7              : //!
       8              : //! * get a broker subscription, stream data from it to determine that a timeline needs WAL streaming.
       9              : //! For that, it watches specific keys in storage_broker and pulls the relevant data periodically.
      10              : //! The data is produced by safekeepers, that push it periodically and pull it to synchronize between each other.
      11              : //! Without this data, no WAL streaming is possible currently.
      12              : //!
      13              : //! Only one active WAL streaming connection is allowed at a time.
      14              : //! The connection is supposed to be updated periodically, based on safekeeper timeline data.
      15              : //!
      16              : //! * handle the actual connection and WAL streaming
      17              : //!
      18              : //! Handling happens dynamically, by portions of WAL being processed and registered in the server.
      19              : //! Along with the registration, certain metadata is written to show WAL streaming progress and rely on that when considering safekeepers for connection.
      20              : //!
      21              : //! The current module contains high-level primitives used in the submodules; general synchronization, timeline acknowledgement and shutdown logic.
      22              : 
      23              : mod connection_manager;
      24              : mod walreceiver_connection;
      25              : 
      26              : use crate::context::{DownloadBehavior, RequestContext};
      27              : use crate::task_mgr::{self, TaskKind, WALRECEIVER_RUNTIME};
      28              : use crate::tenant::debug_assert_current_span_has_tenant_and_timeline_id;
      29              : use crate::tenant::timeline::walreceiver::connection_manager::{
      30              :     connection_manager_loop_step, ConnectionManagerState,
      31              : };
      32              : 
      33              : use std::future::Future;
      34              : use std::num::NonZeroU64;
      35              : use std::ops::ControlFlow;
      36              : use std::sync::Arc;
      37              : use std::time::Duration;
      38              : use storage_broker::BrokerClientChannel;
      39              : use tokio::select;
      40              : use tokio::sync::watch;
      41              : use tokio_util::sync::CancellationToken;
      42              : use tracing::*;
      43              : 
      44              : use utils::id::TenantTimelineId;
      45              : 
      46              : use self::connection_manager::ConnectionManagerStatus;
      47              : 
      48              : use super::Timeline;
      49              : 
      50            0 : #[derive(Clone)]
      51              : pub struct WalReceiverConf {
      52              :     /// The timeout on the connection to safekeeper for WAL streaming.
      53              :     pub wal_connect_timeout: Duration,
      54              :     /// The timeout to use to determine when the current connection is "stale" and reconnect to the other one.
      55              :     pub lagging_wal_timeout: Duration,
      56              :     /// The Lsn lag to use to determine when the current connection is lagging to much behind and reconnect to the other one.
      57              :     pub max_lsn_wal_lag: NonZeroU64,
      58              :     pub auth_token: Option<Arc<String>>,
      59              :     pub availability_zone: Option<String>,
      60              : }
      61              : 
      62              : pub struct WalReceiver {
      63              :     timeline: TenantTimelineId,
      64              :     manager_status: Arc<std::sync::RwLock<Option<ConnectionManagerStatus>>>,
      65              : }
      66              : 
      67              : impl WalReceiver {
      68         1190 :     pub fn start(
      69         1190 :         timeline: Arc<Timeline>,
      70         1190 :         conf: WalReceiverConf,
      71         1190 :         mut broker_client: BrokerClientChannel,
      72         1190 :         ctx: &RequestContext,
      73         1190 :     ) -> Self {
      74         1190 :         let tenant_id = timeline.tenant_id;
      75         1190 :         let timeline_id = timeline.timeline_id;
      76         1190 :         let walreceiver_ctx =
      77         1190 :             ctx.detached_child(TaskKind::WalReceiverManager, DownloadBehavior::Error);
      78         1190 : 
      79         1190 :         let loop_status = Arc::new(std::sync::RwLock::new(None));
      80         1190 :         let manager_status = Arc::clone(&loop_status);
      81         1190 :         task_mgr::spawn(
      82         1190 :             WALRECEIVER_RUNTIME.handle(),
      83         1190 :             TaskKind::WalReceiverManager,
      84         1190 :             Some(tenant_id),
      85         1190 :             Some(timeline_id),
      86         1190 :             &format!("walreceiver for timeline {tenant_id}/{timeline_id}"),
      87              :             false,
      88         1190 :             async move {
      89         1190 :                 debug_assert_current_span_has_tenant_and_timeline_id();
      90         1190 :                 debug!("WAL receiver manager started, connecting to broker");
      91         1190 :                 let mut connection_manager_state = ConnectionManagerState::new(
      92         1190 :                     timeline,
      93         1190 :                     conf,
      94         1190 :                 );
      95              :                 loop {
      96         1190 :                     select! {
      97              :                         _ = task_mgr::shutdown_watcher() => {
      98            0 :                             trace!("WAL receiver shutdown requested, shutting down");
      99              :                             break;
     100              :                         },
     101          396 :                         loop_step_result = connection_manager_loop_step(
     102              :                             &mut broker_client,
     103              :                             &mut connection_manager_state,
     104              :                             &walreceiver_ctx,
     105              :                             &loop_status,
     106              :                         ) => match loop_step_result {
     107              :                             ControlFlow::Continue(()) => continue,
     108              :                             ControlFlow::Break(()) => {
     109            0 :                                 trace!("Connection manager loop ended, shutting down");
     110              :                                 break;
     111              :                             }
     112              :                         },
     113              :                     }
     114              :                 }
     115              : 
     116          493 :                 connection_manager_state.shutdown().await;
     117          493 :                 *loop_status.write().unwrap() = None;
     118          493 :                 Ok(())
     119          493 :             }
     120         1190 :             .instrument(info_span!(parent: None, "wal_connection_manager", tenant_id = %tenant_id, timeline_id = %timeline_id))
     121              :         );
     122              : 
     123         1190 :         Self {
     124         1190 :             timeline: TenantTimelineId::new(tenant_id, timeline_id),
     125         1190 :             manager_status,
     126         1190 :         }
     127         1190 :     }
     128              : 
     129          236 :     pub async fn stop(self) {
     130          236 :         task_mgr::shutdown_tasks(
     131          236 :             Some(TaskKind::WalReceiverManager),
     132          236 :             Some(self.timeline.tenant_id),
     133          236 :             Some(self.timeline.timeline_id),
     134          236 :         )
     135           90 :         .await;
     136          236 :     }
     137              : 
     138            4 :     pub(super) fn status(&self) -> Option<ConnectionManagerStatus> {
     139            4 :         self.manager_status.read().unwrap().clone()
     140            4 :     }
     141              : }
     142              : 
     143              : /// A handle of an asynchronous task.
     144              : /// The task has a channel that it can use to communicate its lifecycle events in a certain form, see [`TaskEvent`]
     145              : /// and a cancellation token that it can listen to for earlier interrupts.
     146              : ///
     147              : /// Note that the communication happens via the `watch` channel, that does not accumulate the events, replacing the old one with the never one on submission.
     148              : /// That may lead to certain events not being observed by the listener.
     149            0 : #[derive(Debug)]
     150              : struct TaskHandle<E> {
     151              :     join_handle: Option<tokio::task::JoinHandle<anyhow::Result<()>>>,
     152              :     events_receiver: watch::Receiver<TaskStateUpdate<E>>,
     153              :     cancellation: CancellationToken,
     154              : }
     155              : 
     156              : enum TaskEvent<E> {
     157              :     Update(TaskStateUpdate<E>),
     158              :     End(anyhow::Result<()>),
     159              : }
     160              : 
     161       708362 : #[derive(Debug, Clone)]
     162              : enum TaskStateUpdate<E> {
     163              :     Started,
     164              :     Progress(E),
     165              : }
     166              : 
     167              : impl<E: Clone> TaskHandle<E> {
     168              :     /// Initializes the task, starting it immediately after the creation.
     169         1227 :     fn spawn<Fut>(
     170         1227 :         task: impl FnOnce(watch::Sender<TaskStateUpdate<E>>, CancellationToken) -> Fut + Send + 'static,
     171         1227 :     ) -> Self
     172         1227 :     where
     173         1227 :         Fut: Future<Output = anyhow::Result<()>> + Send,
     174         1227 :         E: Send + Sync + 'static,
     175         1227 :     {
     176         1227 :         let cancellation = CancellationToken::new();
     177         1227 :         let (events_sender, events_receiver) = watch::channel(TaskStateUpdate::Started);
     178         1227 : 
     179         1227 :         let cancellation_clone = cancellation.clone();
     180         1227 :         let join_handle = WALRECEIVER_RUNTIME.spawn(async move {
     181         1227 :             events_sender.send(TaskStateUpdate::Started).ok();
     182      6020590 :             task(events_sender, cancellation_clone).await
     183              :             // events_sender is dropped at some point during the .await above.
     184              :             // But the task is still running on WALRECEIVER_RUNTIME.
     185              :             // That is the window when `!jh.is_finished()`
     186              :             // is true inside `fn next_task_event()` below.
     187         1227 :         });
     188         1227 : 
     189         1227 :         TaskHandle {
     190         1227 :             join_handle: Some(join_handle),
     191         1227 :             events_receiver,
     192         1227 :             cancellation,
     193         1227 :         }
     194         1227 :     }
     195              : 
     196       714721 :     async fn next_task_event(&mut self) -> TaskEvent<E> {
     197       714721 :         match self.events_receiver.changed().await {
     198       708362 :             Ok(()) => TaskEvent::Update((self.events_receiver.borrow()).clone()),
     199          906 :             Err(_task_channel_part_dropped) => {
     200          906 :                 TaskEvent::End(match self.join_handle.as_mut() {
     201          906 :                     Some(jh) => {
     202          906 :                         if !jh.is_finished() {
     203              :                             // See: https://github.com/neondatabase/neon/issues/2885
     204            0 :                             trace!("sender is dropped while join handle is still alive");
     205          865 :                         }
     206              : 
     207          906 :                         let res = match jh.await {
     208          906 :                             Ok(res) => res,
     209            0 :                             Err(je) if je.is_cancelled() => unreachable!("not used"),
     210            0 :                             Err(je) if je.is_panic() => {
     211            0 :                                 // already logged
     212            0 :                                 Ok(())
     213              :                             }
     214            0 :                             Err(je) => Err(anyhow::Error::new(je).context("join walreceiver task")),
     215              :                         };
     216              : 
     217              :                         // For cancellation-safety, drop join_handle only after successful .await.
     218          906 :                         self.join_handle = None;
     219          906 : 
     220          906 :                         res
     221              :                     }
     222              :                     None => {
     223              :                         // Another option is to have an enum, join handle or result and give away the reference to it
     224            0 :                         Err(anyhow::anyhow!("Task was joined more than once"))
     225              :                     }
     226              :                 })
     227              :             }
     228              :         }
     229       709268 :     }
     230              : 
     231              :     /// Aborts current task, waiting for it to finish.
     232          256 :     async fn shutdown(self) {
     233          256 :         if let Some(jh) = self.join_handle {
     234          256 :             self.cancellation.cancel();
     235          305 :             match jh.await {
     236            0 :                 Ok(Ok(())) => debug!("Shutdown success"),
     237            0 :                 Ok(Err(e)) => error!("Shutdown task error: {e:?}"),
     238            0 :                 Err(je) if je.is_cancelled() => unreachable!("not used"),
     239            0 :                 Err(je) if je.is_panic() => {
     240            0 :                     // already logged
     241            0 :                 }
     242            0 :                 Err(je) => {
     243            0 :                     error!("Shutdown task join error: {je}")
     244              :                 }
     245              :             }
     246            0 :         }
     247          256 :     }
     248              : }
        

Generated by: LCOV version 2.1-beta