use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
use rusty_v8 as v8;
use std::time::{Duration, Instant};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::collections::VecDeque;
use std::time::SystemTime;
pub struct IsolatePool {
available: Mutex<VecDeque<v8::OwnedIsolate>>,
in_use: Mutex<usize>,
max_size: usize,
initialized: bool,
stats: PoolStatistics,
}
#[derive(Debug, Default)]
pub struct PoolStatistics {
pub total_acquires: Arc<AtomicUsize>,
pub total_releases: Arc<AtomicUsize>,
pub cache_hits: Arc<AtomicUsize>,
pub cache_misses: Arc<AtomicUsize>,
pub warmup_count: Arc<AtomicUsize>,
pub total_creation_time_ns: Arc<AtomicUsize>,
pub last_warmup: Arc<AtomicUsize>,
}
impl PoolStatistics {
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn hit_rate(&self) -> f64 {
let hits: _ = self.cache_hits.load(Ordering::Relaxed) as f64;
let total: _ = hits + self.cache_misses.load(Ordering::Relaxed) as f64;
if total > 0.0 { hits / total } else { 0.0 }
}
#[allow(dead_code)]
pub fn avg_creation_time_us(&self) -> f64 {
let total_time: _ = self.total_creation_time_ns.load(Ordering::Relaxed) as f64;
let count: _ = self.cache_misses.load(Ordering::Relaxed) as f64;
if count > 0.0 { total_time / count / 1000.0 } else { 0.0 }
}
}
unsafe impl Sync for IsolatePool {}
unsafe impl Send for IsolatePool {}
impl IsolatePool {
pub fn new(max_size: usize) -> Self {
Self {
available: Mutex::new(VecDeque::new()),
in_use: Mutex::new(0),
max_size,
initialized: false,
stats: PoolStatistics::new(),
}
}
pub fn pre_warm(&mut self, count: usize) -> Result<(), String> {
if self.initialized {
return Ok(());
}
let actual_count: _ = count.min(self.max_size);
let mut pool = self.available.lock().map_err(|e| e.to_string())?;
let warmup_count: _ = if self.max_size >= 16 {
(actual_count * 3 / 2).min(self.max_size)
} else {
actual_count
};
for i in 0..warmup_count {
let isolate_start: _ = Instant::now();
let isolate: _ = v8::Isolate::new(Default::default());
let creation_time: _ = isolate_start.elapsed();
pool.push_back(isolate);
self.stats.total_creation_time_ns.fetch_add(
creation_time.as_nanos() as usize,
Ordering::Relaxed
);
if self.max_size >= 16 && i % 4 == 0 {
thread::sleep(Duration::from_millis(1));
}
}
self.initialized = true;
self.stats.warmup_count.fetch_add(1, Ordering::Relaxed);
self.stats.last_warmup.store(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as usize,
Ordering::Relaxed
);
Ok(())
}
pub fn acquire(&self) -> Option<v8::OwnedIsolate> {
self.stats.total_acquires.fetch_add(1, Ordering::Relaxed);
let mut pool = self.available.lock().unwrap();
let mut in_use = self.in_use.lock().unwrap();
if let Some(isolate) = pool.pop_front() {
*in_use += 1;
self.stats.cache_hits.fetch_add(1, Ordering::Relaxed);
if pool.len() < self.max_size / 4 && pool.len() + *in_use < self.max_size {
let needed: _ = (self.max_size / 4).min(self.max_size - pool.len() - *in_use);
for _ in 0..needed {
if pool.len() + *in_use >= self.max_size {
break;
}
let isolate: _ = v8::Isolate::new(Default::default());
pool.push_back(isolate);
}
}
Some(isolate)
} else {
let total_in_use: _ = *in_use;
if total_in_use < self.max_size {
*in_use += 1;
self.stats.cache_misses.fetch_add(1, Ordering::Relaxed);
Some(v8::Isolate::new(Default::default())
} else {
None
}
}
}
pub fn release(&self, mut isolate: v8::OwnedIsolate) {
self.stats.total_releases.fetch_add(1, Ordering::Relaxed);
let mut pool = self.available.lock().unwrap();
let mut in_use = self.in_use.lock().unwrap();
isolate.low_memory_notification();
if pool.len() < self.max_size {
pool.push_back(isolate);
}
*in_use = in_use.saturating_sub(1);
}
#[allow(dead_code)]
pub fn stats(&self) -> PoolStats {
let pool: _ = self.available.lock().unwrap();
let in_use: _ = self.in_use.lock().unwrap();
PoolStats {
available: pool.len(),
in_use: *in_use,
max_size: self.max_size,
}
}
#[allow(dead_code)]
pub fn detailed_stats(&self) -> PoolStatistics {
PoolStatistics {
total_acquires: Arc::clone(&self.stats.total_acquires),
total_releases: Arc::clone(&self.stats.total_releases),
cache_hits: Arc::clone(&self.stats.cache_hits),
cache_misses: Arc::clone(&self.stats.cache_misses),
warmup_count: Arc::clone(&self.stats.warmup_count),
total_creation_time_ns: Arc::clone(&self.stats.total_creation_time_ns),
last_warmup: Arc::clone(&self.stats.last_warmup),
}
}
#[allow(dead_code)]
pub fn shrink_to_fit(&self) -> usize {
let mut pool = self.available.lock().unwrap();
let before_len: _ = pool.len();
while pool.len() > 1 {
pool.pop_back();
}
before_len - pool.len()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PoolStats {
pub available: usize,
pub in_use: usize,
pub max_size: usize,
}
impl PoolStats {
#[allow(dead_code)]
pub fn is_near_full(&self) -> bool {
self.in_use as f64 / self.max_size as f64 > 0.8
}
#[allow(dead_code)]
pub fn utilization_percent(&self) -> f64 {
if self.max_size == 0 {
0.0
} else {
(self.in_use as f64 / self.max_size as f64) * 100.0
}
}
}
static POOL: once_cell::sync::OnceCell<Box<IsolatePool>> = once_cell::sync::OnceCell::new();
fn is_test_environment() -> bool {
#[cfg(test)]
return true;
#[allow(unreachable_code)]
let env_check: _ = std::env::var("AMBER_TEST_MODE").is_ok();
env_check
}
#[allow(dead_code)]
pub fn initialize_pool(max_size: usize) -> Result<(), String> {
if is_test_environment() {
return Ok(());
}
let mut pool = IsolatePool::new(max_size);
let warmup_count: _ = (max_size * 3 / 4).max(4);
pool.pre_warm(warmup_count)?;
let pool_box: _ = Box::new(pool);
POOL.set(pool_box)
.map_err(|_| "Pool already initialized".to_string())
}
#[allow(dead_code)]
pub fn get_pool() -> Option<&'static IsolatePool> {
if is_test_environment() {
return None;
}
POOL.get().map(|p| p.as_ref())
}
#[allow(dead_code)]
pub fn get_test_isolate() -> Option<v8::OwnedIsolate> {
#[cfg(not(test))]
return None;
#[cfg(test)]
crate::is_v8_available().then(|| v8::Isolate::new(Default::default())
}
#[allow(dead_code)]
pub fn return_test_isolate(isolate: v8::OwnedIsolate) {
drop(isolate);
}
#[allow(dead_code)]
pub fn cleanup_test_isolate() {
}
pub fn acquire_isolate() -> Option<v8::OwnedIsolate> {
if is_test_environment() {
return None;
}
POOL.get().and_then(|pool| pool.acquire())
}
#[allow(dead_code)]
pub fn release_isolate(isolate: v8::OwnedIsolate) {
if is_test_environment() {
drop(isolate);
return;
}
if let Some(pool) = POOL.get() {
pool.release(isolate);
}
}
#[cfg(test)]
mod tests {
fn init_v8_for_tests() {
static INIT: std::sync::Once = std::sync::Once::new();
INIT.call_once(|| {
let platform: _ = v8::new_default_platform().unwrap();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
});
}
#[test]
fn test_isolate_pool_creation() {
init_v8_for_tests();
let pool: _ = IsolatePool::new(4);
let stats: _ = pool.stats();
assert_eq!(stats.available, 0);
assert_eq!(stats.in_use, 0);
assert_eq!(stats.max_size, 4);
}
#[test]
#[ignore]
fn test_isolate_pool_pre_warm() {
init_v8_for_tests();
let mut pool = IsolatePool::new(4);
pool.pre_warm(2).unwrap();
let stats: _ = pool.stats();
assert_eq!(stats.available, 2);
assert_eq!(stats.in_use, 0);
}
#[test]
#[ignore = "V8 Isolate生命周期限制 - 需要在创建线程销毁"]
fn test_isolate_acquire_release() {
init_v8_for_tests();
let mut pool = IsolatePool::new(4);
pool.pre_warm(2).unwrap();
let isolate: _ = pool.acquire().unwrap();
let stats: _ = pool.stats();
assert_eq!(stats.available, 1);
assert_eq!(stats.in_use, 1);
pool.release(isolate);
let stats: _ = pool.stats();
assert_eq!(stats.available, 2);
assert_eq!(stats.in_use, 0);
}
#[test]
#[ignore]
fn test_pool_stats() {
init_v8_for_tests();
let mut pool = IsolatePool::new(10);
pool.pre_warm(5).unwrap();
let stats: _ = pool.stats();
assert_eq!(stats.utilization_percent(), 0.0);
assert!(!stats.is_near_full());
let _isolate1: _ = pool.acquire().unwrap();
let _isolate2: _ = pool.acquire().unwrap();
let _isolate3: _ = pool.acquire().unwrap();
let _isolate4: _ = pool.acquire().unwrap();
let _isolate5: _ = pool.acquire().unwrap();
let _isolate6: _ = pool.acquire().unwrap();
let _isolate7: _ = pool.acquire().unwrap();
let _isolate8: _ = pool.acquire().unwrap();
let stats: _ = pool.stats();
assert_eq!(stats.utilization_percent(), 80.0);
assert!(stats.is_near_full());
}
}