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
//! `std.task` — structured async task primitives for Lua scripts.
//!
//! # API surface
//!
//! - `std.task.spawn(fn, opts?)` — fire-and-forget child, returns `Handle`
//! - `std.task.sleep(ms)` / `std.task.yield()` — cancel-aware suspension
//! - `std.task.checkpoint()` — bare cancel yield point
//! - `std.task.scope(name?, fn)` — structured nursery
//! - `std.task.with_timeout(ms, fn, opts?)` — scope with deadline
//! - `std.task.cancel_token()` — standalone `CancelToken`
//! - `std.task.current()` — `{id, name, cancelled}`
//! - `Scope:spawn`, `:cancel`, `:token`, `.name`
//! - `Handle:join`, `:abort`, `:is_finished`, `:elapsed`, `.id`, `.name`
//! - `CancelToken:cancel`, `:is_cancelled`, `:check`
//!
//! # Structured concurrency
//!
//! `task.scope(fn)` creates a `Scope`, installs it as the task-local
//! **current scope** (`LOCAL_SCOPE`) for the duration of `fn(scope)`, and
//! — regardless of how `fn` exits — waits for every task spawned into that
//! scope to finish before returning. On error the scope's `CancelToken`
//! is set so cooperative children unwind; `scope` itself performs **no
//! hard abort** (matches Trio / Swift `TaskGroup` / Kotlin
//! `coroutineScope` / tokio-util `TaskTracker`). A non-cooperative child
//! (never reaching a cancel checkpoint) therefore blocks the scope
//! indefinitely — the caller is expected to wrap with `task.with_timeout`
//! to bound teardown. Top-level `task.spawn` attaches to the VM root
//! scope when no scope is installed.
//!
//! `LOCAL_SCOPE` is propagated via `tokio::task_local!` rather than a
//! shared VM-wide stack so a grandchild spawned with `task.spawn`
//! attaches to the correct ancestor scope even when concurrent siblings
//! are running their own `task.scope` bodies across `await` points.
//!
//! # Cancellation
//!
//! Cancellation is **cooperative + level-triggered** (Trio model): every
//! `std.task.*` suspension point (`sleep`, `yield`, `checkpoint`, and the
//! `coroutine` driver's `coroutine.yield`) consults the effective cancel
//! token (see [`effective_token`]) and raises `"task cancelled"`
//! when it fires. `pcall`-swallowed cancellations reappear at the next
//! checkpoint, so cleanup code cannot accidentally suppress a cancel.
//!
//! `task.with_timeout(ms, fn, opts?)` layers a **3-stage graceful-abort**
//! pattern on top (Kubernetes / ASP.NET Core / Spring Boot):
//! 1. deadline trips → `token.cancel()`
//! 2. `drain_scope` runs under `timeout(grace_ms)` (default from
//! [`TaskConfig::grace_ms`], 1 s if the host did not override)
//! 3. any child still alive is hard-aborted via tokio `AbortHandle`
//! and a final drain reaps it
//!
//! `grace_ms = 0` yields strict/immediate-abort semantics. The scope's
//! RAII `ScopeGuard` also aborts children if the entire scope future
//! is dropped mid-await (outer timeout, VM teardown).
//!
//! # Configuration
//!
//! [`TaskConfig`] carries the runtime-tunable knobs (default driver,
//! default grace window). This crate **does not read environment
//! variables** — the host (e.g. `agent-block`) is expected to build a
//! `TaskConfig` from its own env / config sources and pass it to
//! [`register_with`]. [`register`] is the convenience entry point that
//! uses defaults.
//!
//! # Runtime contract
//!
//! Must run inside a `tokio::task::LocalSet` driven by a current-thread
//! runtime. All primitives are `!Send`; tasks share an
//! `Rc<RefCell<Scope>>` across task-locals.
//!
//! # Drivers
//!
//! - `async_fn` (default) — drives the user function via
//! `Function::call_async`, so `sleep` / `yield` / `checkpoint` suspend
//! through mlua's async bridge.
//! - `coroutine` (opt-in) — drives a raw Lua thread via `Thread::resume`
//! in a loop; `coroutine.yield()` yields cooperatively and
//! `coroutine.yield(ms)` sleeps (cancel-aware). Selected via
//! `opts.driver = "coroutine"` per-spawn or [`TaskConfig::default_driver`].
//!
//! Every task is wrapped in a `tracing::info_span!("task", id, name,
//! driver)` so downstream tool logs (sh / mesh / mcp / sql) carry task
//! context. `std.task.current()` inside a spawned task returns
//! `{id, name, cancelled}` for Lua-side introspection.
//!
//! # Module layout
//!
//! - `cancel` — `CancelToken` + `effective_token` + cancel-aware sleep/yield
//! - `scope` — `Scope` + `ScopeGuard` + `drain_scope` / `abort_all` + `ScopeHandle`
//! - `driver` — `Driver` enum + `parse_opts` + `run_coroutine` + `Handle` + `spawn_into`
//! - `api` — Lua-facing `std.task.*` callables (`spawn`, `scope`, `with_timeout`, …)
use RefCell;
use Rc;
use Duration;
use *;
use Scope;
pub use ;
pub use Driver;
/// Runtime configuration for the task bridge.
///
/// The host is responsible for constructing this (typically from its own
/// environment / config sources) and passing it to [`register_with`].
/// Defaults match the historical behaviour of the agent-block host
/// before extraction (driver = `AsyncFn`, grace = 1000 ms).
/// Lua-visible descriptor returned by `std.task.current()`. Carried via
/// the `TASK_INFO` task-local rather than threaded through the Lua function
/// signature so any frame inside a spawned task can query it.
pub
task_local!
/// Convert a Lua `ms` argument into a `Duration`, rejecting non-finite
/// (NaN / ±∞), negative, and out-of-range values. `ctx` is the caller
/// name used in the error message.
/// Register `std.task` into the given Lua VM with default configuration.
///
/// Equivalent to [`register_with`] with [`TaskConfig::default()`]. The
/// caller is expected to have already created a `std` table on the
/// globals (this matches how the host wires sibling bridges).
/// Register `std.task` with caller-provided [`TaskConfig`].
///
/// Stores the config in `lua.app_data` so the Lua-facing primitives can
/// consult it without threading it through every closure capture.
/// Read the registered [`TaskConfig`] (set by [`register_with`]). Falls
/// back to defaults if the bridge was not registered, which keeps the
/// internal helpers infallible at the cost of silently using defaults
/// when called from a misconfigured VM.
pub