Skip to content

Errors

Two error types. Error is what the library returns to you; JobError is what a handler returns to the library.

pub type Result<T, E = Error> = std::result::Result<T, E>;

queuey::Result<()> is what the examples’ main returns.

pub enum Error {
Serde(serde_json::Error),
Backend(Box<dyn std::error::Error + Send + Sync + 'static>),
UnknownQueue(String),
NoHandler(String),
DuplicateHandler(String),
ShutDown,
ConsumerStopped(String),
}
impl Error {
pub fn backend<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self;
}
Variant Message When
Serde serialization error: {0} A job would not serialize, or a body would not decode
Backend backend error: {0} Anything the transport reported. The real cause is the source()
UnknownQueue unknown queue `{0}` A delayed or deferred publish onto a queue this backend never declared
NoHandler no handler registered for job type `{0}` No handler for a job type
DuplicateHandler handler for job type `{0}` registered twice Two handlers with the same Job::NAME, raised by build()
ShutDown worker is shut down The backend was closed
ConsumerStopped consumer for queue `{0}` stopped unexpectedly A consumer stream ended without a shutdown being requested

Serde converts with ? from serde_json::Error. Backend carries a source, so print with {:#} or walk source() to see what actually failed.

ConsumerStopped is the one to alert on: it normally means the connection to the broker went away. queuey does not reconnect, so the process should restart.

pub enum JobError {
Retryable(Box<dyn std::error::Error + Send + Sync + 'static>),
Fatal(Box<dyn std::error::Error + Send + Sync + 'static>),
Deferred { delay: std::time::Duration, reason: String },
}
impl JobError {
pub fn retryable<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self;
pub fn fatal<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self;
pub fn retryable_msg(msg: impl Into<String>) -> Self;
pub fn fatal_msg(msg: impl Into<String>) -> Self;
pub fn deferred(delay: std::time::Duration) -> Self; // reason = "deferred"
pub fn deferred_msg(delay: std::time::Duration, msg: impl Into<String>) -> Self;
}
Variant Message Effect
Retryable job failed (retryable): {0} The retry policy decides
Fatal job failed (fatal): {0} Dead-lettered immediately
Deferred job deferred for {delay:?}: {reason} Held for delay, back at the front, no attempt spent

Retryable and Fatal carry a source(); Deferred does not, because nothing failed.

Box<dyn Error> converts into Retryable, so ? inside a handler produces a retryable failure. When that is the wrong default, say so:

self.db.insert(&row).await.map_err(JobError::fatal)?;

See Handlers for how to choose.

Message Meaning
broker nacked the message published to `q` The broker refused the publish
broker returned the message published to `q` as unroutable: 312 NO_ROUTE The queue does not exist. Usually a producer built with new_undeclared
publisher confirms are not enabled; cannot confirm publish to `q` The publishing channel lost confirm mode; normally a reopened channel
cannot ack delivery of job `id` on `q`: it was already settled or its channel is gone The channel closed while the job was running. The message will be redelivered
invalid AMQP name `...`: ... A name over 255 bytes
delay of 30d is longer than a hold queue can wait (24d 19h 21m); it was refused rather than released early Over the roughly 24.8-day ceiling
queue `q` leaves no room for its hold queues: `...` is 261 bytes, over the 255-byte AMQP limit Raised by declare() at startup, before anything is created

Raw transport failures arrive as Error::Backend(lapin::Error).

Some failures are handled rather than propagated, because propagating them would stall a consumer:

Failure What happens instead
A body that is not a valid Envelope Copied verbatim to q.dead with x-death-reason: "malformed envelope", then acked. Never a stream error
A payload that does not decode into the job type Dead-lettered with decode error: ...
No handler for the job type Dead-lettered with no handler for job type `X`
A handler panic Becomes Retryable("handler panicked")
A job_timeout expiry Becomes Retryable("job timed out after {limit:?}")
A failed ack, retry, defer or dead-letter Logged at ERROR and counted in settle_failures()
match producer.enqueue(&job).await {
Ok(id) => tracing::info!(%id, "enqueued"),
// `{:#}` walks the source chain, so the lapin cause is visible.
Err(e) => tracing::error!(error = %format!("{e:#}"), "enqueue failed"),
}

The outer message alone is usually just backend error: ..., which tells you nothing on its own.

queuey_core::error on docs.rs.