#![allow(unsafe_code)]
use core::hash::Hash;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
use kovan_map::HopscotchMap as ConcurrentMap;
struct Node<K, V> {
key: K,
value: V,
next: *mut Node<K, V>,
}
struct NodeRef<K: 'static, V: 'static>(*mut Node<K, V>);
impl<K, V> Clone for NodeRef<K, V> {
fn clone(&self) -> Self {
*self
}
}
impl<K, V> Copy for NodeRef<K, V> {}
unsafe impl<K: Send + Sync, V: Send + Sync> Send for NodeRef<K, V> {}
unsafe impl<K: Send + Sync, V: Send + Sync> Sync for NodeRef<K, V> {}
pub(crate) struct TxnBuffer<K: 'static, V: 'static> {
head: AtomicPtr<Node<K, V>>,
len: AtomicUsize,
spill: OnceLock<ConcurrentMap<K, NodeRef<K, V>>>,
spill_at: usize,
spill_seeded: AtomicBool,
}
unsafe impl<K: Send + Sync + 'static, V: Send + Sync + 'static> Send for TxnBuffer<K, V> {}
unsafe impl<K: Send + Sync + 'static, V: Send + Sync + 'static> Sync for TxnBuffer<K, V> {}
impl<K: 'static, V: 'static> Default for TxnBuffer<K, V> {
fn default() -> Self {
Self {
head: AtomicPtr::new(core::ptr::null_mut()),
len: AtomicUsize::new(0),
spill: OnceLock::new(),
spill_at: 0,
spill_seeded: AtomicBool::new(false),
}
}
}
impl<K, V> TxnBuffer<K, V>
where
K: Hash + Eq + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
pub(crate) fn new(spill_at: usize) -> Self {
Self {
head: AtomicPtr::new(core::ptr::null_mut()),
len: AtomicUsize::new(0),
spill: OnceLock::new(),
spill_at,
spill_seeded: AtomicBool::new(false),
}
}
pub(crate) fn len(&self) -> usize {
self.len.load(Ordering::Acquire)
}
pub(crate) fn insert(&self, key: K, value: V) {
let indexed = self.spill.get();
let index_key = indexed.map(|_| key.clone());
let node = Box::into_raw(Box::new(Node {
key,
value,
next: core::ptr::null_mut(),
}));
let mut head = self.head.load(Ordering::Acquire);
loop {
unsafe { (*node).next = head };
match self
.head
.compare_exchange_weak(head, node, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => break,
Err(current) => head = current,
}
}
if let (Some(spill), Some(index_key)) = (indexed, index_key) {
spill.insert(index_key, NodeRef(node));
}
let len = self.len.fetch_add(1, Ordering::AcqRel) + 1;
if self.spill_at > 0 && len > self.spill_at && self.spill.get().is_none() {
self.start_spilling();
}
}
pub(crate) fn get<Q>(&self, key: &Q) -> Option<V>
where
K: core::borrow::Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
if let Some(spill) = self.spill.get() {
if let Some(NodeRef(node)) = spill.get(key) {
return Some(unsafe { &*node }.value.clone());
}
if self.spill_seeded.load(Ordering::Acquire) {
return None;
}
}
self.walk(key)
}
fn walk<Q>(&self, key: &Q) -> Option<V>
where
K: core::borrow::Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut cursor = self.head.load(Ordering::Acquire);
while !cursor.is_null() {
let node = unsafe { &*cursor };
if node.key.borrow() == key {
return Some(node.value.clone());
}
cursor = node.next;
}
None
}
fn start_spilling(&self) {
if self.spill.set(ConcurrentMap::new()).is_err() {
return;
}
let spill = self.spill.get().expect("just set");
let mut cursor = self.head.load(Ordering::Acquire);
while !cursor.is_null() {
let node = unsafe { &*cursor };
spill.insert_if_absent(node.key.clone(), NodeRef(cursor));
cursor = node.next;
}
self.spill_seeded.store(true, Ordering::Release);
}
pub(crate) fn get_or_insert(&self, key: K, value: V) -> V {
if let Some(existing) = self.get(&key) {
return existing;
}
self.insert(key.clone(), value);
self.get(&key)
.expect("the entry just inserted is on the list")
}
pub(crate) fn snapshot(&self) -> Vec<(K, V)> {
let mut out = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut cursor = self.head.load(Ordering::Acquire);
while !cursor.is_null() {
let node = unsafe { &*cursor };
if seen.insert(node.key.clone()) {
out.push((node.key.clone(), node.value.clone()));
}
cursor = node.next;
}
out
}
pub(crate) fn drain(&mut self) -> Vec<(K, V)> {
let mut drained = Vec::with_capacity(self.len.load(Ordering::Acquire));
let mut cursor = self.head.swap(core::ptr::null_mut(), Ordering::AcqRel);
while !cursor.is_null() {
let node = unsafe { Box::from_raw(cursor) };
cursor = node.next;
drained.push((node.key, node.value));
}
self.len.store(0, Ordering::Release);
self.spill = OnceLock::new();
self.spill_seeded.store(false, Ordering::Release);
drained
}
}
impl<K: 'static, V: 'static> Drop for TxnBuffer<K, V> {
fn drop(&mut self) {
let mut cursor = self.head.swap(core::ptr::null_mut(), Ordering::AcqRel);
while !cursor.is_null() {
let node = unsafe { Box::from_raw(cursor) };
cursor = node.next;
}
}
}