1use crate::storage::{Storage, StorageError};
28use crate::types::{AppliedTransaction, Output};
29use std::{
30 any::Any,
31 cell::RefCell,
32 collections::{HashMap, HashSet},
33 future::Future,
34 pin::Pin,
35 rc::Rc,
36 task::{Context, Poll},
37};
38
39#[derive(Clone)]
41pub enum PageWrite {
42 Output { output: Rc<Output>, gasp: bool },
44 Applied(AppliedTransaction),
46 Backend(Rc<dyn Any>),
49}
50
51pub(crate) struct Known(pub Option<Output>);
54
55type OutputKey = (String, u32, String);
57
58#[derive(Default)]
59struct State {
60 open: bool,
61 writes: Vec<PageWrite>,
62 outputs: HashMap<OutputKey, Option<Output>>,
65 applied: HashMap<(String, String), bool>,
67}
68
69#[derive(Clone, Default)]
71pub struct PageBuffer(Rc<RefCell<State>>, Rc<RefCell<Option<Bound>>>);
72
73type Bound = (crate::engine::SleepFactory, u64);
75
76impl PageBuffer {
77 pub fn with_bound(self, sleep: crate::engine::SleepFactory, budget_ms: u64) -> Self {
80 *self.1.borrow_mut() = Some((sleep, budget_ms));
81 self
82 }
83
84 pub fn open(&self) {
86 let mut state = self.0.borrow_mut();
87 state.open = true;
88 if state.writes.is_empty() {
90 state.outputs.clear();
91 state.applied.clear();
92 }
93 }
94
95 pub fn close(&self) {
97 let mut state = self.0.borrow_mut();
98 state.open = false;
99 if state.writes.is_empty() {
100 state.outputs.clear();
101 state.applied.clear();
102 }
103 }
104
105 pub fn is_open(&self) -> bool {
106 self.0.borrow().open
107 }
108
109 pub fn pending(&self) -> usize {
111 self.0.borrow().writes.len()
112 }
113
114 pub(crate) fn queue_output(&self, output: &Output, gasp: bool) {
115 let mut state = self.0.borrow_mut();
116 let mut held = output.clone();
117 held.beef = None;
118 state.outputs.insert(
119 (
120 output.txid.clone(),
121 output.output_index,
122 output.topic.clone(),
123 ),
124 Some(held),
125 );
126 state.writes.push(PageWrite::Output {
127 output: Rc::new(output.clone()),
128 gasp,
129 });
130 }
131
132 pub(crate) fn queue_applied(&self, tx: &AppliedTransaction) {
133 let mut state = self.0.borrow_mut();
134 state
135 .applied
136 .insert((tx.txid.clone(), tx.topic.clone()), true);
137 state.writes.push(PageWrite::Applied(tx.clone()));
138 }
139
140 pub(crate) fn output(&self, txid: &str, vout: u32, topic: &str) -> Option<Known> {
142 self.0
143 .borrow()
144 .outputs
145 .get(&(txid.to_string(), vout, topic.to_string()))
146 .cloned()
147 .map(Known)
148 }
149
150 pub(crate) fn remember_output(
151 &self,
152 txid: &str,
153 vout: u32,
154 topic: &str,
155 answer: Option<Output>,
156 ) {
157 self.0
158 .borrow_mut()
159 .outputs
160 .entry((txid.to_string(), vout, topic.to_string()))
161 .or_insert(answer);
162 }
163
164 pub(crate) fn applied(&self, tx: &AppliedTransaction) -> Option<bool> {
165 self.0
166 .borrow()
167 .applied
168 .get(&(tx.txid.clone(), tx.topic.clone()))
169 .copied()
170 }
171
172 pub(crate) fn remember_applied(&self, tx: &AppliedTransaction, held: bool) {
173 self.0
174 .borrow_mut()
175 .applied
176 .entry((tx.txid.clone(), tx.topic.clone()))
177 .or_insert(held);
178 }
179
180 pub fn discard(&self) {
182 let mut state = self.0.borrow_mut();
183 state.writes.clear();
184 state.outputs.clear();
185 state.applied.clear();
186 }
187
188 pub(crate) fn forget_answers(&self) {
190 let mut state = self.0.borrow_mut();
191 state.outputs.clear();
192 state.applied.clear();
193 }
194
195 pub async fn flush(&self, storage: &dyn Storage) -> Result<(), StorageError> {
199 let writes = self.0.borrow().writes.clone();
200 if writes.is_empty() {
201 return Ok(());
202 }
203 let bound = self.1.borrow().clone();
204 match bound {
205 Some((sleep, budget_ms)) => {
206 crate::gasp::race_or_deadline(storage.write_batch(&writes), sleep(budget_ms))
207 .await
208 .ok_or_else(|| {
209 StorageError::Database(format!(
210 "the page's batch had no answer within {budget_ms} ms"
211 ))
212 })??;
213 }
214 None => {
215 storage.write_batch(&writes).await?;
216 }
217 }
218 let mut state = self.0.borrow_mut();
219 state.writes.drain(..writes.len());
220 Ok(())
221 }
222}
223
224thread_local! {
225 static CURRENT: RefCell<Option<PageBuffer>> = const { RefCell::new(None) };
226}
227
228struct Restore(Option<PageBuffer>);
229impl Drop for Restore {
230 fn drop(&mut self) {
231 CURRENT.with(|c| *c.borrow_mut() = self.0.take());
232 }
233}
234
235pub struct Scoped<F> {
237 buffer: PageBuffer,
238 future: Pin<Box<F>>,
239}
240
241pub fn scope<F: Future>(buffer: PageBuffer, future: F) -> Scoped<F> {
242 Scoped {
243 buffer,
244 future: Box::pin(future),
245 }
246}
247
248impl<F: Future> Future for Scoped<F> {
249 type Output = F::Output;
250 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
251 let _restore = Restore(CURRENT.with(|c| c.borrow_mut().replace(self.buffer.clone())));
252 self.future.as_mut().poll(cx)
253 }
254}
255
256pub fn installed() -> Option<PageBuffer> {
258 CURRENT.with(|c| c.borrow().clone())
259}
260
261pub fn current() -> Option<PageBuffer> {
263 installed().filter(PageBuffer::is_open)
264}
265
266pub fn defer(statement: Rc<dyn Any>) -> Result<(), Rc<dyn Any>> {
269 match current() {
270 Some(page) => {
271 page.0
272 .borrow_mut()
273 .writes
274 .push(PageWrite::Backend(statement));
275 Ok(())
276 }
277 None => Err(statement),
278 }
279}
280
281pub async fn prefetch(
287 storage: &dyn Storage,
288 topic: &str,
289 inputs: &[(String, u32)],
290 txids: &[String],
291) {
292 let Some(page) = current() else {
293 return;
294 };
295 let inputs: Vec<_> = {
296 let mut seen = HashSet::new();
297 inputs
298 .iter()
299 .filter(|(txid, vout)| page.output(txid, *vout, topic).is_none())
300 .filter(|key| seen.insert((*key).clone()))
301 .cloned()
302 .collect()
303 };
304 if !inputs.is_empty() {
305 if let Ok(held) = storage.find_topic_outputs(topic, &inputs).await {
306 let held: HashMap<_, _> = held
307 .into_iter()
308 .map(|o| ((o.txid.clone(), o.output_index), o))
309 .collect();
310 for (txid, vout) in &inputs {
311 let answer = held.get(&(txid.clone(), *vout)).cloned();
312 page.remember_output(txid, *vout, topic, answer);
313 }
314 }
315 }
316 let txids: Vec<_> = {
317 let mut seen = HashSet::new();
318 txids
319 .iter()
320 .filter(|txid| {
321 page.applied(&AppliedTransaction {
322 txid: (*txid).clone(),
323 topic: topic.to_string(),
324 })
325 .is_none()
326 })
327 .filter(|txid| seen.insert((*txid).clone()))
328 .cloned()
329 .collect()
330 };
331 if !txids.is_empty() {
332 if let Ok(applied) = storage.find_applied_transactions(topic, &txids).await {
333 let applied: HashSet<_> = applied.into_iter().collect();
334 for txid in txids {
335 let held = applied.contains(&txid);
336 page.remember_applied(
337 &AppliedTransaction {
338 txid,
339 topic: topic.to_string(),
340 },
341 held,
342 );
343 }
344 }
345 }
346}