Skip to main content

taskvisor/controller/
mod.rs

1//! Coordinates tasks that target the same application resource.
2//!
3//! The controller is an optional admission layer in front of the runtime registry.
4//! It gives each application-defined **slot** at most one owner.
5//! Work in different slots can proceed independently.
6//!
7//! Use controller `submit*` methods when tasks for the same customer, device, document, deployment,
8//! or other key must not overlap. Use direct `add*` methods when keyed admission is not needed;
9//! direct adds bypass this module.
10//!
11//! The `controller` crate feature is enabled by default. A supervisor still needs an explicit
12//! [`SupervisorBuilder::with_controller`](crate::SupervisorBuilder::with_controller)
13//! call before controller methods can accept work.
14//!
15//! # Quick start
16//!
17//! This example submits a job to a customer-specific lane and receives its final result through a dedicated waiter:
18//!
19//! ```rust,no_run
20//! use taskvisor::prelude::*;
21//!
22//! # #[tokio::main]
23//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
24//! let supervisor = Supervisor::builder(SupervisorConfig::default())
25//!     .with_controller(ControllerConfig::default())
26//!     .build();
27//! let handle = supervisor.serve()?;
28//!
29//! let task = TaskFn::arc(|_ctx| async { Ok(()) });
30//! let request = ControllerSpec::queue(TaskSpec::once("customer-42-job-7", task))
31//!     .with_slot("customer-42");
32//!
33//! let (_id, waiter) = handle.submit_and_watch(request).await?;
34//! println!("{:?}", waiter.wait().await?);
35//! handle.shutdown().await?;
36//! # Ok(())
37//! # }
38//! ```
39//!
40//! # Architecture
41//!
42//! ```text
43//! application
44//!      │ ControllerSpec
45//!      ▼
46//! SupervisorHandle::submit*
47//!      │ command intake
48//!      ▼
49//! controller slot
50//!      ├── idle ──► runtime registry ──► managed task
51//!      └── busy ──► queue, replace, or reject
52//! ```
53//!
54//! A slot stays occupied while its owner is being admitted, registered, or physically released.
55//! "One owner" does not mean that a task body is polling at every moment.
56//! Runtime-wide admission limits still apply after the controller selects work from a slot.
57//!
58//! # Slot, task name, and task ID
59//!
60//! These values have separate roles:
61//!
62//! - a **slot** groups work that must not overlap;
63//! - a [`TaskSpec`](crate::TaskSpec) name is a unique registry key and label;
64//! - a [`TaskId`](crate::TaskId) is the identity of one submission and outcome.
65//!
66//! The task name is the default slot. Use [`ControllerSpec::with_slot`] to put differently named
67//! tasks in one admission lane. Slot admission does not reserve a task name.
68//! The runtime registry still checks name uniqueness.
69//!
70//! Cancellation and removal never act on an entire slot. `TaskId` methods can claim queued or registered work.
71//! [`SupervisorHandle::remove_by_name`](crate::SupervisorHandle::remove_by_name) and
72//! [`SupervisorHandle::cancel_by_name`](crate::SupervisorHandle::cancel_by_name) see only work already in the
73//! registry because queued submissions do not own a registered name.
74//! Removing one queued item leaves the other submissions in its slot unchanged.
75//!
76//! # Choose a busy-slot policy
77//!
78//! - [`AdmissionPolicy::Queue`] appends to a bounded FIFO queue. Use it when every item should be considered in order.
79//! - [`AdmissionPolicy::Replace`] retires the owner and replaces the queue head. Use it when the next item should carry the newest value.
80//! - [`AdmissionPolicy::DropIfRunning`] rejects the new item without running it. Use it when duplicate work can be skipped.
81//!
82//! After preflight, every policy takes the same idle-slot path and attempts registry admission.
83//! `Replace` changes only the queue head; older FIFO entries behind it remain.
84//!
85//! # Choose a submission API
86//!
87//! - Wait for intake capacity with [`SupervisorHandle::submit`](crate::SupervisorHandle::submit).
88//! - Use [`SupervisorHandle::try_submit`](crate::SupervisorHandle::try_submit) to fail fast when intake is full.
89//! - Receive rejection or the final task result with [`SupervisorHandle::submit_and_watch`](crate::SupervisorHandle::submit_and_watch).
90//! - Fail fast and receive that result with [`SupervisorHandle::try_submit_and_watch`](crate::SupervisorHandle::try_submit_and_watch).
91//! - Allocate the `TaskId` before intake or events with [`SupervisorHandle::prepare_submission`](crate::SupervisorHandle::prepare_submission).
92//!
93//! `Ok(id)` from a submit method confirms only command intake. Slot admission and runtime registration happen later.
94//! Use a watched method when application logic must know whether work was rejected or how an admitted task ended.
95//! [`TaskWaiter`](crate::TaskWaiter) delivers that result directly; lifecycle events remain a best-effort observability path.
96//!
97//! During shutdown, buffered and controller-owned pending submissions are rejected. A watched pending submission reports
98//! [`RejectionKind::ControllerShuttingDown`](crate::RejectionKind::ControllerShuttingDown). Work already accepted by
99//! the runtime follows the normal runtime shutdown process.
100//!
101//! # Operations
102//!
103//! - [`ControllerSpec`] combines a task, slot, and admission policy.
104//! - [`PreparedSubmission`] exposes an allocated `TaskId` before intake.
105//! - [`ControllerConfig`] bounds intake, slots, pending work, and operations.
106//! - [`ControllerSnapshot`] provides a rolling operational view of slot state.
107//! - [`ControllerError`] reports failures before command intake completes.
108
109mod snapshot;
110pub use snapshot::{ControllerSnapshot, SlotStatusKind, SlotView};
111
112mod policy;
113pub use policy::AdmissionPolicy;
114
115mod config;
116pub use config::ControllerConfig;
117
118mod engine;
119pub(crate) use engine::Controller;
120
121mod error;
122pub use error::ControllerError;
123
124mod prepared;
125pub use prepared::PreparedSubmission;
126
127mod spec;
128pub use spec::ControllerSpec;