use core::cell::{Cell, RefCell};
use crate::host::capabilities::LeanCapabilities;
use crate::host::session::LeanSession;
use lean_rs::LeanRuntime;
use lean_rs::Obj;
use lean_rs::error::LeanResult;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct PoolStats {
pub imports_performed: u64,
pub reused: u64,
pub acquired: u64,
pub released_to_pool: u64,
pub released_dropped: u64,
}
#[derive(Clone, Eq, PartialEq)]
struct ImportsKey(Vec<String>);
impl ImportsKey {
fn from_slice(imports: &[&str]) -> Self {
Self(imports.iter().map(|&s| s.to_owned()).collect())
}
}
struct PooledEntry<'lean> {
imports_key: ImportsKey,
environment: Obj<'lean>,
}
struct PoolInner<'lean> {
free: Vec<PooledEntry<'lean>>,
}
impl<'lean> PoolInner<'lean> {
fn take_matching(&mut self, key: &ImportsKey) -> Option<Obj<'lean>> {
let idx = self.free.iter().rposition(|entry| &entry.imports_key == key)?;
Some(self.free.remove(idx).environment)
}
}
pub struct SessionPool<'lean> {
runtime: &'lean LeanRuntime,
capacity: usize,
inner: RefCell<PoolInner<'lean>>,
stats: Cell<PoolStats>,
}
impl<'lean> SessionPool<'lean> {
#[must_use]
pub fn with_capacity(runtime: &'lean LeanRuntime, capacity: usize) -> Self {
Self {
runtime,
capacity,
inner: RefCell::new(PoolInner {
free: Vec::with_capacity(capacity),
}),
stats: Cell::new(PoolStats::default()),
}
}
pub fn acquire<'p, 'c>(
&'p self,
caps: &'c LeanCapabilities<'lean, 'c>,
imports: &[&str],
) -> LeanResult<PooledSession<'lean, 'p, 'c>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.pool.acquire",
imports_len = imports.len(),
imports_first = imports.first().copied().unwrap_or("<empty>"),
)
.entered();
debug_assert!(
core::ptr::eq(self.runtime, caps.host().runtime()),
"pool runtime and capability runtime must agree; the shared 'lean parameter normally enforces this",
);
let key = ImportsKey::from_slice(imports);
let (session, hit) = {
let mut inner = self.inner.borrow_mut();
if let Some(env) = inner.take_matching(&key) {
self.bump_reused();
(LeanSession::from_environment(caps, env), true)
} else {
drop(inner);
let session = caps.session(imports)?;
self.bump_imported();
(session, false)
}
};
tracing::debug!(target: "lean_rs", hit = hit, "lean_rs.host.pool.acquire.result");
Ok(PooledSession {
pool: self,
imports_key: key,
session: Some(session),
})
}
#[must_use]
pub fn stats(&self) -> PoolStats {
self.stats.get()
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.borrow().free.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
fn bump_reused(&self) {
let mut s = self.stats.get();
s.reused = s.reused.saturating_add(1);
s.acquired = s.acquired.saturating_add(1);
self.stats.set(s);
}
fn bump_imported(&self) {
let mut s = self.stats.get();
s.imports_performed = s.imports_performed.saturating_add(1);
s.acquired = s.acquired.saturating_add(1);
self.stats.set(s);
}
fn release(&self, key: ImportsKey, env: Obj<'lean>) {
let mut inner = self.inner.borrow_mut();
let mut s = self.stats.get();
let kept = inner.free.len() < self.capacity;
if kept {
inner.free.push(PooledEntry {
imports_key: key,
environment: env,
});
s.released_to_pool = s.released_to_pool.saturating_add(1);
} else {
drop(env);
s.released_dropped = s.released_dropped.saturating_add(1);
}
self.stats.set(s);
tracing::trace!(
target: "lean_rs",
kept = kept,
"lean_rs.host.pool.release",
);
}
}
impl core::fmt::Debug for SessionPool<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SessionPool")
.field("capacity", &self.capacity)
.field("len", &self.len())
.field("stats", &self.stats.get())
.finish()
}
}
pub struct PooledSession<'lean, 'p, 'c> {
pool: &'p SessionPool<'lean>,
imports_key: ImportsKey,
session: Option<LeanSession<'lean, 'c>>,
}
impl<'lean, 'c> core::ops::Deref for PooledSession<'lean, '_, 'c> {
type Target = LeanSession<'lean, 'c>;
#[allow(clippy::expect_used, reason = "see PROOF OBLIGATION above")]
fn deref(&self) -> &Self::Target {
self.session
.as_ref()
.expect("session is Some between PooledSession::acquire and Drop::drop")
}
}
#[allow(
single_use_lifetimes,
clippy::elidable_lifetime_names,
reason = "the named lifetimes line up with `Deref::Target = LeanSession<'lean, 'c>` above; \
elision flips the inferred bound and breaks the trait-signature check"
)]
impl<'lean, 'c> core::ops::DerefMut for PooledSession<'lean, '_, 'c> {
#[allow(clippy::expect_used, reason = "see PROOF OBLIGATION on Deref impl")]
fn deref_mut(&mut self) -> &mut LeanSession<'lean, 'c> {
self.session
.as_mut()
.expect("session is Some between PooledSession::acquire and Drop::drop")
}
}
impl Drop for PooledSession<'_, '_, '_> {
fn drop(&mut self) {
if let Some(session) = self.session.take() {
let env = session.into_environment();
self.pool.release(self.imports_key.clone(), env);
}
}
}
impl core::fmt::Debug for PooledSession<'_, '_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PooledSession").finish_non_exhaustive()
}
}