use std::fmt;
use std::future::Future;
use std::num::NonZeroU32;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, PoisonError};
use std::time::Duration;
use futures::future::join_all;
use ruststream::ConnectedBroker;
use ruststream_sqlx_dialect::Dialect;
use sqlx::{Database, Pool};
use tokio::runtime::Handle;
use tokio_util::sync::CancellationToken;
use crate::inbox::FormDialect;
use crate::inbox::database::notify::{self, Listening};
use crate::inbox::database::{BuiltIn, QueueDatabase};
use crate::inbox::error::SqlxBrokerError;
use crate::inbox::form::advisory::LockBook;
use crate::inbox::form::advisory::session::Closing;
use crate::inbox::publish::{Routes, TableWake, Wakes};
use crate::inbox::threads::InboxThreads;
use super::SqlxBroker;
pub(crate) struct Shared<DB: Database> {
pub(crate) pool: Pool<DB>,
pub(crate) routes: Routes<DB, FormDialect, &'static TableWake>,
pub(crate) wakes: Wakes,
pub(crate) poll_interval: Duration,
pub(crate) lease: Duration,
pub(crate) runtime: Handle,
connection_limit: Option<NonZeroU32>,
thread_connections: Mutex<u64>,
closed: AtomicBool,
pub(crate) stopping: CancellationToken,
pub(crate) queues: Mutex<Vec<(&'static str, Option<String>)>>,
pub(crate) locks: Mutex<Vec<&'static LockBook<DB>>>,
pub(crate) closing: &'static Closing,
pub(crate) listening: Option<Listening>,
#[cfg(feature = "testing")]
pub(crate) harness: crate::inbox::testing::Harness,
}
impl<DB: Database> Shared<DB> {
pub(crate) fn is_closed(&self) -> bool {
self.closed.load(Ordering::Acquire)
}
pub(crate) fn count_threads(
&self,
name: &str,
threads: &InboxThreads,
) -> Result<(), SqlxBrokerError> {
let pool = u64::from(self.pool.options().get_max_connections());
let mut counted = self
.thread_connections
.lock()
.unwrap_or_else(PoisonError::into_inner);
let wanted = counted.saturating_add(threads.total_connections());
if let Some(limit) = self.connection_limit
&& pool.saturating_add(wanted) > u64::from(limit.get())
{
drop(counted);
return Err(SqlxBrokerError::ConnectionLimit {
subscription: name.to_owned(),
limit: limit.get(),
pool,
threads: wanted,
});
}
*counted = wanted;
drop(counted);
Ok(())
}
}
pub struct ConnectedSqlxBroker<DB: Database, D = BuiltIn<DB>> {
pub(crate) shared: Arc<Shared<DB>>,
pub(crate) dialect: Arc<D>,
}
impl<DB: Database, D: Dialect> fmt::Debug for ConnectedSqlxBroker<DB, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConnectedSqlxBroker")
.field("dialect", &self.dialect.name())
.field("closed", &self.shared.is_closed())
.finish_non_exhaustive()
}
}
impl<DB: Database, D> ConnectedSqlxBroker<DB, D> {
pub(crate) fn new(
broker: SqlxBroker<DB, D>,
dialect: Arc<D>,
runtime: Handle,
listening: Option<Listening>,
) -> Self {
let wakes = Wakes::default();
let routes = broker.routes.resolve(
|form_of| form_of(&dialect),
|route| wakes.table(&route.description().spec),
);
Self {
shared: Arc::new(Shared {
pool: broker.pool,
routes,
wakes,
poll_interval: broker.poll_interval,
lease: broker.lease,
connection_limit: broker.connection_limit,
thread_connections: Mutex::new(0),
closing: Closing::leak(runtime.clone()),
runtime,
closed: AtomicBool::new(false),
stopping: CancellationToken::new(),
queues: Mutex::new(Vec::new()),
locks: Mutex::new(Vec::new()),
listening,
#[cfg(feature = "testing")]
harness: crate::inbox::testing::Harness::default(),
}),
dialect,
}
}
}
impl<DB: QueueDatabase, D: Dialect + 'static> ConnectedBroker for ConnectedSqlxBroker<DB, D> {
type Error = SqlxBrokerError;
type Closed = ClosedSqlxBroker;
fn shutdown(self) -> impl Future<Output = Result<Self::Closed, Self::Error>> + Send {
self.shared.closed.store(true, Ordering::Release);
self.shared.stopping.cancel();
self.shared.closing.begin_shutdown();
let shared = self.shared;
async move {
let books = shared
.locks
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone();
let released = join_all(books.into_iter().map(LockBook::release_all)).await;
shared.closing.settled().await;
notify::close(&shared).await;
Ok(ClosedSqlxBroker {
locks_released: released.into_iter().sum(),
connections_closed: shared.closing.forced(),
})
}
}
}
#[derive(Debug)]
pub struct ClosedSqlxBroker {
locks_released: usize,
connections_closed: usize,
}
impl ClosedSqlxBroker {
#[must_use]
pub const fn locks_released(&self) -> usize {
self.locks_released
}
#[must_use]
pub const fn connections_closed(&self) -> usize {
self.connections_closed
}
}
#[cfg(test)]
mod tests {
use super::ClosedSqlxBroker;
#[test]
fn the_closed_broker_reports_the_counts_its_debug_shows() {
let closed = ClosedSqlxBroker {
locks_released: 2,
connections_closed: 1,
};
assert_eq!(
(closed.locks_released(), closed.connections_closed()),
(2, 1)
);
assert_eq!(
format!("{closed:?}"),
"ClosedSqlxBroker { locks_released: 2, connections_closed: 1 }"
);
}
}