use std::borrow::Cow;
use std::mem::{self, ManuallyDrop};
use sqlx::Database;
use super::session::Session;
use super::{Borrower, LockBook, LockSlot, Slots, Standing};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Unlent {
Released,
Borrowed,
}
pub(crate) struct LockHold<DB: Database> {
pub(super) book: &'static LockBook<DB>,
pub(super) slot: LockSlot,
}
pub(crate) struct Locked<'k, DB: Database> {
pub(super) session: Option<Session<DB>>,
pub(super) key: Cow<'k, str>,
pub(super) book: &'static LockBook<DB>,
}
impl<DB: Database> LockHold<DB> {
pub(crate) const fn book(&self) -> &'static LockBook<DB> {
self.book
}
pub(crate) const fn slot(&self) -> LockSlot {
self.slot
}
pub(crate) fn lend(&self) -> Result<(Locked<'static, DB>, bool), Unlent> {
let mut slots = self.book.slots();
let lent = slots
.entries
.get_mut(self.slot.index)
.map_or(Err(Unlent::Released), |entry| {
match mem::replace(&mut entry.standing, Standing::Lent(Borrower::Settlement)) {
Standing::Held(session) => {
Ok((session, mem::take(&mut entry.key), entry.panicked))
}
other => {
let unlent = if matches!(other, Standing::Lent(Borrower::Handler)) {
Unlent::Borrowed
} else {
Unlent::Released
};
entry.standing = other;
Err(unlent)
}
}
});
drop(slots);
lent.map(|(session, key, panicked)| {
let locked = Locked {
session: Some(session),
key: Cow::Owned(key),
book: self.book,
};
(locked, panicked)
})
}
pub(crate) fn leave(self, key: String) {
let hold = ManuallyDrop::new(self);
let mut slots = hold.book.slots();
let Slots { entries, free, .. } = &mut *slots;
let left = entries.get_mut(hold.slot.index).and_then(|entry| {
if matches!(entry.standing, Standing::Free) {
return None;
}
entry.key = key;
Some(mem::replace(&mut entry.standing, Standing::Free))
});
if left.is_some() {
free.push(hold.slot.index);
}
drop(slots);
hold.book.returned.notify_waiters();
drop(left);
}
}
impl<DB: Database> Drop for LockHold<DB> {
fn drop(&mut self) {
let book = self.book;
let mut slots = book.slots();
let Slots { entries, free, .. } = &mut *slots;
let Some(entry) = entries.get_mut(self.slot.index) else {
return;
};
let (session, key) = match mem::replace(&mut entry.standing, Standing::Free) {
Standing::Free => return,
Standing::Lent(Borrower::Handler) | Standing::Abandoned => {
entry.standing = Standing::Abandoned;
drop(slots);
book.returned.notify_waiters();
return;
}
Standing::Lent(Borrower::Settlement) | Standing::Released => (None, None),
Standing::Held(session) if book.process() => (Some(session), None),
Standing::Held(session) => (Some(session), Some(mem::take(&mut entry.key))),
};
free.push(self.slot.index);
drop(slots);
book.returned.notify_waiters();
let Some(session) = session else {
return;
};
match key {
Some(key) => book.end(session, Cow::Owned(key)),
None => session.release(),
}
}
}
impl<'k, DB: Database> Locked<'k, DB> {
pub(crate) fn session(&mut self) -> &mut Session<DB> {
self.session
.as_mut()
.expect("a locked session stays until it is let go")
}
pub(crate) fn key(&self) -> &str {
&self.key
}
pub(crate) fn conn_ref(&self) -> &DB::Connection {
self.session
.as_ref()
.expect("a locked session stays until it is let go")
.conn_ref()
}
pub(crate) fn conn(&mut self) -> &mut DB::Connection {
self.session().conn()
}
pub(crate) fn take_in_process(&mut self) -> bool {
let database = self.book.database;
self.session
.as_mut()
.expect("a locked session stays until it is let go")
.take_in_process(database, &self.key)
}
pub(crate) fn conn_and_key(&mut self) -> (&mut DB::Connection, &str) {
let session = self
.session
.as_mut()
.expect("a locked session stays until it is let go");
(session.conn(), &self.key)
}
pub(crate) fn into_session(self) -> Session<DB> {
self.into_parts().0
}
pub(crate) fn into_parts(mut self) -> (Session<DB>, Cow<'k, str>) {
let session = self
.session
.take()
.expect("a locked session stays until it is let go");
(session, mem::take(&mut self.key))
}
}
impl<DB: Database> Drop for Locked<'_, DB> {
fn drop(&mut self) {
if let Some(session) = self.session.take() {
self.book.end(session, mem::take(&mut self.key));
}
}
}