1use std::error::Error;
4use std::fmt::{self, Display, Formatter};
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, AtomicUsize, Ordering};
7use std::sync::{Condvar, Mutex};
8use std::time::Instant;
9
10const NO_BEST_WORKER_COUNT: usize = usize::MAX;
11
12#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
14#[non_exhaustive]
15pub enum DecoderPath {
16 #[default]
18 Starting,
19 Sequential,
21 Stored,
23 DenseMembers,
25 MarkerAdmission,
27 MarkerWindow,
29 Bgzf,
31 IndexedParallel,
33}
34
35impl DecoderPath {
36 const fn encoded(self) -> u8 {
37 match self {
38 Self::Starting => 0,
39 Self::Sequential => 1,
40 Self::Stored => 2,
41 Self::DenseMembers => 3,
42 Self::MarkerAdmission => 4,
43 Self::MarkerWindow => 5,
44 Self::Bgzf => 6,
45 Self::IndexedParallel => 7,
46 }
47 }
48
49 const fn from_encoded(value: u8) -> Self {
50 match value {
51 1 => Self::Sequential,
52 2 => Self::Stored,
53 3 => Self::DenseMembers,
54 4 => Self::MarkerAdmission,
55 5 => Self::MarkerWindow,
56 6 => Self::Bgzf,
57 7 => Self::IndexedParallel,
58 _ => Self::Starting,
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq)]
68#[non_exhaustive]
69pub enum DecoderPressure {
70 Starting,
72 ConsumerBound {
74 idle_worker_fraction: f32,
76 },
77 DecoderBound {
79 queued_tasks: usize,
81 },
82 Converged {
84 at_workers: usize,
86 },
87 Idle,
89 Finished,
91}
92
93#[derive(Clone, Copy, Debug, PartialEq)]
99#[non_exhaustive]
100pub struct DecoderStats {
101 pub path: DecoderPath,
103 pub configured_workers: usize,
105 pub worker_limit: usize,
107 pub active_workers: usize,
112 pub busy_workers: usize,
115 pub spawned_workers: usize,
122 pub auxiliary_threads: usize,
124 pub best_workers: Option<usize>,
126 pub decompressed_bytes: u64,
128 pub consumed_bytes: u64,
130 pub member_count: u64,
132 pub decode_throughput_bps: f64,
134 pub consumer_throughput_bps: f64,
136 pub pressure: DecoderPressure,
138}
139
140#[derive(Clone, Copy, Debug, Eq, PartialEq)]
142pub struct WorkerLimitError {
143 requested: usize,
144 configured: usize,
145}
146
147impl WorkerLimitError {
148 pub const fn requested(self) -> usize {
150 self.requested
151 }
152
153 pub const fn configured(self) -> usize {
155 self.configured
156 }
157}
158
159impl Display for WorkerLimitError {
160 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
161 write!(
162 formatter,
163 "worker limit {} is outside 1..={}",
164 self.requested, self.configured
165 )
166 }
167}
168
169impl Error for WorkerLimitError {}
170
171#[derive(Clone)]
176pub struct DecoderHandle {
177 pub(crate) state: Arc<RuntimeState>,
178}
179
180impl fmt::Debug for DecoderHandle {
181 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
182 formatter
183 .debug_struct("DecoderHandle")
184 .field("stats", &self.stats())
185 .finish()
186 }
187}
188
189impl DecoderHandle {
190 pub(crate) fn new(state: Arc<RuntimeState>) -> Self {
191 Self { state }
192 }
193
194 pub fn stats(&self) -> DecoderStats {
196 self.state.stats()
197 }
198
199 pub fn set_worker_limit(&self, workers: usize) -> Result<(), WorkerLimitError> {
213 self.state.set_worker_limit(workers)
214 }
215}
216
217#[derive(Clone, Copy, Debug, Eq, PartialEq)]
218pub(crate) enum AuxiliaryKind {
219 Coordinator,
220 Scanner,
221}
222
223pub(crate) struct ThreadRegistration {
224 state: Arc<RuntimeState>,
225 auxiliary: bool,
226}
227
228impl Drop for ThreadRegistration {
229 fn drop(&mut self) {
230 let counter = if self.auxiliary {
231 &self.state.auxiliary_threads
232 } else {
233 &self.state.spawned_workers
234 };
235 counter.fetch_sub(1, Ordering::Relaxed);
236 }
237}
238
239pub(crate) struct BusyRegistration<'a>(&'a RuntimeState);
240
241impl Drop for BusyRegistration<'_> {
242 fn drop(&mut self) {
243 self.0.busy_workers.fetch_sub(1, Ordering::Relaxed);
244 }
245}
246
247pub(crate) struct RuntimeState {
249 configured_workers: usize,
250 worker_limit: AtomicUsize,
251 adaptive_target: AtomicUsize,
252 limit_epoch: AtomicUsize,
253 path: AtomicU8,
254 busy_workers: AtomicUsize,
255 spawned_workers: AtomicUsize,
256 auxiliary_threads: AtomicUsize,
257 queued_tasks: AtomicUsize,
258 best_workers: AtomicUsize,
259 decompressed_bytes: AtomicU64,
260 consumed_bytes: AtomicU64,
261 member_count: AtomicU64,
262 consumer_blocked: AtomicBool,
263 terminal: AtomicBool,
264 terminal_elapsed_nanos: AtomicU64,
265 started: Instant,
266 limit_mutex: Mutex<()>,
267 limit_signal: Condvar,
268}
269
270impl RuntimeState {
271 pub(crate) fn new(configured_workers: usize) -> Arc<Self> {
272 Arc::new(Self {
273 configured_workers,
274 worker_limit: AtomicUsize::new(configured_workers),
275 adaptive_target: AtomicUsize::new(1),
276 limit_epoch: AtomicUsize::new(0),
277 path: AtomicU8::new(DecoderPath::Starting.encoded()),
278 busy_workers: AtomicUsize::new(0),
279 spawned_workers: AtomicUsize::new(0),
280 auxiliary_threads: AtomicUsize::new(0),
281 queued_tasks: AtomicUsize::new(0),
282 best_workers: AtomicUsize::new(NO_BEST_WORKER_COUNT),
283 decompressed_bytes: AtomicU64::new(0),
284 consumed_bytes: AtomicU64::new(0),
285 member_count: AtomicU64::new(0),
286 consumer_blocked: AtomicBool::new(false),
287 terminal: AtomicBool::new(false),
288 terminal_elapsed_nanos: AtomicU64::new(0),
289 started: Instant::now(),
290 limit_mutex: Mutex::new(()),
291 limit_signal: Condvar::new(),
292 })
293 }
294
295 fn set_worker_limit(&self, workers: usize) -> Result<(), WorkerLimitError> {
296 if workers == 0 || workers > self.configured_workers {
297 return Err(WorkerLimitError {
298 requested: workers,
299 configured: self.configured_workers,
300 });
301 }
302 let previous = self.worker_limit.swap(workers, Ordering::Relaxed);
303 if previous != workers {
304 self.limit_epoch.fetch_add(1, Ordering::Relaxed);
305 self.limit_signal.notify_all();
306 }
307 Ok(())
308 }
309
310 pub(crate) fn limit_epoch(&self) -> usize {
311 self.limit_epoch.load(Ordering::Relaxed)
312 }
313
314 pub(crate) fn application_worker_limit(&self) -> usize {
316 self.worker_limit.load(Ordering::Relaxed)
317 }
318
319 pub(crate) fn set_adaptive_target(&self, workers: usize) {
320 let workers = workers.clamp(1, self.configured_workers);
321 let previous = self.adaptive_target.swap(workers, Ordering::Relaxed);
322 if previous != workers {
323 self.limit_signal.notify_all();
324 }
325 }
326
327 pub(crate) fn effective_worker_limit(&self) -> usize {
328 let adaptive = self.adaptive_target.load(Ordering::Relaxed);
329 let requested = self.worker_limit.load(Ordering::Relaxed);
330 if self.consumer_blocked.load(Ordering::Relaxed) {
331 1
332 } else {
333 adaptive.min(requested).max(1)
334 }
335 }
336
337 pub(crate) fn wait_for_limit_change(&self, timeout: std::time::Duration) {
338 let guard = self
339 .limit_mutex
340 .lock()
341 .expect("runtime limit mutex poisoned");
342 let _guard = self
343 .limit_signal
344 .wait_timeout(guard, timeout)
345 .expect("runtime limit mutex poisoned");
346 }
347
348 pub(crate) fn notify_limit_waiters(&self) {
349 self.limit_signal.notify_all();
350 }
351
352 pub(crate) fn set_path(&self, path: DecoderPath) {
353 self.path.store(path.encoded(), Ordering::Relaxed);
354 }
355
356 pub(crate) fn register_worker(self: &Arc<Self>) -> ThreadRegistration {
357 self.spawned_workers.fetch_add(1, Ordering::Relaxed);
358 ThreadRegistration {
359 state: Arc::clone(self),
360 auxiliary: false,
361 }
362 }
363
364 pub(crate) fn register_auxiliary(self: &Arc<Self>, _kind: AuxiliaryKind) -> ThreadRegistration {
365 self.auxiliary_threads.fetch_add(1, Ordering::Relaxed);
366 ThreadRegistration {
367 state: Arc::clone(self),
368 auxiliary: true,
369 }
370 }
371
372 pub(crate) fn begin_task(&self) -> BusyRegistration<'_> {
373 self.busy_workers.fetch_add(1, Ordering::Relaxed);
374 BusyRegistration(self)
375 }
376
377 pub(crate) fn set_queued_tasks(&self, count: usize) {
378 self.queued_tasks.store(count, Ordering::Relaxed);
379 }
380
381 pub(crate) fn set_best_workers(&self, workers: Option<usize>) {
382 self.best_workers
383 .store(workers.unwrap_or(NO_BEST_WORKER_COUNT), Ordering::Relaxed);
384 }
385
386 pub(crate) fn add_decompressed_bytes(&self, count: usize) {
387 self.decompressed_bytes
388 .fetch_add(count as u64, Ordering::Relaxed);
389 }
390
391 pub(crate) fn add_consumed_bytes(&self, count: usize) {
392 self.consumed_bytes
393 .fetch_add(count as u64, Ordering::Relaxed);
394 }
395
396 pub(crate) fn set_member_count(&self, count: u64) {
397 self.member_count.store(count, Ordering::Relaxed);
398 }
399
400 pub(crate) fn set_consumer_blocked(&self, blocked: bool) {
401 let previous = self.consumer_blocked.swap(blocked, Ordering::Relaxed);
402 if previous != blocked {
403 self.limit_signal.notify_all();
404 }
405 }
406
407 pub(crate) fn mark_terminal(&self) {
408 let elapsed_nanos = u64::try_from(self.started.elapsed().as_nanos())
409 .unwrap_or(u64::MAX)
410 .max(1);
411 let _ = self.terminal_elapsed_nanos.compare_exchange(
412 0,
413 elapsed_nanos,
414 Ordering::Relaxed,
415 Ordering::Relaxed,
416 );
417 self.terminal.store(true, Ordering::Relaxed);
418 self.consumer_blocked.store(false, Ordering::Relaxed);
419 self.queued_tasks.store(0, Ordering::Relaxed);
420 self.notify_limit_waiters();
421 }
422
423 fn stats(&self) -> DecoderStats {
424 let path = DecoderPath::from_encoded(self.path.load(Ordering::Relaxed));
425 let configured_workers = self.configured_workers;
426 let worker_limit = self.worker_limit.load(Ordering::Relaxed);
427 let adaptive_target = self.adaptive_target.load(Ordering::Relaxed);
428 let consumer_blocked = self.consumer_blocked.load(Ordering::Relaxed);
429 let terminal = self.terminal.load(Ordering::Relaxed);
430 let active_workers = if terminal {
431 0
432 } else if consumer_blocked {
433 1
434 } else {
435 adaptive_target.min(worker_limit).max(1)
436 };
437 let busy_workers = self.busy_workers.load(Ordering::Relaxed);
438 let spawned_workers = self.spawned_workers.load(Ordering::Relaxed);
439 let auxiliary_threads = self.auxiliary_threads.load(Ordering::Relaxed);
440 let queued_tasks = self.queued_tasks.load(Ordering::Relaxed);
441 let best_workers = match self.best_workers.load(Ordering::Relaxed) {
442 NO_BEST_WORKER_COUNT => None,
443 workers => Some(workers),
444 };
445 let decompressed_bytes = self.decompressed_bytes.load(Ordering::Relaxed);
446 let consumed_bytes = self.consumed_bytes.load(Ordering::Relaxed);
447 let member_count = self.member_count.load(Ordering::Relaxed);
448 let terminal_elapsed_nanos = self.terminal_elapsed_nanos.load(Ordering::Relaxed);
449 let elapsed = if terminal_elapsed_nanos == 0 {
450 self.started.elapsed().as_secs_f64()
451 } else {
452 terminal_elapsed_nanos as f64 / 1_000_000_000.0
453 }
454 .max(f64::MIN_POSITIVE);
455 let pressure = if terminal {
456 DecoderPressure::Finished
457 } else if consumer_blocked {
458 let idle = spawned_workers.saturating_sub(busy_workers);
459 let idle_worker_fraction = if spawned_workers == 0 {
460 1.0
461 } else {
462 idle as f32 / spawned_workers as f32
463 };
464 DecoderPressure::ConsumerBound {
465 idle_worker_fraction,
466 }
467 } else if queued_tasks != 0 && busy_workers >= active_workers {
468 DecoderPressure::DecoderBound { queued_tasks }
469 } else if let Some(at_workers) = best_workers {
470 DecoderPressure::Converged { at_workers }
471 } else if busy_workers == 0 && queued_tasks == 0 {
472 DecoderPressure::Idle
473 } else {
474 DecoderPressure::Starting
475 };
476
477 DecoderStats {
478 path,
479 configured_workers,
480 worker_limit,
481 active_workers,
482 busy_workers,
483 spawned_workers,
484 auxiliary_threads,
485 best_workers,
486 decompressed_bytes,
487 consumed_bytes,
488 member_count,
489 decode_throughput_bps: decompressed_bytes as f64 / elapsed,
490 consumer_throughput_bps: consumed_bytes as f64 / elapsed,
491 pressure,
492 }
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::{DecoderHandle, DecoderPath, DecoderPressure, RuntimeState};
499
500 #[test]
501 fn runtime_limits_are_validated_and_visible() {
502 let state = RuntimeState::new(8);
503 let handle = DecoderHandle::new(state);
504 handle.set_worker_limit(3).unwrap();
505 let stats = handle.stats();
506 assert_eq!(stats.configured_workers, 8);
507 assert_eq!(stats.worker_limit, 3);
508 assert_eq!(stats.active_workers, 1);
509 assert_eq!(handle.set_worker_limit(0).unwrap_err().requested(), 0);
510 assert_eq!(handle.set_worker_limit(9).unwrap_err().configured(), 8);
511 }
512
513 #[test]
514 fn consumer_backpressure_caps_admission() {
515 let state = RuntimeState::new(8);
516 state.set_adaptive_target(6);
517 let worker = state.register_worker();
518 let _busy = state.begin_task();
519 state.set_consumer_blocked(true);
520 let stats = DecoderHandle::new(Arc::clone(&state)).stats();
521 assert_eq!(stats.active_workers, 1);
522 assert!(matches!(
523 stats.pressure,
524 DecoderPressure::ConsumerBound { .. }
525 ));
526 drop(worker);
527 }
528
529 #[test]
530 fn marker_admission_path_is_visible_in_telemetry() {
531 let state = RuntimeState::new(4);
532 state.set_path(DecoderPath::MarkerAdmission);
533 assert_eq!(
534 DecoderHandle::new(state).stats().path,
535 DecoderPath::MarkerAdmission
536 );
537 }
538
539 use std::sync::Arc;
540}