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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Single-threaded, thread-aware application runtime.
//!
//! An Arty runtime can have several workers, but each async task stays on one
//! worker throughout its life. This lets it use thread-local and non-[`Send`]
//! state across awaits. Thread-aware values can be explicitly relocated when
//! starting new work on another worker.
//!
//! There is no process-global runtime: each instance owns its workers and
//! services. Workers use thread-local bookkeeping rather than a global
//! runtime singleton.
//!
//! Arty provides task scheduling, clocks, telemetry, and pools for offloading
//! blocking callbacks. It does not provide async I/O drivers or move running
//! tasks between workers.
//!
//! # Quickstart
//!
//! Add Arty with its default runtime and macro features:
//!
//! ```sh
//! cargo add arty
//! ```
//!
//! ```rust
//! # #[cfg(all(feature = "macros", feature = "rt"))]
//! use arty::task::{Builtins, JoinError};
//!
//! # #[cfg(all(feature = "macros", feature = "rt"))]
//! #[arty::main]
//! async fn main(cx: Builtins) -> Result<(), JoinError> {
//! let answer = cx.scheduler().spawn(async |_| 6 * 7).await?;
//! # assert_eq!(answer, 42);
//! println!("{answer}");
//! Ok(())
//! }
//! # #[cfg(not(all(feature = "macros", feature = "rt")))] fn main() {}
//! ```
//!
//! This prints `42`. [`main`] starts and stops the runtime.
//! The `?` returns a [`JoinError`](crate::task::JoinError) if the child fails.
//! Await child work before returning; shutdown cancels pending async tasks.
//!
//! # Overview
//!
//! - [`main`] and [`test`] manage a runtime for an
//! async entry point or test; [`Runtime`](crate::runtime::Runtime) gives you
//! direct control over its lifetime and settings.
//! - [`Builtins`](crate::task::Builtins) gives each task its scheduler, clock,
//! and worker. [`RuntimeScheduler`](crate::task::RuntimeScheduler) lets the
//! runtime place new work.
//! - [`Scheduler`](crate::task::Scheduler) keeps child work on its worker.
//! - [`Clock`](crate::time::Clock) provides timers and timeouts;
//! [`ClockControl`](crate::time::ClockControl) controls time in tests.
//! - [`Thread`](core::Thread) describes a worker, and
//! [`ThreadAware`](core::ThreadAware) values can relocate between workers.
//!
//! # Why Arty?
//!
//! Unlike Tokio's multi-thread runtime, Arty keeps each task on one worker.
//! This supports thread-local and non-`Send` state, keeps worker-local data
//! nearby, and can reduce contention for that data. Shared data may still contend.
//! Choose Tokio for automatic task distribution or its async I/O ecosystem;
//! Arty does not provide Tokio's I/O or timer drivers.
//!
//! # Blocking work
//!
//! Join handles are futures: await them. Arty deliberately does not expose a
//! synchronous join operation because async workers must stay free to poll
//! tasks and advance timers. Blocking a worker stalls every task assigned to
//! it. If the blocked code waits for work on that worker, or blocking callbacks
//! wait on each other in an exhausted pool, the stall can become a deadlock.
//!
//! This deadlocks: the worker waits synchronously for a child that only the
//! same worker can poll.
//!
//! ```no_run
//! # #[cfg(all(feature = "macros", feature = "rt"))]
//! #[arty::main]
//! async fn main(cx: arty::task::Builtins) {
//! let child = cx.scheduler().spawn(async |_| 42);
//! let _ = futures::executor::block_on(child);
//! }
//! # #[cfg(not(all(feature = "macros", feature = "rt")))] fn main() {}
//! ```
//!
//! [`RuntimeScheduler::block_on`](crate::task::RuntimeScheduler::block_on) is
//! the narrow blocking entry point for running async work from synchronous
//! code. It rejects calls from async workers. For synchronous I/O or library
//! calls, use
//! [`Scheduler::spawn_blocking`](crate::task::Scheduler::spawn_blocking) or
//! [`RuntimeScheduler::spawn_blocking`](crate::task::RuntimeScheduler::spawn_blocking)
//! and await the join. The callback runs in a blocking pool instead of on an
//! async worker.
//!
//! # Detailed documentation
//!
//! The [guides](crate::documentation) explain Arty's capabilities in more detail:
//!
//! - [Scheduling](crate::documentation::scheduling) explains where tasks run,
//! local non-`Send` work, and blocking pools.
//! - [Configuration](crate::documentation::configuration) explains worker counts,
//! blocking-pool policies, clocks, and telemetry sinks.
//! - [Shutdown](crate::documentation::shutdown) explains runtime ownership,
//! task cancellation, and what stopping the workers waits for.
//! - [Thread awareness](crate::documentation::thread_awareness) explains
//! stable task placement and explicit relocation of values.
//! - [Time](crate::documentation::time) explains worker-driven timers, timeouts,
//! and controlled time in tests.
//! - [Telemetry](crate::documentation::telemetry) explains runtime events
//! and links to [`observed`] for further details.
//!
//! The `documentation` module is included only for docs.rs builds and
//! doc-test collection with all documentation features, including `test-util`;
//! it is not part of the public API available to applications.
//!
//! # Features
//!
//! The default feature set enables `rt` and `macros`.
//!
//! - **`rt`** - Enables the runtime and task APIs, and implies `time`.
//! - **`macros`** - Enables [`main`] and [`test`], and implies `rt`.
//! - **`time`** - Enables clocks, timers, and timeouts.
//! - **`test-util`** - Enables testing utilities, including [`ClockControl`](crate::time::ClockControl) with `time`.
//! Enable it in dev-dependencies, not production dependencies.
use arty_io_core as _;
/// Runs an async entry point on an Arty runtime.
///
/// Apply it to an `async fn` taking one owned
/// [`Builtins`](crate::task::Builtins) argument. It generates a synchronous
/// entry point with no arguments and the same visibility and return type.
/// The async body runs on an Arty worker, not on the calling thread. Name the
/// argument `cx` or `_cx`, for example; `_` and destructuring patterns are not supported.
///
/// The runtime stops when the body returns. Await child tasks you need first;
/// shutdown cancels pending async work.
///
/// Enable the `macros` feature to use this attribute.
///
/// # Configuration
///
/// Without options, the attribute uses
/// [`Runtime::new`](crate::runtime::Runtime::new). The optional configuration is:
///
/// - `workers = N` caps the number of async workers. Use an integer literal
/// that fits `usize` (an explicit `usize` suffix is fine). If fewer processors
/// are available, Arty starts fewer workers; zero fails at construction.
///
/// A worker limit does not limit blocking-pool threads. Without a limit, the
/// runtime uses [`WorkersPolicy::default`](crate::runtime::WorkersPolicy::default).
///
/// Use explicit [`RuntimeBuilder`](crate::runtime::RuntimeBuilder) construction
/// instead of the attribute when clocks, telemetry, blocking pools, stack size,
/// or an exact/all-workers policy must be configured.
///
/// # Panics
///
/// Runtime construction errors, root-task cancellation, and shutdown errors
/// become panics. The runtime is stopped before returning the body's value or
/// resuming its original panic on the calling thread. A root-task failure takes
/// precedence if shutdown also fails.
///
/// An application error returned by the body remains its return value; it is
/// not converted to a panic. Use [`RuntimeBuilder::build`](crate::runtime::RuntimeBuilder::build),
/// [`RuntimeScheduler::block_on`](crate::task::RuntimeScheduler::block_on), and
/// [`Runtime::stop`](crate::runtime::Runtime::stop) directly to handle returned
/// runtime errors. Worker-thread creation and initialization can still panic.
///
/// # Examples
///
/// Wait briefly and print a message:
///
/// ```
/// # #[cfg(all(feature = "macros", feature = "rt"))]
/// # #[arty::main]
/// # async fn main(cx: arty::task::Builtins) {
/// cx.clock().delay(std::time::Duration::from_millis(1)).await;
/// println!("Hello from Arty!");
/// # }
/// # #[cfg(not(all(feature = "macros", feature = "rt")))] fn main() {}
/// ```
///
/// Cap the async worker count:
///
/// ```
/// # #[cfg(all(feature = "macros", feature = "rt"))]
/// #[arty::main(workers = 4)]
/// async fn main(cx: arty::task::Builtins) -> Result<(), arty::task::JoinError> {
/// let answer = cx.scheduler().spawn(async |_| 42).await?;
/// assert_eq!(answer, 42);
/// Ok(())
/// }
/// # #[cfg(not(all(feature = "macros", feature = "rt")))] fn main() {}
/// ```
pub use main;
/// Runs an async test on an Arty runtime.
///
/// Apply it to an `async fn` taking one owned
/// [`Builtins`](crate::task::Builtins) argument. It registers a synchronous
/// test that starts its own runtime, runs the body on a worker, and stops the
/// runtime when the body returns.
/// Name the argument `_cx` if it is unused.
///
/// The return value, visibility, and attributes such as `#[should_panic]`
/// and `#[ignore]` are preserved. An ignored test starts no runtime unless
/// the test harness runs it.
///
/// Enable `macros` to use this attribute. The [`main`] attribute's `workers`
/// option also applies. By default, a test uses one worker.
///
/// # Simulated time
///
/// Enable `test-util` in an Arty dev-dependency and add a second owned
/// `arty::time::ClockControl` argument. This gives the test control over its
/// runtime's clocks. Time starts at the UNIX epoch and does not advance
/// automatically; cloned controls share that time. Use different names for
/// the two arguments. Type aliases and associated types are supported.
///
/// Use `control.advance(duration)` to move time forward. Poll a delay before
/// advancing: it starts its timer on the first poll. For sequential delays,
/// `auto_advance_timers(true)` advances time eagerly; it does not wait for an
/// idle runtime or guarantee the order of concurrent timers.
///
/// The control argument works with `workers`. To use a custom runtime builder
/// with controlled time, create the control in a synchronous test and call
/// `builder.clock(control.clone()).build()`. Enabling `test-util` alone does
/// not change a one-argument test's clock.
///
/// # Panics
///
/// Runtime construction errors, test-body cancellation, and shutdown errors
/// become panics. The runtime is stopped before reporting the test's outcome.
/// A panic in the body takes precedence over a shutdown error and is resumed
/// with its original payload, so
/// `#[should_panic(expected = "...")]` can match the original message.
///
/// An application error returned by the body remains its return value. Use
/// explicit runtime construction, [`RuntimeScheduler::block_on`](crate::task::RuntimeScheduler::block_on),
/// and [`Runtime::stop`](crate::runtime::Runtime::stop) when runtime errors
/// should be handled without the attribute converting them to panics.
///
/// # Examples
///
/// Await a task and check its result:
///
/// ```test_harness
/// # #[cfg(all(feature = "macros", feature = "rt"))]
/// #[arty::test]
/// async fn answer(cx: arty::task::Builtins) -> Result<(), arty::task::JoinError> {
/// assert_eq!(cx.scheduler().spawn(async |_| 42).await?, 42);
/// Ok(())
/// }
/// ```
///
/// Test a sequential delay without waiting for real time:
///
/// ```test_harness
/// # #[cfg(all(feature = "macros", feature = "rt", feature = "test-util"))]
/// #[arty::test]
/// async fn simulated_delay(cx: arty::task::Builtins, control: arty::time::ClockControl) {
/// use std::time::Duration;
///
/// let control = control.auto_advance_timers(true);
/// let watch = cx.clock().stopwatch();
/// cx.clock().delay(Duration::from_secs(30)).await;
/// assert_eq!(watch.elapsed(), Duration::from_secs(30));
///
/// control.advance(Duration::from_secs(5));
/// assert_eq!(watch.elapsed(), Duration::from_secs(35));
/// }
/// ```
pub use test;
/// Worker coordinates and thread-aware relocation.
///
/// [`Thread`](crate::core::Thread) records a runtime [`Owner`](crate::core::Owner),
/// an OS thread, and its [`NumaNode`](crate::core::NumaNode). It describes
/// where a value belongs; it does not own that thread or keep its runtime running.
///
/// Implement [`ThreadAware`](crate::core::ThreadAware) when a value needs to
/// update worker-bound state after relocation. Relocation does not move a
/// running task or change the OS thread executing your code.
/// Runtime clocks and controlled test time.
///
/// Enable `time` to use [`Clock`](crate::time::Clock) without starting an Arty
/// runtime. A task's `Builtins::clock()` has timers driven by its worker.
/// Applications that need timer future types or extension traits should use
/// the underlying [`tick`] API directly.
///
/// Enable `test-util` in dev-dependencies to use `ClockControl` for simulated time.
///
/// # Examples
///
/// With `rt` and `macros` enabled, await a delay on the task's clock:
///
/// ```
/// # #[cfg(all(feature = "macros", feature = "rt"))]
/// # #[arty::main]
/// # async fn main(cx: arty::task::Builtins) {
/// let duration = std::time::Duration::from_millis(1);
/// let watch = cx.clock().stopwatch();
/// cx.clock().delay(duration).await;
/// assert!(watch.elapsed() >= duration);
/// # }
/// # #[cfg(not(all(feature = "macros", feature = "rt")))] fn main() {}
/// ```
init_tracing!;