use crate::storage::{Storage, StorageError};
use crate::types::{AppliedTransaction, Output};
use std::{
any::Any,
cell::RefCell,
collections::{HashMap, HashSet},
future::Future,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
#[derive(Clone)]
pub enum PageWrite {
Output { output: Rc<Output>, gasp: bool },
Applied(AppliedTransaction),
Backend(Rc<dyn Any>),
}
pub(crate) struct Known(pub Option<Output>);
type OutputKey = (String, u32, String);
#[derive(Default)]
struct State {
open: bool,
writes: Vec<PageWrite>,
outputs: HashMap<OutputKey, Option<Output>>,
applied: HashMap<(String, String), bool>,
}
#[derive(Clone, Default)]
pub struct PageBuffer(Rc<RefCell<State>>, Rc<RefCell<Option<Bound>>>);
type Bound = (crate::engine::SleepFactory, u64);
impl PageBuffer {
pub fn with_bound(self, sleep: crate::engine::SleepFactory, budget_ms: u64) -> Self {
*self.1.borrow_mut() = Some((sleep, budget_ms));
self
}
pub fn open(&self) {
let mut state = self.0.borrow_mut();
state.open = true;
if state.writes.is_empty() {
state.outputs.clear();
state.applied.clear();
}
}
pub fn close(&self) {
let mut state = self.0.borrow_mut();
state.open = false;
if state.writes.is_empty() {
state.outputs.clear();
state.applied.clear();
}
}
pub fn is_open(&self) -> bool {
self.0.borrow().open
}
pub fn pending(&self) -> usize {
self.0.borrow().writes.len()
}
pub(crate) fn queue_output(&self, output: &Output, gasp: bool) {
let mut state = self.0.borrow_mut();
let mut held = output.clone();
held.beef = None;
state.outputs.insert(
(
output.txid.clone(),
output.output_index,
output.topic.clone(),
),
Some(held),
);
state.writes.push(PageWrite::Output {
output: Rc::new(output.clone()),
gasp,
});
}
pub(crate) fn queue_applied(&self, tx: &AppliedTransaction) {
let mut state = self.0.borrow_mut();
state
.applied
.insert((tx.txid.clone(), tx.topic.clone()), true);
state.writes.push(PageWrite::Applied(tx.clone()));
}
pub(crate) fn output(&self, txid: &str, vout: u32, topic: &str) -> Option<Known> {
self.0
.borrow()
.outputs
.get(&(txid.to_string(), vout, topic.to_string()))
.cloned()
.map(Known)
}
pub(crate) fn remember_output(
&self,
txid: &str,
vout: u32,
topic: &str,
answer: Option<Output>,
) {
self.0
.borrow_mut()
.outputs
.entry((txid.to_string(), vout, topic.to_string()))
.or_insert(answer);
}
pub(crate) fn applied(&self, tx: &AppliedTransaction) -> Option<bool> {
self.0
.borrow()
.applied
.get(&(tx.txid.clone(), tx.topic.clone()))
.copied()
}
pub(crate) fn remember_applied(&self, tx: &AppliedTransaction, held: bool) {
self.0
.borrow_mut()
.applied
.entry((tx.txid.clone(), tx.topic.clone()))
.or_insert(held);
}
pub fn discard(&self) {
let mut state = self.0.borrow_mut();
state.writes.clear();
state.outputs.clear();
state.applied.clear();
}
pub(crate) fn forget_answers(&self) {
let mut state = self.0.borrow_mut();
state.outputs.clear();
state.applied.clear();
}
pub async fn flush(&self, storage: &dyn Storage) -> Result<(), StorageError> {
let writes = self.0.borrow().writes.clone();
if writes.is_empty() {
return Ok(());
}
let bound = self.1.borrow().clone();
match bound {
Some((sleep, budget_ms)) => {
crate::gasp::race_or_deadline(storage.write_batch(&writes), sleep(budget_ms))
.await
.ok_or_else(|| {
StorageError::Database(format!(
"the page's batch had no answer within {budget_ms} ms"
))
})??;
}
None => {
storage.write_batch(&writes).await?;
}
}
let mut state = self.0.borrow_mut();
state.writes.drain(..writes.len());
Ok(())
}
}
thread_local! {
static CURRENT: RefCell<Option<PageBuffer>> = const { RefCell::new(None) };
}
struct Restore(Option<PageBuffer>);
impl Drop for Restore {
fn drop(&mut self) {
CURRENT.with(|c| *c.borrow_mut() = self.0.take());
}
}
pub struct Scoped<F> {
buffer: PageBuffer,
future: Pin<Box<F>>,
}
pub fn scope<F: Future>(buffer: PageBuffer, future: F) -> Scoped<F> {
Scoped {
buffer,
future: Box::pin(future),
}
}
impl<F: Future> Future for Scoped<F> {
type Output = F::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let _restore = Restore(CURRENT.with(|c| c.borrow_mut().replace(self.buffer.clone())));
self.future.as_mut().poll(cx)
}
}
pub fn installed() -> Option<PageBuffer> {
CURRENT.with(|c| c.borrow().clone())
}
pub fn current() -> Option<PageBuffer> {
installed().filter(PageBuffer::is_open)
}
pub fn defer(statement: Rc<dyn Any>) -> Result<(), Rc<dyn Any>> {
match current() {
Some(page) => {
page.0
.borrow_mut()
.writes
.push(PageWrite::Backend(statement));
Ok(())
}
None => Err(statement),
}
}
pub async fn prefetch(
storage: &dyn Storage,
topic: &str,
inputs: &[(String, u32)],
txids: &[String],
) {
let Some(page) = current() else {
return;
};
let inputs: Vec<_> = {
let mut seen = HashSet::new();
inputs
.iter()
.filter(|(txid, vout)| page.output(txid, *vout, topic).is_none())
.filter(|key| seen.insert((*key).clone()))
.cloned()
.collect()
};
if !inputs.is_empty() {
if let Ok(held) = storage.find_topic_outputs(topic, &inputs).await {
let held: HashMap<_, _> = held
.into_iter()
.map(|o| ((o.txid.clone(), o.output_index), o))
.collect();
for (txid, vout) in &inputs {
let answer = held.get(&(txid.clone(), *vout)).cloned();
page.remember_output(txid, *vout, topic, answer);
}
}
}
let txids: Vec<_> = {
let mut seen = HashSet::new();
txids
.iter()
.filter(|txid| {
page.applied(&AppliedTransaction {
txid: (*txid).clone(),
topic: topic.to_string(),
})
.is_none()
})
.filter(|txid| seen.insert((*txid).clone()))
.cloned()
.collect()
};
if !txids.is_empty() {
if let Ok(applied) = storage.find_applied_transactions(topic, &txids).await {
let applied: HashSet<_> = applied.into_iter().collect();
for txid in txids {
let held = applied.contains(&txid);
page.remember_applied(
&AppliedTransaction {
txid,
topic: topic.to_string(),
},
held,
);
}
}
}
}