apalis_core/backend/mod.rs
1//! Core traits for interacting with backends
2//!
3//! The core traits and types for backends, responsible for providing sources of tasks, handling their lifecycle, and exposing middleware for internal processing.
4//! The traits here abstract over different backend implementations, allowing for extensibility and interoperability.
5//!
6//! # Overview
7//! - [`Backend`]: The primary trait representing a task source, defining methods for polling tasks, heartbeats, and middleware.
8//! - [`TaskSink`]: An extension trait for backends that support pushing tasks.
9//! - [`FetchById`], [`Update`], [`Reschedule`]: Additional traits for managing tasks.
10//! - [`Vacuum`], [`ResumeById`], [`ResumeAbandoned`]: Traits for backend maintenance and task recovery.
11//! - [`RegisterWorker`], [`ListWorkers`], [`ListTasks`]: Traits for worker management and task listing.
12//! - [`WaitForCompletion`]: A trait for waiting on task completion and checking their status.
13//!
14//!
15//! ## Provided Implementations
16//!
17//! The module includes several default backend implementations, such as:
18//! - [`MemoryStorage`](memory::MemoryStorage): An in-memory backend for testing and lightweight use cases
19//! - [`CustomBackend`](custom::CustomBackend): A flexible backend allowing custom functions for task management
20//! - [`VecDequeBackend`](dequeue::VecDequeBackend): A simple in-memory backend that uses a `VecDeque` to store tasks.
21//! - [`Persisted`](persistence::Persisted): A backend that drives a [`Persistence`](persistence::Persistence) instance to emit tasks
22use std::{
23 fmt::Debug,
24 future::Future,
25 task::{Context, Poll},
26 time::Duration,
27};
28
29use crate::{backend::codec::Codec, task::task_id::TaskId, worker::context::WorkerContext};
30
31pub mod codec;
32pub mod custom;
33pub mod ext;
34pub mod factory;
35/// Finalizes an intermediate backend configuration into a concrete backend.
36///
37/// Used internally by workers
38pub mod finalize;
39pub mod queue;
40
41mod expose;
42/// Provides utilities for working with futures
43pub mod future;
44mod impls;
45/// Persistence layer for managing workers, tasks and events
46///
47/// Provides the `Persistence` trait which eases making backends with predictable persistence
48#[cfg(feature = "sleep")]
49pub mod persistence;
50mod results;
51mod sink;
52mod worker;
53
54pub use expose::*;
55pub use results::*;
56pub use sink::*;
57pub use worker::*;
58/// In-memory backend based on channels
59pub mod memory {
60 pub use crate::backend::impls::memory::*;
61}
62
63/// In-memory dequeue backend
64pub mod dequeue {
65 pub use crate::backend::impls::dequeue::*;
66}
67
68/// A task backend that drives task acquisition and lifecycle management for a worker.
69///
70/// A backend is responsible for:
71///
72/// - determining when it is ready to perform work;
73/// - yielding available tasks;
74/// - registering the worker's waker when no work is currently available; and
75/// - completing any pending cleanup or acknowledgement work during shutdown.
76///
77/// In other systems, this abstraction may be called a queue, broker, or
78/// consumer. The Backend abstraction is intentionally broader than a queue:
79/// it may also manage acknowledgements, leases, heartbeats, subscriptions, and
80/// other state associated with processing tasks.
81///
82/// The worker drives a backend by repeatedly polling [poll_ready], [poll_next],
83/// and [poll_close] as appropriate. Implementations must not block inside these
84/// methods.
85///
86/// # Polling
87///
88/// Backend methods follow the same conventions as [Future] and [Stream]:
89///
90/// - Poll::Ready means the operation can make progress immediately.
91/// - Poll::Pending means the operation cannot make progress yet and the
92/// implementation has registered the supplied waker.
93/// - The implementation must arrange for the waker to be woken when polling
94/// again may make progress.
95///
96/// A backend should avoid busy-polling. In particular, [poll_next] should
97/// return Poll::Pending once all currently available tasks have been drained
98/// and wait for its underlying source to signal that more work is available.
99///
100/// # Shutdown
101///
102/// [poll_close] is called when the worker is shutting down. It should continue
103/// making progress until all backend-owned resources and pending operations
104/// have been released. A backend must not report completion from poll_close
105/// while work that it is responsible for flushing or releasing remains.
106///
107/// [poll_next]: Backend::poll_next
108/// [poll_ready]: Backend::poll_ready
109/// [poll_close]: Backend::poll_close
110/// [Future]: std::future::Future
111/// [Stream]: futures_core::Stream
112#[must_use = "a Backend does nothing unless it is polled"]
113pub trait Backend: Sized {
114 /// The type of task the backend emits.
115 type Task;
116
117 /// The error type returned by backend operations
118 type Error: std::error::Error + Send + Sync + 'static;
119
120 /// Polls whether the backend is ready for the worker to request more work.
121 ///
122 /// This method may be used to flush pending backend operations or perform
123 /// other work required before [`poll_next`] can be called.
124 /// Returning `Poll::Ready(Ok(()))` indicates that the worker may proceed.
125 /// Returning `Poll::Pending` indicates that the backend cannot make progress
126 /// yet. The implementation must register `cx.waker()` and wake it when the
127 /// backend becomes ready.
128 ///
129 /// [`poll_next`]: Backend::poll_next
130 fn poll_ready(
131 &mut self,
132 cx: &mut Context<'_>,
133 worker: &WorkerContext,
134 ) -> Poll<Result<(), Self::Error>>;
135
136 /// Polls the backend for the next available task for this worker.
137 ///
138 /// Like `poll_ready`, this is lazy: a backend should only produce a
139 /// task when one is actually available, not busy-poll its source.
140 /// Once the buffer of ready tasks is drained and this returns
141 /// `Poll::Pending`, the backend is expected to register the waker and
142 /// go to sleep until new work arrives rather than being polled again
143 /// right away. Returns `None` when the backend is exhausted and will
144 /// never produce another task.
145 ///
146 /// # Example
147 ///
148 /// A minimal in-memory backend might look like this:
149 ///
150 /// ```ignore
151 /// fn poll_next(
152 /// &mut self,
153 /// cx: &mut Context<'_>,
154 /// _worker: &WorkerContext,
155 /// ) -> Poll<Option<Result<Task, Self::Error>>> {
156 /// match self.queue.pop_front() {
157 /// // A task was ready, return it immediately.
158 /// Some(task) => Poll::Ready(Some(Ok(task))),
159 /// // Nothing to do right now: register the waker and sleep
160 /// // until `wake()` is called (e.g. when a task is pushed).
161 /// None => {
162 /// self.waker = Some(cx.waker().clone());
163 /// Poll::Pending
164 /// }
165 /// }
166 /// }
167 /// ```
168 fn poll_next(
169 &mut self,
170 cx: &mut Context<'_>,
171 worker: &WorkerContext,
172 ) -> Poll<Option<Result<Self::Task, Self::Error>>>;
173
174 /// Flushes/releases any resources the backend holds (pending acks,
175 /// open subscriptions, connections) before the worker fully shuts down.
176 fn poll_close(
177 &mut self,
178 cx: &mut Context<'_>,
179 worker: &WorkerContext,
180 ) -> Poll<Result<(), Self::Error>>;
181}
182
183/// Decorates a backend with middleware, configuration and associated types
184pub trait BackendConfig {
185 /// The type of argument this backend emits
186 type Args;
187
188 /// The internal type of this Backend's [`TaskId`]
189 type Id;
190
191 /// This defines the kind of Backend
192 type Kind;
193
194 /// The config for the backend
195 type Config;
196
197 /// The type representing backend middleware layer.
198 type Layer;
199
200 /// Returns the config associated with the backend.
201 fn config(&self) -> &Self::Config;
202
203 /// Returns the backend's middleware layer.
204 fn middleware(&mut self, worker: &mut WorkerContext) -> Self::Layer;
205}
206
207/// Provides wire-format support for a backend.
208///
209/// A `WireFormatBackend` associates a backend with a codec used to encode and
210/// decode tasks and with the compact representation used when tasks are
211/// transmitted or persisted in their encoded form.
212pub trait WireFormatBackend {
213 /// The codec used to encode and decode tasks.
214 type Codec: Send + 'static;
215
216 /// The compact representation of task arguments.
217 type Compact;
218
219 /// Returns a reference to the codec used by the backend.
220 fn codec(&self) -> &Self::Codec;
221}
222
223/// Allows fetching a task by its ID
224pub trait FetchById: Backend {
225 /// Fetch a task by its unique identifier
226 #[allow(clippy::type_complexity)]
227 fn fetch_by_id(
228 &mut self,
229 task_id: &TaskId,
230 ) -> impl Future<Output = Result<Option<Self::Task>, Self::Error>> + Send;
231}
232
233/// Allows updating an existing task
234pub trait Update: Backend {
235 /// Update the given task
236 fn update(&mut self, task: Self::Task) -> impl Future<Output = Result<(), Self::Error>> + Send;
237}
238
239/// Allows rescheduling a task for later execution
240pub trait Reschedule: Backend {
241 /// Reschedule the task after a specified duration
242 fn reschedule(
243 &mut self,
244 task: Self::Task,
245 wait: Duration,
246 ) -> impl Future<Output = Result<(), Self::Error>> + Send;
247}
248
249/// Allows cleaning up resources in the backend
250pub trait Vacuum: Backend {
251 /// Cleans up all resources and returns the number of tasks vacuumed
252 fn vacuum(&mut self) -> impl Future<Output = Result<usize, Self::Error>> + Send;
253
254 /// Cleans up all resources committed before a specific duration
255 /// and returns the number of tasks vacuumed
256 fn vacuum_before(
257 &mut self,
258 duration: Duration,
259 ) -> impl Future<Output = Result<usize, Self::Error>> + Send;
260}
261
262/// Allows resuming a task by its ID
263pub trait ResumeById: Backend {
264 /// Resume a task by its ID
265 fn resume_by_id(
266 &mut self,
267 id: TaskId,
268 ) -> impl Future<Output = Result<bool, Self::Error>> + Send;
269}
270
271/// A [`Backend`] that can be fallibly constructed from its own configuration.
272pub trait TryNewBackend: BackendConfig + Backend + Sized {
273 /// The final backend after decoration
274 ///
275 /// This value may easily be Self;
276 /// But in some cases we want to add some `BackendExt` compositions
277 type Backend;
278 /// Attempt to construct a new backend instance from its configuration.
279 fn try_new(config: Self::Config) -> Result<Self::Backend, Self::Error>;
280}