Skip to main content

InboxSubscriber

Struct InboxSubscriber 

Source
pub struct InboxSubscriber<DB: QueueDatabase, Row: Events<DB>, Mode = Plain> { /* private fields */ }
Expand description

The subscriber an InboxQueue opens: a stream of deliveries claimed from the table.

The stream claims when it is polled: up to the batch size for a batch handler, and one row for a single-message handler, with a claim in flight for each free worker of a handler mounted with workers(n). The subscription holds at most the pool’s size less one connection, and a claim beside another one starts only while the pool has a connection to spare, so the pool keeps one for the handlers. It keeps one claim in flight on SQLite, which takes one writer at a time, and on a table whose groups keep their order or that has a partition_key column, whose order claims that run at once could break, and where a lease claim stamps its rows in a transaction, as on MySQL and MariaDB. After a claim that filled its limit the next one runs at once; after one that found fewer rows it waits the poll interval, or until a publisher of the same broker writes a row of its table and group (a row the service writes through its own SQL or a handler’s transaction waits for the interval). A write that lands while the subscription claims ends its next wait at once. A failed claim reaches the stream as an error item and the next claim waits one second. After shutdown the stream ends.

In the row lock form a message in work holds one connection, and a batch one for all its messages. A batch’s settlements take effect together, when the last of its deliveries finishes: a settlement whose statement fails rolls the whole batch back, and its rows return. A batch that holds a row whose statement always fails therefore rolls back and returns all of its rows on every attempt, until the service’s SQL or schema is fixed.

In the lease form the claim commits at once and holds no connection: each delivery, in a batch or alone, takes a connection only while it settles, and its settlement takes effect on its own; in transactional mode a delivery holds its transaction’s connection until it settles. A task on the runtime the broker connected on extends the lease of every delivery in work each half lease, on one connection, until the subscriber drops or the broker shuts down.

In the advisory lock form each message in work holds a connection of its own, in a batch too, and its session holds the lock on the row’s key: outside transactional mode no transaction stays open while the handler works. A claim selects candidates, locks each key on a session and takes the row while it is still claimable; a batch takes idle connections first and opens new ones while the pool has room, so a batch larger than the pool shrinks instead of waiting. A settlement runs its statement on the session, then releases the lock and returns the connection to the pool. A delivery dropped unsettled closes its connection on the runtime the broker connected on, after releasing the lock, and its row returns at once. SQLite keeps no such locks: the process keeps the keys in work instead, so one process serves a database file.

In transactional mode, a registration mounted with transactional, each delivery lends its handler the transaction it settles in, opened at the table’s isolation level or SQLite mode. In the row lock form that is the claim’s: the subscription sets a savepoint after each claim that took a row, so a settlement other than acknowledgement can discard what the handler wrote. In the lease form the subscription opens a transaction for each delivery once its claim committed; in the advisory lock form, on the delivery’s session once its row is taken. Transactional mode serves single deliveries: the subscriber delivers no batches in it.

