use std::any::Any;
use smallbox::{SmallBox, smallbox, space::S4};
use crate::label::BackendLabel;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CacheStatus {
Hit,
#[default]
Miss,
Stale,
}
impl CacheStatus {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
CacheStatus::Hit => "hit",
CacheStatus::Miss => "miss",
CacheStatus::Stale => "stale",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum ResponseSource {
#[default]
Upstream,
Backend(BackendLabel),
}
impl ResponseSource {
#[inline]
pub fn as_str(&self) -> &str {
match self {
ResponseSource::Upstream => "upstream",
ResponseSource::Backend(label) => label.as_str(),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ReadMode {
#[default]
Direct,
Refill,
}
pub trait Context: Send + Sync {
fn status(&self) -> CacheStatus;
fn set_status(&mut self, status: CacheStatus);
fn source(&self) -> &ResponseSource;
fn set_source(&mut self, source: ResponseSource);
fn read_mode(&self) -> ReadMode {
ReadMode::default()
}
fn set_read_mode(&mut self, _mode: ReadMode) {
}
fn as_any(&self) -> &dyn Any;
fn clone_box(&self) -> BoxContext;
fn into_cache_context(self: Box<Self>) -> CacheContext;
fn merge_from(&mut self, other: &dyn Context, prefix: &BackendLabel) {
let inner_status = other.status();
if inner_status == CacheStatus::Hit || inner_status == CacheStatus::Stale {
self.set_status(inner_status);
}
match other.source() {
ResponseSource::Backend(inner_label) => {
let composed = prefix.compose(inner_label);
self.set_source(ResponseSource::Backend(composed));
}
ResponseSource::Upstream => {
}
}
}
}
pub type BoxContext = SmallBox<dyn Context, S4>;
pub fn finalize_context(ctx: BoxContext) -> CacheContext {
let boxed: Box<dyn Context> = SmallBox::into_box(ctx);
boxed.into_cache_context()
}
#[derive(Debug, Clone, Default)]
pub struct CacheContext {
pub status: CacheStatus,
pub read_mode: ReadMode,
pub source: ResponseSource,
}
impl CacheContext {
pub fn boxed(self) -> BoxContext {
smallbox!(self)
}
}
impl Context for CacheContext {
fn status(&self) -> CacheStatus {
self.status
}
fn set_status(&mut self, status: CacheStatus) {
self.status = status;
}
fn source(&self) -> &ResponseSource {
&self.source
}
fn set_source(&mut self, source: ResponseSource) {
self.source = source;
}
fn read_mode(&self) -> ReadMode {
self.read_mode
}
fn set_read_mode(&mut self, mode: ReadMode) {
self.read_mode = mode;
}
fn as_any(&self) -> &dyn Any {
self
}
fn clone_box(&self) -> BoxContext {
smallbox!(self.clone())
}
fn into_cache_context(self: Box<Self>) -> CacheContext {
*self
}
}
pub trait CacheStatusExt {
type Config;
fn cache_status(&mut self, status: CacheStatus, config: &Self::Config);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_sizes() {
use std::mem::size_of;
let cache_ctx_size = size_of::<CacheContext>();
let box_ctx_size = size_of::<BoxContext>();
let s4_space = 4 * size_of::<usize>();
println!("CacheContext size: {} bytes", cache_ctx_size);
println!(" - CacheStatus: {} bytes", size_of::<CacheStatus>());
println!(" - ResponseSource: {} bytes", size_of::<ResponseSource>());
println!("BoxContext size: {} bytes", box_ctx_size);
println!("S4 inline space: {} bytes", s4_space);
assert!(
cache_ctx_size <= s4_space,
"CacheContext ({} bytes) should fit in S4 ({} bytes)",
cache_ctx_size,
s4_space
);
}
}