use std::{
cell::{Cell, OnceCell},
panic::AssertUnwindSafe,
rc::{Rc, Weak},
};
use futures_util::FutureExt as _;
use super::{runner::poll_it, RunnableTransaction};
struct DropFlag(Rc<Cell<bool>>);
impl Drop for DropFlag {
fn drop(&mut self) {
self.0.set(true);
}
}
pub struct ScopeCallback<Args> {
state: Rc<OnceCell<Weak<RunnableTransaction<'static>>>>,
_dropped: DropFlag,
maker: Box<dyn 'static + FnOnce(Args) -> RunnableTransaction<'static>>,
}
impl<Args> ScopeCallback<Args> {
pub fn run(self, args: Args) {
let made_state = Rc::new((self.maker)(args));
let _ = self.state.set(Rc::downgrade(&made_state));
poll_it(&made_state);
}
}
pub async fn extend_lifetime_to_scope_and_run<'scope, MakerArgs, ScopeRet>(
maker: Box<dyn 'scope + FnOnce(MakerArgs) -> RunnableTransaction<'scope>>,
scope: impl 'scope + AsyncFnOnce(ScopeCallback<MakerArgs>) -> ScopeRet,
) -> ScopeRet {
let maker: Box<dyn 'static + FnOnce(MakerArgs) -> RunnableTransaction<'static>> =
unsafe { std::mem::transmute(maker) };
let state = Rc::new(OnceCell::new());
let dropped = Rc::new(Cell::new(false));
let callback = ScopeCallback {
state: state.clone(),
_dropped: DropFlag(dropped.clone()),
maker,
};
let result = AssertUnwindSafe((scope)(callback)).catch_unwind().await;
if !dropped.get() {
let _ = std::panic::catch_unwind(|| {
panic!("Bug in the indexed-db crate: the ScopeCallback was not consumed before the end of its logical lifetime")
});
std::process::abort();
}
if let Some(state) = state.get() {
if Weak::strong_count(&state) != 0 {
let _ = std::panic::catch_unwind(|| {
panic!("Bug in the indexed-db crate: the transaction was not dropped before the end of its lifetime")
});
std::process::abort();
}
}
match result {
Ok(result) => result,
Err(err) => std::panic::resume_unwind(err),
}
}