A table whose groups keep their order (#[field(group, fifo = true)]) has one row of a group in work at a time. A claim takes the group’s head, its first unfinished row in claim order, and takes nothing while another row of the group is in work, a row that entered the group ahead of it included. The claim first takes the group in its transaction with the guard its dialect builds, so subscriptions to one group on separate brokers keep the order too. A batch of such a table holds one row, and a handler mounted with workers(n) handles the group’s rows one after another.

§Examples

use ruststream_sqlx::prelude::*;
use serde::Deserialize;
use sqlx::PgPool;

/// Changes to push to a CRM, one group per customer, each group in order.
#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "crm_jobs")]
pub struct SyncChange {
    #[field(id, generated)]
    id: i64,
    #[field(group, fifo = true)]
    customer: String,
    #[field(payload)]
    payload: Vec<u8>,
}

#[derive(Deserialize)]
struct Change {
    field: String,
}

// The mount's subscription is an `InboxSubscriber<Postgres, SyncChange>` reading group `acme`.
#[subscriber(InboxQueue::<SyncChange>::new("acme"))]
async fn push(change: &Change) -> HandlerOutcome {
    tracing::info!(field = %change.field, "pushing");
    HandlerOutcome::ack()
}

pub fn app(pool: PgPool) -> RustStream {
    RustStream::new(AppInfo::new("crm-sync", "0.1.0")).with_broker(SqlxBroker::new(pool), |b| {
        // Four workers, and still one change of the group at a time, in order.
        b.include(push.workers(nonzero!(4)));
    })
}

Trait Implementations§

Source§

impl<DB, Row> BatchSubscriber for InboxSubscriber<DB, Row, Plain>
where DB: QueueDatabase, Row: Events<DB>, Row::Lane: BatchLane<DB, Row>,

Source§

type Batch = <<Row as QueueRow>::Lane as BatchLane<DB, Row>>::Batch

Container yielded by batches. Implementations choose between Vec, custom iterators, or anything else that yields the underlying Subscriber::Message.
Source§

fn batches( &mut self, size: NonZeroUsize, ) -> impl Stream<Item = Result<Self::Batch, Self::Error>> + Send + '_

Returns a stream of batches of at most size messages. Read more
Source§

impl<DB: QueueDatabase, Row: Events<DB>, Mode> Debug for InboxSubscriber<DB, Row, Mode>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<DB, Row, Mode> Subscriber for InboxSubscriber<DB, Row, Mode>
where DB: QueueDatabase, Row: Events<DB>, Mode: InboxMode,

Source§

type Message = InboxDelivery<DB, Row, Mode>

The message type yielded by this subscriber.
Source§

type Error = SqlxBrokerError

The error type yielded by the stream when delivery fails.
Source§

fn stream( &mut self, ) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_

Returns a stream of broker deliveries. Read more

Auto Trait Implementations§

§

impl<DB, Row, Mode = Plain> !RefUnwindSafe for InboxSubscriber<DB, Row, Mode>

§

impl<DB, Row, Mode = Plain> !UnwindSafe for InboxSubscriber<DB, Row, Mode>

§

impl<DB, Row, Mode> Freeze for InboxSubscriber<DB, Row, Mode>
where Arc<Shared<DB>>: Freeze, Holding<DB, Row>: Freeze, &'static ServicePool<DB>: Freeze, Option<&'static TxBook<DB>>: Freeze, ClaimBuffers<DB, Row>: Freeze, Registration<DB>: Freeze, PhantomData<fn() -> Mode>: Freeze,

§

impl<DB, Row, Mode> Send for InboxSubscriber<DB, Row, Mode>
where Arc<Shared<DB>>: Send, Holding<DB, Row>: Send, &'static ServicePool<DB>: Send, Option<&'static TxBook<DB>>: Send, ClaimBuffers<DB, Row>: Send, Registration<DB>: Send, PhantomData<fn() -> Mode>: Send,

§

impl<DB, Row, Mode> Sync for InboxSubscriber<DB, Row, Mode>
where Arc<Shared<DB>>: Sync, Holding<DB, Row>: Sync, &'static ServicePool<DB>: Sync, Option<&'static TxBook<DB>>: Sync, ClaimBuffers<DB, Row>: Sync, Registration<DB>: Sync, PhantomData<fn() -> Mode>: Sync,

§

impl<DB, Row, Mode> Unpin for InboxSubscriber<DB, Row, Mode>
where Arc<Shared<DB>>: Unpin, Holding<DB, Row>: Unpin, &'static ServicePool<DB>: Unpin, Option<&'static TxBook<DB>>: Unpin, ClaimBuffers<DB, Row>: Unpin, Registration<DB>: Unpin, PhantomData<fn() -> Mode>: Unpin,

§

impl<DB, Row, Mode> UnsafeUnpin for InboxSubscriber<DB, Row, Mode>
where Arc<Shared<DB>>: UnsafeUnpin, Holding<DB, Row>: UnsafeUnpin, &'static ServicePool<DB>: UnsafeUnpin, Option<&'static TxBook<DB>>: UnsafeUnpin, ClaimBuffers<DB, Row>: UnsafeUnpin, Registration<DB>: UnsafeUnpin, PhantomData<fn() -> Mode>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, W> HasTypeWitness<W> for T
where W: MakeTypeWitness<Arg = T>, T: ?Sized,

Source§

const WITNESS: W = W::MAKE

A constant of the type witness
Source§

impl<T> Identity for T
where T: ?Sized,

Source§

const TYPE_EQ: TypeEq<T, <T as Identity>::Type> = TypeEq::NEW

Proof that Self is the same type as Self::Type, provides methods for casting between Self and Self::Type.
Source§

type Type = T

The same type as Self, used to emulate type equality bounds (T == U) with associated type equality constraints (T: Identity<Type = U>).
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PublishedThrough<DefaultSlot> for T

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more