1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! A hardware-aware work scheduler: key-affine dispatch and CPU-bound offload,
//! hosted on the async runtime you already use.
//!
//! Work carries an affine key. Every item for one key is handled by one shard,
//! in submission order, one at a time, so the state behind that key needs no
//! locking and no atomics: while an item is being processed it holds the only
//! copy. Shards are pinned to cores chosen from the machine's own topology.
//!
//! Grommet does not drive futures. Polling IO, waking a socket and owning the
//! thread belong to a host runtime, selected by a Cargo feature: `driver-tokio`
//! gives each shard thread its own current-thread tokio runtime. A processor
//! keeps using the database and HTTP clients it already has, and grommet adds
//! affinity, fairness and offload above them.
//!
//! Within a shard, keys are dispatched round-robin from per-class ready rings,
//! which bounds starvation strictly rather than statistically: a key at
//! position `k` runs within `k` dispatches, no matter how much work a busier
//! key has queued. Each class has its own in-flight budget, so saturating one
//! class: CPU-bound work, a slow dependency: cannot starve another.
//!
//! # What you provide
//!
//! - [`Work`]: an item, its affine [`ShardKey`], its class, and optionally how
//! long it stays worth doing.
//! - [`Processor`]: what to do with an item, given the key's resident state.
//! - Optionally an [`Offload`] pool for CPU-bound work, so a long computation
//! never stalls the shard core that submitted it.
//!
//! ```no_run
//! use std::convert::Infallible;
//! use std::time::Duration;
//! use grommet::{Call, ClassId, Disposition, IO, Processor, Scheduler, Work};
//!
//! struct Job { account: u64, amount: i64, attempt: u128 }
//!
//! impl Work for Job {
//! type Key = u64;
//! type Id = u128;
//! fn key(&self) -> u64 { self.account }
//! fn class(&self) -> ClassId { IO }
//! fn request_id(&self) -> Option<u128> { Some(self.attempt) }
//! fn time_to_live(&self) -> Option<Duration> { Some(Duration::from_millis(50)) }
//! }
//!
//! #[derive(Clone)]
//! struct Ledger;
//!
//! impl Processor for Ledger {
//! // Wrapping in `Call` attaches a reply channel to each item.
//! type Work = Call<Job, i64>;
//! type State = i64;
//! type Error = Infallible;
//!
//! async fn process(
//! &self,
//! _key: u64,
//! balance: Option<i64>,
//! call: Call<Job, i64>,
//! ) -> Result<Disposition<i64>, Infallible> {
//! let (job, responder) = call.into_parts();
//! let balance = balance.unwrap_or(0) + job.amount;
//! responder.send(balance);
//! Ok(Disposition::Keep(balance))
//! }
//! }
//!
//! # async fn run() {
//! // The clock defaults to `SystemClock` and the class count to the IO +
//! // COMPUTE split, so the common case names neither.
//! let runtime = Scheduler::<Ledger>::builder(4, [2048, 64])
//! .spawn(|_shard| Ledger)
//! .expect("start shards");
//!
//! let balance = runtime.router().call(Job { account: 7, amount: 100, attempt: 1 }).await;
//! # }
//! ```
//!
//! # Replies are opt-in
//!
//! Submission itself is one-way: it reports whether work was accepted, not what
//! it produced. Wrapping work in a [`Call`] as above adds a reply channel and
//! gives you [`Router::call`], which is what most request/response services
//! want.
//!
//! It is a wrapper rather than a built-in because a reply channel costs a heap
//! allocation and two atomics per item, and plenty of workloads have no caller
//! to answer: ingestion and feed handling, processors that reply by writing to
//! their own socket, and anything that wants to answer a batch of items with a
//! single syscall. Those keep the steady state allocation-free by submitting
//! plain [`Work`].
//!
//! # `!Send` on purpose
//!
//! Work is `Send`, because it crosses once from the submitter to its shard.
//! Nothing after that is: per-key state, processor futures and anything held
//! across an await stay on one core. That is what makes `Rc` and `Cell` correct
//! here, and it is also a real constraint: code written against `Send` futures
//! and work stealing will not fit. If you want that, use an ordinary
//! multi-threaded executor; this crate is deliberately the other thing.
pub
pub
pub
pub
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ShardConfig;
pub use ;
pub use ;
pub use ;