use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use lru::LruCache;
use once_cell::sync::Lazy;
use std::num::NonZeroUsize;
use skillet::{Value, evaluate_with_assignments, evaluate_with_assignments_and_context};
#[derive(Clone, Debug)]
pub struct CachedResult {
pub result: Result<Value, String>,
pub variable_context: Option<HashMap<String, Value>>,
pub execution_time_ms: f64,
pub cache_hit: bool,
}
#[derive(Clone, Debug)]
struct CacheEntry {
result: Value,
variable_context: Option<HashMap<String, Value>>,
execution_time_ms: f64,
hit_count: u64,
last_accessed: std::time::Instant,
}
pub struct ExpressionCache {
cache: LruCache<String, CacheEntry>,
stats: CacheStats,
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub entries: usize,
pub evictions: u64,
pub total_saved_time_ms: f64,
}
impl CacheStats {
pub fn hit_rate(&self) -> f64 {
if self.hits + self.misses == 0 {
0.0
} else {
self.hits as f64 / (self.hits + self.misses) as f64
}
}
}
static EXPRESSION_CACHE: Lazy<Arc<Mutex<ExpressionCache>>> = Lazy::new(|| {
Arc::new(Mutex::new(ExpressionCache::new(1000))) });
impl ExpressionCache {
fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
stats: CacheStats::default(),
}
}
fn get(&mut self, key: &str) -> Option<CacheEntry> {
if let Some(entry) = self.cache.get_mut(key) {
entry.hit_count += 1;
entry.last_accessed = std::time::Instant::now();
self.stats.hits += 1;
self.stats.total_saved_time_ms += entry.execution_time_ms;
Some(entry.clone())
} else {
self.stats.misses += 1;
None
}
}
fn put(&mut self, key: String, entry: CacheEntry) {
if self.cache.put(key, entry).is_some() {
self.stats.evictions += 1;
}
self.stats.entries = self.cache.len();
}
fn get_stats(&self) -> CacheStats {
self.stats.clone()
}
fn clear(&mut self) {
self.cache.clear();
self.stats = CacheStats::default();
}
}
fn generate_cache_key(expression: &str, variables: &HashMap<String, Value>) -> String {
if variables.is_empty() {
expression.to_string()
} else {
let mut var_parts: Vec<String> = variables
.iter()
.map(|(k, v)| format!("{}:{:?}", k, v))
.collect();
var_parts.sort();
format!("{}|{}", expression, var_parts.join(","))
}
}
pub fn evaluate_cached(
expression: &str,
variables: &HashMap<String, Value>,
include_variables: bool,
) -> CachedResult {
let cache_key = generate_cache_key(expression, variables);
if let Ok(mut cache) = EXPRESSION_CACHE.lock() {
if let Some(entry) = cache.get(&cache_key) {
return CachedResult {
result: Ok(entry.result.clone()),
variable_context: entry.variable_context.clone(),
execution_time_ms: entry.execution_time_ms,
cache_hit: true,
};
}
}
let start_time = std::time::Instant::now();
let (result, variable_context) = if expression.contains(";") || expression.contains(":=") {
if include_variables {
match evaluate_with_assignments_and_context(expression, variables) {
Ok((val, ctx)) => (Ok(val), Some(ctx)),
Err(e) => (Err(e), None),
}
} else {
(evaluate_with_assignments(expression, variables), None)
}
} else {
(skillet::evaluate_with_custom(expression, variables), None)
};
let execution_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
if let Ok(ref value) = result {
let cache_entry = CacheEntry {
result: value.clone(),
variable_context: variable_context.clone(),
execution_time_ms,
hit_count: 0,
last_accessed: std::time::Instant::now(),
};
if let Ok(mut cache) = EXPRESSION_CACHE.lock() {
cache.put(cache_key, cache_entry);
}
}
CachedResult {
result: result.map_err(|e| e.to_string()),
variable_context,
execution_time_ms,
cache_hit: false,
}
}
pub fn get_cache_stats() -> CacheStats {
EXPRESSION_CACHE
.lock()
.map(|cache| cache.get_stats())
.unwrap_or_default()
}
pub fn clear_cache() {
if let Ok(mut cache) = EXPRESSION_CACHE.lock() {
cache.clear();
}
}
pub struct BufferPool {
buffers: Vec<Vec<u8>>,
capacity: usize,
buffer_size: usize,
}
impl BufferPool {
fn new(capacity: usize, buffer_size: usize) -> Self {
Self {
buffers: Vec::with_capacity(capacity),
capacity,
buffer_size,
}
}
fn get_buffer(&mut self) -> Vec<u8> {
self.buffers.pop().unwrap_or_else(|| Vec::with_capacity(self.buffer_size))
}
fn return_buffer(&mut self, mut buffer: Vec<u8>) {
if self.buffers.len() < self.capacity {
buffer.clear();
if buffer.capacity() > self.buffer_size * 2 {
buffer.shrink_to(self.buffer_size);
}
self.buffers.push(buffer);
}
}
}
thread_local! {
static HTTP_BUFFER_POOL: std::cell::RefCell<BufferPool> =
std::cell::RefCell::new(BufferPool::new(4, 65536)); }
pub fn get_pooled_buffer() -> Vec<u8> {
HTTP_BUFFER_POOL.with(|pool| pool.borrow_mut().get_buffer())
}
pub fn return_pooled_buffer(buffer: Vec<u8>) {
HTTP_BUFFER_POOL.with(|pool| pool.borrow_mut().return_buffer(buffer));
}
pub struct ResponsePool<T> {
pool: Vec<T>,
capacity: usize,
}
impl<T> ResponsePool<T> {
fn new(capacity: usize) -> Self {
Self {
pool: Vec::with_capacity(capacity),
capacity,
}
}
fn get(&mut self) -> Option<T> {
self.pool.pop()
}
fn return_item(&mut self, item: T) {
if self.pool.len() < self.capacity {
self.pool.push(item);
}
}
}
thread_local! {
static RESPONSE_POOL: std::cell::RefCell<ResponsePool<super::types::EvalResponse>> =
std::cell::RefCell::new(ResponsePool::new(8));
}
pub fn get_pooled_response() -> Option<super::types::EvalResponse> {
RESPONSE_POOL.with(|pool| pool.borrow_mut().get())
}
pub fn return_pooled_response(response: super::types::EvalResponse) {
RESPONSE_POOL.with(|pool| pool.borrow_mut().return_item(response));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expression_cache() {
clear_cache();
let vars = HashMap::new();
let result1 = evaluate_cached("2+2", &vars, false);
assert!(!result1.cache_hit);
assert!(result1.result.is_ok());
let result2 = evaluate_cached("2+2", &vars, false);
assert!(result2.cache_hit);
assert!(result2.result.is_ok());
let stats = get_cache_stats();
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 1);
assert!(stats.hit_rate() > 0.0);
}
#[test]
fn test_cache_key_generation() {
let mut vars1 = HashMap::new();
vars1.insert("a".to_string(), Value::Number(1.0));
vars1.insert("b".to_string(), Value::Number(2.0));
let mut vars2 = HashMap::new();
vars2.insert("b".to_string(), Value::Number(2.0));
vars2.insert("a".to_string(), Value::Number(1.0));
let key1 = generate_cache_key("test", &vars1);
let key2 = generate_cache_key("test", &vars2);
assert_eq!(key1, key2);
}
#[test]
fn test_buffer_pool() {
let buffer = get_pooled_buffer();
assert!(buffer.capacity() > 0);
let original_capacity = buffer.capacity();
return_pooled_buffer(buffer);
let buffer2 = get_pooled_buffer();
assert!(buffer2.capacity() >= original_capacity || buffer2.capacity() > 0);
}
}