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
//! Resource ceilings applied to every query the projected read path runs.
//!
//! [`QueryLimits`] is the one resource-ceilings struct this crate exports:
//! `polyc-query-service` builds it from deployment configuration and passes
//! it through [`crate::core_service::ProjectedCoreComposition`]. Every
//! field is one the projected path reads:
//!
//! - `row_cap` and `timeout` bound every admitted statement when
//! `crate::core_resolution` mints the effective per-request bounds;
//! both are build constants today — the deployment sets neither from
//! configuration, and no environment key feeds either (POLY-374).
//! - `spill_dir` and `spill_quota_bytes` configure the execution
//! `RuntimeEnv` `crate::core_service` builds at composition.
//!
//! The execution memory pool is NOT one of these fields: it is sized from
//! `polyc-query-service`'s own policy (`POLYCHROME_QUERY_MEMORY_BYTES`),
//! which is why this struct carries no `memory_bytes`.
use PathBuf;
use Duration;
/// Resource ceilings applied to every query the projected read path runs.
///
/// Every query enforces a hard wall-clock timeout and a row cap whose
/// truncation is flagged in the response — [`QueryLimits::timeout`] and
/// [`QueryLimits::row_cap`], both consulted when `crate::core_resolution`
/// mints the effective bounds for one request. [`QueryLimits::spill_dir`]
/// and [`QueryLimits::spill_quota_bytes`] configure where and how much
/// `DataFusion`'s own execution-time spill is allowed to write.
///
/// Not `Copy` — [`QueryLimits::spill_dir`] owns a [`PathBuf`] — so a caller
/// holding one behind a shared reference clones it explicitly where an
/// owned copy is needed rather than relying on an implicit bitwise copy.
/// Default [`QueryLimits::spill_dir`] directory name, created directly under
/// [`std::env::temp_dir`] — portable, always-writable, no Kubernetes
/// assumption. See that field's own doc for why a real deployment overrides
/// this through its own config layer instead of relying on it as-is.
const DEFAULT_QUERY_SPILL_DIR_NAME: &str = "polychrome-query-spill";
/// [`DEFAULT_QUERY_SPILL_DIR_NAME`], suffixed with this process's id.
///
/// `datafusion-execution`'s `disk_manager.rs::create_local_dirs` (the
/// function [`QueryLimits::spill_dir`]'s own doc names as creating this
/// directory eagerly) is `if !root.exists() { std::fs::create_dir(root)? }`
/// — a check-then-create with no atomicity between the two. A BARE
/// `DEFAULT_QUERY_SPILL_DIR_NAME` under `std::env::temp_dir()` is one fixed
/// path shared by every consumer that takes this default, and this crate's
/// own test suite alone constructs a `QueryLimits` from many
/// `#[tokio::test]`s. `cargo nextest` runs each as its OWN process, so two
/// tests racing to compose a service at the same instant can both observe
/// the shared path absent, then have one process's `create_dir` fail with
/// `AlreadyExists` — a nondeterministic startup refusal. Per-process
/// uniqueness removes the shared path two DIFFERENT, concurrently-running
/// processes could ever race on — the failure mode this exists to prevent —
/// without changing the "portable, always-writable, no Kubernetes
/// assumption" contract the field's own doc promises: a process id is
/// exactly as portable as the bare name it replaces.
/// Default [`QueryLimits::spill_quota_bytes`] — 16 GiB, a generic backstop
/// sized for [`DEFAULT_QUERY_SPILL_DIR_NAME`]'s own portable default, not a
/// Kubernetes deployment's tier-ordered quota. See that field's own doc for
/// where the deployment-specific value lives instead.
const DEFAULT_QUERY_SPILL_QUOTA_BYTES: u64 = 16 * 1024 * 1024 * 1024;
/// Owns [`QueryLimits::spill_dir`] for the life of the
/// [`crate::core_service::ProjectedCoreService`] that built its `RuntimeEnv`,
/// and removes it on drop.
///
/// `RuntimeEnvBuilder::build_arc` creates `spill_dir` eagerly at composition
/// (see [`QueryLimits::spill_dir`]'s own doc). `DataFusion`'s `DiskManager`
/// never removes its own working directory afterward. Nothing else reclaims
/// it either. A Kubernetes deployment points `spill_dir` at a dedicated
/// `emptyDir`, which the pod lifecycle cleans up on its own. The portable
/// default above has no such reclaim. One `ProjectedCoreService` is composed
/// once per process, and normally held for the process's whole life. So this
/// guard's drop lines up with process shutdown — `polychrome stop`, a
/// graceful restart — rather than firing mid-run (POLY-314).
///
/// Like every `Drop`-based guard, this only runs on a graceful shutdown
/// path. It does nothing against `SIGKILL`. But that covers the common case
/// a leaked spill directory is actually seen in.
pub ;