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;