Skip to main content

taskvisor/
lib.rs

1//! # Taskvisor
2//!
3//! Taskvisor supervises in-process Tokio tasks that need retries, cancellation, final outcomes, or coordinated shutdown.
4//! Its optional controller applies queue, replace, or reject policy per application key. Supervisor-wide limits still apply.
5//!
6//! ## Check the fit
7//!
8//! Taskvisor is useful when an application needs one or more of these:
9//!
10//! - tasks are added, removed, or watched while the service is running;
11//! - task attempts need timeouts, retry limits, or backoff;
12//! - application logic needs the final outcome of one submitted task;
13//! - competing work for the same key must queue, replace older work, or be rejected.
14//!
15//! Taskvisor is not a persistent job queue. Runtime state, queued submissions, and task IDs
16//! do not survive process exit. Use durable external storage when work must resume after a restart.
17//!
18//! ## Quick start
19//!
20//! A [`TaskFn`] turns an async closure into supervised work.
21//! A [`TaskSpec`] gives that work a name and selects its lifecycle.
22//!
23//! ```rust
24//! use taskvisor::prelude::*;
25//!
26//! #[tokio::main(flavor = "current_thread")]
27//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
28//!     let supervisor = Supervisor::new(SupervisorConfig::default(), vec![]);
29//!     let hello = TaskFn::arc(|_ctx| async {
30//!         println!("hello from Taskvisor");
31//!         Ok(())
32//!     });
33//!
34//!     supervisor
35//!         .run(vec![TaskSpec::once("hello", hello)])
36//!         .await?;
37//!     Ok(())
38//! }
39//! ```
40//!
41//! [`Supervisor::run`] accepts the complete static batch or rejects it.
42//! The method returns after the shared cleanup workflow, not with each task's outcome.
43//! Use a watched dynamic add when application logic needs that result.
44//!
45//! ## Continue with a runnable example
46//!
47//! The [user guide] explains the application workflow from task definition through production boundaries.
48//! The [examples guide] includes the learning path, commands, feature flags, and stop behavior.
49//!
50//! - Foundations: [basic], [task type], [graceful worker], [application shutdown], [periodic],
51//!   [restart policies], and [configuration].
52//! - Runtime patterns: [outcomes], [dynamic tasks], [queue consumer], and [CPU job].
53//! - Observability: [custom subscriber], [logging], [tracing], and [metrics].
54//! - Keyed admission: [controller slots], [controller admission], and [tenant sync].
55//!
56//! [user guide]: https://github.com/soltiHQ/taskvisor/blob/main/guide.md
57//! [examples guide]: https://github.com/soltiHQ/taskvisor/blob/main/examples/README.md
58//! [basic]: https://github.com/soltiHQ/taskvisor/blob/main/examples/basic.rs
59//! [task type]: https://github.com/soltiHQ/taskvisor/blob/main/examples/task_type.rs
60//! [graceful worker]: https://github.com/soltiHQ/taskvisor/blob/main/examples/graceful_worker.rs
61//! [application shutdown]: https://github.com/soltiHQ/taskvisor/blob/main/examples/application_shutdown.rs
62//! [periodic]: https://github.com/soltiHQ/taskvisor/blob/main/examples/periodic.rs
63//! [restart policies]: https://github.com/soltiHQ/taskvisor/blob/main/examples/restart_policies.rs
64//! [configuration]: https://github.com/soltiHQ/taskvisor/blob/main/examples/configuration.rs
65//! [outcomes]: https://github.com/soltiHQ/taskvisor/blob/main/examples/outcomes.rs
66//! [dynamic tasks]: https://github.com/soltiHQ/taskvisor/blob/main/examples/dynamic_tasks.rs
67//! [queue consumer]: https://github.com/soltiHQ/taskvisor/blob/main/examples/queue_consumer.rs
68//! [CPU job]: https://github.com/soltiHQ/taskvisor/blob/main/examples/cpu_job.rs
69//! [custom subscriber]: https://github.com/soltiHQ/taskvisor/blob/main/examples/custom_subscriber.rs
70//! [logging]: https://github.com/soltiHQ/taskvisor/blob/main/examples/logging.rs
71//! [tracing]: https://github.com/soltiHQ/taskvisor/blob/main/examples/tracing.rs
72//! [metrics]: https://github.com/soltiHQ/taskvisor/blob/main/examples/metrics.rs
73//! [tenant sync]: https://github.com/soltiHQ/taskvisor/blob/main/examples/tenant_sync.rs
74//! [controller slots]: https://github.com/soltiHQ/taskvisor/blob/main/examples/controller_slots.rs
75//! [controller admission]: https://github.com/soltiHQ/taskvisor/blob/main/examples/controller_admission.rs
76//!
77//! ## Choose the runtime entry point
78//!
79//! | Entry point                         | Use it when                                  |
80//! |-------------------------------------|----------------------------------------------|
81//! | [`Supervisor::run`]                 | A fixed batch finishes naturally             |
82//! | [`Supervisor::run_until`]           | A fixed batch stops on an application future |
83//! | [`Supervisor::run_with_os_signals`] | Taskvisor should install signal handlers     |
84//! | [`Supervisor::serve`]               | Work is added and managed at runtime         |
85//!
86//! `run` and `run_until` do not install operating-system signal handlers.
87//! `run_with_os_signals` is the explicit process-wide opt-in. Dynamic mode
88//! returns a [`SupervisorHandle`] with add, query, cancel, remove, and shutdown methods.
89//!
90//! [`Supervisor::new`] accepts runtime configuration and subscribers with default task settings.
91//! Use [`Supervisor::builder`] when you need custom [`TaskDefaults`], controller admission,
92//! or typed construction errors through [`SupervisorBuilder::try_build`].
93//!
94//! ## Choose task behavior
95//!
96//! | Constructor               | After success                  | After a retry-eligible failure |
97//! |---------------------------|--------------------------------|--------------------------------|
98//! | [`TaskSpec::once`]        | Stop                           | Stop                           |
99//! | [`TaskSpec::restartable`] | Stop                           | Retry if the limit allows      |
100//! | [`TaskSpec::periodic`]    | Wait its interval, then repeat | Retry if the limit allows      |
101//!
102//! Each registration has one [`TaskId`] and one internal actor. Attempts for that ID never overlap.
103//! [`RestartPolicy`] decides whether success repeats and whether a retryable failure may run again.
104//! The retry limit restricts only repeats after failure. [`BackoffPolicy`] and [`JitterPolicy`]
105//! control failure delays. A timeout applies to one attempt. The default retry limit is unlimited;
106//! set [`TaskSpec::with_max_retries`] or a [`TaskDefaults`] limit when repeated failure must eventually stop the task.
107//!
108//! [`Task::spawn`] should return its future promptly. Put the task's work inside that future, and move
109//! blocking or CPU-heavy work off Tokio worker threads. Long-running work must observe [`TaskContext::cancelled`]
110//! or use [`TaskContext::run_until_cancelled`]. Return [`TaskError::Canceled`] after a cooperative stop.
111//! Return [`TaskError::Fail`] for a retry-eligible failure or [`TaskError::Fatal`] when the actor must stop.
112//!
113//! ## Get results or observe events
114//!
115//! [`SupervisorHandle::add_and_watch`] returns a [`TaskWaiter`] for a direct final [`TaskOutcome`].
116//! A watched result does not depend on the lossy event path, but it is still in-memory and is not
117//! durable across process termination.
118#![cfg_attr(
119    feature = "controller",
120    doc = "Controller users can also choose [`SupervisorHandle::submit_and_watch`]."
121)]
122//!
123//! [`Event`] and [`Subscribe`] are for logs, metrics, tracing, and live diagnostics. The shared event bus
124//! and each subscriber queue are bounded. Event delivery is best-effort and must not drive application correctness.
125#![cfg_attr(
126    feature = "controller",
127    doc = r#"
128## Coordinate work by key
129
130The default `controller` feature adds keyed admission before registry entry.
131Enable the controller for a supervisor with [`SupervisorBuilder::with_controller`], then submit a [`ControllerSpec`].
132
133```text
134ControllerSpec ──► controller slot
135                        ├── idle ──► registry admission
136                        └── busy ──► queue, replace, or reject
137```
138
139A task name is the registry uniqueness key inside one supervisor. A controller slot is the key used to coordinate competing submissions.
140Different task names can share a slot. Direct `add*` methods bypass this layer; `submit*` methods use it.
141See [`AdmissionPolicy`] for the exact queue, replace, and reject behavior.
142"#
143)]
144//!
145//! ## Cancellation and shutdown boundary
146//!
147//! Cancellation starts cooperatively. At the configured grace deadline, Taskvisor may report [`TaskOutcome::ForceAborted`]
148//! while it keeps owning the unfinished actor until physical exit. While that actor remains active, its synchronous
149//! task code or attempt-future destructor may keep its task name and capacity reservation owned.
150//! Later isolated destruction of terminal task values keeps capacity reserved but does not keep the task name reserved.
151//!
152//! Dropping a non-final public owner leaves the runtime running. Dropping the final owner can request cancellation but
153//! cannot wait for cleanup. Call [`SupervisorHandle::shutdown`] when the cleanup result matters.
154//!
155//! ## Architecture at a glance
156//!
157//! ```text
158//! application
159//!      ├── static batch ──► Supervisor::run*
160//!      ├── dynamic task ──► SupervisorHandle::add*
161//!      └── keyed task ──► SupervisorHandle::submit* ──► controller
162//!
163//! registry ──► TaskActor ──► sequential attempts
164//!
165//! runtime components ──► bounded event bus ──► subscriber queues
166//!
167//! registry cleanup or watched rejection ──► TaskWaiter
168//! ```
169//!
170//! The registry is the source of truth for registered task membership. The controller owns
171//! submissions that have not reached the registry. Events only observe the lifecycle.
172//! Watched outcomes use a separate one-shot path.
173//!
174//! ## Crate layout
175//!
176//! - [`tasks`] defines work, cancellation context, and task specifications.
177//! - [`policies`] defines restart and retry timing.
178//! - [`core`] exposes construction, runtime control, outcomes, and configuration.
179#![cfg_attr(
180    feature = "controller",
181    doc = "- [`controller`] defines optional keyed admission."
182)]
183//! - [`events`] and [`subscribers`] define best-effort observability.
184//! - [`error`] maps the public error types to their API boundaries.
185//! - [`identity`] explains task IDs, names, and controller slots.
186//! - [`prelude`] re-exports the common application-facing types.
187//!
188//! Contributors can follow the [source guide](https://github.com/soltiHQ/taskvisor/blob/main/src/ARCHITECTURE.md)
189//! for runtime ownership, data flow, and test entry points.
190//!
191//! ## Feature flags
192//!
193//! - `controller` enables keyed admission and is enabled by default.
194//! - `logging` enables the built-in standard-output subscriber.
195//! - `tracing` enables the built-in `tracing` bridge.
196//! - `tokio-util-interop` exposes Tokio's cancellation token type.
197//! - `test-util` exposes constructors intended for external tests.
198
199#![forbid(unsafe_code)]
200#![warn(missing_docs)]
201#![cfg_attr(docsrs, feature(doc_cfg))]
202
203/// Compiles runnable Rust code blocks in `README.md` as doctests.
204#[cfg(doctest)]
205#[doc = include_str!("../README.md")]
206struct ReadmeDoctests;
207
208/// Compiles runnable Rust code blocks in `guide.md` as doctests.
209#[cfg(all(doctest, feature = "controller"))]
210#[doc = include_str!("../guide.md")]
211struct GuideDoctests;
212
213pub mod core;
214pub use core::{
215    ConfigError, Supervisor, SupervisorBuilder, SupervisorConfig, SupervisorHandle, TaskDefaults,
216    TaskOutcome, TaskOutcomeKind, TaskWaiter,
217};
218
219pub mod tasks;
220pub use tasks::{BoxTaskFuture, Task, TaskContext, TaskFn, TaskRef, TaskSetting, TaskSpec};
221
222pub mod policies;
223pub use policies::{BackoffError, BackoffPolicy, JitterPolicy, RestartPolicy};
224
225pub mod error;
226pub use error::{BoxError, BuildError, Error, RuntimeError, SharedError, TaskError};
227
228pub mod events;
229pub use events::{BackoffSource, Event, EventKind, RejectionKind};
230
231pub mod subscribers;
232pub use subscribers::Subscribe;
233
234pub mod identity;
235pub use identity::TaskId;
236
237pub mod prelude;
238
239pub(crate) mod reasons;
240
241#[cfg(feature = "controller")]
242#[cfg_attr(docsrs, doc(cfg(feature = "controller")))]
243pub mod controller;
244#[cfg(feature = "controller")]
245#[cfg_attr(docsrs, doc(cfg(feature = "controller")))]
246pub use controller::{
247    AdmissionPolicy, ControllerConfig, ControllerError, ControllerSnapshot, ControllerSpec,
248    PreparedSubmission, SlotStatusKind, SlotView,
249};
250
251#[cfg(feature = "logging")]
252#[cfg_attr(docsrs, doc(cfg(feature = "logging")))]
253pub use subscribers::LogWriter;
254
255#[cfg(feature = "tracing")]
256#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
257pub use subscribers::{TracingBridge, TracingBridgeWithReasons};