use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use bytes::Bytes;
use super::Error;
#[derive(Debug)]
pub struct Meter {
live: AtomicUsize,
limit: usize,
tripped: AtomicBool,
}
pub type StopCheck = Box<dyn Fn() -> bool>;
pub const TICKS_PER_POLL: usize = 4096;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Abort {
Memory,
Interrupted,
Recursion,
}
struct Poll {
stop: StopCheck,
ticks: usize,
}
thread_local! {
static CURRENT: RefCell<Option<Arc<Meter>>> = const { RefCell::new(None) };
static POLL: RefCell<Option<Poll>> = const { RefCell::new(None) };
}
pub struct MeterGuard {
prev: Option<Arc<Meter>>,
prev_poll: Option<Poll>,
}
impl Drop for MeterGuard {
fn drop(&mut self) {
let prev = self.prev.take();
CURRENT.with(|c| *c.borrow_mut() = prev);
let prev_poll = self.prev_poll.take();
POLL.with(|p| *p.borrow_mut() = prev_poll);
}
}
pub fn install(limit: usize, stop: Option<StopCheck>) -> (MeterGuard, Arc<Meter>) {
let meter = Arc::new(Meter {
live: AtomicUsize::new(0),
limit,
tripped: AtomicBool::new(false),
});
let prev = CURRENT.with(|c| c.borrow_mut().replace(meter.clone()));
let poll = stop.map(|stop| Poll { stop, ticks: 0 });
let prev_poll = POLL.with(|p| core::mem::replace(&mut *p.borrow_mut(), poll));
(MeterGuard { prev, prev_poll }, meter)
}
fn abort(kind: Abort) {
if kind == Abort::Memory {
trip();
}
#[cfg(panic = "unwind")]
std::panic::resume_unwind(Box::new(kind));
#[cfg(not(panic = "unwind"))]
let _ = kind;
}
pub fn abort_recursion() {
abort(Abort::Recursion);
}
pub fn tick() {
let stop = POLL.with(|p| {
let mut p = p.borrow_mut();
let Some(poll) = p.as_mut() else { return false };
poll.ticks += 1;
poll.ticks.is_multiple_of(TICKS_PER_POLL) && (poll.stop)()
});
if stop {
abort(Abort::Interrupted);
}
}
fn current() -> Option<Arc<Meter>> {
CURRENT.with(|c| c.borrow().clone())
}
impl Meter {
pub fn tripped(&self) -> bool {
self.tripped.load(Ordering::Relaxed)
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn message(&self) -> String {
format!("value size limit ({} bytes) exceeded", self.limit)
}
fn fits(&self, extra: usize) -> bool {
let live = self.live.load(Ordering::Relaxed);
if self.tripped() || live.saturating_add(extra) > self.limit {
self.tripped.store(true, Ordering::Relaxed);
return false;
}
true
}
fn charge(&self, bytes: usize) {
self.live.fetch_add(bytes, Ordering::Relaxed);
}
fn release(&self, bytes: usize) {
self.live.fetch_sub(bytes, Ordering::Relaxed);
}
}
pub fn check(extra: usize) -> Result<(), Error> {
match current() {
Some(m) if !m.fits(extra) => Err(Error::str(m.message())),
_ => Ok(()),
}
}
pub fn headroom() -> usize {
match current() {
Some(m) if !m.tripped() => m.limit.saturating_sub(m.live.load(Ordering::Relaxed)),
Some(_) => 0,
None => usize::MAX,
}
}
pub fn trip() {
if let Some(m) = current() {
m.tripped.store(true, Ordering::Relaxed);
}
}
struct Charge {
meter: Arc<Meter>,
bytes: usize,
}
impl Drop for Charge {
fn drop(&mut self) {
self.meter.release(self.bytes);
}
}
#[derive(Clone, Default)]
pub struct Str {
data: Bytes,
charge: Option<Arc<Charge>>,
}
impl Str {
pub fn new(data: Bytes) -> Self {
tick();
let charge = current().map(|meter| {
let bytes = data.len();
if !meter.fits(bytes) {
abort(Abort::Memory);
}
meter.charge(bytes);
Arc::new(Charge { meter, bytes })
});
Self { data, charge }
}
pub fn into_bytes(self) -> Bytes {
self.data
}
}
impl Deref for Str {
type Target = Bytes;
fn deref(&self) -> &Bytes {
&self.data
}
}
impl AsRef<[u8]> for Str {
fn as_ref(&self) -> &[u8] {
&self.data
}
}
impl PartialEq for Str {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
impl Eq for Str {}
impl PartialEq<Str> for &[u8] {
fn eq(&self, other: &Str) -> bool {
*self == &other.data[..]
}
}
impl PartialOrd for Str {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Str {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.data.cmp(&other.data)
}
}
impl core::hash::Hash for Str {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.data.hash(state)
}
}
impl core::fmt::Debug for Str {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.data.fmt(f)
}
}
pub trait Footprint {
fn footprint(&self) -> usize;
}
impl<T> Footprint for Vec<T> {
fn footprint(&self) -> usize {
self.capacity() * core::mem::size_of::<T>()
}
}
impl<K, V, S> Footprint for indexmap::IndexMap<K, V, S> {
fn footprint(&self) -> usize {
self.capacity() * (core::mem::size_of::<(K, V)>() + 2 * core::mem::size_of::<usize>())
}
}
pub struct Metered<T: Footprint> {
inner: T,
charged: usize,
meter: Option<Arc<Meter>>,
}
impl<T: Footprint> Metered<T> {
pub fn new(inner: T) -> Self {
tick();
let meter = current();
let charged = inner.footprint();
if let Some(m) = &meter {
if !m.fits(charged) {
abort(Abort::Memory);
}
m.charge(charged);
}
Self {
inner,
charged,
meter,
}
}
pub fn resync(&mut self) {
let now = self.inner.footprint();
if let Some(m) = &self.meter {
if now > self.charged {
if !m.fits(now - self.charged) {
abort(Abort::Memory);
}
m.charge(now - self.charged);
} else {
m.release(self.charged - now);
}
}
self.charged = now;
}
pub fn into_inner(mut self) -> T
where
T: Default,
{
core::mem::take(&mut self.inner)
}
}
impl<T: Footprint> Drop for Metered<T> {
fn drop(&mut self) {
if let Some(m) = &self.meter {
m.release(self.charged);
}
}
}
impl<T: Footprint + Clone> Clone for Metered<T> {
fn clone(&self) -> Self {
Self::new(self.inner.clone())
}
}
impl<T: Footprint + Default> Default for Metered<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: Footprint> Deref for Metered<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: Footprint> DerefMut for Metered<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T: Footprint + PartialEq> PartialEq for Metered<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<T: Footprint + Eq> Eq for Metered<T> {}
impl<T: Footprint + core::hash::Hash> core::hash::Hash for Metered<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.inner.hash(state)
}
}
impl<T: Footprint + core::fmt::Debug> core::fmt::Debug for Metered<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.inner.fmt(f)
}
}
impl<T: Footprint + Default + IntoIterator> IntoIterator for Metered<T> {
type Item = T::Item;
type IntoIter = T::IntoIter;
fn into_iter(self) -> T::IntoIter {
self.into_inner().into_iter()
}
}
impl<'a, T: Footprint> IntoIterator for &'a Metered<T>
where
&'a T: IntoIterator,
{
type Item = <&'a T as IntoIterator>::Item;
type IntoIter = <&'a T as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
pub fn collect_vec<T>(iter: impl IntoIterator<Item = T>) -> Metered<Vec<T>> {
let room = headroom() / core::mem::size_of::<T>().max(1);
let mut v = Vec::new();
for x in iter {
if v.len() >= room {
abort(Abort::Memory);
break;
}
v.push(x);
}
Metered::new(v)
}