1use std::fmt;
4use std::future::{Future, poll_fn};
5use std::marker::PhantomData;
6use std::num::NonZeroUsize;
7use std::sync::Arc;
8use std::task::{Context, Poll};
9use std::time::Duration;
10
11use futures::Stream;
12use ruststream::{BatchSubscriber, Subscriber};
13use ruststream_sqlx_dialect::Role;
14use sqlx::Pool;
15use sync_wrapper::SyncWrapper;
16use tokio::sync::Notify;
17use tokio_util::sync::DropGuard;
18
19use super::batch::{BatchClaim, BatchLane};
20use super::broker::Shared;
21use super::claims::{Claims, Flow, InWork, Settled};
22use super::database::QueueDatabase;
23use super::delivery::InboxDelivery;
24use super::engine::{self, Claimed, Claiming, Events, Leasing, Now};
25use super::error::SqlxBrokerError;
26use super::form::advisory::claim::{Advised, claim_advised};
27use super::form::advisory::{LockBook, LockHold};
28use super::form::lease::LeaseBook;
29use super::form::lease::claim::{begin_work, claim_leased};
30use super::form::row_lock::claim_locked;
31use super::queue::{Queue, Registration};
32#[cfg(feature = "testing")]
33use super::testing::{cancelled, off_clock};
34use super::transactional::{InboxMode, Plain, TxBook};
35use super::tx::PoolTx;
36use crate::inbox::pools::ServicePool;
37
38const CLAIM_RETRY: Duration = Duration::from_secs(1);
41
42pub struct InboxSubscriber<DB: QueueDatabase, Row: Events<DB>, Mode = Plain> {
141 pub(super) shared: Arc<Shared<DB>>,
142 queue: &'static Queue,
143 holding: Holding<DB, Row>,
144 pool: &'static ServicePool<DB>,
146 lending: Option<&'static TxBook<DB>>,
150 claimed: ClaimBuffers<DB, Row>,
152 wait: Option<Wait>,
154 flow: Flow,
156 made: usize,
158 settled: &'static Settled,
160 wake: &'static Notify,
163 _registration: Registration<DB>,
164 _keeper: Option<DropGuard>,
166 _mode: PhantomData<fn() -> Mode>,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171enum Wait {
172 Interval,
174 Failed,
177}
178
179pub(crate) struct Opened<DB: QueueDatabase, Row: Events<DB>> {
181 pub(crate) queue: &'static Queue,
182 pub(crate) holding: Holding<DB, Row>,
183 pub(crate) registration: Registration<DB>,
184 pub(crate) keeper: Option<DropGuard>,
185 pub(crate) pool: &'static ServicePool<DB>,
186 pub(crate) lending: Option<&'static TxBook<DB>>,
187 pub(crate) wake: &'static Notify,
188}
189
190pub(crate) enum Holding<DB: QueueDatabase, Row: Events<DB>> {
192 Transaction,
194 Leases(&'static LeaseBook<DB, Row>),
197 Advisory(&'static LockBook<DB>),
200}
201
202impl<DB: QueueDatabase, Row: Events<DB>> Clone for Holding<DB, Row> {
203 fn clone(&self) -> Self {
204 *self
205 }
206}
207
208impl<DB: QueueDatabase, Row: Events<DB>> Copy for Holding<DB, Row> {}
209
210pub(crate) enum Taken<DB: QueueDatabase, Row: Events<DB>> {
212 Locked(PoolTx<DB>),
214 Leased(&'static LeaseBook<DB, Row>, Row::Token),
216 Advised,
218}
219
220pub(crate) struct ClaimBuffers<DB: QueueDatabase, Row: Events<DB>> {
222 rows: Vec<Claimed<Row>>,
224 ids: Row::Ids,
227 advised: Option<Box<Advised<DB, Row>>>,
230 transactions: SyncWrapper<Vec<PoolTx<DB>>>,
234}
235
236impl<DB: QueueDatabase, Row: Events<DB>> Default for ClaimBuffers<DB, Row> {
237 fn default() -> Self {
238 Self {
239 rows: Vec::new(),
240 ids: Row::Ids::default(),
241 advised: None,
242 transactions: SyncWrapper::new(Vec::new()),
243 }
244 }
245}
246
247impl<DB: QueueDatabase, Row: Events<DB>> ClaimBuffers<DB, Row> {
248 fn clear(&mut self) {
251 self.rows.clear();
252 self.transactions.get_mut().clear();
253 if let Some(advised) = self.advised.as_deref_mut() {
254 advised.holds.clear();
255 advised.taken.clear();
256 }
257 }
258}
259
260impl<DB: QueueDatabase, Row: Events<DB>, Mode> fmt::Debug for InboxSubscriber<DB, Row, Mode> {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 f.debug_struct("InboxSubscriber")
263 .field("subscription", &self.queue.name)
264 .field("table", &self.queue.table)
265 .field("row", &self.queue.row)
266 .finish_non_exhaustive()
267 }
268}
269
270impl<DB, Row, Mode> InboxSubscriber<DB, Row, Mode>
271where
272 DB: QueueDatabase,
273 Row: Events<DB>,
274 Mode: InboxMode,
275{
276 pub(crate) fn new(shared: Arc<Shared<DB>>, opened: Opened<DB, Row>) -> Self {
277 Self {
278 shared,
279 queue: opened.queue,
280 holding: opened.holding,
281 pool: opened.pool,
282 lending: opened.lending,
283 claimed: ClaimBuffers::default(),
284 wait: None,
285 flow: Flow::new(
286 opened.pool.options().get_max_connections(),
287 !matches!(opened.holding, Holding::Leases(_))
288 || opened.queue.prepared.transactional,
289 one_claim(opened.queue),
290 ),
291 made: 0,
292 settled: Box::leak(Box::default()),
295 wake: opened.wake,
296 _registration: opened.registration,
297 _keeper: opened.keeper,
298 _mode: PhantomData,
299 }
300 }
301
302 fn failed(&self, statement: &'static str, source: sqlx::Error) -> SqlxBrokerError {
303 SqlxBrokerError::Sqlx {
304 subscription: self.queue.name.to_owned(),
305 table: self.queue.table.to_owned(),
306 row: self.queue.row,
307 statement,
308 source: Box::new(source),
309 }
310 }
311
312 pub(crate) async fn claim(
315 &mut self,
316 limit: usize,
317 ) -> Option<Result<(Taken<DB, Row>, usize), SqlxBrokerError>> {
318 loop {
319 match self.wait.take() {
320 Some(Wait::Interval) => tokio::select! {
321 () = tokio::time::sleep(self.queue.poll_interval) => {}
322 () = self.wake.notified() => {}
323 () = self.shared.stopping.cancelled() => return None,
324 },
325 Some(Wait::Failed) => tokio::select! {
326 () = tokio::time::sleep(CLAIM_RETRY) => {}
327 () = self.shared.stopping.cancelled() => return None,
328 },
329 None => {}
330 }
331 if self.shared.is_closed() {
332 return None;
333 }
334 let claimed = self.claim_once(limit).await;
335 match claimed {
336 Ok((taken, 0)) => {
337 self.end_empty(taken).await;
338 self.wait = Some(Wait::Interval);
339 }
340 Ok((taken, count)) => {
341 self.wait = (count < limit).then_some(Wait::Interval);
342 return Some(Ok((taken, count)));
343 }
344 Err(error) => {
345 self.wait = Some(Wait::Failed);
346 return Some(Err(error));
347 }
348 }
349 }
350 }
351
352 async fn claim_once(
353 &mut self,
354 limit: usize,
355 ) -> Result<(Taken<DB, Row>, usize), SqlxBrokerError> {
356 #[cfg(feature = "testing")]
357 if self.shared.harness.in_process() {
358 return self.claim_in_process(limit).await;
359 }
360 let taken = claim_rows(
361 &self.shared.pool,
362 self.queue,
363 self.holding,
364 limit,
365 0,
366 Now::default(),
367 &mut self.claimed,
368 )
369 .await
370 .map_err(|(statement, source)| self.failed(statement, source))?;
371 Ok((taken, self.claimed.rows.len()))
372 }
373
374 #[cfg(feature = "testing")]
377 async fn claim_in_process(
378 &mut self,
379 limit: usize,
380 ) -> Result<(Taken<DB, Row>, usize), SqlxBrokerError> {
381 let pool = self.shared.pool.clone();
382 let queue = self.queue;
383 let holding = self.holding;
384 let now = self.shared.harness.now();
385 let mut buffers = std::mem::take(&mut self.claimed);
386 let (claimed, buffers) = off_clock(async move {
387 let claimed = claim_rows(&pool, queue, holding, limit, 0, now, &mut buffers).await;
388 (claimed, buffers)
389 })
390 .await
391 .unwrap_or_else(|| (Err(("BEGIN", cancelled())), ClaimBuffers::default()));
392 self.claimed = buffers;
393 let taken = claimed.map_err(|(statement, source)| self.failed(statement, source))?;
394 self.shared
395 .harness
396 .claimed(queue.name, self.claimed.rows.len());
397 Ok((taken, self.claimed.rows.len()))
398 }
399
400 pub(crate) fn take_rows(&mut self) -> impl Iterator<Item = Claimed<Row>> + '_ {
402 self.claimed.rows.drain(..)
403 }
404
405 pub(crate) fn take_advised(
407 &mut self,
408 ) -> impl Iterator<Item = (Claimed<Row>, LockHold<DB>)> + '_ {
409 let ClaimBuffers { rows, advised, .. } = &mut self.claimed;
410 let holds = advised
411 .as_deref_mut()
412 .map(|advised| advised.holds.drain(..));
413 rows.drain(..).zip(holds.into_iter().flatten())
414 }
415
416 pub(crate) const fn queue(&self) -> &'static Queue {
417 self.queue
418 }
419
420 pub(crate) const fn pool(&self) -> &'static ServicePool<DB> {
422 self.pool
423 }
424
425 fn in_work(&self) -> usize {
427 self.made.wrapping_sub(self.settled.count())
428 }
429
430 fn one(&self, running: usize) -> ClaimOne<DB, Row> {
432 ClaimOne {
433 pool: self.pool,
434 queue: self.queue,
435 holding: self.holding,
436 beside: running,
437 #[cfg(feature = "testing")]
438 in_process: self
439 .shared
440 .harness
441 .in_process()
442 .then(|| Arc::clone(&self.shared)),
443 }
444 }
445
446 async fn next_one<Make, Fut>(
450 &mut self,
451 claims: &mut Claims<ClaimOne<DB, Row>, ClaimBuffers<DB, Row>, Make, Fut>,
452 ) -> Option<Result<InboxDelivery<DB, Row, Mode>, SqlxBrokerError>>
453 where
454 Make: Fn(ClaimOne<DB, Row>, ClaimBuffers<DB, Row>) -> Fut,
455 Fut: Future<Output = OneClaimed<DB, Row>>,
456 {
457 self.flow.polled(self.in_work());
458 loop {
459 if claims.running() == 0 {
460 match self.wait.take() {
461 Some(Wait::Interval) => tokio::select! {
462 () = tokio::time::sleep(self.queue.poll_interval) => {}
463 () = self.wake.notified() => {}
464 () = self.shared.stopping.cancelled() => return None,
465 },
466 Some(Wait::Failed) => tokio::select! {
467 () = tokio::time::sleep(CLAIM_RETRY) => {}
468 () = self.shared.stopping.cancelled() => return None,
469 },
470 None => {}
471 }
472 if self.shared.is_closed() {
473 return None;
474 }
475 let in_work = self.in_work();
476 if self.flow.waits_for_a_settlement(0, in_work) {
477 let seen = self.settled.count();
478 tokio::select! {
479 () = self.settled.change(seen) => {}
480 () = self.shared.stopping.cancelled() => return None,
481 }
482 continue;
483 }
484 }
485 let in_work = self.in_work();
486 let (claimed, mut buffers) = if claims.running() == 0 && self.flow.wanted(in_work) == 1
487 {
488 claim_one(self.one(0), claims.buffers()).await
491 } else {
492 let Some(done) = poll_fn(|cx| self.turn(claims, cx)).await else {
493 continue;
494 };
495 done
496 };
497 match claimed {
498 Ok(taken) if buffers.rows.is_empty() => {
499 self.end_empty(taken).await;
500 claims.put_back(buffers);
501 self.flow.found_nothing();
502 if claims.running() == 0 {
503 self.wait = Some(Wait::Interval);
504 }
505 }
506 Ok(taken) => {
507 self.flow.took();
508 let delivery = self.deliver(taken, &mut buffers);
509 claims.put_back(buffers);
510 return delivery.map(Ok);
511 }
512 Err((statement, source)) => {
513 claims.put_back(buffers);
514 self.flow.found_nothing();
515 self.wait = Some(Wait::Failed);
516 return Some(Err(self.failed(statement, source)));
517 }
518 }
519 }
520 }
521
522 fn turn<Make, Fut>(
524 &self,
525 claims: &mut Claims<ClaimOne<DB, Row>, ClaimBuffers<DB, Row>, Make, Fut>,
526 cx: &mut Context<'_>,
527 ) -> Poll<Option<OneClaimed<DB, Row>>>
528 where
529 Make: Fn(ClaimOne<DB, Row>, ClaimBuffers<DB, Row>) -> Fut,
530 Fut: Future<Output = OneClaimed<DB, Row>>,
531 {
532 if self.wait.is_none() && !self.shared.is_closed() {
533 let in_work = self.in_work();
534 while self
535 .flow
536 .may_start(claims.running(), in_work, || spare(self.pool))
537 {
538 let started = claims.start(self.one(claims.running()));
541 if let Poll::Ready(done) = claims.poll_slot(started, cx) {
542 return Poll::Ready(Some(done));
543 }
544 }
545 }
546 claims.poll_any(cx)
547 }
548
549 fn deliver(
551 &mut self,
552 taken: Taken<DB, Row>,
553 buffers: &mut ClaimBuffers<DB, Row>,
554 ) -> Option<InboxDelivery<DB, Row, Mode>> {
555 let (queue, pool) = (self.queue, self.pool);
556 let delivery = match taken {
557 Taken::Locked(tx) => {
558 let claimed = buffers.rows.pop()?;
559 match self.lending {
560 Some(book) => InboxDelivery::lent(claimed, book.enter(tx), queue, pool),
562 None => InboxDelivery::own(claimed, tx, queue, pool),
563 }
564 }
565 Taken::Leased(book, lease) => {
566 let claimed = buffers.rows.pop()?;
567 let lent = self
570 .lending
571 .zip(buffers.transactions.get_mut().pop())
572 .map(|(lending, tx)| lending.enter(tx));
573 InboxDelivery::leased(claimed, book, lease, lent, queue, pool)
574 }
575 Taken::Advised => {
576 let claimed = buffers.rows.pop()?;
577 let hold = buffers.advised.as_deref_mut()?.holds.pop()?;
578 InboxDelivery::advised(claimed, hold, queue, pool)
579 }
580 };
581 self.made = self.made.wrapping_add(1);
582 let delivery = delivery.counted(InWork::new(self.settled));
583 #[cfg(feature = "testing")]
584 let delivery = delivery.on(&self.shared);
585 Some(delivery)
586 }
587
588 pub(crate) fn into_stream(
590 self,
591 ) -> impl Stream<Item = Result<InboxDelivery<DB, Row, Mode>, SqlxBrokerError>> + Send + 'static
592 {
593 let claims = Claims::new(claim_one::<DB, Row>);
594 futures::stream::unfold((self, claims), |(mut subscriber, mut claims)| async move {
595 let next = subscriber.next_one(&mut claims).await?;
596 Some((next, (subscriber, claims)))
597 })
598 }
599}
600
601pub(crate) struct ClaimOne<DB: QueueDatabase, Row: Events<DB>> {
603 pool: &'static ServicePool<DB>,
604 queue: &'static Queue,
605 holding: Holding<DB, Row>,
606 beside: usize,
608 #[cfg(feature = "testing")]
610 in_process: Option<Arc<Shared<DB>>>,
611}
612
613pub(crate) type OneClaimed<DB, Row> = (Result<Taken<DB, Row>, Failed>, ClaimBuffers<DB, Row>);
615
616async fn claim_one<DB, Row>(
618 one: ClaimOne<DB, Row>,
619 mut buffers: ClaimBuffers<DB, Row>,
620) -> OneClaimed<DB, Row>
621where
622 DB: QueueDatabase,
623 Row: Events<DB>,
624{
625 let ClaimOne {
626 pool,
627 queue,
628 holding,
629 beside,
630 ..
631 } = one;
632 #[cfg(feature = "testing")]
634 if let Some(shared) = one.in_process {
635 let now = shared.harness.now();
636 let (claimed, buffers) = off_clock(async move {
637 let claimed = claim_rows(pool, queue, holding, 1, beside, now, &mut buffers).await;
638 (claimed, buffers)
639 })
640 .await
641 .unwrap_or_else(|| (Err(("BEGIN", cancelled())), ClaimBuffers::default()));
642 if claimed.is_ok() {
643 shared.harness.claimed(queue.name, buffers.rows.len());
644 }
645 return (claimed, buffers);
646 }
647 let claimed = claim_rows(
648 pool,
649 queue,
650 holding,
651 1,
652 beside,
653 Now::default(),
654 &mut buffers,
655 )
656 .await;
657 (claimed, buffers)
658}
659
660fn one_claim(queue: &Queue) -> bool {
666 queue.prepared.fifo_guard.is_some()
667 || queue.spec.column(Role::PartitionKey).is_some()
668 || queue.one_writer
669 || queue.prepared.stamps
670}
671
672fn spare<DB: sqlx::Database>(pool: &Pool<DB>) -> usize {
674 let room = pool
675 .options()
676 .get_max_connections()
677 .saturating_sub(pool.size());
678 pool.num_idle()
679 .saturating_add(usize::try_from(room).unwrap_or(usize::MAX))
680}
681
682pub(crate) type Failed = (&'static str, sqlx::Error);
684
685async fn claim_rows<DB, Row>(
693 pool: &Pool<DB>,
694 queue: &'static Queue,
695 holding: Holding<DB, Row>,
696 limit: usize,
697 beside: usize,
698 now: Now,
699 claimed: &mut ClaimBuffers<DB, Row>,
700) -> Result<Taken<DB, Row>, Failed>
701where
702 DB: QueueDatabase,
703 Row: Events<DB>,
704{
705 let cx = Claiming {
706 queue,
707 limit: i64::try_from(limit).unwrap_or(i64::MAX),
708 now,
709 };
710 claimed.clear();
711 let ClaimBuffers {
712 rows,
713 ids,
714 advised,
715 transactions,
716 } = claimed;
717 let book = match holding {
718 Holding::Transaction => {
719 return claim_locked::<DB, Row>(pool, &cx, ids, rows)
720 .await
721 .map(Taken::Locked);
722 }
723 Holding::Leases(book) => book,
724 Holding::Advisory(book) => {
725 let advised = advised.get_or_insert_with(Box::default);
726 claim_advised::<DB, Row>(pool, book, &cx, limit, beside, rows, advised).await?;
727 return Ok(Taken::Advised);
728 }
729 };
730 let lease = claim_leased::<DB, Row>(pool, &cx, now, ids, rows).await?;
731 if queue.prepared.transactional {
736 for _ in 0..rows.len() {
737 transactions.get_mut().push(begin_work(pool, queue).await?);
738 }
739 }
740 Ok(Taken::Leased(book, lease))
741}
742
743pub(crate) async fn take_group<DB, Row>(
747 conn: &mut DB::Connection,
748 cx: &Claiming,
749 lease: Option<&Leasing<Row::Token>>,
750) -> Result<bool, Failed>
751where
752 DB: QueueDatabase,
753 Row: Events<DB>,
754{
755 let Some(guard) = cx.queue.prepared.fifo_guard else {
756 return Ok(true);
757 };
758 engine::take_group::<DB, Row>(conn, cx, guard, lease)
759 .await
760 .map_err(|source| (guard.sql, source))
761}
762
763pub(crate) async fn begin<DB: QueueDatabase>(
766 pool: &Pool<DB>,
767 queue: &Queue,
768) -> Result<PoolTx<DB>, Failed> {
769 PoolTx::begin(pool, queue.begin_claim)
770 .await
771 .map_err(|source| (queue.begin_claim.unwrap_or("BEGIN"), source))
772}
773
774impl<DB, Row, Mode> Subscriber for InboxSubscriber<DB, Row, Mode>
775where
776 DB: QueueDatabase,
777 Row: Events<DB>,
778 Mode: InboxMode,
779{
780 type Message = InboxDelivery<DB, Row, Mode>;
781 type Error = SqlxBrokerError;
782
783 fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
784 let claims = Claims::new(claim_one::<DB, Row>);
785 futures::stream::unfold((self, claims), |(subscriber, mut claims)| async move {
786 let next = subscriber.next_one(&mut claims).await?;
787 Some((next, (subscriber, claims)))
788 })
789 }
790}
791
792impl<DB, Row> BatchSubscriber for InboxSubscriber<DB, Row, Plain>
796where
797 DB: QueueDatabase,
798 Row: Events<DB>,
799 Row::Lane: BatchLane<DB, Row>,
800{
801 type Batch = <Row::Lane as BatchLane<DB, Row>>::Batch;
802
803 fn batches(
804 &mut self,
805 size: NonZeroUsize,
806 ) -> impl Stream<Item = Result<Self::Batch, Self::Error>> + Send + '_ {
807 futures::stream::unfold(self, move |subscriber| async move {
808 let (taken, count) = match subscriber.claim(size.get()).await? {
809 Ok(claimed) => claimed,
810 Err(error) => return Some((Err(error), subscriber)),
811 };
812 let batch =
813 <Row::Lane as BatchLane<DB, Row>>::batch(BatchClaim::new(subscriber, taken, count));
814 Some((Ok(batch), subscriber))
815 })
816 }
817}
818
819#[cfg(test)]
820mod tests {
821 use std::time::Duration;
822
823 use ruststream_sqlx_dialect::{Column, Form, TableSpec};
824
825 use super::one_claim;
826 use crate::inbox::engine::{IdAt, Prepared};
827 use crate::inbox::queue::Queue;
828
829 fn queue(spec: &TableSpec<'static>, one_writer: bool) -> Queue {
830 Queue {
831 name: "jobs",
832 table: "jobs",
833 row: "app::Job",
834 spec: *spec,
835 id_at: IdAt::First,
836 native_retry_after: false,
837 kinds: None,
838 prepared: Prepared::default(),
839 begin_claim: None,
840 counted_attempt: false,
841 one_writer,
842 poll_interval: Duration::from_secs(1),
843 lease: None,
844 cap: None,
845 }
846 }
847
848 const PLAIN: TableSpec<'static> = TableSpec::new("jobs", Column::new("id"), Form::RowLock);
849
850 #[test]
851 fn a_plain_table_on_a_server_claims_for_every_free_worker() {
852 assert!(!one_claim(&queue(&PLAIN, false)));
853 }
854
855 #[test]
856 fn a_keyed_table_keeps_its_claim_order() {
857 let keyed = PLAIN.partition_key(Column::new("customer"));
858 assert!(one_claim(&queue(&keyed, false)));
859 }
860
861 #[test]
862 fn a_lease_claim_that_stamps_in_a_transaction_keeps_one_claim() {
863 let mut stamping = queue(&PLAIN, false);
864 stamping.prepared.stamps = true;
865 assert!(one_claim(&stamping));
866 }
867
868 #[test]
869 fn a_database_of_one_writer_keeps_one_claim() {
870 assert!(one_claim(&queue(&PLAIN, true)));
871 }
872}