Skip to main content

ruststream_sqlx/inbox/
subscriber.rs

1//! `InboxSubscriber`: the claim loop a subscription's stream runs.
2
3use std::collections::VecDeque;
4use std::fmt;
5use std::future::{Future, poll_fn};
6use std::marker::PhantomData;
7use std::num::NonZeroUsize;
8use std::sync::Arc;
9use std::task::{Context, Poll};
10use std::time::Duration;
11
12use futures::Stream;
13use ruststream::{BatchSubscriber, Subscriber};
14use ruststream_sqlx_dialect::Role;
15use sqlx::Pool;
16use sync_wrapper::SyncWrapper;
17use tokio::sync::Notify;
18use tokio_util::sync::DropGuard;
19
20use super::batch::{BatchClaim, BatchLane};
21use super::broker::Shared;
22use super::claims::{Claims, Flow, InWork, Settled, Spread};
23use super::database::QueueDatabase;
24use super::delivery::InboxDelivery;
25use super::engine::{self, Claimed, Claiming, Events, Leasing, Now};
26use super::error::SqlxBrokerError;
27use super::form::advisory::claim::{Advised, claim_advised};
28use super::form::advisory::session::Closing;
29use super::form::advisory::{LockBook, LockHold};
30use super::form::lease::LeaseBook;
31use super::form::lease::claim::{begin_work, claim_leased};
32use super::form::row_lock::claim_locked;
33use super::queue::{Queue, Registration};
34#[cfg(feature = "testing")]
35use super::testing::{QueueAt, cancelled, off_clock};
36use super::transactional::{InboxMode, Plain, TxBook};
37use super::tx::PoolTx;
38use crate::inbox::pools::ServicePool;
39
40/// How long a subscription waits after a claim failed, so a persistent failure cannot spin the
41/// loop.
42const CLAIM_RETRY: Duration = Duration::from_secs(1);
43
44/// The subscriber an [`InboxQueue`](crate::InboxQueue) opens: a stream of deliveries claimed from
45/// the table.
46///
47/// The stream claims when it is polled: up to the batch size for a batch handler, and one row for
48/// a single-message handler, with a claim in flight for each free worker of a handler mounted with
49/// `workers(n)`. The subscription holds at most the pool's size less one connection, and a claim
50/// beside another one starts only while the pool has a connection to spare, so the pool keeps one
51/// for the handlers. It keeps one claim in flight on SQLite, which takes one writer at a time, and
52/// on a table whose groups keep their order or that has a `partition_key` column, whose order claims
53/// that run at once could break, and where a lease claim stamps its rows in a transaction, as on
54/// MySQL and MariaDB. On SQLite a lease claim takes a row for every worker the subscription has
55/// seen, and the rows past the free workers wait in the subscription for the next free worker.
56/// After a claim that filled its limit the next one runs at once;
57/// after one that found fewer rows it waits the poll interval, or until a publisher of the same
58/// broker writes a row of its table and group (a row the service writes through its own SQL or a
59/// handler's transaction waits for the interval). A write that lands while the subscription
60/// claims ends its next wait at once. A failed claim reaches the stream as an error item and the
61/// next claim waits one second. After `shutdown` the stream ends.
62///
63/// In the row lock form a message in work holds one connection, and a batch one for all its
64/// messages. A batch's settlements take effect together, when the last of its deliveries
65/// finishes: a settlement whose statement fails rolls the whole batch back, and its rows return.
66/// A batch that holds a row whose statement always fails therefore rolls back and returns all of
67/// its rows on every attempt, until the service's SQL or schema is fixed.
68///
69/// In the lease form the claim commits at once and holds no connection: each delivery, in a batch
70/// or alone, takes a connection only while it settles, and its settlement takes effect on its own;
71/// in transactional mode a delivery holds its transaction's connection until it settles.
72/// A task on the runtime the broker connected on extends the lease of every delivery in work each
73/// half lease, on one connection, until the subscriber drops or the broker shuts down; a delivery
74/// that waits in the subscription for a worker counts as in work there. A subscriber dropped with
75/// deliveries waiting returns their rows at once.
76///
77/// In the advisory lock form each message in work holds a connection of its own, in a batch too,
78/// and its session holds the lock on the row's key: outside transactional mode no transaction
79/// stays open while the handler works. A claim selects candidates, locks each key on a session
80/// and takes the row while it is still claimable; a batch takes idle connections first and opens
81/// new ones while the pool has room, so a batch larger than the pool shrinks instead of waiting. A
82/// settlement runs its statement on the session, then releases the lock and returns the connection
83/// to the pool. A delivery dropped unsettled closes its connection on the runtime the broker
84/// connected on, after releasing the lock, and its row returns at once. SQLite keeps no such
85/// locks: the process keeps the keys in work instead, so one process serves a database file.
86///
87/// In transactional mode, a registration mounted with
88/// [`transactional`](crate::InboxSettings::transactional), each delivery lends its handler the
89/// transaction it settles in, opened at the table's isolation level or SQLite mode. In the row lock
90/// form that is the claim's: the subscription sets a savepoint after each claim that took a row, so
91/// a settlement other than acknowledgement can discard what the handler wrote. In the lease form
92/// the subscription opens a transaction for each delivery once its claim committed; in the advisory
93/// lock form, on the delivery's session once its row is taken. Transactional mode serves single
94/// deliveries: the subscriber delivers no batches in it.
95///
96/// A table whose groups keep their order (`#[field(group, fifo = true)]`) has one row of a group
97/// in work at a time. A claim takes the group's head, its first unfinished row in claim order, and
98/// takes nothing while another row of the group is in work, a row that entered the group ahead of
99/// it included. The claim first takes the group in its transaction with the guard its dialect
100/// builds, so subscriptions to one group on separate brokers keep the order too. A batch of such a
101/// table holds one row, and a handler mounted with `workers(n)` handles the group's rows one after
102/// another.
103///
104/// # Examples
105///
106/// ```
107/// # #[cfg(feature = "postgres")]
108/// # mod demo {
109/// use ruststream_sqlx::prelude::*;
110/// use serde::Deserialize;
111/// use sqlx::PgPool;
112///
113/// /// Changes to push to a CRM, one group per customer, each group in order.
114/// #[derive(Inbox, sqlx::FromRow)]
115/// #[inbox(table = "crm_jobs")]
116/// pub struct SyncChange {
117///     #[field(id, generated)]
118///     id: i64,
119///     #[field(group, fifo = true)]
120///     customer: String,
121///     #[field(payload)]
122///     payload: Vec<u8>,
123/// }
124///
125/// #[derive(Deserialize)]
126/// struct Change {
127///     field: String,
128/// }
129///
130/// // The mount's subscription is an `InboxSubscriber<Postgres, SyncChange>` reading group `acme`.
131/// #[subscriber(InboxQueue::<SyncChange>::new("acme"))]
132/// async fn push(change: &Change) -> HandlerOutcome {
133///     tracing::info!(field = %change.field, "pushing");
134///     HandlerOutcome::ack()
135/// }
136///
137/// pub fn app(pool: PgPool) -> RustStream {
138///     RustStream::new(AppInfo::new("crm-sync", "0.1.0")).with_broker(SqlxBroker::new(pool), |b| {
139///         // Four workers, and still one change of the group at a time, in order.
140///         b.include(push.workers(nonzero!(4)));
141///     })
142/// }
143/// # }
144/// # fn main() {}
145/// ```
146///
147/// A handler of a slice takes one claim of up to `batch(n)` rows, and each delivery settles its own
148/// row:
149///
150/// ```no_run
151/// # #[cfg(all(feature = "inbox", feature = "postgres"))]
152/// # mod demo {
153/// use ruststream_sqlx::prelude::*;
154/// use serde::Deserialize;
155/// use sqlx::PgPool;
156///
157/// #[derive(Inbox, sqlx::FromRow)]
158/// #[inbox(table = "email_jobs")]
159/// pub struct SendEmail {
160///     #[field(id, generated)]
161///     job_id: i64,
162///     #[field(group)]
163///     name: String,
164///     #[field(payload)]
165///     payload: Vec<u8>,
166/// }
167///
168/// #[derive(Deserialize)]
169/// pub struct Email {
170///     to: String,
171/// }
172///
173/// # async fn deliver(email: &Email) -> bool { !email.to.is_empty() }
174/// // One claim is the batch, and each email settles its own row.
175/// #[subscriber(InboxQueue::<SendEmail>::new("emails"))]
176/// async fn send_all(emails: &[Email]) -> Vec<HandlerOutcome> {
177///     let mut outcomes = Vec::with_capacity(emails.len());
178///     for email in emails {
179///         outcomes.push(if deliver(email).await {
180///             HandlerOutcome::ack()
181///         } else {
182///             HandlerOutcome::retry()
183///         });
184///     }
185///     outcomes
186/// }
187///
188/// pub fn app(pool: PgPool) -> RustStream {
189///     RustStream::new(AppInfo::new("mailer", "0.1.0")).with_broker(SqlxBroker::new(pool), |b| {
190///         b.include(send_all.batch(nonzero!(50)));
191///     })
192/// }
193/// # }
194/// # fn main() {}
195/// ```
196pub struct InboxSubscriber<DB: QueueDatabase, Row: Events<DB>, Mode = Plain> {
197    pub(super) shared: Arc<Shared<DB>>,
198    queue: &'static Queue,
199    holding: Holding<DB, Row>,
200    /// The subscription's handle on the pool, which its deliveries lend their handlers.
201    pool: &'static ServicePool<DB>,
202    /// Where a transactional delivery's transaction waits while its handler does not hold it, in
203    /// the row lock and lease forms; `None` in the plain mode, and in the advisory lock form, whose
204    /// book keeps the session that holds the transaction.
205    lending: Option<&'static TxBook<DB>>,
206    /// What the last claim took, its storage reused from claim to claim.
207    claimed: ClaimBuffers<DB, Row>,
208    /// What the next claim waits for first.
209    wait: Option<Wait>,
210    /// How many claims of single deliveries the subscription keeps in flight.
211    flow: Flow,
212    /// The deliveries of single claims made so far; less those `settled` counts, the ones in work.
213    made: usize,
214    /// The deliveries of single claims settled or dropped so far.
215    settled: &'static Settled,
216    /// The deliveries a claim for every worker took past the first, oldest first, each holding its
217    /// lease, waiting for the next free worker. Dropped, each returns its row at once.
218    ahead: VecDeque<InboxDelivery<DB, Row, Mode>>,
219    /// What the connection's publishers wake the subscription with after they write a row of its
220    /// table and group.
221    wake: &'static Notify,
222    _registration: Registration<DB>,
223    /// Stops the lease keeper when the subscriber drops; `None` outside the lease form.
224    _keeper: Option<DropGuard>,
225    _mode: PhantomData<fn() -> Mode>,
226}
227
228/// What a claim loop waits for before its next claim.
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230enum Wait {
231    /// The poll interval, or a publish of the connection into the subscription's group.
232    Interval,
233    /// A second after a failed claim, whatever is published meanwhile: a persistent failure
234    /// cannot spin the loop at the rate of the publishes.
235    Failed,
236}
237
238/// What opening a subscription made for its subscriber.
239pub(crate) struct Opened<DB: QueueDatabase, Row: Events<DB>> {
240    pub(crate) queue: &'static Queue,
241    pub(crate) holding: Holding<DB, Row>,
242    pub(crate) registration: Registration<DB>,
243    pub(crate) keeper: Option<DropGuard>,
244    pub(crate) pool: &'static ServicePool<DB>,
245    pub(crate) lending: Option<&'static TxBook<DB>>,
246    pub(crate) wake: &'static Notify,
247}
248
249/// How a subscription holds the rows it claimed until they settle.
250pub(crate) enum Holding<DB: QueueDatabase, Row: Events<DB>> {
251    /// In the claim's transaction: the row lock form. A claim cut short drains through the
252    /// broker's closes.
253    Transaction(&'static Closing),
254    /// By the lease the claim wrote and committed, each delivery's kept in the subscription's
255    /// book: the lease form.
256    Leases(&'static LeaseBook<DB, Row>),
257    /// By the lock on each row's key, held by the session of its delivery, which the
258    /// subscription's book keeps: the advisory lock form.
259    Advisory(&'static LockBook<DB>),
260}
261
262impl<DB: QueueDatabase, Row: Events<DB>> Clone for Holding<DB, Row> {
263    fn clone(&self) -> Self {
264        *self
265    }
266}
267
268impl<DB: QueueDatabase, Row: Events<DB>> Copy for Holding<DB, Row> {}
269
270/// What a claim leaves the deliveries of its rows.
271pub(crate) enum Taken<DB: QueueDatabase, Row: Events<DB>> {
272    /// The claim's transaction, which holds the rows until they settle.
273    Locked(PoolTx<DB>),
274    /// The lease the claim wrote and committed, and the book its deliveries enter.
275    Leased(&'static LeaseBook<DB, Row>, Row::Token),
276    /// Each row's place in the book, whose session holds the row's lock, next to the row.
277    Advised,
278}
279
280/// What a claim took, kept by the subscriber from claim to claim so their storage is reused.
281pub(crate) struct ClaimBuffers<DB: QueueDatabase, Row: Events<DB>> {
282    /// The claim's rows, oldest first.
283    rows: Vec<Claimed<Row>>,
284    /// The ids a claim of ids for a fetch of the service's own reads, before the fetch reads their
285    /// rows; nothing for a table whose claim reads its rows whole.
286    ids: Row::Ids,
287    /// What only the advisory lock form keeps, made at its first claim: a subscription in another
288    /// form carries the pointer alone.
289    advised: Option<Box<Advised<DB, Row>>>,
290    /// In the lease form's transactional mode, the transaction of each row's delivery, opened once
291    /// the claim committed its leases; empty otherwise. Wrapped because a connection is not
292    /// `Sync`, and only the subscriber's own `&mut` reaches it.
293    transactions: SyncWrapper<Vec<PoolTx<DB>>>,
294}
295
296impl<DB: QueueDatabase, Row: Events<DB>> Default for ClaimBuffers<DB, Row> {
297    fn default() -> Self {
298        Self {
299            rows: Vec::new(),
300            ids: Row::Ids::default(),
301            advised: None,
302            transactions: SyncWrapper::new(Vec::new()),
303        }
304    }
305}
306
307impl<DB: QueueDatabase, Row: Events<DB>> ClaimBuffers<DB, Row> {
308    /// Empties them for the next claim. A hold left from a claim that failed midway drops here,
309    /// which ends its session, and so does a transaction, which closes its connection.
310    fn clear(&mut self) {
311        self.rows.clear();
312        self.transactions.get_mut().clear();
313        if let Some(advised) = self.advised.as_deref_mut() {
314            advised.holds.clear();
315            advised.taken.clear();
316        }
317    }
318}
319
320impl<DB: QueueDatabase, Row: Events<DB>, Mode> fmt::Debug for InboxSubscriber<DB, Row, Mode> {
321    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322        f.debug_struct("InboxSubscriber")
323            .field("subscription", &self.queue.name)
324            .field("table", &self.queue.table)
325            .field("row", &self.queue.row)
326            .finish_non_exhaustive()
327    }
328}
329
330impl<DB, Row, Mode> InboxSubscriber<DB, Row, Mode>
331where
332    DB: QueueDatabase,
333    Row: Events<DB>,
334    Mode: InboxMode,
335{
336    pub(crate) fn new(shared: Arc<Shared<DB>>, opened: Opened<DB, Row>) -> Self {
337        Self {
338            shared,
339            queue: opened.queue,
340            holding: opened.holding,
341            pool: opened.pool,
342            lending: opened.lending,
343            claimed: ClaimBuffers::default(),
344            wait: None,
345            flow: Flow::new(
346                opened.pool.options().get_max_connections(),
347                !matches!(opened.holding, Holding::Leases(_))
348                    || opened.queue.prepared.transactional,
349                spread(opened.queue, matches!(opened.holding, Holding::Leases(_))),
350            ),
351            made: 0,
352            // Once per subscription, as its queue and books are: deliveries outlive the borrow of
353            // the subscriber.
354            settled: Box::leak(Box::default()),
355            ahead: VecDeque::new(),
356            wake: opened.wake,
357            _registration: opened.registration,
358            _keeper: opened.keeper,
359            _mode: PhantomData,
360        }
361    }
362
363    fn failed(&self, statement: &'static str, source: sqlx::Error) -> SqlxBrokerError {
364        SqlxBrokerError::Sqlx {
365            subscription: self.queue.name.to_owned(),
366            table: self.queue.table.to_owned(),
367            row: self.queue.row,
368            statement,
369            source: Box::new(source),
370        }
371    }
372
373    /// Waits for what the last claim asked, then claims up to `limit` rows. `None` once the
374    /// broker is shut down.
375    pub(crate) async fn claim(
376        &mut self,
377        limit: usize,
378    ) -> Option<Result<(Taken<DB, Row>, usize), SqlxBrokerError>> {
379        loop {
380            match self.wait.take() {
381                Some(Wait::Interval) => tokio::select! {
382                    () = tokio::time::sleep(self.queue.poll_interval) => {}
383                    () = self.wake.notified() => {}
384                    () = self.shared.stopping.cancelled() => return None,
385                },
386                Some(Wait::Failed) => tokio::select! {
387                    () = tokio::time::sleep(CLAIM_RETRY) => {}
388                    () = self.shared.stopping.cancelled() => return None,
389                },
390                None => {}
391            }
392            if self.shared.is_closed() {
393                return None;
394            }
395            let claimed = self.claim_once(limit).await;
396            match claimed {
397                Ok((taken, 0)) => {
398                    self.end_empty(taken).await;
399                    self.wait = Some(Wait::Interval);
400                }
401                Ok((taken, count)) => {
402                    self.wait = (count < limit).then_some(Wait::Interval);
403                    return Some(Ok((taken, count)));
404                }
405                Err(error) => {
406                    self.wait = Some(Wait::Failed);
407                    return Some(Err(error));
408                }
409            }
410        }
411    }
412
413    async fn claim_once(
414        &mut self,
415        limit: usize,
416    ) -> Result<(Taken<DB, Row>, usize), SqlxBrokerError> {
417        #[cfg(feature = "testing")]
418        if self.shared.harness.in_process() {
419            return self.claim_in_process(limit).await;
420        }
421        let taken = claim_rows(
422            &self.shared.pool,
423            self.queue,
424            self.holding,
425            limit,
426            0,
427            Now::default(),
428            &mut self.claimed,
429        )
430        .await
431        .map_err(|(statement, source)| self.failed(statement, source))?;
432        Ok((taken, self.claimed.rows.len()))
433    }
434
435    /// The claim of an in-process connection: on the test's clock, off a paused one, and in the
436    /// harness's books.
437    #[cfg(feature = "testing")]
438    async fn claim_in_process(
439        &mut self,
440        limit: usize,
441    ) -> Result<(Taken<DB, Row>, usize), SqlxBrokerError> {
442        let pool = self.shared.pool.clone();
443        let queue = self.queue;
444        let holding = self.holding;
445        let now = self.shared.harness.now();
446        let mut buffers = std::mem::take(&mut self.claimed);
447        let (claimed, buffers) = off_clock(async move {
448            let claimed = claim_rows(&pool, queue, holding, limit, 0, now, &mut buffers).await;
449            (claimed, buffers)
450        })
451        .await
452        .unwrap_or_else(|| (Err(("BEGIN", cancelled())), ClaimBuffers::default()));
453        self.claimed = buffers;
454        let taken = claimed.map_err(|(statement, source)| self.failed(statement, source))?;
455        self.shared
456            .harness
457            .claimed(QueueAt::read(queue), self.claimed.rows.len());
458        Ok((taken, self.claimed.rows.len()))
459    }
460
461    /// The deliveries of the last claim, oldest first.
462    pub(crate) fn take_rows(&mut self) -> impl Iterator<Item = Claimed<Row>> + '_ {
463        self.claimed.rows.drain(..)
464    }
465
466    /// The deliveries of the last advisory claim, oldest first, each with its place in the book.
467    pub(crate) fn take_advised(
468        &mut self,
469    ) -> impl Iterator<Item = (Claimed<Row>, LockHold<DB>)> + '_ {
470        let ClaimBuffers { rows, advised, .. } = &mut self.claimed;
471        let holds = advised
472            .as_deref_mut()
473            .map(|advised| advised.holds.drain(..));
474        rows.drain(..).zip(holds.into_iter().flatten())
475    }
476
477    pub(crate) const fn queue(&self) -> &'static Queue {
478        self.queue
479    }
480
481    /// The subscription's handle on the pool, which its deliveries lend their handlers.
482    pub(crate) const fn pool(&self) -> &'static ServicePool<DB> {
483        self.pool
484    }
485
486    /// The deliveries of single claims in work.
487    fn in_work(&self) -> usize {
488        self.made.wrapping_sub(self.settled.count())
489    }
490
491    /// What a single claim of the subscription runs with, beside `running` claims in flight.
492    fn one(&self, running: usize) -> ClaimOne<DB, Row> {
493        ClaimOne {
494            pool: self.pool,
495            queue: self.queue,
496            holding: self.holding,
497            beside: running,
498            limit: self.flow.claim_limit(self.in_work()),
499            #[cfg(feature = "testing")]
500            in_process: self
501                .shared
502                .harness
503                .in_process()
504                .then(|| Arc::clone(&self.shared)),
505        }
506    }
507
508    /// The next single delivery: a claim in flight for each free worker, within the connections
509    /// the subscription may hold, and the first one that took a row. `None` once the broker is
510    /// shut down.
511    async fn next_one<Make, Fut>(
512        &mut self,
513        claims: &mut Claims<ClaimOne<DB, Row>, ClaimBuffers<DB, Row>, Make, Fut>,
514    ) -> Option<Result<InboxDelivery<DB, Row, Mode>, SqlxBrokerError>>
515    where
516        Make: Fn(ClaimOne<DB, Row>, ClaimBuffers<DB, Row>) -> Fut,
517        Fut: Future<Output = OneClaimed<DB, Row>>,
518    {
519        self.flow.polled(self.in_work());
520        if !self.ahead.is_empty() {
521            if self.shared.is_closed() {
522                return None;
523            }
524            return self
525                .ahead
526                .pop_front()
527                .map(|delivery| Ok(self.hand_out(delivery)));
528        }
529        loop {
530            if claims.running() == 0 {
531                match self.wait.take() {
532                    Some(Wait::Interval) => tokio::select! {
533                        () = tokio::time::sleep(self.queue.poll_interval) => {}
534                        () = self.wake.notified() => {}
535                        () = self.shared.stopping.cancelled() => return None,
536                    },
537                    Some(Wait::Failed) => tokio::select! {
538                        () = tokio::time::sleep(CLAIM_RETRY) => {}
539                        () = self.shared.stopping.cancelled() => return None,
540                    },
541                    None => {}
542                }
543                if self.shared.is_closed() {
544                    return None;
545                }
546                let in_work = self.in_work();
547                if self.flow.waits_for_a_settlement(0, in_work) {
548                    let seen = self.settled.count();
549                    tokio::select! {
550                        () = self.settled.change(seen) => {}
551                        () = self.shared.stopping.cancelled() => return None,
552                    }
553                    continue;
554                }
555            }
556            let in_work = self.in_work();
557            let (claimed, mut buffers) = if claims.running() == 0 && self.flow.wanted(in_work) == 1
558            {
559                // One claim and nothing beside it, as a plain mount always claims: awaited in place,
560                // so it costs no slot.
561                claim_one(self.one(0), claims.buffers()).await
562            } else {
563                let Some(done) = poll_fn(|cx| self.turn(claims, cx)).await else {
564                    continue;
565                };
566                done
567            };
568            match claimed {
569                Ok(taken) if buffers.rows.is_empty() => {
570                    self.end_empty(taken).await;
571                    claims.put_back(buffers);
572                    self.flow.found_nothing();
573                    if claims.running() == 0 {
574                        self.wait = Some(Wait::Interval);
575                    }
576                }
577                Ok(taken) => {
578                    self.flow.took();
579                    let delivery = self.deliver(taken, &mut buffers);
580                    claims.put_back(buffers);
581                    return delivery.map(Ok);
582                }
583                Err((statement, source)) => {
584                    claims.put_back(buffers);
585                    self.flow.found_nothing();
586                    self.wait = Some(Wait::Failed);
587                    return Some(Err(self.failed(statement, source)));
588                }
589            }
590        }
591    }
592
593    /// Starts the claims the flow allows, then polls every claim in flight. `None` when none runs.
594    fn turn<Make, Fut>(
595        &self,
596        claims: &mut Claims<ClaimOne<DB, Row>, ClaimBuffers<DB, Row>, Make, Fut>,
597        cx: &mut Context<'_>,
598    ) -> Poll<Option<OneClaimed<DB, Row>>>
599    where
600        Make: Fn(ClaimOne<DB, Row>, ClaimBuffers<DB, Row>) -> Fut,
601        Fut: Future<Output = OneClaimed<DB, Row>>,
602    {
603        if self.wait.is_none() && !self.shared.is_closed() {
604            let in_work = self.in_work();
605            while self
606                .flow
607                .may_start(claims.running(), in_work, || spare(self.pool))
608            {
609                // Each claim is polled once as it starts, so it takes its connection before the
610                // pool is read for the next one.
611                let started = claims.start(self.one(claims.running()));
612                if let Poll::Ready(done) = claims.poll_slot(started, cx) {
613                    return Poll::Ready(Some(done));
614                }
615            }
616        }
617        claims.poll_any(cx)
618    }
619
620    /// The delivery of the first row a single claim took into `buffers`. The rows past it, which
621    /// only a lease claim for every worker takes, wait ahead in the subscription as deliveries that
622    /// hold their leases.
623    fn deliver(
624        &mut self,
625        taken: Taken<DB, Row>,
626        buffers: &mut ClaimBuffers<DB, Row>,
627    ) -> Option<InboxDelivery<DB, Row, Mode>> {
628        let (queue, pool) = (self.queue, self.pool);
629        let delivery = match taken {
630            Taken::Locked(tx) => {
631                let claimed = buffers.rows.pop()?;
632                match self.lending {
633                    // The claim's transaction waits in the book for the handler to borrow it.
634                    Some(book) => InboxDelivery::lent(claimed, book.enter(tx), queue, pool),
635                    None => InboxDelivery::own(claimed, tx, queue, pool),
636                }
637            }
638            Taken::Leased(book, lease) => {
639                // Built newest first, each in front of the ones after it, so the deliveries wait
640                // oldest first.
641                while buffers.rows.len() > 1 {
642                    let claimed = buffers.rows.pop()?;
643                    let ahead = self.leased(claimed, book, lease, buffers);
644                    self.ahead.push_front(ahead);
645                }
646                let claimed = buffers.rows.pop()?;
647                let delivery = self.leased(claimed, book, lease, buffers);
648                return Some(self.hand_out(delivery));
649            }
650            Taken::Advised => {
651                let claimed = buffers.rows.pop()?;
652                let hold = buffers.advised.as_deref_mut()?.holds.pop()?;
653                InboxDelivery::advised(claimed, hold, queue, pool)
654            }
655        };
656        #[cfg(feature = "testing")]
657        let delivery = delivery.on(&self.shared);
658        Some(self.hand_out(delivery))
659    }
660
661    /// The delivery of a row a lease claim took, entered in the lease book, which extends its
662    /// lease from now on; in transactional mode with a transaction of the claim's.
663    fn leased(
664        &self,
665        claimed: Claimed<Row>,
666        book: &'static LeaseBook<DB, Row>,
667        lease: Row::Token,
668        buffers: &mut ClaimBuffers<DB, Row>,
669    ) -> InboxDelivery<DB, Row, Mode> {
670        // In transactional mode the delivery's own transaction waits in the book for the handler
671        // to borrow it.
672        let lent = self
673            .lending
674            .zip(buffers.transactions.get_mut().pop())
675            .map(|(lending, tx)| lending.enter(tx));
676        let delivery = InboxDelivery::leased(claimed, book, lease, lent, self.queue, self.pool);
677        #[cfg(feature = "testing")]
678        let delivery = delivery.on(&self.shared);
679        delivery
680    }
681
682    /// `delivery`, handed to a worker: from now on it counts as in work.
683    fn hand_out(&mut self, delivery: InboxDelivery<DB, Row, Mode>) -> InboxDelivery<DB, Row, Mode> {
684        self.made = self.made.wrapping_add(1);
685        delivery.counted(InWork::new(self.settled))
686    }
687
688    /// The subscription's deliveries, as a stream that owns it.
689    pub(crate) fn into_stream(
690        self,
691    ) -> impl Stream<Item = Result<InboxDelivery<DB, Row, Mode>, SqlxBrokerError>> + Send + 'static
692    {
693        let claims = Claims::new(claim_one::<DB, Row>);
694        futures::stream::unfold((self, claims), |(mut subscriber, mut claims)| async move {
695            let next = subscriber.next_one(&mut claims).await?;
696            Some((next, (subscriber, claims)))
697        })
698    }
699}
700
701/// What a single claim runs with.
702pub(crate) struct ClaimOne<DB: QueueDatabase, Row: Events<DB>> {
703    pool: &'static ServicePool<DB>,
704    queue: &'static Queue,
705    holding: Holding<DB, Row>,
706    /// The other claims of the subscription in flight.
707    beside: usize,
708    /// The rows the claim takes.
709    limit: usize,
710    /// The connection, where it runs in process.
711    #[cfg(feature = "testing")]
712    in_process: Option<Arc<Shared<DB>>>,
713}
714
715/// What a single claim ends with: its outcome, and the buffers it hands back.
716pub(crate) type OneClaimed<DB, Row> = (Result<Taken<DB, Row>, Failed>, ClaimBuffers<DB, Row>);
717
718/// Claims the rows `one` asks for, into `buffers` it owns while it runs, so several run at once.
719async fn claim_one<DB, Row>(
720    one: ClaimOne<DB, Row>,
721    mut buffers: ClaimBuffers<DB, Row>,
722) -> OneClaimed<DB, Row>
723where
724    DB: QueueDatabase,
725    Row: Events<DB>,
726{
727    let ClaimOne {
728        pool,
729        queue,
730        holding,
731        beside,
732        limit,
733        ..
734    } = one;
735    // In process the claim runs on the test's clock, off a paused one, and in the harness's books.
736    #[cfg(feature = "testing")]
737    if let Some(shared) = one.in_process {
738        let now = shared.harness.now();
739        let (claimed, buffers) = off_clock(async move {
740            let claimed = claim_rows(pool, queue, holding, limit, beside, now, &mut buffers).await;
741            (claimed, buffers)
742        })
743        .await
744        .unwrap_or_else(|| (Err(("BEGIN", cancelled())), ClaimBuffers::default()));
745        if claimed.is_ok() {
746            shared
747                .harness
748                .claimed(QueueAt::read(queue), buffers.rows.len());
749        }
750        return (claimed, buffers);
751    }
752    let claimed = claim_rows(
753        pool,
754        queue,
755        holding,
756        limit,
757        beside,
758        Now::default(),
759        &mut buffers,
760    )
761    .await;
762    (claimed, buffers)
763}
764
765/// How a subscription to `queue`, in the lease form where `leased`, claims for its workers.
766///
767/// It keeps one claim in flight where claims that run at once may finish out of their claim order,
768/// which a table whose groups keep their order and a keyed table read their order from; on a
769/// database of one writer, where they only collide; and where a lease claim stamps its rows in a
770/// transaction of several statements, which locks the rows it reads, leases in work included,
771/// until it commits, so a claim nobody polls would hold up their settlements. On a database of one
772/// writer a lease claim takes a row for every worker: it writes their leases in one statement.
773fn spread(queue: &Queue, leased: bool) -> Spread {
774    if queue.prepared.fifo_guard.is_some() || queue.spec.column(Role::PartitionKey).is_some() {
775        Spread::One
776    } else if queue.one_writer && leased {
777        Spread::OneForAll
778    } else if queue.one_writer || queue.prepared.stamps {
779        Spread::One
780    } else {
781        Spread::PerWorker
782    }
783}
784
785/// The connections `pool` can lend at once: its idle ones, and room for new ones.
786fn spare<DB: sqlx::Database>(pool: &Pool<DB>) -> usize {
787    let room = pool
788        .options()
789        .get_max_connections()
790        .saturating_sub(pool.size());
791    pool.num_idle()
792        .saturating_add(usize::try_from(room).unwrap_or(usize::MAX))
793}
794
795/// A statement that failed, and why.
796pub(crate) type Failed = (&'static str, sqlx::Error);
797
798/// Claims up to `limit` rows of `queue` into `claimed`, holding them as `holding` says: in a
799/// transaction of `pool` it returns open, by a lease it commits, or by the lock on each row's key,
800/// held by a session of its own. An advisory claim reads a candidate more for each of `beside`
801/// claims of the subscription in flight.
802///
803/// The claim of a table whose groups keep their order takes the group first, in the claim's
804/// transaction, and takes nothing while another transaction holds it.
805async fn claim_rows<DB, Row>(
806    pool: &Pool<DB>,
807    queue: &'static Queue,
808    holding: Holding<DB, Row>,
809    limit: usize,
810    beside: usize,
811    now: Now,
812    claimed: &mut ClaimBuffers<DB, Row>,
813) -> Result<Taken<DB, Row>, Failed>
814where
815    DB: QueueDatabase,
816    Row: Events<DB>,
817{
818    let cx = Claiming {
819        queue,
820        limit: i64::try_from(limit).unwrap_or(i64::MAX),
821        now,
822    };
823    claimed.clear();
824    let ClaimBuffers {
825        rows,
826        ids,
827        advised,
828        transactions,
829    } = claimed;
830    let book = match holding {
831        Holding::Transaction(closing) => {
832            return claim_locked::<DB, Row>(pool, closing, &cx, ids, rows)
833                .await
834                .map(Taken::Locked);
835        }
836        Holding::Leases(book) => book,
837        Holding::Advisory(book) => {
838            let advised = advised.get_or_insert_with(Box::default);
839            claim_advised::<DB, Row>(pool, book, &cx, limit, beside, rows, advised).await?;
840            return Ok(Taken::Advised);
841        }
842    };
843    let lease = claim_leased::<DB, Row>(pool, book.closing(), &cx, now, ids, rows).await?;
844    // In transactional mode each row's delivery writes in a transaction of its own, opened once
845    // the claim committed: the lease, not this transaction, keeps other claims off the row. A
846    // transaction that fails to open fails the claim, and the rows it took return once their
847    // leases run out, as after a crash.
848    if queue.prepared.transactional {
849        for _ in 0..rows.len() {
850            transactions.get_mut().push(begin_work(pool, queue).await?);
851        }
852    }
853    Ok(Taken::Leased(book, lease))
854}
855
856/// Takes the group of `cx` for the claim's transaction on `conn`, where its table keeps its groups
857/// in order: `false` when another transaction holds the group. A queue without a guard takes no
858/// group and claims at once.
859pub(crate) async fn take_group<DB, Row>(
860    conn: &mut DB::Connection,
861    cx: &Claiming,
862    lease: Option<&Leasing<Row::Token>>,
863) -> Result<bool, Failed>
864where
865    DB: QueueDatabase,
866    Row: Events<DB>,
867{
868    let Some(guard) = cx.queue.prepared.fifo_guard else {
869        return Ok(true);
870    };
871    engine::take_group::<DB, Row>(conn, cx, guard, lease)
872        .await
873        .map_err(|source| (guard.sql, source))
874}
875
876/// Opens a claim's transaction on a connection of `pool`, with the statement `queue`'s dialect
877/// opens it with, or `BEGIN`; cut short, it drains through `closing`.
878pub(crate) async fn begin<DB: QueueDatabase>(
879    pool: &Pool<DB>,
880    queue: &Queue,
881    closing: &'static Closing,
882) -> Result<PoolTx<DB>, Failed> {
883    PoolTx::begin_claim(pool, queue.begin_claim, closing)
884        .await
885        .map_err(|source| (queue.begin_claim.unwrap_or("BEGIN"), source))
886}
887
888impl<DB, Row, Mode> Subscriber for InboxSubscriber<DB, Row, Mode>
889where
890    DB: QueueDatabase,
891    Row: Events<DB>,
892    Mode: InboxMode,
893{
894    type Message = InboxDelivery<DB, Row, Mode>;
895    type Error = SqlxBrokerError;
896
897    fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
898        let claims = Claims::new(claim_one::<DB, Row>);
899        futures::stream::unfold((self, claims), |(subscriber, mut claims)| async move {
900            let next = subscriber.next_one(&mut claims).await?;
901            Some((next, (subscriber, claims)))
902        })
903    }
904}
905
906// Transactional mode serves single deliveries: a batch handler reads no delivery's context, so
907// it could take no delivery's transaction. The table's mode picks the batch: a payload-mode
908// table's is its deliveries, a row-mode table's lends its rows as one slice.
909impl<DB, Row> BatchSubscriber for InboxSubscriber<DB, Row, Plain>
910where
911    DB: QueueDatabase,
912    Row: Events<DB>,
913    Row::Lane: BatchLane<DB, Row>,
914{
915    type Batch = <Row::Lane as BatchLane<DB, Row>>::Batch;
916
917    fn batches(
918        &mut self,
919        size: NonZeroUsize,
920    ) -> impl Stream<Item = Result<Self::Batch, Self::Error>> + Send + '_ {
921        futures::stream::unfold(self, move |subscriber| async move {
922            let (taken, count) = match subscriber.claim(size.get()).await? {
923                Ok(claimed) => claimed,
924                Err(error) => return Some((Err(error), subscriber)),
925            };
926            let batch =
927                <Row::Lane as BatchLane<DB, Row>>::batch(BatchClaim::new(subscriber, taken, count));
928            Some((Ok(batch), subscriber))
929        })
930    }
931}
932
933#[cfg(test)]
934mod tests {
935    use std::time::Duration;
936
937    use ruststream_sqlx_dialect::{Column, Form, TableSpec};
938
939    use super::spread;
940    use crate::inbox::claims::Spread;
941    use crate::inbox::engine::{IdAt, Prepared};
942    use crate::inbox::queue::Queue;
943
944    fn queue(spec: &TableSpec<'static>, one_writer: bool) -> Queue {
945        Queue {
946            name: "jobs",
947            table: "jobs",
948            row: "app::Job",
949            spec: *spec,
950            id_at: IdAt::First,
951            native_retry_after: false,
952            kinds: None,
953            prepared: Prepared::default(),
954            begin_claim: None,
955            counted_attempt: false,
956            one_writer,
957            poll_interval: Duration::from_secs(1),
958            lease: None,
959            cap: None,
960        }
961    }
962
963    const PLAIN: TableSpec<'static> = TableSpec::new("jobs", Column::new("id"), Form::RowLock);
964
965    #[test]
966    fn a_plain_table_on_a_server_claims_for_every_free_worker() {
967        assert_eq!(spread(&queue(&PLAIN, false), false), Spread::PerWorker);
968        assert_eq!(spread(&queue(&PLAIN, false), true), Spread::PerWorker);
969    }
970
971    #[test]
972    fn a_keyed_table_keeps_its_claim_order() {
973        let keyed = PLAIN.partition_key(Column::new("customer"));
974        assert_eq!(spread(&queue(&keyed, false), false), Spread::One);
975        assert_eq!(spread(&queue(&keyed, true), true), Spread::One);
976    }
977
978    #[test]
979    fn a_lease_claim_that_stamps_in_a_transaction_keeps_one_claim() {
980        let mut stamping = queue(&PLAIN, false);
981        stamping.prepared.stamps = true;
982        assert_eq!(spread(&stamping, true), Spread::One);
983    }
984
985    #[test]
986    fn a_database_of_one_writer_keeps_one_claim() {
987        assert_eq!(spread(&queue(&PLAIN, true), false), Spread::One);
988    }
989
990    #[test]
991    fn a_lease_claim_on_a_database_of_one_writer_takes_a_row_for_every_worker() {
992        assert_eq!(spread(&queue(&PLAIN, true), true), Spread::OneForAll);
993        let mut stamping = queue(&PLAIN, true);
994        stamping.prepared.stamps = true;
995        assert_eq!(spread(&stamping, true), Spread::OneForAll);
996    }
997}