Перейти к основному содержимому
Эта страница ещё не переведена

Ниже показан английский оригинал.

SQLite

The sqlite feature serves the lease and advisory lock forms in the service's process: one writer at a time, times as text, and no server to run in tests.

SQLite locks the whole database for a writer, so no claim can hold rows for a handler. Its dialect implements no row lock form, and a subscription to a table without locked_until or advisory_lock does not compile. A lease claim is one UPDATE .. RETURNING that leases its rows, and one writer at a time keeps two claims apart. The rows of one lease claim come in no particular order. A subscription mounted with workers(n) claims a row for every worker in that one statement. A writer that finds the write lock taken waits in SQLite's busy handler, which sleeps a millisecond or more instead of queueing it. So a transactional subscription whose handlers write runs one worker in the advisory lock form, where its deliveries' transactions would mostly wait for each other (Workers). A claim of the service's own opens its transaction with BEGIN IMMEDIATE and comes with a Fetch of its own.

The advisory lock form keeps its keys in the process, beside the pool. One process therefore serves a database file: two processes on one file may deliver a row twice. A table in that form needs no time column, so it needs neither chrono nor time. A transaction the crate opens takes the mode the table declares: deferred, immediate or exclusive.

SQLite keeps times as text and compares them as text. chrono times sort exactly. time values sort right only across seconds, so with them a lease may end up to a second late, and a delayed retry may come back up to a second early or late (QueueTime). The AsyncAPI document describes the server by its protocol alone, with no host.

An in-memory database named in a shared-cache URL, sqlite:file:<name>?mode=memory&cache=shared, serves every connection that names it and lives while one of them is open. A test runs the service on one with no server.

Example​

Thumbnails in the advisory lock form: two workers each hold a row's key in the process while their handler runs.

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

/// One row of `thumbnail_jobs`, held by the lock on its key while a handler runs.
#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "thumbnail_jobs", advisory_lock = "thumbnails-{id}")]
struct MakeThumbnail {
#[field(id, generated)]
id: i64,
#[field(attempt, generated)]
attempt: i16,
#[field(payload)]
payload: Vec<u8>,
}

// The keys live in this process: one process serves the database file.
#[subscriber(InboxQueue::<MakeThumbnail>::new("thumbnails"))]
async fn thumbnail(image: &Image) -> HandlerOutcome {
tracing::info!(path = %image.path, "making a thumbnail");
HandlerOutcome::ack()
}

/// 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("thumbnails", "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::<MakeThumbnail>("thumbnails"), |b| {
b.include(thumbnail.workers(nonzero!(2)));
})
}

#[ruststream::app]
fn app() -> impl App {
service("sqlite:thumbnails.db?mode=rwc")
}