Skip to content

Releases: AlexeyMatskevich/apalis-diesel-postgres

v0.4.1

20 Jun 16:22

Choose a tag to compare

Fixed

  • list_queues() (the ListQueues admin trait) silently returned an empty stats list for any queue with no completed jobs: the per-queue AVG_JOB_DURATION_MINS aggregate is SQL NULL in that state, which serialized to a JSON null that cannot decode into apalis_core::Statistic, failing the whole Vec<Statistic> decode and dropping every stat for the queue. The queue_stats CTE now COALESCEs null stat values to "0", so a queue with jobs always reports its full stat set.

Changed

  • The unscoped lock_task no longer lists "or in another queue" in its TaskNotFound hint: that entry point does not filter by job_type, so a task in another queue is locked rather than reported missing. lock_task_in_queue keeps the queue-aware hint.

Documentation

  • Every public Result-returning function now carries an # Errors section.
  • MIGRATIONS and the schema module are documented; the crate enables #![warn(missing_docs)] and #![warn(rustdoc::broken_intra_doc_links)].
  • README links to examples/* and CONTRIBUTING.md are now absolute GitHub URLs (relative links 404 on docs.rs); added an MSRV note (Rust 1.88).
  • Cargo.toml gained [package.metadata.docs.rs] so docs.rs documents the ntex path alongside tokio.
  • CONTRIBUTING.md no longer lists --no-default-features check/test commands (building without a runtime feature is an intentional compile_error!).

Full changelog: v0.4.0...v0.4.1

v0.4.0

10 Jun 09:12

Choose a tag to compare

Breaking change (minor): PgFetcher's phantom _marker field is no longer public — construct the marker fetcher via Default instead.

Fixed

  • Decode-stranding: a claimed row whose payload failed to decode was stranded in Running for as long as the claiming worker kept heartbeating (ack needs a decoded task; orphan recovery only reclaims rows of stale workers). The decode stage now releases such rows through the normal retry budget — Failed with the decode error in last_result, terminal Killed once attempts are exhausted — guarded by the exact claim epoch (lock_by, lock_at, attempts) so a delayed release never touches a row that was acked, swept, or re-claimed in the meantime.
  • Concurrent reenqueue_orphaned sweeps could double-apply to the same stale row under READ COMMITTED: burning an extra attempt, prematurely killing a job, or flipping an already-acked row back to Pending. The sweep now repeats the status predicate on the outer UPDATE and claims candidates with FOR UPDATE OF jobs SKIP LOCKED.
  • The shared notify listener returned its pooled connection to r2d2 without UNLISTEN, so the next pool user inherited the subscription and notifications accumulated unread in libpq's receive buffer. Every listener exit now removes the subscription first.
  • Debug output of PgAck (and the public PgMiddleware) printed the per-process lease_token verbatim; it is now redacted.
  • with_codec rebuilt the sink from scratch, silently dropping buffered tasks and any in-flight flush; both now carry over.
  • With both tokio and ntex features enabled, calling the backend from the ntex executor panicked inside tokio::task::spawn_blocking; the backend now falls back to ntex's blocking pool when no Tokio runtime is present.
  • The checked-in Diesel schema was missing workers.lease_token; new specs pin src/schema.rs against information_schema so the next migration cannot leave the typed schema stale silently.
  • CI's postgres job ran only 3 of the 11 integration test binaries; it now runs --tests, gating every current and future test binary.

Changed

  • Pool-path enqueue batches without an idempotency_key skip the conflict-recovery machinery on the sink's hot flush path; the outbox path (push_with_conn / push_task_with_conn) keeps full SAVEPOINT semantics — a failing INSERT rolls back only the batch and never aborts the caller's outer transaction.
  • apalis RC dependencies are pinned exactly (=…-rc.9) so cargo update cannot silently pull a breaking rc.10.
  • The Sink impl on PostgresStorage no longer requires Args: Send + Sync + 'static.

Documentation

  • The handler examples now run business transactions on a separate backend pool injected via Data<PgPool>, matching the "Connection pool isolation" guidance they previously contradicted.

Full details: CHANGELOG.md

v0.3.0

01 Jun 08:20

Choose a tag to compare

Breaking change. Idempotency-key conflicts on enqueue now return a dedicated, typed error variant instead of the stringly-typed Error::InvalidArgument("idempotency_key conflict: …"):

Error::IdempotencyConflict { job_type: String, conflicting_keys: Vec<String>, total: usize }

Match the variant (not the message text) to tell a benign duplicate apart from a real failure:

match storage.push_task_with_conn(conn, task) {
    Ok(id) => { /* enqueued */ }
    Err(Error::IdempotencyConflict { .. }) => { /* duplicate — swallow it */ }
    Err(other) => return Err(other),
}

For batch enqueues, conflicting_keys lists exactly which idempotency_keys collided, so you can drop them and re-enqueue the rest.

Behavior is unchanged: a single duplicate still rolls back the whole batch via SAVEPOINT (one duplicate undoes every row in the batch, not just the colliding one) while a surrounding transaction stays alive. Every other Error::InvalidArgument case (queue-name / metadata / idempotency-key length caps, unreachable run_at) is unchanged.

Error is #[non_exhaustive], so future variants won't be a breaking change for downstreams that already match with a wildcard arm.

v0.2.0

30 May 15:10

Choose a tag to compare

What's new in 0.2.0

⚠️ Breaking

  • Removed the public SharedPostgresError::NamespaceExists variant (dead after the broadcast redesign). Code that matched on it must be updated. Under 0.x semver this warrants the minor bump.

Features

  • wait_for_completion is now resilient to transient database errors. A failed completion poll is retried with backoff instead of abandoning the whole batch; the stream surfaces an error and ends only after several consecutive failures with no successful poll in between. Any successful poll resets the streak, and completed results stay durable in apalis.jobs, so a surfaced error is always safe to recover from by re-issuing wait_for.

Fixes

  • Concurrent setup() migrations are serialized with a session-level advisory lock, so several replicas booting against a fresh database no longer race on migration-0 DDL / the __diesel_schema_migrations insert.

Tests & internals

  • Large methodology-driven test pass (exhaustive specification through nested contexts): closed coverage gaps (queue_by_id, list_tasks defaults, the lock_task re-lock and queue-scoping arms, metadata-cap and claimability boundary values), tightened assertions, and de-flaked the suite (shared pool, temp-DB isolation, deterministic worker-integration terminal status). 521 tests, clippy clean.

Docs

  • Installation now points at crates.io.

Full diff: v0.1.1...v0.2.0

v0.1.1

21 May 19:18

Choose a tag to compare

Fix

  • PostgresStorage is now Send + Sync under both tokio and ntex runtimes, so it can be used with apalis::WorkerBuilder::build(). The published 0.1.0 failed to compile inside a WorkerBuilder because ntex's BlockingResult is !Sync; PgSink::flush_future is now wrapped in Mutex<Option<…>> with Mutex::get_mut keeping the hot path lock-free. Compile-time assert_send_sync guards the invariant.

Notes

  • 0.1.0 has been yanked from crates.io — it does not work with apalis.
  • Added an integration test covering the full worker round-trip (handler-ok and handler-err branches).
  • Added examples/worker.rs (tokio) and examples/worker-ntex.rs (ntex / neon-polling) demonstrating end-to-end use with WorkerBuilder and in-handler push_with_conn.
  • README documents in-handler transactional fan-out.