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
//! Process-wide dream concurrency bound and per-palace first-tick stagger.
//!
//! Why (#7106): the daemon spawned one dream loop per resident palace, every
//! loop slept the same `idle_secs` from the same startup instant, and nothing
//! limited how many cycles ran at once. On a host with 61 resident palaces all
//! 61 loops woke in the same second and each held its palace's whole corpus —
//! snapshot, embed inputs, and the embedder's own copies — while queued behind
//! one ONNX mutex. Live verification measured a 43 GB transient at launch+300 s
//! and an 8 GB one 300 s later. Bounding the steady state (#7115) did not touch
//! that peak, because the peak is `per-cycle working set x concurrency`.
//! What: a process-wide `tokio::sync::Semaphore` sized from
//! [`DREAM_MAX_CONCURRENT_ENV`] (default [`DEFAULT_DREAM_MAX_CONCURRENT`]) that
//! every dream cycle acquires before it runs and releases on drop — the idle
//! loops wait indefinitely, an interactive caller waits a bounded time and gets
//! [`DreamBusy`] instead of a silent hang; an
//! independent in-flight gauge so an operator (and the regression test) can see
//! how many cycles are actually running; and [`stagger_offset`], the
//! deterministic phase spread that keeps a cold start from firing every palace
//! in the same second.
//! Test: `concurrency_tests::dream_permits_cap_concurrent_holders`,
//! `concurrency_tests::ten_palaces_never_exceed_the_concurrency_cap`,
//! `concurrency_tests::stagger_offsets_spread_first_ticks_across_the_interval`.
use Arc;
use OnceLock;
use ;
use Duration;
use ;
/// Environment variable that caps how many dream cycles run at once (#7106).
///
/// Why: the right number depends on the host — a 128 GB workstation with two
/// palaces can afford more than a 16 GB laptop with sixty. An env var keeps
/// that tunable without a recompile, matching `TRUSTY_MEMORY_REDB_CACHE_MB`
/// and `TRUSTY_MEMORY_STARTUP_OPEN_LIMIT`.
/// What: read once, at the first dream cycle or scheduler start, by
/// [`dream_max_concurrent`]. Unset, empty, `0`, or unparsable keeps the
/// default and logs a `warn` naming the rejected value.
/// Test: `parse_max_concurrent_rejects_junk_and_zero`.
pub const DREAM_MAX_CONCURRENT_ENV: &str = "TRUSTY_DREAM_MAX_CONCURRENT";
/// Dream cycles allowed to run concurrently when nothing overrides it.
///
/// Why: two keeps one slow palace from stalling every other palace's
/// consolidation while holding the transient peak to twice one cycle's working
/// set rather than sixty-one times it.
pub const DEFAULT_DREAM_MAX_CONCURRENT: usize = 2;
/// The semaphore and its permit count, resolved once per process.
static DREAM_LIMIT: = new;
/// Dream cycles currently holding a permit and running.
static IN_FLIGHT: AtomicUsize = new;
/// High-water mark of [`IN_FLIGHT`] over this process's lifetime.
static PEAK_IN_FLIGHT: AtomicUsize = new;
/// Interpret the raw [`DREAM_MAX_CONCURRENT_ENV`] value.
///
/// Why: the parse is the whole policy — which spellings mean "use the default"
/// and which are honoured — so it is a pure function the tests can pin without
/// mutating the process environment.
/// What: a positive decimal integer is honoured verbatim. `None`, empty, `0`,
/// and anything unparsable answer [`DEFAULT_DREAM_MAX_CONCURRENT`]; the two
/// cases where the operator clearly meant something warn first. Never returns
/// `0` — an unbounded fan-out is what this module exists to prevent.
/// Test: `parse_max_concurrent_rejects_junk_and_zero`.
pub
/// The semaphore and permit count for this process, initialising on first use.
/// How many dream cycles this process permits to run at once.
///
/// Why: the scheduler logs it at startup so an operator can tell from the log
/// alone whether an override took effect.
/// What: the resolved [`DREAM_MAX_CONCURRENT_ENV`] value, read once per
/// process. Always at least 1.
/// Test: `the_default_cap_is_two`.
/// Dream cycles currently running.
///
/// Why: an operator looking at a memory spike wants to know whether the dreamer
/// is the cause, and the doctor has no other way to see it. Reading the
/// semaphore's free permits cannot answer this — it stays full when nothing
/// acquires — so the gauge is kept independently of the permit.
/// What: relaxed load of a process-wide counter that every cycle increments on
/// entry and decrements on drop.
/// Test: `the_in_flight_gauge_counts_a_held_cycle`.
/// The most dream cycles that have ever run at once in this process.
///
/// Why: the transient this module bounds lasts seconds, so a poll of
/// [`dream_cycles_in_flight`] almost always misses it. The high-water mark is
/// the only reading that survives long enough to be checked.
/// What: monotonic; never decreases. `0` before the first cycle.
/// Test: `the_in_flight_gauge_counts_a_held_cycle`.
/// A held slot in the process-wide dream concurrency bound.
///
/// Why: the release must happen on every arm a cycle can leave by — an early
/// `?`, a `bail!`, a panic — and the only way to get that for free is to tie it
/// to a value's lifetime. A permit returned by value and dropped by the
/// compiler cannot leak the way a paired acquire/release call can.
/// What: wraps an [`OwnedSemaphorePermit`]; dropping it returns the permit.
/// Test: `dream_permits_cap_concurrent_holders`.
/// Wait for a slot in the process-wide dream concurrency bound.
///
/// Why (#7106): every path that runs a dream cycle — the idle scheduler loop
/// and the on-demand `palace_dream` / `dream_consolidate_room` MCP tools — must
/// queue behind the same bound, or the bound is only advisory.
/// What: acquires one permit from the shared semaphore, waiting when all are
/// held. The returned [`DreamPermit`] releases on drop.
/// Test: `dream_permits_cap_concurrent_holders`,
/// `a_dream_cycle_waits_when_every_permit_is_held`.
pub async
/// Every dream slot was busy for the whole of an interactive caller's wait.
///
/// Why (#7106): the on-demand `palace_dream` / `dream_consolidate_room` tools
/// share the bound with the idle loops, and a semantic-consolidation call alone
/// is bounded at 120 s per palace, so a caller can sit behind minutes of work
/// it cannot see. An error naming the cap and the live count is something the
/// caller can retry on or report; an unbounded await is not.
/// What: carries the cap, the cycles in flight when the wait expired, and how
/// long the caller waited.
/// Test: `an_interactive_dream_errors_when_the_dreamer_is_busy`.
/// Wait at most `wait` for a slot in the process-wide dream bound.
///
/// Why (#7106): [`acquire_dream_permit`] is right for the idle loop, which
/// nothing is waiting on and which may queue indefinitely. It is wrong for a
/// user-invoked dream: before this bound existed that path had no shared gate
/// at all, so an unbounded wait here would turn a working interactive call into
/// a silent hang behind two slow idle cycles.
/// What: races the acquire against `wait`; returns [`DreamBusy`] naming the cap
/// and the live count when the wait expires. The permit, when granted, releases
/// on drop exactly as [`acquire_dream_permit`]'s does.
/// Test: `an_interactive_dream_errors_when_the_dreamer_is_busy`.
pub async
/// The process-wide dream semaphore.
///
/// Why: exposed so a caller that needs to hold several slots, or to observe the
/// bound, does not construct a second semaphore and silently opt out of it.
/// What: a clone of the `Arc` initialised on first use from
/// [`DREAM_MAX_CONCURRENT_ENV`].
/// Test: `dream_permits_cap_concurrent_holders`.
/// RAII counter for [`dream_cycles_in_flight`] / [`dream_cycles_peak_in_flight`].
///
/// Why: kept separate from [`DreamPermit`] on purpose. If the gauge rode on the
/// permit it would read zero the moment the permit was removed, and the
/// regression test that proves the cap holds would pass against code with no
/// cap at all.
/// What: increments on [`Self::enter`], updates the peak, decrements on drop.
/// Test: `the_in_flight_gauge_counts_a_held_cycle`.
/// Extra delay before palace `index` of `total` takes its first dream tick.
///
/// Why (#7106): every loop slept the same `idle_secs` from the same startup
/// instant, so `idle_secs` after launch all of them woke together. Spreading
/// the first tick spreads every later tick too, because each loop keeps ticking
/// at the same `idle_secs` cadence from wherever its first tick landed.
/// What: `interval * index / total` — palace `k` of `n` waits
/// `interval + interval * k / n` before its first cycle and `interval` between
/// every cycle after that. Deterministic (no RNG, so a restart reproduces the
/// same schedule) and `Duration::ZERO` for a single palace or an out-of-range
/// index. Which palace gets which slot is the caller's `index`, and the
/// scheduler's comes from `PalaceRegistry::list()` — LRU-recency order, not a
/// stable one. That only decides who goes first; the spread is the point.
/// Test: `stagger_offsets_spread_first_ticks_across_the_interval`.