use std::{cell::Cell, error::Error, fmt};
thread_local! {
static FUNDING_IN_PROGRESS: Cell<bool> = const { Cell::new(false) };
}
pub struct BlobStorageFundingOps;
impl BlobStorageFundingOps {
pub fn try_acquire() -> Result<BlobStorageFundingGuard, BlobStorageFundingInProgress> {
FUNDING_IN_PROGRESS.with(|flag| {
if flag.get() {
Err(BlobStorageFundingInProgress)
} else {
flag.set(true);
Ok(BlobStorageFundingGuard { _private: () })
}
})
}
#[cfg(test)]
#[must_use]
fn in_progress() -> bool {
FUNDING_IN_PROGRESS.with(Cell::get)
}
}
#[must_use]
#[derive(Debug)]
pub struct BlobStorageFundingGuard {
_private: (),
}
impl Drop for BlobStorageFundingGuard {
fn drop(&mut self) {
FUNDING_IN_PROGRESS.with(|flag| flag.set(false));
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BlobStorageFundingInProgress;
impl fmt::Display for BlobStorageFundingInProgress {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("blob-storage funding is already in progress")
}
}
impl Error for BlobStorageFundingInProgress {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn funding_guard_rejects_reentrant_acquire() {
let guard = BlobStorageFundingOps::try_acquire().expect("first acquire succeeds");
assert!(BlobStorageFundingOps::in_progress());
assert_eq!(
BlobStorageFundingOps::try_acquire().expect_err("second acquire should fail"),
BlobStorageFundingInProgress
);
drop(guard);
}
#[test]
fn funding_guard_releases_on_drop() {
{
let _guard = BlobStorageFundingOps::try_acquire().expect("acquire succeeds");
assert!(BlobStorageFundingOps::in_progress());
}
assert!(!BlobStorageFundingOps::in_progress());
let _guard = BlobStorageFundingOps::try_acquire().expect("drop releases guard");
}
}