Skip to main content

reth_dst/
lib.rs

1//! Bedrock assertion macros for reth DST workloads.
2//!
3//! `always!` records a condition that must always hold; `sometimes!` records
4//! one that must hold at least once. False observations are written without
5//! panicking. Reporting errors panic with the assertion name.
6
7use std::{
8    fs::{File, OpenOptions},
9    io::{self, Write},
10    sync::{Mutex, OnceLock},
11    time::{SystemTime, UNIX_EPOCH},
12};
13
14use bedrock_assertions::Assertion;
15use serde::Serialize;
16
17#[doc(hidden)]
18pub use bedrock_assertions as __bedrock;
19#[doc(hidden)]
20pub use serde_json as __serde_json;
21
22static OUTPUT: OnceLock<io::Result<Option<Mutex<File>>>> = OnceLock::new();
23
24/// Appends one assertion to `BEDROCK_ASSERTIONS_PATH` when it is set.
25/// The output path is read once per process. Details are evaluated by the
26/// caller but serialized only when the output is enabled.
27#[doc(hidden)]
28pub fn __report(assertion: Assertion, details: impl Serialize) {
29    let result = (|| {
30        let output = OUTPUT.get_or_init(|| {
31            std::env::var_os("BEDROCK_ASSERTIONS_PATH")
32                .map(|path| OpenOptions::new().create(true).append(true).open(path))
33                .transpose()
34                .map(|file| file.map(Mutex::new))
35        });
36        let Some(output) =
37            output.as_ref().map_err(|error| io::Error::new(error.kind(), error.to_string()))?
38        else {
39            return Ok(());
40        };
41
42        let mut record = serde_json::to_value(&assertion).map_err(io::Error::other)?;
43        let details = serde_json::to_value(details).map_err(io::Error::other)?;
44        let kind = match assertion {
45            Assertion::Always(_) => "Always",
46            Assertion::Sometimes(_) => "Sometimes",
47        };
48        let data = record[kind].as_object_mut().expect("Bedrock assertion has an object payload");
49        data.insert(
50            "timestamp_unix_nano".into(),
51            (SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos() as u64)
52                .into(),
53        );
54        if !details.is_null() {
55            data.insert("details".into(), details);
56        }
57        let mut line = serde_json::to_vec(&record).map_err(io::Error::other)?;
58        line.push(b'\n');
59        if line.len() > 16 * 1024 {
60            return Err(io::Error::new(io::ErrorKind::InvalidInput, "DST record exceeds 16 KiB"));
61        }
62        let mut file = output.lock().map_err(|_| io::Error::other("DST writer poisoned"))?;
63        // One append syscall keeps records from different processes from interleaving.
64        if file.write(&line)? != line.len() {
65            return Err(io::Error::new(io::ErrorKind::WriteZero, "partial DST write"));
66        }
67        Ok::<_, io::Error>(())
68    })();
69    if let Err(error) = result {
70        panic!("reth-dst could not report assertion {:?}: {error}", assertion.data().message);
71    }
72}
73
74/// Records a safety assertion. The name must be a nonempty constant string.
75#[macro_export]
76macro_rules! always {
77    ($condition:expr, $name:expr $(, $details:expr)? $(,)?) => {{
78        $crate::__assert!($condition, $name, always $(, $details)?);
79    }};
80}
81
82/// Records an assertion that must be true at least once.
83/// The name must be a nonempty constant string.
84#[macro_export]
85macro_rules! sometimes {
86    ($condition:expr, $name:expr $(, $details:expr)? $(,)?) => {{
87        $crate::__assert!($condition, $name, sometimes $(, $details)?);
88    }};
89}
90
91#[doc(hidden)]
92#[macro_export]
93macro_rules! __assert {
94    ($condition:expr, $name:expr, $kind:ident $(,)?) => {{
95        $crate::__assert!($condition, $name, $kind, $crate::__serde_json::Value::Null);
96    }};
97    ($condition:expr, $name:expr, $kind:ident, $details:expr $(,)?) => {{
98        const NAME: &str = $name;
99        const _: () = assert!(!NAME.is_empty(), "assertion name must not be empty");
100        let condition: bool = $condition;
101        let details = $details;
102        $crate::__report(
103            $crate::__bedrock::Assertion::$kind(
104                $crate::__bedrock::Condition::Bool(condition),
105                NAME,
106                $crate::__bedrock::Location::new(file!(), line!(), column!()),
107            ),
108            details,
109        );
110    }};
111}