use crate::async_isle::{AsyncIsle, AsyncIsleDriver};
use crate::error::IsleError;
use crate::pool::{PoolConfig, PoolStrategy};
use std::ops::Deref;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
use tokio::sync::Notify;
type Factory = dyn Fn(&mlua::Lua) -> Result<(), mlua::Error> + Send + Sync;
struct Slot {
isle: AsyncIsle,
driver: AsyncIsleDriver,
}
struct PoolInner {
idle: Vec<Slot>,
active: usize,
closed: bool,
}
pub struct AsyncIslePool {
inner: Mutex<PoolInner>,
notify: Notify,
factory: Arc<Factory>,
config: PoolConfig,
}
impl AsyncIslePool {
pub fn new<F>(factory: F, config: PoolConfig) -> Result<Self, IsleError>
where
F: Fn(&mlua::Lua) -> Result<(), mlua::Error> + Send + Sync + 'static,
{
if config.max_size == 0 {
return Err(IsleError::Init("max_size must be > 0".into()));
}
Ok(Self {
inner: Mutex::new(PoolInner {
idle: Vec::with_capacity(config.max_size),
active: 0,
closed: false,
}),
notify: Notify::new(),
factory: Arc::new(factory),
config,
})
}
pub async fn checkout(&self) -> Result<AsyncPooledIsle<'_>, IsleError> {
loop {
let notified = self.notify.notified();
match self.try_acquire()? {
AcquireOutcome::Acquired(pooled) => return Ok(pooled),
AcquireOutcome::SpawnNeeded => {
let slot = self.spawn_slot().await.inspect_err(|_| {
self.dec_active();
})?;
return Ok(AsyncPooledIsle::new(self, slot));
}
AcquireOutcome::Wait => {
notified.await;
}
}
}
}
pub async fn try_checkout(&self) -> Result<Option<AsyncPooledIsle<'_>>, IsleError> {
match self.try_acquire()? {
AcquireOutcome::Acquired(pooled) => Ok(Some(pooled)),
AcquireOutcome::SpawnNeeded => {
let slot = self.spawn_slot().await.inspect_err(|_| {
self.dec_active();
})?;
Ok(Some(AsyncPooledIsle::new(self, slot)))
}
AcquireOutcome::Wait => Ok(None),
}
}
pub async fn checkout_timeout(
&self,
timeout: Duration,
) -> Result<AsyncPooledIsle<'_>, IsleError> {
let deadline = tokio::time::Instant::now() + timeout;
loop {
let notified = self.notify.notified();
match self.try_acquire()? {
AcquireOutcome::Acquired(pooled) => return Ok(pooled),
AcquireOutcome::SpawnNeeded => {
let slot = self.spawn_slot().await.inspect_err(|_| {
self.dec_active();
})?;
return Ok(AsyncPooledIsle::new(self, slot));
}
AcquireOutcome::Wait => {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return Err(IsleError::PoolExhausted(self.config.max_size));
}
match tokio::time::timeout(remaining, notified).await {
Ok(()) => continue,
Err(_) => return Err(IsleError::PoolExhausted(self.config.max_size)),
}
}
}
}
}
pub fn active(&self) -> usize {
self.inner.lock().map(|g| g.active).unwrap_or(0)
}
pub fn idle(&self) -> usize {
self.inner.lock().map(|g| g.idle.len()).unwrap_or(0)
}
pub async fn shutdown(&self) {
let drained = {
let mut inner = match self.inner.lock() {
Ok(g) => g,
Err(e) => e.into_inner(),
};
inner.closed = true;
std::mem::take(&mut inner.idle)
};
self.notify.notify_waiters();
for slot in drained {
let _ = slot.driver.shutdown().await;
drop(slot.isle);
}
}
fn try_acquire(&self) -> Result<AcquireOutcome<'_>, IsleError> {
let mut inner = self.lock_inner()?;
if inner.closed {
return Err(IsleError::Shutdown);
}
if let Some(slot) = self.take_alive_slot(&mut inner) {
inner.active += 1;
return Ok(AcquireOutcome::Acquired(AsyncPooledIsle::new(self, slot)));
}
if self.can_grow(&inner) {
inner.active += 1;
Ok(AcquireOutcome::SpawnNeeded)
} else {
Ok(AcquireOutcome::Wait)
}
}
async fn spawn_slot(&self) -> Result<Slot, IsleError> {
let factory = Arc::clone(&self.factory);
let (isle, driver) = AsyncIsle::spawn(move |lua| factory(lua)).await?;
Ok(Slot { isle, driver })
}
fn take_alive_slot(&self, inner: &mut PoolInner) -> Option<Slot> {
while let Some(slot) = inner.idle.pop() {
if slot.isle.is_alive() {
return Some(slot);
}
}
None
}
fn can_grow(&self, inner: &PoolInner) -> bool {
inner.active + inner.idle.len() < self.config.max_size
}
fn return_slot_warm(&self, slot: Slot) {
let mut inner = match self.inner.lock() {
Ok(g) => g,
Err(e) => e.into_inner(),
};
inner.active = inner.active.saturating_sub(1);
if inner.closed {
drop(inner);
self.spawn_driver_shutdown(slot);
self.notify.notify_one();
return;
}
if slot.isle.is_alive() {
inner.idle.push(slot);
self.notify.notify_one();
} else {
drop(inner);
self.spawn_driver_shutdown(slot);
self.notify.notify_one();
}
}
fn discard_slot(&self, slot: Slot) {
let mut inner = match self.inner.lock() {
Ok(g) => g,
Err(e) => e.into_inner(),
};
inner.active = inner.active.saturating_sub(1);
drop(inner);
self.spawn_driver_shutdown(slot);
self.notify.notify_one();
}
fn spawn_driver_shutdown(&self, slot: Slot) {
if let Ok(handle) = tokio::runtime::Handle::try_current() {
handle.spawn(async move {
let Slot { isle, driver } = slot;
drop(isle); let _ = driver.shutdown().await;
});
}
}
fn lock_inner(&self) -> Result<MutexGuard<'_, PoolInner>, IsleError> {
self.inner
.lock()
.map_err(|e| IsleError::PoolPoisoned(e.to_string()))
}
fn dec_active(&self) {
let mut inner = match self.inner.lock() {
Ok(g) => g,
Err(e) => e.into_inner(),
};
inner.active = inner.active.saturating_sub(1);
self.notify.notify_one();
}
}
enum AcquireOutcome<'pool> {
Acquired(AsyncPooledIsle<'pool>),
SpawnNeeded,
Wait,
}
pub struct AsyncPooledIsle<'pool> {
pool: &'pool AsyncIslePool,
slot: Option<Slot>,
killed: bool,
}
impl std::fmt::Debug for AsyncPooledIsle<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncPooledIsle")
.field("alive", &self.slot.as_ref().map(|s| s.isle.is_alive()))
.field("killed", &self.killed)
.finish()
}
}
impl<'pool> AsyncPooledIsle<'pool> {
fn new(pool: &'pool AsyncIslePool, slot: Slot) -> Self {
Self {
pool,
slot: Some(slot),
killed: false,
}
}
pub fn kill(&mut self) {
self.killed = true;
}
}
impl Deref for AsyncPooledIsle<'_> {
type Target = AsyncIsle;
fn deref(&self) -> &AsyncIsle {
&self
.slot
.as_ref()
.expect("AsyncPooledIsle used after drop")
.isle
}
}
impl Drop for AsyncPooledIsle<'_> {
fn drop(&mut self) {
if let Some(slot) = self.slot.take() {
if self.killed {
self.pool.discard_slot(slot);
return;
}
match self.pool.config.strategy {
PoolStrategy::Cold => self.pool.discard_slot(slot),
PoolStrategy::Warm => self.pool.return_slot_warm(slot),
}
}
}
}