Skip to main content

apalis_core/
lib.rs

1#![crate_name = "apalis_core"]
2#![warn(
3    missing_debug_implementations,
4    missing_docs,
5    rust_2018_idioms,
6    unreachable_pub,
7    bad_style,
8    dead_code,
9    improper_ctypes,
10    non_shorthand_field_patterns,
11    overflowing_literals,
12    path_statements,
13    patterns_in_fns_without_body,
14    unconditional_recursion,
15    unused,
16    unused_allocation,
17    unused_comparisons,
18    unused_parens,
19    while_true
20)]
21#![cfg_attr(docsrs, feature(doc_cfg))]
22//! A high-performance, type-safe task processing framework for rust.
23//!
24//! `apalis-core` provides the fundamental abstractions and runtime components for building
25//! scalable background task systems with middleware support, graceful shutdown, and monitoring capabilities.
26//!
27//! This is advanced documentation, for guide level documentation is found on the [website](https://apalis.dev).
28//!
29//! # Core Concepts
30//!
31//! `apalis-core` is built around four primary abstractions that provide a flexible and extensible task processing system:
32//!
33//! - **[`Tasks`](#tasks)**: Type-safe task data structures with processing metadata
34//! - **[`Backends`](#backends)**: Pluggable task storage and streaming implementations  
35//! - **[`Workers`](#workers)**: Task processing engines with lifecycle management
36//! - **[`Monitor`](#monitor)**: Multi-worker coordination and observability
37//!
38//! The framework leverages the `tower` service abstraction to provide a rich middleware
39//! ecosystem like error handling, timeouts, rate limiting,
40//! and observability.
41//!
42//!
43//! ## Tasks
44//!
45//! The task struct provides type-safe components for task data and metadata:
46//! - [`Args`](crate::task::task_fn) - The primary structure for the task
47//! - [`ExecutionContext`](crate::task::ExecutionContext) - Backend persisted info on task execution.
48//! - [`TaskContext`](crate::task::context::TaskContext) - Worker provided context for a currently task in execution.
49//! - [`Status`](crate::task::status::Status) - Represents the current state of a task
50//! - [`TaskId`](crate::task::task_id::TaskId) - Unique identifier for task tracking
51//! - [`Attempt`](crate::task::attempt::Attempt) - Retry tracking and attempt information
52//! - [`Extensions`](crate::task::data) - Type-safe storage for additional task data
53//! - [`Metadata`](crate::task::metadata) - metadata associated with the task
54//!
55//! ### Example: Using `TaskBuilder`
56//!
57//! ```ignore
58//! let task: Task<String> = TaskBuilder::new("my-task".to_string())
59//!     .id("task-123".into())
60//!     .max_attempts(3)
61//!     .timeout(Duration::from_secs(30))
62//!     .run_in_minutes(10)
63//!     .build();
64//! ```
65//! Specific documentation for tasks can be found in the [`task`] and [`task::builder`] modules.
66//!
67//! #### Relevant Guides:
68//! - [**Defining Task arguments**](crate::task::task_fn) - Creating effective task arguments that are scalable and type-safe
69//!
70//! ## Backends
71//!
72//! The [`Backend`](crate::backend::Backend) trait serves as the core abstraction for all task sources.
73//! It defines task polling mechanisms, streaming interfaces, and middleware integration points.
74//!
75//! ### Inbuilt Implementations
76//! - [`MemoryStorage`](crate::backend::memory::MemoryStorage) : In-memory storage based on channels
77//! - [`CustomBackend`](crate::backend::custom) : Flexible backend composition allowing custom functions for task management
78//!
79//! Backends handle task persistence, distribution, and reliability concerns while providing
80//! a uniform interface for worker consumption.
81//!
82//! ## Workers
83//!
84//! The [`Worker`](crate::worker) is the core runtime component responsible for task polling, execution, and lifecycle management:
85//!
86//! ### Worker Lifecycle
87//!
88//! - Workers are responsible for task polling, processing, and lifecycle management.
89//! - Workers can be run as a future or as a stream of events.
90//! - Workers readiness is conditioned on the backend and service (and middleware) being ready.
91//! - This means any blocking middleware eg (concurrency) will block the worker from polling tasks.
92//!
93//! ### Worker Components
94//!
95//! The following are the main components the worker module:
96//!
97//! - [`WorkerBuilder`] - Fluent builder for configuring and constructing workers
98//! - [`Worker`] - Actual worker implementation that processes tasks
99//! - [`WorkerContext`] - Runtime state including task counts and execution status
100//! - [`Event`] - Worker event enumeration (`Start`, `Engage`, `Idle`, `Error`, `Stop`)
101//! - [`Ext`](crate::worker::ext) - Extension traits and middleware for adding functionality to workers
102//!
103//! ### Example: Building and Running a Worker
104//! ```rust
105//! # use apalis_core::worker::{builder::WorkerBuilder, context::WorkerContext};
106//! # use apalis_core::backend::memory::MemoryStorage;
107//! # use apalis_core::error::BoxDynError;
108//! # use std::time::Duration;
109//! # use crate::apalis_core::worker::ext::event_listener::EventListenerExt;
110//! # use crate::apalis_core::backend::TaskSink;
111//! #[tokio::main]
112//! async fn main() {
113//!     let mut in_memory = MemoryStorage::new();
114//!     in_memory.push(1u32).await.unwrap();
115//!
116//!     async fn task(
117//!         task: u32,
118//!         worker: WorkerContext,
119//!     ) -> Result<(), BoxDynError> {
120//!          /// Do some work
121//!         tokio::time::sleep(Duration::from_secs(1)).await;
122//!         worker.stop().unwrap();
123//!         Ok(())
124//!     }
125//!
126//!     let worker = WorkerBuilder::new("rango-tango")
127//!         .backend(in_memory)
128//!         .on_event(|worker, ev| {
129//!             println!("On Event = {:?}, {:?}", ev, worker.name());
130//!         })
131//!         .build(task);
132//!     worker.run().await.unwrap();
133//! }
134//! ```
135//!
136//! Learn more about workers in the [`worker`](crate::worker) and [`worker::builder`](crate::worker::builder) modules.
137//!
138//! #### Relevant Tutorials:
139//! - [**Creating task handlers**](crate::task::task_fn) - Defining task processing functions using the [`TaskFn`] trait
140//!
141//! ## Monitor
142//!
143//! The [`Monitor`](crate::monitor::Monitor) helps manage and coordinate multiple workers:
144//!
145//! **Main Features:**
146//! - **Worker Registry** - Keeps track of active workers
147//! - **Event Handling** - Handles and processes worker events
148//! - **Graceful Shutdown** - Stops all workers together safely
149//! - **Health Monitoring** - Restarts and manages worker health
150//! ### Example: Using `Monitor` with a Worker
151//!
152//! ```rust
153//! # use apalis_core::monitor::Monitor;
154//! # use apalis_core::worker::builder::WorkerBuilder;
155//! # use apalis_core::task::Task;
156//! # use apalis_core::backend::{TaskSink, dequeue::VecDequeBackend};
157//! # use tower::service_fn;
158//! # use std::time::Duration;
159//! # use apalis_core::worker::context::WorkerContext;
160//! #[tokio::main]
161//! async fn main() {
162//!     let mut storage = VecDequeBackend::new();
163//!     storage.push(1u32).await.unwrap();
164//!
165//!     let monitor = Monitor::new()
166//!         .on_event(|worker, event| println!("{}: {:?}", worker.name(), event))
167//!         .register(move |_| {
168//!             WorkerBuilder::new("demo-worker")
169//!                 .backend(storage.clone())
170//!                 .build(|req: u32, worker: WorkerContext| async move {
171//!                     println!("Processing task: {:?}", req);
172//! #                   worker.stop().unwrap();
173//!                     Ok::<_, std::io::Error>(req)
174//!                 })
175//!         });
176//!
177//!     // Start monitor and run all registered workers
178//!     monitor.run().await.unwrap();
179//! }
180//! ```
181//!
182//! Learn more about the monitor in the [`monitor` module](crate::monitor).
183//!
184//! ## Middleware
185//!
186//! Built on the `tower` ecosystem, `apalis-core` provides extensive middleware support like error handling, timeouts, rate limiting, and observability.
187//!
188//! ### Core Middleware
189//!
190//! The following middleware layers are included with their worker extensions:
191//! - [`AcknowledgmentLayer`] - Task acknowledgment after processing
192//! - [`EventListenerLayer`] - Worker event emission and handling
193//! - [`CircuitBreakerLayer`] - Circuit breaker pattern for failure handling
194//! - [`LongRunningLayer`] - Support for tracking long-running tasks
195//!
196//! ### Extending with middleware
197//!
198//! You can write your own middleware to run code before or after a task is processed.
199//!
200//! <details>
201//! <summary>Creating Custom Middleware</summary>
202//!
203//! Here's a simple example of a logging middleware layer:
204//!
205//! ```rust
206//! use apalis_core::task::Task;
207//! use tower::{Layer, Service};
208//! use std::task::{Context, Poll};
209//!
210//! // Define a logging service that wraps another service
211//! pub struct LoggingService<S> {
212//!     inner: S,
213//! }
214//!
215//! impl<S, Args, Res, Err> Service<Task<Args>> for LoggingService<S>
216//! where
217//!     S: Service<Task<Args>, Response = Res, Error = Err>,
218//!     Args: std::fmt::Debug,
219//! {
220//!     type Response = Res;
221//!     type Error = Err;
222//!     type Future = S::Future;
223//!
224//!     fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
225//!         self.inner.poll_ready(cx)
226//!     }
227//!
228//!     fn call(&mut self, req: Task<Args>) -> Self::Future {
229//!         println!("Processing task: {:?}", req.args);
230//!         self.inner.call(req)
231//!     }
232//! }
233//!
234//! // Define a layer that wraps services with LoggingService
235//! pub struct LoggingLayer;
236//!
237//! impl<S> Layer<S> for LoggingLayer {
238//!     type Service = LoggingService<S>;
239//!
240//!     fn layer(&self, service: S) -> Self::Service {
241//!         LoggingService { inner: service }
242//!     }
243//! }
244//! ```
245//! </details>
246//!
247//! If you want your middleware to do more than just intercept requests and responses, you can use extension traits. See the [`worker::ext`](crate::worker::ext) module for examples.
248//!
249//! ## Error Handling
250//!
251//! `apalis-core` defines a comprehensive error taxonomy for robust error handling:
252//!
253//! - [`AbortError`] - Non-retryable fatal errors requiring immediate termination
254//! - [`RetryAfterError`] - Retryable execution errors triggering retry mechanisms after a delay
255//! - [`DeferredError`] - Retryable execution errors triggering immediate retry
256//!
257//! This error classification enables precise error handling strategies and
258//! appropriate retry behavior for different failure scenarios.
259//!
260//! ## Graceful Shutdown
261//!
262//! `apalis-core` has a reliable graceful shutdown system that makes sure
263//! workers stop safely and all tasks finish before shutting down:
264//!
265//! **Key Features:**
266//! - Task tracking: Workers keep track of how many tasks are running.
267//! - Shutdown control: The system waits until all tasks are finished before shutting down.
268//! - Monitor coordination: A shared [`Shutdown`] token helps all workers stop together.
269//! - Timeout: You can set a time limit for shutdown using [`with_terminator`](crate::monitor::Monitor::with_terminator).
270//!
271//! Learn more about the graceful shutdown process in the [`monitor`](crate::monitor#graceful-shutdown-with-timeout) module.
272//!
273//! # Feature flags
274#![cfg_attr(
275    feature = "docsrs",
276    cfg_attr(doc, doc = ::document_features::document_features!())
277)]
278//!
279//! # Development
280//! `apalis-core` provides comprehensive extensibility mechanisms such as middleware and ext traits.
281//!  Beyond there one may want to dive deeper into the following topics:
282//!
283//! - [**Using CustomBackend**](crate::backend::custom) - using custom backend to integrate with already existing systems
284//! - [**Extending Workers using extension traits**](crate::worker::ext#creating-a-custom-worker-extension-trait) - implementing custom worker functionality via extension traits
285//!
286//! # Observability
287//! You can track tasks using [apalis-board](https://github.com/apalis-dev/apalis-board).
288//! ![Task](https://github.com/apalis-dev/apalis-board/raw/main/screenshots/task.png)
289//!
290//! [`Backend`]: crate::backend::Backend
291//! [`TaskFn`]: crate::task::task_fn::TaskFn
292//! [`Service`]: tower_service::Service
293//! [`Task`]: crate::task
294//! [`WorkerBuilder`]: crate::worker::builder
295//! [`Worker`]: crate::worker
296//! [`Monitor`]: crate::monitor
297//! [`AcknowledgmentLayer`]: crate::worker::ext::ack
298//! [`EventListenerLayer`]: crate::worker::ext::event_listener
299//! [`CircuitBreakerLayer`]: crate::worker::ext::circuit_breaker
300//! [`LongRunningLayer`]: crate::worker::ext::long_running
301//! [`AbortError`]: crate::error::AbortError
302//! [`RetryAfterError`]: crate::error::RetryAfterError
303//! [`DeferredError`]: crate::error::DeferredError
304//! [`WorkerContext`]: crate::worker::context::WorkerContext
305//! [`Event`]: crate::worker::event::Event
306//! [`ExecutionContext`]: crate::task::ExecutionContext
307//! [`Status`]: crate::task::status::Status
308//! [`TaskId`]: crate::task::task_id::TaskId
309//! [`Attempt`]: crate::task::attempt::Attempt
310//! [`FromRequest`]: crate::task::from_request::FromRequest
311//! [`Shutdown`]: crate::monitor::shutdown::Shutdown
312
313#[macro_use]
314pub(crate) mod macros;
315
316pub mod backend;
317/// Includes internal error types.
318pub mod error;
319
320/// Actively manage and observe workers
321pub mod monitor;
322pub mod task;
323pub mod worker;
324
325/// Layers for building middleware stacks
326pub mod layers {
327    pub use tower_layer::*;
328    pub use tower_service::Service;
329}
330/// Timing and delaying utilities
331#[cfg(feature = "sleep")]
332pub mod timer {
333    pub use futures_timer::Delay;
334    /// Runtime agnostic sleep function based on [Delay]
335    pub async fn sleep(duration: std::time::Duration) {
336        futures_timer::Delay::new(duration).await;
337    }
338}