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.
JobError
Section titled “JobError”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.
RabbitMQ failures
Section titled “RabbitMQ failures”| 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).
Errors that are not returned to you
Section titled “Errors that are not returned to you”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() |
Logging them well
Section titled “Logging them well”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.
See also
Section titled “See also”queuey_core::error
on docs.rs.