use std::cell::RefCell;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use crate::columnar::layout::ColumnarUpdate as Update;
use crate::columnar::updates::{Updates, UpdatesTyped};
pub trait BytesStore {
fn store(&mut self, bytes: &[u8]) -> Box<dyn BytesSource>;
}
pub trait BytesSource {
fn load(&self) -> Vec<u8>;
}
#[derive(Default)]
pub struct SpillStats {
pub spilled_chunks: AtomicUsize,
pub spilled_records: AtomicUsize,
pub fetched_chunks: AtomicUsize,
pub bytes_written: AtomicUsize,
}
struct SpillState {
budget_records: usize,
resident_records: usize,
store: Box<dyn BytesStore>,
stats: Arc<SpillStats>,
}
thread_local! {
static STATE: RefCell<Option<SpillState>> = const { RefCell::new(None) };
}
pub fn install(budget_records: usize, store: Box<dyn BytesStore>, stats: Arc<SpillStats>) {
STATE.with(|s| *s.borrow_mut() = Some(SpillState { budget_records, resident_records: 0, store, stats }));
}
pub fn uninstall() {
STATE.with(|s| *s.borrow_mut() = None);
}
pub(crate) fn active() -> bool {
STATE.with(|s| s.borrow().is_some())
}
pub(crate) fn try_page<U: Update>(updates: UpdatesTyped<U>) -> Result<Box<dyn BytesSource>, UpdatesTyped<U>> {
STATE.with(|s| {
let mut guard = s.borrow_mut();
let Some(state) = guard.as_mut() else { return Err(updates) };
let records = updates.len();
if state.resident_records + records <= state.budget_records {
state.resident_records += records;
return Err(updates);
}
let mut buf = Vec::new();
Updates::<U>::from(updates).write_to(&mut buf);
state.stats.spilled_chunks.fetch_add(1, Relaxed);
state.stats.spilled_records.fetch_add(records, Relaxed);
state.stats.bytes_written.fetch_add(buf.len(), Relaxed);
Ok(state.store.store(&buf))
})
}
pub(crate) fn note_fetched() {
STATE.with(|s| {
if let Some(state) = s.borrow_mut().as_mut() {
state.stats.fetched_chunks.fetch_add(1, Relaxed);
}
});
}
pub(crate) fn decode<U: Update>(source: &dyn BytesSource) -> UpdatesTyped<U> {
let bytes = timely::bytes::arc::BytesMut::from(source.load()).freeze();
Updates::<U>::read_from(bytes).into_typed()
}