Skip to main content

Crate boson

Crate boson 

Source
Expand description

Boson is a Rust job-work runtime: durable background tasks, retries, rate limits, and pluggable persistence behind QueueBackend.

Wire a backend once with Boson::builder(), define handlers with task, then enqueue with typed send_with (after configure) or Boson::enqueue. Swap mem, sqlite, postgres, or fleet crates (boson-backend-redis / boson-backend-nats) without changing task code.

§Features

Background jobs without locking you into one queue store.

This crate ships with no default features (default = []). Enable explicitly:

Fleet backends (boson-backend-redis, boson-backend-nats) are separate workspace crates.

§Getting started

You always define and enqueue tasks the same way (#[task], send_with). What changes is which process runs the worker loop.

§Choose your topology

After you pick a mode, continue with define tasks (shared by every mode).

§Mode 1 — Embedded (one binary)

This process enqueues jobs and runs the worker loop (or drives ManualWorker in tests). There is no second binary. Default lease TTL is 0 (no distributed lease coordination).

Your app ──enqueue──► Boson ──worker loop──► mem / `SQLite` / Postgres / …
BackendTypeFeature / crateTopologyWhen to use
In-memoryMemQueueBackendmemembedded onlyLocal experiments and tests
SQLiteSqliteQueueBackendsqliteembedded (or Mode 2 on one host)Durable single host
PostgresPostgresQueueBackendpostgresembedded or remoteShared durable state
RedisRedisQueueBackendboson-backend-redisremote / fleetBroker-backed multi-host
NATSNatsQueueBackendboson-backend-natsremote / fleetBroker-backed multi-host

In-memory first run (feature mem):

use std::sync::Arc;

use boson::{
    configure, task, Boson, ExecutionContext, JsonExecutionContextFactory, MemQueueBackend,
};

#[task(name = "greet")]
async fn greet(ctx: Box<dyn ExecutionContext>, name: String) -> boson_core::Result<()> {
    let _ = (ctx, name);
    Ok(())
}

let boson = Boson::builder()
    .queue_backend(Arc::new(MemQueueBackend::new()))
    .execution_context_factory(JsonExecutionContextFactory)
    .auto_registry()
    .build()?; // background worker loop
configure(boson);

Greet::send_with(
    serde_json::json!({"System": {"operation": "demo"}}),
    GreetParams { name: "world".into() },
)
.await?;

For step-driven tests, use BosonBuilder::without_worker + BosonBuilder::build_manual and ManualWorker::try_run_next instead of BosonBuilder::build.

Runnable: task_macro, minimal_enqueue, idempotency_and_rate_limit (cargo run -p uf-boson --example <name> --features mem). Then continue with define tasks.

§Mode 2 — Remote worker (two binaries)

Use this when HTTP/API processes should enqueue only, and a dedicated worker process (or many workers) should claim and run jobs against shared persistence.

MemQueueBackend cannot cross process boundaries — Mode 2 needs SQLite (shared path), Postgres, Redis, or NATS.

Enqueue binary(ies) ──send_with──► shared QueueBackend ◄──claim── Worker binary(ies)

§What you create

PiecePurpose
Shared task crate (recommended)Same #[task] handlers + inventory on the worker
Enqueue binaryBoots Boson without a worker loop; auto_registry so
descriptors exist for send_with; calls configure + send_with
Worker binarySame backend URL/path; auto_registry; unique
worker_id; lease_ttl_secs > 0; build
Shared backendSQLite path, Postgres URL, or Redis/NATS fleet

§Enqueue binary

This process must not spawn the drain loop. Use BosonBuilder::without_worker then BosonBuilder::build, still call BosonBuilder::auto_registry (enqueue looks up task descriptors for priority/pool/policies), install with configure, and enqueue.

Pick a shared backend (each link has a Mode 2 enqueue-binary example):

SQLite sketch (same pattern on every backend page above):

use std::sync::Arc;

use boson::{
    configure, Boson, JsonExecutionContextFactory, SqliteQueueBackend,
};

let path = std::env::var("BOSON_SQLITE_PATH").unwrap_or_else(|_| "/tmp/boson-remote.db".into());
let backend = SqliteQueueBackend::new(&path).await?;
let boson = Boson::builder()
    .queue_backend(Arc::new(backend))
    .execution_context_factory(JsonExecutionContextFactory)
    .auto_registry() // descriptors for send_with — no claim loop
    .without_worker()
    .build()?;
configure(boson);
// Greet::send_with(...).await?;  // same API as Mode 1

API detail: BosonBuilder::without_worker, BosonBuilder::auto_registry, configure.

§Worker binary

A different binary owns the drain loop. Link every crate that defines #[task] handlers (use my_tasks as _;) so inventory discovery works.

Pick the same shared backend (each link has a Mode 2 worker-binary example):

SQLite sketch (same pattern on every backend page above):

use std::sync::Arc;

use boson::{Boson, JsonExecutionContextFactory, SqliteQueueBackend};

let path = std::env::var("BOSON_SQLITE_PATH").unwrap_or_else(|_| "/tmp/boson-remote.db".into());
let backend = SqliteQueueBackend::new(&path).await?;
let _boson = Boson::builder()
    .queue_backend(Arc::new(backend))
    .execution_context_factory(JsonExecutionContextFactory)
    .worker_id(std::env::var("BOSON_WORKER_ID").unwrap_or_else(|_| "worker-1".into()))
    .lease_ttl_secs(30) // required when multiple processes share the backend
    .auto_registry()
    .build()?; // background claim + dispatch loop
// keep the process alive (await shutdown, serve health, …)

API detail: BosonBuilder::worker_id, BosonBuilder::lease_ttl_secs, WorkerSettings, BosonBuilder::auto_registry.

§Run both

  1. Start the worker (so claims are ready).
  2. Start one or more enqueue hosts.
  3. Each worker process needs a unique BOSON_WORKER_ID (or builder worker_id) and a positive lease TTL (BOSON_LEASE_TTL_SECS or lease_ttl_secs).

Runnable: remote_worker, remote_enqueue

export BOSON_SQLITE_PATH=/tmp/boson-remote.db
cargo run -p uf-boson --example remote_worker --features sqlite &
cargo run -p uf-boson --example remote_enqueue --features sqlite

§3. Define tasks

When the worker is already booted (Mode 1) or the worker binary discovers inventory (Mode 2), adding a handler is the macro plus an enqueue call:

use boson::{task, ExecutionContext};

#[task(name = "notify")]
async fn notify(ctx: Box<dyn ExecutionContext>, message: String) -> boson_core::Result<()> {
    let _ = (ctx, message);
    Ok(())
}

Notify::send_with(
    serde_json::json!({"System": {"operation": "notify"}}),
    NotifyParams { message: "hello".into() },
)
.await?;

Policy attributes (priority, pool, max_attempts, …) are documented on task. Persisted overrides use TaskConfig.

§4. Choose persistence

Pick from the Mode 1 backend table. Connect examples live on each backend type. Fleet Redis/NATS: see those crate docs for connect_fleet_from_env and URL precedence (BOSON_*_POOL_ROUTINGBOSON_*_URLS).

Custom adapters implement QueueBackend (start from MemQueueBackend or the trait’s How to implement section).

§5. Mount HTTP admin (optional)

With feature axum, nest boson_router at NEST_PATH (/api/boson) using BosonState:

Runnable: cargo run -p uf-boson --example axum_admin --features mem,axum (BOSON_EXAMPLE_SERVE=1 to listen).

§Prerequisites and gotchas

  • Enable the backend feature (or fleet crate) that matches your topology — mem is Mode 1 only.
  • Mode 2 workers need lease_ttl_secs > 0 and unique worker_id values.
  • Worker binaries must link every crate that submits #[task] inventory.
  • configure is required in any process that calls macro send_with (including enqueue-only hosts).

§Configuration precedence

LayerResolution order
Worker settingsBosonBuilder field → environment variable → hardcoded default
Task config at enqueuePersisted backend config → macro/descriptor defaults
Idempotency modePer-task override → BosonBuilder::idempotency_mode (default lease-backed)
Queue backendExplicit BosonBuilder::queue_backend → global router
Ops logBosonBuilder::ops_logNoOpsLog; or ops_log_from_env separately
Fleet URLs (Redis/NATS)BOSON_*_POOL_ROUTINGBOSON_*_URLS

See WorkerSettings and TaskConfig for field-level defaults.

§Runnable examples

ExampleTopologyFeatures
task_macroMode 1 (manual drain)mem
minimal_enqueueMode 1mem
idempotency_and_rate_limitMode 1mem
axum_adminMode 1 + HTTPmem,axum
remote_workerMode 2 workersqlite
remote_enqueueMode 2 enqueuesqlite
cargo run -p uf-boson --example task_macro --features mem

Modules§

prelude
Convenient re-exports for application code.

Structs§

Boson
Boson work engine — enqueue, admin reads, and worker orchestration.
BosonBuilder
Builder for Boson.
BosonState
Extractable state holding a Boson runtime.
ConsoleOpsLog
stderr structured lines (default dev adapter).
Job
An enqueued unit of work (one task invocation).
JsonExecutionContextFactory
Default factory that wraps actor JSON in a labeled ExecutionContext.
ManualWorker
Manual single-step worker for tests (no background task).
MemQueueBackend
Process-local queue backend (not durable).
NoOpsLog
Zero-cost no-op (benchmark telemetry=off and minimal CI).
PostgresQueueBackend
PostgreSQL-backed queue backend.
QueueRouter
Registry for named queue backends (multi-backend hosts).
RateLimitPolicy
Rate limiting for enqueue backpressure.
RetryPolicy
Retry policy for a task.
Run
One execution attempt of a job.
SqliteQueueBackend
SQLite-backed queue backend.
TaskConfig
Per-task config persisted for admin UI and enqueue defaults.
TaskDescriptor
Descriptor for a registered task.
TaskRegistry
Registry of tasks discovered via inventory (auto_discover) or register. Prefer BosonBuilder::auto_registry at boot.
TaskRunStats
Run outcome counts for one task name.
WorkerSettings
Resolved worker settings for claim, lease, and telemetry labels.

Enums§

BosonError
Errors that can occur in Boson operations.
IdentityError
Identity reconstruction failure at handler boundary.
JobEnqueueDisposition
Whether QueueBackend::enqueue_with_policies inserted or reused a job.
JobStatus
Status of a job in the queue.
RunStatus
Status of a run.

Constants§

NEST_PATH
Nest path for the Boson API router (/api/boson).

Traits§

ExecutionContext
Opaque execution context for task handlers.
ExecutionContextFactory
Builds handler execution context from captured actor JSON at enqueue time.
OpsLog
Structured ops metrics and events for enqueue, runs, leases, and runtime health.
QueueBackend
Stable async trait for queue persistence (jobs, runs, config, leases).

Functions§

boson_router
Create the Boson API router (mount at NEST_PATH).
configure
Install the process-wide default Boson instance.
default
Return the configured default Boson instance, if any.
default_backend_from_global
Resolve the default backend from the process-global QueueRouter.
install_default_mem_backend
Install a new MemQueueBackend as the process-global default backend.
install_default_postgres_backend
Install a new PostgresQueueBackend as the process-global default backend.
install_default_sqlite_backend
Install a new SqliteQueueBackend as the process-global default backend.
install_isolated_postgres_backend
Install postgres backend with an isolated schema for test sessions.
install_ops_log
Install the process-wide ops log (typically at server boot before Boson runtime).
ops_log
Resolved ops log — NoOpsLog until install_ops_log.
ops_log_from_env
Resolve from BOSON_TELEMETRY (off | console; default console).
postgres_test_url
Resolve postgres URL from env (test preferred, then bench, then default).

Type Aliases§

InvokeFn
Invokes a registered task with execution context and JSON parameters.

Attribute Macros§

task
Background task handler — typed params, send_with enqueue, and link-time registration.