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