LCOV - code coverage report
Current view: top level - libs/utils/src - failpoint_support.rs (source / functions) Coverage Total Hit
Test: 691a4c28fe7169edd60b367c52d448a0a6605f1f.info Lines: 7.0 % 86 6
Test Date: 2024-05-10 13:18:37 Functions: 9.3 % 43 4

            Line data    Source code
       1              : //! Failpoint support code shared between pageserver and safekeepers.
       2              : 
       3              : use crate::http::{
       4              :     error::ApiError,
       5              :     json::{json_request, json_response},
       6              : };
       7              : use hyper::{Body, Request, Response, StatusCode};
       8              : use serde::{Deserialize, Serialize};
       9              : use tokio_util::sync::CancellationToken;
      10              : use tracing::*;
      11              : 
      12              : /// use with fail::cfg("$name", "return(2000)")
      13              : ///
      14              : /// The effect is similar to a "sleep(2000)" action, i.e. we sleep for the
      15              : /// specified time (in milliseconds). The main difference is that we use async
      16              : /// tokio sleep function. Another difference is that we print lines to the log,
      17              : /// which can be useful in tests to check that the failpoint was hit.
      18              : ///
      19              : /// Optionally pass a cancellation token, and this failpoint will drop out of
      20              : /// its sleep when the cancellation token fires.  This is useful for testing
      21              : /// cases where we would like to block something, but test its clean shutdown behavior.
      22              : #[macro_export]
      23              : macro_rules! __failpoint_sleep_millis_async {
      24              :     ($name:literal) => {{
      25              :         // If the failpoint is used with a "return" action, set should_sleep to the
      26              :         // returned value (as string). Otherwise it's set to None.
      27       146698 :         let should_sleep = (|| {
      28       146698 :             ::fail::fail_point!($name, |x| x);
      29       146698 :             ::std::option::Option::None
      30              :         })();
      31              : 
      32              :         // Sleep if the action was a returned value
      33              :         if let ::std::option::Option::Some(duration_str) = should_sleep {
      34              :             $crate::failpoint_support::failpoint_sleep_helper($name, duration_str).await
      35              :         }
      36              :     }};
      37              :     ($name:literal, $cancel:expr) => {{
      38              :         // If the failpoint is used with a "return" action, set should_sleep to the
      39              :         // returned value (as string). Otherwise it's set to None.
      40          118 :         let should_sleep = (|| {
      41          118 :             ::fail::fail_point!($name, |x| x);
      42          118 :             ::std::option::Option::None
      43              :         })();
      44              : 
      45              :         // Sleep if the action was a returned value
      46              :         if let ::std::option::Option::Some(duration_str) = should_sleep {
      47              :             $crate::failpoint_support::failpoint_sleep_cancellable_helper(
      48              :                 $name,
      49              :                 duration_str,
      50              :                 $cancel,
      51              :             )
      52              :             .await
      53              :         }
      54              :     }};
      55              : }
      56              : pub use __failpoint_sleep_millis_async as sleep_millis_async;
      57              : 
      58              : // Helper function used by the macro. (A function has nicer scoping so we
      59              : // don't need to decorate everything with "::")
      60              : #[doc(hidden)]
      61            0 : pub async fn failpoint_sleep_helper(name: &'static str, duration_str: String) {
      62            0 :     let millis = duration_str.parse::<u64>().unwrap();
      63            0 :     let d = std::time::Duration::from_millis(millis);
      64            0 : 
      65            0 :     tracing::info!("failpoint {:?}: sleeping for {:?}", name, d);
      66            0 :     tokio::time::sleep(d).await;
      67            0 :     tracing::info!("failpoint {:?}: sleep done", name);
      68            0 : }
      69              : 
      70              : // Helper function used by the macro. (A function has nicer scoping so we
      71              : // don't need to decorate everything with "::")
      72              : #[doc(hidden)]
      73            0 : pub async fn failpoint_sleep_cancellable_helper(
      74            0 :     name: &'static str,
      75            0 :     duration_str: String,
      76            0 :     cancel: &CancellationToken,
      77            0 : ) {
      78            0 :     let millis = duration_str.parse::<u64>().unwrap();
      79            0 :     let d = std::time::Duration::from_millis(millis);
      80            0 : 
      81            0 :     tracing::info!("failpoint {:?}: sleeping for {:?}", name, d);
      82            0 :     tokio::time::timeout(d, cancel.cancelled()).await.ok();
      83            0 :     tracing::info!("failpoint {:?}: sleep done", name);
      84            0 : }
      85              : 
      86            0 : pub fn init() -> fail::FailScenario<'static> {
      87            0 :     // The failpoints lib provides support for parsing the `FAILPOINTS` env var.
      88            0 :     // We want non-default behavior for `exit`, though, so, we handle it separately.
      89            0 :     //
      90            0 :     // Format for FAILPOINTS is "name=actions" separated by ";".
      91            0 :     let actions = std::env::var("FAILPOINTS");
      92            0 :     if actions.is_ok() {
      93            0 :         std::env::remove_var("FAILPOINTS");
      94            0 :     } else {
      95            0 :         // let the library handle non-utf8, or nothing for not present
      96            0 :     }
      97              : 
      98            0 :     let scenario = fail::FailScenario::setup();
      99              : 
     100            0 :     if let Ok(val) = actions {
     101            0 :         val.split(';')
     102            0 :             .enumerate()
     103            0 :             .map(|(i, s)| s.split_once('=').ok_or((i, s)))
     104            0 :             .for_each(|res| {
     105            0 :                 let (name, actions) = match res {
     106            0 :                     Ok(t) => t,
     107            0 :                     Err((i, s)) => {
     108            0 :                         panic!(
     109            0 :                             "startup failpoints: missing action on the {}th failpoint; try `{s}=return`",
     110            0 :                             i + 1,
     111            0 :                         );
     112              :                     }
     113              :                 };
     114            0 :                 if let Err(e) = apply_failpoint(name, actions) {
     115            0 :                     panic!("startup failpoints: failed to apply failpoint {name}={actions}: {e}");
     116            0 :                 }
     117            0 :             });
     118            0 :     }
     119              : 
     120            0 :     scenario
     121            0 : }
     122              : 
     123            0 : pub fn apply_failpoint(name: &str, actions: &str) -> Result<(), String> {
     124            0 :     if actions == "exit" {
     125            0 :         fail::cfg_callback(name, exit_failpoint)
     126              :     } else {
     127            0 :         fail::cfg(name, actions)
     128              :     }
     129            0 : }
     130              : 
     131              : #[inline(never)]
     132            0 : fn exit_failpoint() {
     133            0 :     tracing::info!("Exit requested by failpoint");
     134            0 :     std::process::exit(1);
     135              : }
     136              : 
     137              : pub type ConfigureFailpointsRequest = Vec<FailpointConfig>;
     138              : 
     139              : /// Information for configuring a single fail point
     140            0 : #[derive(Debug, Serialize, Deserialize)]
     141              : pub struct FailpointConfig {
     142              :     /// Name of the fail point
     143              :     pub name: String,
     144              :     /// List of actions to take, using the format described in `fail::cfg`
     145              :     ///
     146              :     /// We also support `actions = "exit"` to cause the fail point to immediately exit.
     147              :     pub actions: String,
     148              : }
     149              : 
     150              : /// Configure failpoints through http.
     151            0 : pub async fn failpoints_handler(
     152            0 :     mut request: Request<Body>,
     153            0 :     _cancel: CancellationToken,
     154            0 : ) -> Result<Response<Body>, ApiError> {
     155            0 :     if !fail::has_failpoints() {
     156            0 :         return Err(ApiError::BadRequest(anyhow::anyhow!(
     157            0 :             "Cannot manage failpoints because storage was compiled without failpoints support"
     158            0 :         )));
     159            0 :     }
     160              : 
     161            0 :     let failpoints: ConfigureFailpointsRequest = json_request(&mut request).await?;
     162            0 :     for fp in failpoints {
     163            0 :         info!("cfg failpoint: {} {}", fp.name, fp.actions);
     164              : 
     165              :         // We recognize one extra "action" that's not natively recognized
     166              :         // by the failpoints crate: exit, to immediately kill the process
     167            0 :         let cfg_result = apply_failpoint(&fp.name, &fp.actions);
     168              : 
     169            0 :         if let Err(err_msg) = cfg_result {
     170            0 :             return Err(ApiError::BadRequest(anyhow::anyhow!(
     171            0 :                 "Failed to configure failpoints: {err_msg}"
     172            0 :             )));
     173            0 :         }
     174              :     }
     175              : 
     176            0 :     json_response(StatusCode::OK, ())
     177            0 : }
        

Generated by: LCOV version 2.1-beta