Skip to main content

taskvisor/
lib.rs

1//! # taskvisor
2//!
3//! Taskvisor is an in-process Tokio task supervisor with retries, reliable outcomes, and keyed queue/replace/reject admission.
4//! It can time out attempts, stop tasks during shutdown, and dynamically manage work.
5//!
6//! Use it for dynamic or keyed background work that needs conflict handling and a clear lifecycle.
7//!
8//! ## Start Here
9//!
10//! 1. Create a [`Task`] with [`TaskFn`] or your own type.
11//! 2. Wrap it in a [`TaskSpec`] to choose restart rules.
12//! 3. Start a [`Supervisor`] in static or dynamic mode.
13//! 4. Make long-running tasks listen to [`TaskContext`] cancellation.
14//!
15//! ```text
16//! TaskFn or impl Task
17//!          ▼
18//!       TaskSpec  ── fills missing values from ──► TaskDefaults
19//!          ▼
20//!      Supervisor
21//!       │       │
22//!       │       └── best-effort events ──► Subscribe
23//!       │
24//!       └── watched final result ────────► TaskWaiter
25//! ```
26//!
27//! ## Choose a Mode
28//!
29//! - **Static:** call [`Supervisor::run`] with a known task set.
30//!   It waits for all tasks to finish or for an OS shutdown signal.
31//! - **Dynamic:** call [`Supervisor::serve`].
32//!   It returns a [`SupervisorHandle`] that can add, remove, cancel, and list tasks while the service is running.
33//!
34//! `run` registers its initial task list as one batch.
35//! If a name is repeated or already in use, no task from that batch starts.
36//!
37//! ## One Task, Many Attempts
38//!
39//! A registered task has one [`TaskId`], but it may run several attempts:
40//!
41//! ```text
42//! register
43//!    ▼
44//! attempt 1 ── failure ──► backoff ──► attempt 2 ── success ──► finish
45//!                                                       ▼
46//!                                                 TaskOutcome
47//! ```
48//!
49//! Attempts for one task never overlap.
50//! [`RestartPolicy`] decides whether a new attempt is allowed.
51//! [`BackoffPolicy`] sets the delay after a retryable failure.
52//! A timeout applies to one attempt, not to the full task lifetime.
53//!
54//! ## Cancellation and Shutdown
55//!
56//! Cancellation is cooperative first.
57//! A long-running task should await [`TaskContext::cancelled`] or use [`TaskContext::run_until_cancelled`], then return [`TaskError::Canceled`].
58//! During shutdown, taskvisor waits for the configured grace period.
59//! It aborts tasks that still have not stopped.
60//!
61//! Dropping one supervisor or handle clone does not stop the runtime.
62//! Dropping the last public owner sends best-effort cancellation, but cannot wait for cleanup.
63//! Call [`SupervisorHandle::shutdown`] when cleanup must be complete before your code continues.
64//!
65//! ## Events or Final Outcomes?
66//!
67//! Lifecycle [`Event`] values are **best-effort**.
68//! They are suitable for logs, metrics, and live status.
69//! A slow subscriber can miss events.
70//!
71//! A [`TaskWaiter`] uses a direct completion channel.
72//! Event-bus lag does not affect it.
73//!
74//! It normally returns the final [`TaskOutcome`] after retries and registry cleanup;
75//! it returns a runtime error if the completion channel closes first.
76//!
77//! Create one with [`SupervisorHandle::add_and_watch`] or its fail-fast `try_*` form.
78//! With a configured controller, use `submit_and_watch` or `try_submit_and_watch`.
79//!
80//! ## Quick Start
81//!
82//! ```rust
83//! use taskvisor::prelude::*;
84//!
85//! #[tokio::main(flavor = "current_thread")]
86//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
87//!     let supervisor = Supervisor::new(SupervisorConfig::default(), vec![]);
88//!
89//!     let hello: TaskRef = TaskFn::arc("hello", |_ctx| async move {
90//!         println!("hello from taskvisor");
91//!         Ok(())
92//!     });
93//!
94//!     supervisor.run(vec![TaskSpec::once(hello)]).await?;
95//!     Ok(())
96//! }
97//! ```
98//!
99//! ## Main Types
100//!
101//! | Need             | Types                                                          |
102//! |------------------|----------------------------------------------------------------|
103//! | Define work      | [`Task`], [`TaskFn`], [`TaskContext`], [`TaskSpec`]            |
104//! | Run work         | [`Supervisor`], [`SupervisorHandle`]                           |
105//! | Set defaults     | [`SupervisorConfig`], [`TaskDefaults`]                         |
106//! | Control retries  | [`RestartPolicy`], [`BackoffPolicy`], [`JitterPolicy`]         |
107//! | Observe progress | [`Event`], [`EventKind`], [`Subscribe`]                        |
108//! | Wait for the end | [`TaskWaiter`], [`TaskOutcome`], [`TaskOutcomeKind`]           |
109//! | Admit keyed work | [`ControllerSpec`], [`PreparedSubmission`], [`AdmissionPolicy`], [`ControllerConfig`] |
110//! | Handle errors    | [`Error`], [`TaskError`], [`RuntimeError`]                     |
111//!
112//! Main types are re-exported at the crate root.
113//! The module pages explain each area in more detail:
114//! [`tasks`], [`policies`], [`events`], [`subscribers`], [`core`], and [`identity`].
115//!
116//! ## Keyed Admission
117//!
118//! The default `controller` feature provides per-slot admission. Configure it with
119//! [`SupervisorBuilder::with_controller`], then submit a [`ControllerSpec`] that
120//! queues, replaces, or rejects work when the slot already has an owner.
121//! Different slots are independent, subject to the supervisor's global limits.
122//!
123//! ## Feature Flags
124//!
125//! - `tracing`: forwards lifecycle events to `tracing`.
126//! - `logging`: simple event logging for examples and development.
127//! - `tokio-util-interop`: exposes the underlying Tokio cancellation token.
128//! - `controller` (default): slot-based admission with queue, replace, and reject rules.
129//! - `test-util`: constructors for task contexts, identities, and outcomes in tests.
130//!
131//! ## Examples
132//!
133//! Choose a short path, or [browse all examples on GitHub](https://github.com/soltiHQ/taskvisor/tree/main/examples):
134//!
135//! - New to supervision: `basic` → `worker` → `outcomes`.
136//! - Need per-key coordination: `tenant_sync` → `slots` → `admission`.
137//!
138//! ### Start here
139//!
140//! | Example                                                                                   | What it shows                                                   |
141//! |-------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
142//! | [basic](https://github.com/soltiHQ/taskvisor/blob/main/examples/basic.rs)                 | Run one task and exit — the minimal wiring                      |
143//! | [worker](https://github.com/soltiHQ/taskvisor/blob/main/examples/worker.rs)               | A long-running worker that stops cleanly on Ctrl+C              |
144//! | [periodic](https://github.com/soltiHQ/taskvisor/blob/main/examples/periodic.rs)           | Repeat a job after each successful cycle                        |
145//! | [multiple](https://github.com/soltiHQ/taskvisor/blob/main/examples/multiple.rs)           | Several restart rules under one supervisor                      |
146//!
147//! ### Real patterns
148//!
149//! | Example                                                                                     | What it shows                                                   |
150//! |---------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
151//! | [queue_consumer](https://github.com/soltiHQ/taskvisor/blob/main/examples/queue_consumer.rs) | Retry a failed broker connection                                |
152//! | [cpu_job](https://github.com/soltiHQ/taskvisor/blob/main/examples/cpu_job.rs)               | Run CPU-heavy work on rayon without blocking Tokio              |
153//!
154//! ### Observability
155//!
156//! | Example                                                                                 | What it shows                                             |
157//! |-----------------------------------------------------------------------------------------|-----------------------------------------------------------|
158//! | [subscriber](https://github.com/soltiHQ/taskvisor/blob/main/examples/subscriber.rs)     | React to lifecycle events with your own handler           |
159//! | [tracing](https://github.com/soltiHQ/taskvisor/blob/main/examples/tracing.rs)           | Send events into `tracing` (feature `tracing`)            |
160//! | [metrics](https://github.com/soltiHQ/taskvisor/blob/main/examples/metrics.rs)           | Build Prometheus counters from lifecycle events           |
161//!
162//! ### Dynamic work and outcomes
163//!
164//! | Example                                                                                 | What it shows                                            |
165//! |-----------------------------------------------------------------------------------------|----------------------------------------------------------|
166//! | [dynamic](https://github.com/soltiHQ/taskvisor/blob/main/examples/dynamic.rs)           | Add, list, cancel, and remove tasks at runtime           |
167//! | [outcomes](https://github.com/soltiHQ/taskvisor/blob/main/examples/outcomes.rs)         | Wait for reliable outcomes, including a timeout          |
168//!
169//! ### Keyed admission
170//!
171//! | Example                                                                                       | What it shows                                         |
172//! |-----------------------------------------------------------------------------------------------|-------------------------------------------------------|
173//! | [tenant_sync](https://github.com/soltiHQ/taskvisor/blob/main/examples/tenant_sync.rs)         | Keep only the latest sync revision per tenant         |
174//! | [slots](https://github.com/soltiHQ/taskvisor/blob/main/examples/slots.rs)                     | Compare queue, replace, and reject policies           |
175//! | [admission](https://github.com/soltiHQ/taskvisor/blob/main/examples/admission.rs)             | Observe typed admission and rejection outcomes       |
176
177#![forbid(unsafe_code)]
178#![warn(missing_docs)]
179#![cfg_attr(docsrs, feature(doc_cfg))]
180
181/// Compiles runnable Rust code blocks in `README.md` as doctests.
182#[cfg(doctest)]
183#[doc = include_str!("../README.md")]
184struct ReadmeDoctests;
185
186pub mod core;
187pub use core::{
188    ConfigError, Supervisor, SupervisorBuilder, SupervisorConfig, SupervisorHandle, TaskDefaults,
189    TaskOutcome, TaskOutcomeKind, TaskWaiter,
190};
191
192pub mod tasks;
193pub use tasks::{BoxTaskFuture, Task, TaskContext, TaskFn, TaskRef, TaskSetting, TaskSpec};
194
195pub mod policies;
196pub use policies::{BackoffError, BackoffPolicy, JitterPolicy, RestartPolicy};
197
198pub mod error;
199pub use error::{BoxError, Error, RuntimeError, SharedError, TaskError};
200
201pub mod events;
202pub use events::{BackoffSource, Event, EventKind, RejectionKind};
203
204pub mod subscribers;
205pub use subscribers::Subscribe;
206
207pub mod identity;
208pub use identity::TaskId;
209
210pub mod prelude;
211
212pub(crate) mod reasons;
213
214#[cfg(feature = "controller")]
215#[cfg_attr(docsrs, doc(cfg(feature = "controller")))]
216pub mod controller;
217#[cfg(feature = "controller")]
218#[cfg_attr(docsrs, doc(cfg(feature = "controller")))]
219pub use controller::{
220    AdmissionPolicy, ControllerConfig, ControllerError, ControllerSnapshot, ControllerSpec,
221    PreparedSubmission, SlotStatusKind, SlotView,
222};
223
224#[cfg(feature = "logging")]
225#[cfg_attr(docsrs, doc(cfg(feature = "logging")))]
226pub use subscribers::LogWriter;
227
228#[cfg(feature = "tracing")]
229#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
230pub use subscribers::TracingBridge;