use smallvec::SmallVec;
use std::borrow::Cow;
use std::cell::RefCell;
use std::sync::atomic::{AtomicUsize, Ordering};
pub type ContextVec = SmallVec<[(Cow<'static, str>, Cow<'static, str>); 1]>;
static NEXT_THREAD_ID: AtomicUsize = AtomicUsize::new(1);
thread_local! {
static THREAD_ID: usize = NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed);
static ARENA: RefCell<ContextArena> = RefCell::new(ContextArena::new());
}
pub fn current_thread_id() -> usize {
THREAD_ID.with(|id| *id)
}
#[derive(Debug, Clone, Default)]
pub enum ContextHandle {
Arena {
offset: usize,
len: usize,
thread_id: usize,
},
Heap(Box<ContextVec>),
#[default]
Empty,
}
#[cfg(feature = "serde")]
impl serde::Serialize for ContextHandle {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let pairs = match self {
Self::Empty => Vec::new(),
Self::Heap(v) => v.to_vec(),
Self::Arena {
offset,
len,
thread_id,
} => {
if *thread_id == current_thread_id() {
with_arena(|arena| arena.get_pairs(*offset, *len))
} else {
Vec::new()
}
}
};
let mut seq = serializer.serialize_seq(Some(pairs.len()))?;
for pair in pairs {
seq.serialize_element(&pair)?;
}
seq.end()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for ContextHandle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let pairs: Vec<(Cow<'static, str>, Cow<'static, str>)> =
serde::Deserialize::deserialize(deserializer)?;
if pairs.is_empty() {
Ok(Self::Empty)
} else {
let mut v = ContextVec::new();
for p in pairs {
v.push(p);
}
Ok(Self::Heap(Box::new(v)))
}
}
}
impl ContextHandle {
pub fn len(&self) -> usize {
match self {
Self::Arena { len, .. } => *len,
Self::Heap(v) => v.len(),
Self::Empty => 0,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub struct ContextArena {
buffer: Vec<u8>,
}
impl ContextArena {
fn new() -> Self {
Self {
buffer: Vec::with_capacity(8192),
}
}
pub fn clear(&mut self) {
self.buffer.clear();
}
pub fn push_pairs(
&mut self,
pairs: &[(Cow<'static, str>, Cow<'static, str>)],
) -> (usize, usize) {
let offset = self.buffer.len();
for (k, v) in pairs {
let k_bytes = k.as_bytes();
let v_bytes = v.as_bytes();
self.buffer
.extend_from_slice(&(k_bytes.len() as u32).to_le_bytes());
self.buffer.extend_from_slice(k_bytes);
self.buffer
.extend_from_slice(&(v_bytes.len() as u32).to_le_bytes());
self.buffer.extend_from_slice(v_bytes);
}
(offset, pairs.len())
}
pub fn get_pairs(
&self,
offset: usize,
len: usize,
) -> Vec<(Cow<'static, str>, Cow<'static, str>)> {
let mut pairs = Vec::with_capacity(len);
let mut pos = offset;
for _ in 0..len {
if pos + 4 > self.buffer.len() {
break;
}
let k_len = u32::from_le_bytes(self.buffer[pos..pos + 4].try_into().unwrap()) as usize;
pos += 4;
if pos + k_len > self.buffer.len() {
break;
}
let k = String::from_utf8_lossy(&self.buffer[pos..pos + k_len]).into_owned();
pos += k_len;
if pos + 4 > self.buffer.len() {
break;
}
let v_len = u32::from_le_bytes(self.buffer[pos..pos + 4].try_into().unwrap()) as usize;
pos += 4;
if pos + v_len > self.buffer.len() {
break;
}
let v = String::from_utf8_lossy(&self.buffer[pos..pos + v_len]).into_owned();
pos += v_len;
pairs.push((Cow::Owned(k), Cow::Owned(v)));
}
pairs
}
}
pub fn with_arena<R>(f: impl FnOnce(&mut ContextArena) -> R) -> R {
ARENA.with(|arena| f(&mut arena.borrow_mut()))
}
pub fn commit_to_arena(pairs: &[(Cow<'static, str>, Cow<'static, str>)]) -> ContextHandle {
let thread_id = current_thread_id();
let (offset, len) = with_arena(|arena| arena.push_pairs(pairs));
ContextHandle::Arena {
offset,
len,
thread_id,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
#[test]
fn test_large_context_spill() {
let mut error = Error::not_found();
for i in 0..100 {
error = error.with_context(format!("key{i}"), format!("value{i}"));
}
assert_eq!(error.context().len(), 100);
}
#[test]
fn test_error_stack_size() {
let size = std::mem::size_of::<Error>();
assert!(size <= 160, "Error size {size} exceeds 160 bytes");
}
#[test]
fn test_context_handle_size() {
let size = std::mem::size_of::<ContextHandle>();
assert!(size <= 32, "ContextHandle size {size} exceeds 32 bytes");
}
#[test]
fn test_arena_push_get() {
let pairs = vec![
(Cow::Borrowed("key1"), Cow::Borrowed("val1")),
(Cow::Borrowed("key2"), Cow::Owned("val2".to_string())),
];
with_arena(|arena| {
let (offset, len) = arena.push_pairs(&pairs);
let retrieved = arena.get_pairs(offset, len);
assert_eq!(retrieved.len(), 2);
assert_eq!(retrieved[0].0, "key1");
assert_eq!(retrieved[1].1, "val2");
});
}
#[test]
fn test_arena_clear() {
with_arena(|arena| {
arena.push_pairs(&[(Cow::Borrowed("k"), Cow::Borrowed("v"))]);
assert!(!arena.buffer.is_empty());
arena.clear();
assert_eq!(arena.buffer.len(), 0);
});
}
}