跳到主要内容
本页尚未翻译

下面显示的是英文原文。

Outcomes

A handler's outcome is a statement on its row: ack deletes it or sets processed_at, retry returns it at once with one more attempt, drop finishes it.

What a handler answers decides the row's fate:

OutcomeThe row
ack()is deleted, or marked with the current time where the table has a processed_at column
retry()returns to the queue at once, and its next delivery reads one more attempt
retry_after(d)stays out of the queue until its retry_after comes
drop()is finished as an acknowledgement finishes it

A row marked with processed_at stays in the table, out of every claim, for an audit or a later cleanup. The statement runs where the form holds the row: in the claim's transaction, by the delivery's lease, or on the session that holds its key. It takes effect together with the end of that hold, so a row and its outcome never part. A settlement whose statement fails reports itself, and the row returns as it would after a crash. max_attempts(n) with dead_letter(..) caps the retries and moves a spent row aside. A table may run its own SQL for an outcome, an acknowledgement that moves the row to a sent group for one (Events); it runs in the transaction the crate commits, as the built-in statement does. An acknowledgement takes the row out of what the claim selects, so a statement of one's own that leaves the row claimable delivers it again.

Example​

The mail server greylists a first delivery, so the handler retries it at once; an address that cannot receive mail is dropped, and both end marked in processed_at.

use ruststream_sqlx::prelude::*;
use sqlx::Connection;
use sqlx::sqlite::SqlitePoolOptions;

/// One row of `mail_jobs`: `processed_at` keeps a finished row in the table, marked.
#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "mail_jobs")]
struct SendMail {
#[field(id, generated)]
id: i64,
#[field(attempt, generated)]
attempt: i16,
#[field(locked_until)]
locked_until: Option<DateTime<Utc>>,
#[field(processed_at)]
processed_at: Option<DateTime<Utc>>,
#[field(payload)]
payload: Vec<u8>,
}

#[subscriber(InboxQueue::<SendMail>::new("mail"))]
async fn send(mail: &Mail, Ctx(attempt): Ctx<keys::Attempt>) -> HandlerOutcome {
if !mail.to.contains('@') {
// Nothing to retry: the row is finished, as an acknowledgement finishes it.
return HandlerOutcome::drop();
}
if deliver(mail, attempt).await {
// `processed_at` is set, and the row stays.
return HandlerOutcome::ack();
}
// Back in the queue at once; the next delivery reads one more attempt.
HandlerOutcome::retry()
}

/// The service on the database `url` names.
fn service(url: &str) -> RustStream {
let schema_url = url.to_owned();
// Nothing connects here: the broker builds its pool when the service starts.
let broker = SqlxBroker::from_url(url, SqlitePoolOptions::new());
RustStream::new(AppInfo::new("mailer", "0.1.0"))
// The table, created before the subscription checks it.
.on_startup(async move |()| {
let mut conn = SqliteConnection::connect(&schema_url).await?;
sqlx::raw_sql(SCHEMA).execute(&mut conn).await?;
conn.close().await
})
.with_broker(broker.route::<SendMail>("mail"), |b| {
b.include(send);
})
}