LCOV - code coverage report
Current view: top level - pageserver/src/tenant - tasks.rs (source / functions) Coverage Total Hit
Test: f081ec316c96fa98335efd15ef501745aa4f015d.info Lines: 9.0 % 368 33
Test Date: 2024-06-25 15:11:17 Functions: 18.2 % 33 6

            Line data    Source code
       1              : //! This module contains functions to serve per-tenant background processes,
       2              : //! such as compaction and GC
       3              : 
       4              : use std::ops::ControlFlow;
       5              : use std::str::FromStr;
       6              : use std::sync::Arc;
       7              : use std::time::{Duration, Instant};
       8              : 
       9              : use crate::context::{DownloadBehavior, RequestContext};
      10              : use crate::metrics::TENANT_TASK_EVENTS;
      11              : use crate::task_mgr;
      12              : use crate::task_mgr::{TaskKind, BACKGROUND_RUNTIME};
      13              : use crate::tenant::config::defaults::DEFAULT_COMPACTION_PERIOD;
      14              : use crate::tenant::throttle::Stats;
      15              : use crate::tenant::timeline::CompactionError;
      16              : use crate::tenant::{Tenant, TenantState};
      17              : use rand::Rng;
      18              : use tokio_util::sync::CancellationToken;
      19              : use tracing::*;
      20              : use utils::{backoff, completion, pausable_failpoint};
      21              : 
      22              : static CONCURRENT_BACKGROUND_TASKS: once_cell::sync::Lazy<tokio::sync::Semaphore> =
      23           20 :     once_cell::sync::Lazy::new(|| {
      24           20 :         let total_threads = task_mgr::TOKIO_WORKER_THREADS.get();
      25           20 :         let permits = usize::max(
      26           20 :             1,
      27           20 :             // while a lot of the work is done on spawn_blocking, we still do
      28           20 :             // repartitioning in the async context. this should give leave us some workers
      29           20 :             // unblocked to be blocked on other work, hopefully easing any outside visible
      30           20 :             // effects of restarts.
      31           20 :             //
      32           20 :             // 6/8 is a guess; previously we ran with unlimited 8 and more from
      33           20 :             // spawn_blocking.
      34           20 :             (total_threads * 3).checked_div(4).unwrap_or(0),
      35           20 :         );
      36           20 :         assert_ne!(permits, 0, "we will not be adding in permits later");
      37           20 :         assert!(
      38           20 :             permits < total_threads,
      39            0 :             "need threads avail for shorter work"
      40              :         );
      41           20 :         tokio::sync::Semaphore::new(permits)
      42           20 :     });
      43              : 
      44          180 : #[derive(Debug, PartialEq, Eq, Clone, Copy, strum_macros::IntoStaticStr, enum_map::Enum)]
      45              : #[strum(serialize_all = "snake_case")]
      46              : pub(crate) enum BackgroundLoopKind {
      47              :     Compaction,
      48              :     Gc,
      49              :     Eviction,
      50              :     IngestHouseKeeping,
      51              :     ConsumptionMetricsCollectMetrics,
      52              :     ConsumptionMetricsSyntheticSizeWorker,
      53              :     InitialLogicalSizeCalculation,
      54              :     HeatmapUpload,
      55              :     SecondaryDownload,
      56              : }
      57              : 
      58              : impl BackgroundLoopKind {
      59            0 :     fn as_static_str(&self) -> &'static str {
      60            0 :         self.into()
      61            0 :     }
      62              : }
      63              : 
      64              : static PERMIT_GAUGES: once_cell::sync::Lazy<
      65              :     enum_map::EnumMap<BackgroundLoopKind, metrics::IntCounterPair>,
      66           20 : > = once_cell::sync::Lazy::new(|| {
      67          180 :     enum_map::EnumMap::from_array(std::array::from_fn(|i| {
      68          180 :         let kind = <BackgroundLoopKind as enum_map::Enum>::from_usize(i);
      69          180 :         crate::metrics::BACKGROUND_LOOP_SEMAPHORE_WAIT_GAUGE.with_label_values(&[kind.into()])
      70          180 :     }))
      71           20 : });
      72              : 
      73              : /// Cancellation safe.
      74          364 : pub(crate) async fn concurrent_background_tasks_rate_limit_permit(
      75          364 :     loop_kind: BackgroundLoopKind,
      76          364 :     _ctx: &RequestContext,
      77          364 : ) -> tokio::sync::SemaphorePermit<'static> {
      78          364 :     let _guard = PERMIT_GAUGES[loop_kind].guard();
      79              : 
      80              :     pausable_failpoint!(
      81              :         "initial-size-calculation-permit-pause",
      82              :         loop_kind == BackgroundLoopKind::InitialLogicalSizeCalculation
      83              :     );
      84              : 
      85              :     // TODO: assert that we run on BACKGROUND_RUNTIME; requires tokio_unstable Handle::id();
      86          364 :     match CONCURRENT_BACKGROUND_TASKS.acquire().await {
      87          364 :         Ok(permit) => permit,
      88            0 :         Err(_closed) => unreachable!("we never close the semaphore"),
      89              :     }
      90          364 : }
      91              : 
      92              : /// Start per tenant background loops: compaction and gc.
      93            0 : pub fn start_background_loops(
      94            0 :     tenant: &Arc<Tenant>,
      95            0 :     background_jobs_can_start: Option<&completion::Barrier>,
      96            0 : ) {
      97            0 :     let tenant_shard_id = tenant.tenant_shard_id;
      98            0 :     task_mgr::spawn(
      99            0 :         BACKGROUND_RUNTIME.handle(),
     100            0 :         TaskKind::Compaction,
     101            0 :         Some(tenant_shard_id),
     102            0 :         None,
     103            0 :         &format!("compactor for tenant {tenant_shard_id}"),
     104            0 :         false,
     105            0 :         {
     106            0 :             let tenant = Arc::clone(tenant);
     107            0 :             let background_jobs_can_start = background_jobs_can_start.cloned();
     108            0 :             async move {
     109            0 :                 let cancel = task_mgr::shutdown_token();
     110              :                 tokio::select! {
     111              :                     _ = cancel.cancelled() => { return Ok(()) },
     112              :                     _ = completion::Barrier::maybe_wait(background_jobs_can_start) => {}
     113              :                 };
     114            0 :                 compaction_loop(tenant, cancel)
     115            0 :                     // If you rename this span, change the RUST_LOG env variable in test_runner/performance/test_branch_creation.py
     116            0 :                     .instrument(info_span!("compaction_loop", tenant_id = %tenant_shard_id.tenant_id, shard_id = %tenant_shard_id.shard_slug()))
     117            0 :                     .await;
     118            0 :                 Ok(())
     119            0 :             }
     120            0 :         },
     121            0 :     );
     122            0 :     task_mgr::spawn(
     123            0 :         BACKGROUND_RUNTIME.handle(),
     124            0 :         TaskKind::GarbageCollector,
     125            0 :         Some(tenant_shard_id),
     126            0 :         None,
     127            0 :         &format!("garbage collector for tenant {tenant_shard_id}"),
     128            0 :         false,
     129            0 :         {
     130            0 :             let tenant = Arc::clone(tenant);
     131            0 :             let background_jobs_can_start = background_jobs_can_start.cloned();
     132            0 :             async move {
     133            0 :                 let cancel = task_mgr::shutdown_token();
     134              :                 tokio::select! {
     135              :                     _ = cancel.cancelled() => { return Ok(()) },
     136              :                     _ = completion::Barrier::maybe_wait(background_jobs_can_start) => {}
     137              :                 };
     138            0 :                 gc_loop(tenant, cancel)
     139            0 :                     .instrument(info_span!("gc_loop", tenant_id = %tenant_shard_id.tenant_id, shard_id = %tenant_shard_id.shard_slug()))
     140            0 :                     .await;
     141            0 :                 Ok(())
     142            0 :             }
     143            0 :         },
     144            0 :     );
     145            0 : 
     146            0 :     task_mgr::spawn(
     147            0 :         BACKGROUND_RUNTIME.handle(),
     148            0 :         TaskKind::IngestHousekeeping,
     149            0 :         Some(tenant_shard_id),
     150            0 :         None,
     151            0 :         &format!("ingest housekeeping for tenant {tenant_shard_id}"),
     152            0 :         false,
     153            0 :         {
     154            0 :             let tenant = Arc::clone(tenant);
     155            0 :             let background_jobs_can_start = background_jobs_can_start.cloned();
     156            0 :             async move {
     157            0 :                 let cancel = task_mgr::shutdown_token();
     158              :                 tokio::select! {
     159              :                     _ = cancel.cancelled() => { return Ok(()) },
     160              :                     _ = completion::Barrier::maybe_wait(background_jobs_can_start) => {}
     161              :                 };
     162            0 :                 ingest_housekeeping_loop(tenant, cancel)
     163            0 :                     .instrument(info_span!("ingest_housekeeping_loop", tenant_id = %tenant_shard_id.tenant_id, shard_id = %tenant_shard_id.shard_slug()))
     164            0 :                     .await;
     165            0 :                 Ok(())
     166            0 :             }
     167            0 :         },
     168            0 :     );
     169            0 : }
     170              : 
     171              : ///
     172              : /// Compaction task's main loop
     173              : ///
     174            0 : async fn compaction_loop(tenant: Arc<Tenant>, cancel: CancellationToken) {
     175            0 :     const MAX_BACKOFF_SECS: f64 = 300.0;
     176            0 :     // How many errors we have seen consequtively
     177            0 :     let mut error_run_count = 0;
     178            0 : 
     179            0 :     let mut last_throttle_flag_reset_at = Instant::now();
     180            0 : 
     181            0 :     TENANT_TASK_EVENTS.with_label_values(&["start"]).inc();
     182            0 :     async {
     183            0 :         let ctx = RequestContext::todo_child(TaskKind::Compaction, DownloadBehavior::Download);
     184            0 :         let mut first = true;
     185            0 :         loop {
     186            0 :             tokio::select! {
     187              :                 _ = cancel.cancelled() => {
     188              :                     return;
     189              :                 },
     190              :                 tenant_wait_result = wait_for_active_tenant(&tenant) => match tenant_wait_result {
     191              :                     ControlFlow::Break(()) => return,
     192              :                     ControlFlow::Continue(()) => (),
     193              :                 },
     194              :             }
     195              : 
     196            0 :             let period = tenant.get_compaction_period();
     197            0 : 
     198            0 :             // TODO: we shouldn't need to await to find tenant and this could be moved outside of
     199            0 :             // loop, #3501. There are also additional "allowed_errors" in tests.
     200            0 :             if first {
     201            0 :                 first = false;
     202            0 :                 if random_init_delay(period, &cancel).await.is_err() {
     203            0 :                     break;
     204            0 :                 }
     205            0 :             }
     206              : 
     207            0 :             let started_at = Instant::now();
     208              : 
     209            0 :             let sleep_duration = if period == Duration::ZERO {
     210              :                 #[cfg(not(feature = "testing"))]
     211              :                 info!("automatic compaction is disabled");
     212              :                 // check again in 10 seconds, in case it's been enabled again.
     213            0 :                 Duration::from_secs(10)
     214              :             } else {
     215              :                 // Run compaction
     216            0 :                 if let Err(e) = tenant.compaction_iteration(&cancel, &ctx).await {
     217            0 :                     let wait_duration = backoff::exponential_backoff_duration_seconds(
     218            0 :                         error_run_count + 1,
     219            0 :                         1.0,
     220            0 :                         MAX_BACKOFF_SECS,
     221            0 :                     );
     222            0 :                     error_run_count += 1;
     223            0 :                     let wait_duration = Duration::from_secs_f64(wait_duration);
     224            0 :                     log_compaction_error(
     225            0 :                         &e,
     226            0 :                         error_run_count,
     227            0 :                         &wait_duration,
     228            0 :                         cancel.is_cancelled(),
     229            0 :                     );
     230            0 :                     wait_duration
     231              :                 } else {
     232            0 :                     error_run_count = 0;
     233            0 :                     period
     234              :                 }
     235              :             };
     236              : 
     237            0 :             let elapsed = started_at.elapsed();
     238            0 :             warn_when_period_overrun(elapsed, period, BackgroundLoopKind::Compaction);
     239            0 : 
     240            0 :             // the duration is recorded by performance tests by enabling debug in this function
     241            0 :             tracing::debug!(elapsed_ms=elapsed.as_millis(), "compaction iteration complete");
     242              : 
     243              :             // Perhaps we did no work and the walredo process has been idle for some time:
     244              :             // give it a chance to shut down to avoid leaving walredo process running indefinitely.
     245            0 :             if let Some(walredo_mgr) = &tenant.walredo_mgr {
     246            0 :                 walredo_mgr.maybe_quiesce(period * 10);
     247            0 :             }
     248              : 
     249              :             // TODO: move this (and walredo quiesce) to a separate task that isn't affected by the back-off,
     250              :             // so we get some upper bound guarantee on when walredo quiesce / this throttling reporting here happens.
     251            0 :             info_span!(parent: None, "timeline_get_throttle", tenant_id=%tenant.tenant_shard_id, shard_id=%tenant.tenant_shard_id.shard_slug()).in_scope(|| {
     252            0 :                 let now = Instant::now();
     253            0 :                 let prev = std::mem::replace(&mut last_throttle_flag_reset_at, now);
     254            0 :                 let Stats { count_accounted, count_throttled, sum_throttled_usecs } = tenant.timeline_get_throttle.reset_stats();
     255            0 :                 if count_throttled == 0 {
     256            0 :                     return;
     257            0 :                 }
     258            0 :                 let allowed_rps = tenant.timeline_get_throttle.steady_rps();
     259            0 :                 let delta = now - prev;
     260            0 :                 info!(
     261            0 :                     n_seconds=%format_args!("{:.3}",
     262            0 :                     delta.as_secs_f64()),
     263              :                     count_accounted,
     264              :                     count_throttled,
     265              :                     sum_throttled_usecs,
     266            0 :                     allowed_rps=%format_args!("{allowed_rps:.0}"),
     267            0 :                     "shard was throttled in the last n_seconds")
     268            0 :             });
     269            0 : 
     270            0 :             // Sleep
     271            0 :             if tokio::time::timeout(sleep_duration, cancel.cancelled())
     272            0 :                 .await
     273            0 :                 .is_ok()
     274              :             {
     275            0 :                 break;
     276            0 :             }
     277              :         }
     278            0 :     }
     279            0 :     .await;
     280            0 :     TENANT_TASK_EVENTS.with_label_values(&["stop"]).inc();
     281            0 : }
     282              : 
     283            0 : fn log_compaction_error(
     284            0 :     e: &CompactionError,
     285            0 :     error_run_count: u32,
     286            0 :     sleep_duration: &std::time::Duration,
     287            0 :     task_cancelled: bool,
     288            0 : ) {
     289              :     use crate::tenant::upload_queue::NotInitialized;
     290              :     use crate::tenant::PageReconstructError;
     291              :     use CompactionError::*;
     292              : 
     293              :     enum LooksLike {
     294              :         Info,
     295              :         Error,
     296              :     }
     297              : 
     298            0 :     let decision = match e {
     299            0 :         ShuttingDown => None,
     300            0 :         _ if task_cancelled => Some(LooksLike::Info),
     301            0 :         Other(e) => {
     302            0 :             let root_cause = e.root_cause();
     303              : 
     304            0 :             let is_stopping = {
     305            0 :                 let upload_queue = root_cause
     306            0 :                     .downcast_ref::<NotInitialized>()
     307            0 :                     .is_some_and(|e| e.is_stopping());
     308            0 : 
     309            0 :                 let timeline = root_cause
     310            0 :                     .downcast_ref::<PageReconstructError>()
     311            0 :                     .is_some_and(|e| e.is_stopping());
     312            0 : 
     313            0 :                 upload_queue || timeline
     314              :             };
     315              : 
     316            0 :             if is_stopping {
     317            0 :                 Some(LooksLike::Info)
     318              :             } else {
     319            0 :                 Some(LooksLike::Error)
     320              :             }
     321              :         }
     322              :     };
     323              : 
     324            0 :     match decision {
     325            0 :         Some(LooksLike::Info) => info!(
     326            0 :             "Compaction failed {error_run_count} times, retrying in {sleep_duration:?}: {e:#}",
     327              :         ),
     328            0 :         Some(LooksLike::Error) => error!(
     329            0 :             "Compaction failed {error_run_count} times, retrying in {sleep_duration:?}: {e:?}",
     330              :         ),
     331            0 :         None => {}
     332              :     }
     333            0 : }
     334              : 
     335              : ///
     336              : /// GC task's main loop
     337              : ///
     338            0 : async fn gc_loop(tenant: Arc<Tenant>, cancel: CancellationToken) {
     339            0 :     const MAX_BACKOFF_SECS: f64 = 300.0;
     340            0 :     // How many errors we have seen consequtively
     341            0 :     let mut error_run_count = 0;
     342            0 : 
     343            0 :     TENANT_TASK_EVENTS.with_label_values(&["start"]).inc();
     344            0 :     async {
     345            0 :         // GC might require downloading, to find the cutoff LSN that corresponds to the
     346            0 :         // cutoff specified as time.
     347            0 :         let ctx =
     348            0 :             RequestContext::todo_child(TaskKind::GarbageCollector, DownloadBehavior::Download);
     349            0 : 
     350            0 :         let mut first = true;
     351            0 :         loop {
     352            0 :             tokio::select! {
     353              :                 _ = cancel.cancelled() => {
     354              :                     return;
     355              :                 },
     356              :                 tenant_wait_result = wait_for_active_tenant(&tenant) => match tenant_wait_result {
     357              :                     ControlFlow::Break(()) => return,
     358              :                     ControlFlow::Continue(()) => (),
     359              :                 },
     360              :             }
     361              : 
     362            0 :             let period = tenant.get_gc_period();
     363            0 : 
     364            0 :             if first {
     365            0 :                 first = false;
     366            0 : 
     367            0 :                 if delay_by_lease_length(tenant.get_lsn_lease_length(), &cancel)
     368            0 :                     .await
     369            0 :                     .is_err()
     370              :                 {
     371            0 :                     break;
     372            0 :                 }
     373            0 : 
     374            0 :                 if random_init_delay(period, &cancel).await.is_err() {
     375            0 :                     break;
     376            0 :                 }
     377            0 :             }
     378              : 
     379            0 :             let started_at = Instant::now();
     380            0 : 
     381            0 :             let gc_horizon = tenant.get_gc_horizon();
     382            0 :             let sleep_duration = if period == Duration::ZERO || gc_horizon == 0 {
     383              :                 #[cfg(not(feature = "testing"))]
     384              :                 info!("automatic GC is disabled");
     385              :                 // check again in 10 seconds, in case it's been enabled again.
     386            0 :                 Duration::from_secs(10)
     387              :             } else {
     388              :                 // Run gc
     389            0 :                 let res = tenant
     390            0 :                     .gc_iteration(None, gc_horizon, tenant.get_pitr_interval(), &cancel, &ctx)
     391            0 :                     .await;
     392            0 :                 match res {
     393              :                     Ok(_) => {
     394            0 :                         error_run_count = 0;
     395            0 :                         period
     396              :                     }
     397              :                     Err(crate::tenant::GcError::TenantCancelled) => {
     398            0 :                         return;
     399              :                     }
     400            0 :                     Err(e) => {
     401            0 :                         let wait_duration = backoff::exponential_backoff_duration_seconds(
     402            0 :                             error_run_count + 1,
     403            0 :                             1.0,
     404            0 :                             MAX_BACKOFF_SECS,
     405            0 :                         );
     406            0 :                         error_run_count += 1;
     407            0 :                         let wait_duration = Duration::from_secs_f64(wait_duration);
     408            0 : 
     409            0 :                         error!(
     410            0 :                         "Gc failed {error_run_count} times, retrying in {wait_duration:?}: {e:?}",
     411              :                     );
     412            0 :                         wait_duration
     413              :                     }
     414              :                 }
     415              :             };
     416              : 
     417            0 :             warn_when_period_overrun(started_at.elapsed(), period, BackgroundLoopKind::Gc);
     418            0 : 
     419            0 :             // Sleep
     420            0 :             if tokio::time::timeout(sleep_duration, cancel.cancelled())
     421            0 :                 .await
     422            0 :                 .is_ok()
     423              :             {
     424            0 :                 break;
     425            0 :             }
     426              :         }
     427            0 :     }
     428            0 :     .await;
     429            0 :     TENANT_TASK_EVENTS.with_label_values(&["stop"]).inc();
     430            0 : }
     431              : 
     432            0 : async fn ingest_housekeeping_loop(tenant: Arc<Tenant>, cancel: CancellationToken) {
     433            0 :     TENANT_TASK_EVENTS.with_label_values(&["start"]).inc();
     434            0 :     async {
     435            0 :         loop {
     436            0 :             tokio::select! {
     437              :                 _ = cancel.cancelled() => {
     438              :                     return;
     439              :                 },
     440              :                 tenant_wait_result = wait_for_active_tenant(&tenant) => match tenant_wait_result {
     441              :                     ControlFlow::Break(()) => return,
     442              :                     ControlFlow::Continue(()) => (),
     443              :                 },
     444              :             }
     445              : 
     446              :             // We run ingest housekeeping with the same frequency as compaction: it is not worth
     447              :             // having a distinct setting.  But we don't run it in the same task, because compaction
     448              :             // blocks on acquiring the background job semaphore.
     449            0 :             let period = tenant.get_compaction_period();
     450              : 
     451              :             // If compaction period is set to zero (to disable it), then we will use a reasonable default
     452            0 :             let period = if period == Duration::ZERO {
     453            0 :                 humantime::Duration::from_str(DEFAULT_COMPACTION_PERIOD)
     454            0 :                     .unwrap()
     455            0 :                     .into()
     456              :             } else {
     457            0 :                 period
     458              :             };
     459              : 
     460              :             // Jitter the period by +/- 5%
     461            0 :             let period =
     462            0 :                 rand::thread_rng().gen_range((period * (95)) / 100..(period * (105)) / 100);
     463            0 : 
     464            0 :             // Always sleep first: we do not need to do ingest housekeeping early in the lifetime of
     465            0 :             // a tenant, since it won't have started writing any ephemeral files yet.
     466            0 :             if tokio::time::timeout(period, cancel.cancelled())
     467            0 :                 .await
     468            0 :                 .is_ok()
     469              :             {
     470            0 :                 break;
     471            0 :             }
     472            0 : 
     473            0 :             let started_at = Instant::now();
     474            0 :             tenant.ingest_housekeeping().await;
     475              : 
     476            0 :             warn_when_period_overrun(
     477            0 :                 started_at.elapsed(),
     478            0 :                 period,
     479            0 :                 BackgroundLoopKind::IngestHouseKeeping,
     480            0 :             );
     481              :         }
     482            0 :     }
     483            0 :     .await;
     484            0 :     TENANT_TASK_EVENTS.with_label_values(&["stop"]).inc();
     485            0 : }
     486              : 
     487            0 : async fn wait_for_active_tenant(tenant: &Arc<Tenant>) -> ControlFlow<()> {
     488            0 :     // if the tenant has a proper status already, no need to wait for anything
     489            0 :     if tenant.current_state() == TenantState::Active {
     490            0 :         ControlFlow::Continue(())
     491              :     } else {
     492            0 :         let mut tenant_state_updates = tenant.subscribe_for_state_updates();
     493              :         loop {
     494            0 :             match tenant_state_updates.changed().await {
     495              :                 Ok(()) => {
     496            0 :                     let new_state = &*tenant_state_updates.borrow();
     497            0 :                     match new_state {
     498              :                         TenantState::Active => {
     499            0 :                             debug!("Tenant state changed to active, continuing the task loop");
     500            0 :                             return ControlFlow::Continue(());
     501              :                         }
     502            0 :                         state => {
     503            0 :                             debug!("Not running the task loop, tenant is not active: {state:?}");
     504            0 :                             continue;
     505              :                         }
     506              :                     }
     507              :                 }
     508            0 :                 Err(_sender_dropped_error) => {
     509            0 :                     return ControlFlow::Break(());
     510              :                 }
     511              :             }
     512              :         }
     513              :     }
     514            0 : }
     515              : 
     516            0 : #[derive(thiserror::Error, Debug)]
     517              : #[error("cancelled")]
     518              : pub(crate) struct Cancelled;
     519              : 
     520              : /// Provide a random delay for background task initialization.
     521              : ///
     522              : /// This delay prevents a thundering herd of background tasks and will likely keep them running on
     523              : /// different periods for more stable load.
     524            0 : pub(crate) async fn random_init_delay(
     525            0 :     period: Duration,
     526            0 :     cancel: &CancellationToken,
     527            0 : ) -> Result<(), Cancelled> {
     528            0 :     if period == Duration::ZERO {
     529            0 :         return Ok(());
     530            0 :     }
     531            0 : 
     532            0 :     let d = {
     533            0 :         let mut rng = rand::thread_rng();
     534            0 :         rng.gen_range(Duration::ZERO..=period)
     535            0 :     };
     536            0 : 
     537            0 :     match tokio::time::timeout(d, cancel.cancelled()).await {
     538            0 :         Ok(_) => Err(Cancelled),
     539            0 :         Err(_) => Ok(()),
     540              :     }
     541            0 : }
     542              : 
     543              : /// Delays GC by defaul lease length at restart.
     544              : ///
     545              : /// We do this as the leases mapping are not persisted to disk. By delaying GC by default
     546              : /// length, we gurantees that all the leases we granted before the restart will expire
     547              : /// when we run GC for the first time after the restart.
     548            0 : pub(crate) async fn delay_by_lease_length(
     549            0 :     length: Duration,
     550            0 :     cancel: &CancellationToken,
     551            0 : ) -> Result<(), Cancelled> {
     552            0 :     match tokio::time::timeout(length, cancel.cancelled()).await {
     553            0 :         Ok(_) => Err(Cancelled),
     554            0 :         Err(_) => Ok(()),
     555              :     }
     556            0 : }
     557              : 
     558              : /// Attention: the `task` and `period` beocme labels of a pageserver-wide prometheus metric.
     559            0 : pub(crate) fn warn_when_period_overrun(
     560            0 :     elapsed: Duration,
     561            0 :     period: Duration,
     562            0 :     task: BackgroundLoopKind,
     563            0 : ) {
     564            0 :     // Duration::ZERO will happen because it's the "disable [bgtask]" value.
     565            0 :     if elapsed >= period && period != Duration::ZERO {
     566              :         // humantime does no significant digits clamping whereas Duration's debug is a bit more
     567              :         // intelligent. however it makes sense to keep the "configuration format" for period, even
     568              :         // though there's no way to output the actual config value.
     569            0 :         info!(
     570              :             ?elapsed,
     571            0 :             period = %humantime::format_duration(period),
     572            0 :             ?task,
     573            0 :             "task iteration took longer than the configured period"
     574              :         );
     575            0 :         crate::metrics::BACKGROUND_LOOP_PERIOD_OVERRUN_COUNT
     576            0 :             .with_label_values(&[task.as_static_str(), &format!("{}", period.as_secs())])
     577            0 :             .inc();
     578            0 :     }
     579            0 : }
        

Generated by: LCOV version 2.1-beta