use std::fmt::Debug;
#[cfg(feature = "testing")]
use std::sync::Arc;
use ruststream::AckError;
use sqlx::Pool;
use tokio::runtime::Handle;
use super::{LeaseBook, Slot};
use crate::home;
#[cfg(feature = "testing")]
use crate::inbox::broker::Shared;
use crate::inbox::database::QueueDatabase;
use crate::inbox::delivery::settle::{Step, run_step};
use crate::inbox::engine::{Events, Now, Settled, Settling};
use crate::inbox::error::SqlxBrokerError;
use crate::inbox::queue::Queue;
#[cfg(feature = "testing")]
use crate::inbox::testing::off_clock;
use crate::inbox::tx::PoolTx;
pub(crate) async fn settle_leased<DB, Row>(
book: &LeaseBook<DB, Row>,
slot: Slot,
cx: &Settling,
id: &Row::Id,
step: Step,
) -> Result<Settled, sqlx::Error>
where
DB: QueueDatabase,
Row: Events<DB>,
{
let Ok(held) = book.settling(slot).await else {
return Ok(Settled::Lost);
};
run_leased::<DB, Row>(book.pool(), book.runtime(), cx, id, &held, step).await
}
pub(crate) async fn run_leased<DB, Row>(
pool: &Pool<DB>,
home: &Handle,
cx: &Settling,
id: &Row::Id,
held: &Row::Token,
step: Step,
) -> Result<Settled, sqlx::Error>
where
DB: QueueDatabase,
Row: Events<DB>,
{
let overridden = step.overridden(Row::SHAPE);
let split = matches!(step, Step::DeadLetter(_)) && cx.queue.prepared.dead_letter_then.is_some();
if !overridden && !split {
let mut conn = home::acquire(pool, home).await?;
return run_step::<DB, Row>(&mut conn, cx, id, Some(held), step).await;
}
let mut tx = PoolTx::open(home::acquire(pool, home).await?, None).await?;
let settled = async {
if overridden && Row::extend(&mut tx, cx, id, held, held).await? == Settled::Lost {
return Ok(Settled::Lost);
}
run_step::<DB, Row>(&mut tx, cx, id, Some(held), step).await
}
.await;
match settled {
Ok(Settled::Written | Settled::Untouched) => tx.commit().await?,
Ok(Settled::Lost) => tx.rollback().await?,
Err(error) => {
let _ = tx.rollback().await;
return Err(error);
}
}
settled
}
pub(crate) async fn release<DB, Row>(
book: &'static LeaseBook<DB, Row>,
slot: Slot,
queue: &'static Queue,
id: Row::Id,
now: Now,
) -> bool
where
DB: QueueDatabase,
Row: Events<DB>,
{
let cx = Settling { queue, now };
match settle_leased::<DB, Row>(book, slot, &cx, &id, Step::Retry).await {
Ok(settled) => settled == Settled::Written,
Err(error) => {
tracing::warn!(
target: "ruststream_sqlx",
subscription = queue.name,
table = queue.table,
row = queue.row,
?id,
%error,
"a delivery dropped unsettled could not release its row; the row returns once its \
lease runs out",
);
false
}
}
}
#[cfg(feature = "testing")]
pub(crate) fn release_in_process<DB, Row>(
connection: Arc<Shared<DB>>,
book: &'static LeaseBook<DB, Row>,
slot: Slot,
queue: &'static Queue,
id: Row::Id,
) where
DB: QueueDatabase,
Row: Events<DB>,
{
connection.harness.expect(queue.name);
drop(book.runtime().spawn(async move {
let now = connection.harness.now();
let released = off_clock(release::<DB, Row>(book, slot, queue, id, now))
.await
.unwrap_or(false);
if !released {
connection.harness.refused(queue.name);
}
connection.harness.released();
}));
}
pub(crate) fn lease_lost<Id: Debug>(queue: &Queue, id: &Id) -> AckError {
AckError::Broker(Box::new(SqlxBrokerError::LeaseLost {
subscription: queue.name.to_owned(),
table: queue.table.to_owned(),
row: queue.row,
id: format!("{id:?}"),
}))
}