1use std::collections::VecDeque;
2use std::sync::atomic::{AtomicUsize, Ordering};
3use std::sync::{Arc, LazyLock, Mutex};
4use std::time::{Duration, Instant};
5
6#[cfg(not(test))]
7const DEFAULT_COLD_BUILD_LIMIT: usize = 2;
8#[cfg(test)]
9const DEFAULT_COLD_BUILD_LIMIT: usize = 1024;
10
11const ADMISSION_EVENT_RETENTION: usize = 64;
15
16static GLOBAL_COLD_BUILD_LIMITER: LazyLock<Arc<ColdBuildLimiter>> =
17 LazyLock::new(|| Arc::new(ColdBuildLimiter::new(DEFAULT_COLD_BUILD_LIMIT)));
18
19pub(crate) fn global_limiter() -> Arc<ColdBuildLimiter> {
20 Arc::clone(&GLOBAL_COLD_BUILD_LIMITER)
21}
22
23pub(crate) fn isolated_limiter(limit: usize) -> Arc<ColdBuildLimiter> {
24 Arc::new(ColdBuildLimiter::new(limit))
25}
26
27pub fn try_acquire() -> Option<ColdBuildPermit> {
28 GLOBAL_COLD_BUILD_LIMITER.try_acquire()
29}
30
31pub fn acquire_blocking(kind: &str) -> ColdBuildPermit {
40 acquire_blocking_while(kind, || true).expect("unconditional cold-build admission")
41}
42
43pub fn acquire_blocking_while(kind: &str, admitted: impl Fn() -> bool) -> Option<ColdBuildPermit> {
47 acquire_blocking_while_with_limiter(&GLOBAL_COLD_BUILD_LIMITER, kind, admitted)
48}
49
50pub(crate) fn acquire_blocking_while_with_limiter(
51 limiter: &Arc<ColdBuildLimiter>,
52 kind: &str,
53 admitted: impl Fn() -> bool,
54) -> Option<ColdBuildPermit> {
55 let request = ColdBuildAdmissionRequest::new(kind, ColdBuildAdmissionClass::Maintenance);
56 acquire_blocking_while_inner(limiter, kind, Some(&request), admitted, || false)
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub(crate) enum ColdBuildAdmissionClass {
67 InspectTriggered,
68 Maintenance,
69 Standing,
70}
71
72const ADMISSION_CLASS_COUNT: usize = 3;
73
74impl ColdBuildAdmissionClass {
75 const fn index(self) -> usize {
76 match self {
77 Self::InspectTriggered => 0,
78 Self::Maintenance => 1,
79 Self::Standing => 2,
80 }
81 }
82
83 const fn label(self) -> &'static str {
84 match self {
85 Self::InspectTriggered => "inspect-triggered",
86 Self::Maintenance => "maintenance",
87 Self::Standing => "standing",
88 }
89 }
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
94pub(crate) struct ColdBuildAdmissionRequest {
95 request_id: String,
96 class: ColdBuildAdmissionClass,
97}
98
99impl ColdBuildAdmissionRequest {
100 pub(crate) fn new(request_id: impl Into<String>, class: ColdBuildAdmissionClass) -> Self {
101 Self {
102 request_id: request_id.into(),
103 class,
104 }
105 }
106}
107
108#[derive(Clone, Debug, Eq, PartialEq)]
114pub(crate) struct ColdBuildAdmissionEvent {
115 pub(crate) request_id: String,
116 pub(crate) class: ColdBuildAdmissionClass,
117 pub(crate) admission_order: u64,
118}
119
120pub(crate) fn try_acquire_classified_with_limiter(
123 limiter: &Arc<ColdBuildLimiter>,
124 request: &ColdBuildAdmissionRequest,
125) -> Option<ColdBuildPermit> {
126 limiter.try_acquire_classified(request, || true)
127}
128
129pub(crate) fn acquire_blocking_while_cancellable_with_limiter(
137 limiter: &Arc<ColdBuildLimiter>,
138 kind: &str,
139 request: ColdBuildAdmissionRequest,
140 admitted: impl Fn() -> bool,
141 cancelled: impl Fn() -> bool,
142) -> Option<ColdBuildPermit> {
143 acquire_blocking_while_inner(limiter, kind, Some(&request), admitted, cancelled)
144}
145
146#[derive(Debug)]
151pub(crate) struct StandingColdBuildPermit {
152 _permit: ColdBuildPermit,
153 pub(crate) admission_epoch: u64,
154}
155
156pub(crate) fn acquire_standing_while_cancellable_with_limiter(
160 limiter: &Arc<ColdBuildLimiter>,
161 kind: &str,
162 request_id: impl Into<String>,
163 admission_epoch: u64,
164 admitted: impl Fn() -> bool,
165 cancelled: impl Fn() -> bool,
166) -> Option<StandingColdBuildPermit> {
167 let request = ColdBuildAdmissionRequest::new(request_id, ColdBuildAdmissionClass::Standing);
168 acquire_blocking_while_inner(limiter, kind, Some(&request), admitted, cancelled).map(|permit| {
169 StandingColdBuildPermit {
170 _permit: permit,
171 admission_epoch,
172 }
173 })
174}
175
176fn acquire_blocking_while_inner(
177 limiter: &Arc<ColdBuildLimiter>,
178 kind: &str,
179 request: Option<&ColdBuildAdmissionRequest>,
180 admitted: impl Fn() -> bool,
181 cancelled: impl Fn() -> bool,
182) -> Option<ColdBuildPermit> {
183 let _waiter = request.map(|request| AdmissionWaiter::register(limiter, request.class));
184 let started = Instant::now();
185 let mut logged = false;
186 loop {
187 if !admitted() || cancelled() {
188 return None;
189 }
190 if request.is_some_and(|request| request.class == ColdBuildAdmissionClass::Standing)
194 && limiter.has_non_standing_waiters()
195 {
196 return None;
197 }
198 let revoked_after_acquire = std::cell::Cell::new(false);
199 let permit = match request {
200 Some(request) => limiter.try_acquire_classified(request, || {
201 let still_admitted = admitted()
202 && !cancelled()
203 && (request.class != ColdBuildAdmissionClass::Standing
204 || !limiter.has_non_standing_waiters());
205 revoked_after_acquire.set(!still_admitted);
206 still_admitted
207 }),
208 None => limiter.try_acquire().and_then(|permit| {
209 if admitted() && !cancelled() {
213 Some(permit)
214 } else {
215 revoked_after_acquire.set(true);
216 drop(permit);
217 None
218 }
219 }),
220 };
221 if revoked_after_acquire.get() {
222 return None;
223 }
224 if let Some(permit) = permit {
225 let wait_ms = started.elapsed().as_millis().min(u64::MAX as u128) as u64;
226 if wait_ms > 0 {
227 crate::logging::note_tool_call_wait(
228 crate::run_tool_call::WaitingOn::Limiter,
229 None,
230 wait_ms,
231 );
232 }
233 if logged {
234 match request {
235 Some(request) => crate::slog_info!(
236 "{} cold-build slot acquired after {}ms wait: request={} kind={}",
237 request.class.label(),
238 wait_ms,
239 request.request_id,
240 kind
241 ),
242 None => crate::slog_info!(
243 "maintenance build slot acquired after {}ms wait: {}",
244 wait_ms,
245 kind
246 ),
247 }
248 }
249 return Some(permit);
250 }
251 if !logged {
252 match request {
253 Some(request) => crate::slog_info!(
254 "{} cold-build request queued behind concurrency cap ({}): request={} kind={}",
255 request.class.label(),
256 limiter.limit(),
257 request.request_id,
258 kind
259 ),
260 None => crate::slog_info!(
261 "maintenance build queued behind concurrency cap ({}): {}",
262 limiter.limit(),
263 kind
264 ),
265 }
266 logged = true;
267 }
268 std::thread::sleep(Duration::from_millis(100));
269 }
270}
271
272pub fn limit() -> usize {
273 GLOBAL_COLD_BUILD_LIMITER.limit()
274}
275
276#[cfg(test)]
277pub(crate) fn test_limiter(limit: usize) -> Arc<ColdBuildLimiter> {
278 Arc::new(ColdBuildLimiter::new(limit))
279}
280
281#[cfg(test)]
282pub(crate) fn acquire_blocking_while_with_test_limiter(
283 limiter: &Arc<ColdBuildLimiter>,
284 kind: &str,
285 admitted: impl Fn() -> bool,
286) -> Option<ColdBuildPermit> {
287 acquire_blocking_while_with_limiter(limiter, kind, admitted)
288}
289
290#[derive(Debug)]
291pub(crate) struct ColdBuildLimiter {
292 available: AtomicUsize,
293 limit: usize,
294 waiting_by_class: [AtomicUsize; ADMISSION_CLASS_COUNT],
297 admission_state: Mutex<AdmissionState>,
298}
299
300#[derive(Debug)]
301struct AdmissionState {
302 last_admitted_class: Option<ColdBuildAdmissionClass>,
303 next_admission_order: u64,
304 events: VecDeque<ColdBuildAdmissionEvent>,
305}
306
307impl ColdBuildLimiter {
308 fn new(limit: usize) -> Self {
309 let limit = limit.max(1);
310 Self {
311 available: AtomicUsize::new(limit),
312 limit,
313 waiting_by_class: std::array::from_fn(|_| AtomicUsize::new(0)),
314 admission_state: Mutex::new(AdmissionState {
315 last_admitted_class: None,
316 next_admission_order: 1,
317 events: VecDeque::with_capacity(ADMISSION_EVENT_RETENTION),
318 }),
319 }
320 }
321
322 pub(crate) fn limit(&self) -> usize {
323 self.limit
324 }
325
326 pub(crate) fn try_acquire(self: &Arc<Self>) -> Option<ColdBuildPermit> {
327 loop {
328 let available = self.available.load(Ordering::Acquire);
329 if available == 0 {
330 return None;
331 }
332 if self
333 .available
334 .compare_exchange(
335 available,
336 available - 1,
337 Ordering::AcqRel,
338 Ordering::Acquire,
339 )
340 .is_ok()
341 {
342 return Some(ColdBuildPermit {
343 limiter: Arc::clone(self),
344 });
345 }
346 }
347 }
348
349 fn try_acquire_classified(
350 self: &Arc<Self>,
351 request: &ColdBuildAdmissionRequest,
352 admitted_after_acquire: impl FnOnce() -> bool,
353 ) -> Option<ColdBuildPermit> {
354 let mut state = self
355 .admission_state
356 .lock()
357 .unwrap_or_else(std::sync::PoisonError::into_inner);
358 let available = self.available.load(Ordering::Acquire);
362 if available <= 1
363 && self.has_waiter_from_another_class(request.class)
364 && state.last_admitted_class == Some(request.class)
365 {
366 return None;
367 }
368 let permit = self.try_acquire()?;
369 if !admitted_after_acquire() {
370 drop(permit);
371 return None;
372 }
373 Self::record_admission_locked(&mut state, request);
374 Some(permit)
375 }
376
377 fn record_admission_locked(state: &mut AdmissionState, request: &ColdBuildAdmissionRequest) {
378 let event = ColdBuildAdmissionEvent {
379 request_id: request.request_id.clone(),
380 class: request.class,
381 admission_order: state.next_admission_order,
382 };
383 state.next_admission_order += 1;
384 state.last_admitted_class = Some(request.class);
385 if state.events.len() == ADMISSION_EVENT_RETENTION {
386 state.events.pop_front();
387 }
388 state.events.push_back(event);
389 }
390
391 #[cfg_attr(not(test), allow(dead_code))]
394 pub(crate) fn admission_events(&self) -> Vec<ColdBuildAdmissionEvent> {
395 self.admission_state
396 .lock()
397 .unwrap_or_else(std::sync::PoisonError::into_inner)
398 .events
399 .iter()
400 .cloned()
401 .collect()
402 }
403
404 pub(crate) fn has_non_standing_waiters(&self) -> bool {
408 self.waiting_by_class[ColdBuildAdmissionClass::InspectTriggered.index()]
409 .load(Ordering::Acquire)
410 > 0
411 || self.waiting_by_class[ColdBuildAdmissionClass::Maintenance.index()]
412 .load(Ordering::Acquire)
413 > 0
414 }
415
416 fn has_waiter_from_another_class(&self, class: ColdBuildAdmissionClass) -> bool {
417 self.waiting_by_class
418 .iter()
419 .enumerate()
420 .any(|(index, waiters)| index != class.index() && waiters.load(Ordering::Acquire) > 0)
421 }
422
423 #[cfg(test)]
424 fn waiting_by_class_for_test(&self) -> [usize; ADMISSION_CLASS_COUNT] {
425 std::array::from_fn(|index| self.waiting_by_class[index].load(Ordering::Acquire))
426 }
427}
428
429struct AdmissionWaiter {
430 limiter: Arc<ColdBuildLimiter>,
431 class: ColdBuildAdmissionClass,
432}
433
434impl AdmissionWaiter {
435 fn register(limiter: &Arc<ColdBuildLimiter>, class: ColdBuildAdmissionClass) -> Self {
436 limiter.waiting_by_class[class.index()].fetch_add(1, Ordering::AcqRel);
437 Self {
438 limiter: Arc::clone(limiter),
439 class,
440 }
441 }
442}
443
444impl Drop for AdmissionWaiter {
445 fn drop(&mut self) {
446 let previous =
447 self.limiter.waiting_by_class[self.class.index()].fetch_sub(1, Ordering::AcqRel);
448 debug_assert!(previous > 0);
449 }
450}
451
452#[derive(Debug)]
453pub struct ColdBuildPermit {
454 limiter: Arc<ColdBuildLimiter>,
455}
456
457impl Drop for ColdBuildPermit {
458 fn drop(&mut self) {
459 let previous = self.limiter.available.fetch_add(1, Ordering::Release);
460 debug_assert!(previous < self.limiter.limit);
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 fn serial() -> std::sync::MutexGuard<'static, ()> {
470 static M: std::sync::OnceLock<std::sync::Mutex<()>> = std::sync::OnceLock::new();
471 M.get_or_init(|| std::sync::Mutex::new(()))
472 .lock()
473 .unwrap_or_else(std::sync::PoisonError::into_inner)
474 }
475
476 fn wait_for_waiters(limiter: &ColdBuildLimiter) {
477 let deadline = Instant::now() + Duration::from_secs(3);
478 loop {
479 let waiting = limiter.waiting_by_class_for_test();
480 if waiting[ColdBuildAdmissionClass::InspectTriggered.index()] > 0
481 && waiting[ColdBuildAdmissionClass::Maintenance.index()] > 0
482 {
483 return;
484 }
485 assert!(
486 Instant::now() < deadline,
487 "both admission classes must remain queued; waiting={waiting:?}"
488 );
489 std::thread::yield_now();
490 }
491 }
492
493 #[test]
494 fn permits_release_on_drop() {
495 let _serial = serial();
496 let before = GLOBAL_COLD_BUILD_LIMITER.available.load(Ordering::Acquire);
497 {
498 let _a = acquire_blocking("test-a");
499 let _b = acquire_blocking("test-b");
500 assert_eq!(
501 GLOBAL_COLD_BUILD_LIMITER.available.load(Ordering::Acquire),
502 before - 2
503 );
504 }
505 assert_eq!(
506 GLOBAL_COLD_BUILD_LIMITER.available.load(Ordering::Acquire),
507 before
508 );
509 }
510
511 #[test]
512 fn acquire_blocking_waits_until_release() {
513 let _serial = serial();
514 let mut held: Vec<ColdBuildPermit> = Vec::new();
516 while let Some(permit) = try_acquire() {
517 held.push(permit);
518 }
519 let waiter = std::thread::spawn(|| {
520 let _p = acquire_blocking("waiter");
521 });
522 std::thread::sleep(std::time::Duration::from_millis(250));
523 assert!(!waiter.is_finished(), "waiter must block while cap is full");
524 drop(held.pop());
525 waiter.join().expect("waiter finishes after release");
526 drop(held);
527 }
528
529 #[test]
530 fn admission_revoked_between_check_and_permit_drops_the_slot() {
531 let _serial = serial();
532 let before = GLOBAL_COLD_BUILD_LIMITER.available.load(Ordering::Acquire);
533 let checks = AtomicUsize::new(0);
534
535 let permit = acquire_blocking_while("revoked-after-cas", || {
536 checks.fetch_add(1, Ordering::SeqCst) == 0
537 });
538
539 assert!(permit.is_none());
540 assert_eq!(checks.load(Ordering::SeqCst), 2);
541 assert_eq!(
542 GLOBAL_COLD_BUILD_LIMITER.available.load(Ordering::Acquire),
543 before,
544 "revoked admission must return the just-acquired slot"
545 );
546 }
547
548 #[test]
549 fn conditional_waiter_cancels_without_consuming_a_released_slot() {
550 let _serial = serial();
551 let mut held = Vec::new();
552 while let Some(permit) = try_acquire() {
553 held.push(permit);
554 }
555 let admitted = Arc::new(std::sync::atomic::AtomicBool::new(true));
556 let waiter_admitted = Arc::clone(&admitted);
557 let waiter = std::thread::spawn(move || {
558 acquire_blocking_while("conditional waiter", || {
559 waiter_admitted.load(Ordering::SeqCst)
560 })
561 });
562 std::thread::sleep(Duration::from_millis(150));
563 admitted.store(false, Ordering::SeqCst);
564 assert!(
565 waiter.join().expect("conditional waiter joins").is_none(),
566 "revoked work must leave the cold-build queue without taking a permit"
567 );
568 drop(held);
569 }
570
571 #[test]
572 fn cancellation_after_acquisition_returns_the_permit_without_an_event() {
573 let limiter = test_limiter(1);
574 let cancellation_checks = AtomicUsize::new(0);
575
576 let permit = acquire_blocking_while_cancellable_with_limiter(
577 &limiter,
578 "cancel-after-acquire",
579 ColdBuildAdmissionRequest::new(
580 "inspect-cancelled",
581 ColdBuildAdmissionClass::InspectTriggered,
582 ),
583 || true,
584 || cancellation_checks.fetch_add(1, Ordering::SeqCst) > 0,
585 );
586
587 assert!(permit.is_none());
588 assert_eq!(cancellation_checks.load(Ordering::SeqCst), 2);
589 assert_eq!(
590 limiter.available.load(Ordering::Acquire),
591 1,
592 "post-acquisition cancellation must return the permit"
593 );
594 assert!(
595 limiter.admission_events().is_empty(),
596 "cancelled work must not emit a successful admission"
597 );
598 }
599
600 #[test]
601 fn standing_yields_before_initial_and_checkpoint_reacquisition_when_non_standing_waits() {
602 let limiter = test_limiter(1);
603 let non_standing_waiter =
604 AdmissionWaiter::register(&limiter, ColdBuildAdmissionClass::Maintenance);
605
606 assert!(acquire_standing_while_cancellable_with_limiter(
607 &limiter,
608 "standing-initial",
609 "standing-initial",
610 41,
611 || true,
612 || false,
613 )
614 .is_none());
615
616 drop(non_standing_waiter);
617 let first = acquire_standing_while_cancellable_with_limiter(
618 &limiter,
619 "standing-checkpoint",
620 "standing-checkpoint",
621 41,
622 || true,
623 || false,
624 )
625 .expect("standing may acquire once ordinary waiters clear");
626 assert_eq!(first.admission_epoch, 41);
627 drop(first);
628
629 let non_standing_waiter =
630 AdmissionWaiter::register(&limiter, ColdBuildAdmissionClass::InspectTriggered);
631 assert!(acquire_standing_while_cancellable_with_limiter(
632 &limiter,
633 "standing-reacquire",
634 "standing-reacquire",
635 41,
636 || true,
637 || false,
638 )
639 .is_none());
640 drop(non_standing_waiter);
641 }
642
643 #[test]
644 fn inspect_waiter_takes_next_release_ahead_of_queued_maintenance() {
645 let limiter = test_limiter(1);
646 let active_request = ColdBuildAdmissionRequest::new(
647 "active-semantic-seed",
648 ColdBuildAdmissionClass::Maintenance,
649 );
650 let active = try_acquire_classified_with_limiter(&limiter, &active_request)
651 .expect("active maintenance build holds the slot");
652 let (admitted_tx, admitted_rx) = std::sync::mpsc::channel();
653 let mut waiters = Vec::new();
654
655 for (request_id, class) in [
656 ("queued-refresh", ColdBuildAdmissionClass::Maintenance),
657 (
658 "blocking-inspect",
659 ColdBuildAdmissionClass::InspectTriggered,
660 ),
661 ] {
662 let limiter = Arc::clone(&limiter);
663 let admitted_tx = admitted_tx.clone();
664 waiters.push(std::thread::spawn(move || {
665 let permit = acquire_blocking_while_cancellable_with_limiter(
666 &limiter,
667 request_id,
668 ColdBuildAdmissionRequest::new(request_id, class),
669 || true,
670 || false,
671 )
672 .expect("queued build is admitted");
673 admitted_tx
674 .send((class, permit))
675 .expect("test receives admitted permit");
676 }));
677 }
678 drop(admitted_tx);
679 wait_for_waiters(&limiter);
680 drop(active);
681
682 let (first_class, first_permit) = admitted_rx
683 .recv_timeout(Duration::from_secs(3))
684 .expect("released slot admits interactive inspect");
685 assert_eq!(first_class, ColdBuildAdmissionClass::InspectTriggered);
686 assert!(
687 admitted_rx.try_recv().is_err(),
688 "maintenance remains deferred"
689 );
690 drop(first_permit);
691
692 let (second_class, second_permit) = admitted_rx
693 .recv_timeout(Duration::from_secs(3))
694 .expect("maintenance resumes after inspect releases its slot");
695 assert_eq!(second_class, ColdBuildAdmissionClass::Maintenance);
696 drop(second_permit);
697 for waiter in waiters {
698 waiter.join().expect("admission waiter joins");
699 }
700 }
701
702 #[test]
703 fn admission_events_cover_both_classes_across_the_fixed_32_release_schedule() {
704 const RELEASE_COUNT: usize = 32;
705
706 let limiter = test_limiter(1);
707 let cancelled = Arc::new(std::sync::atomic::AtomicBool::new(false));
708 let (permit_tx, permit_rx) = std::sync::mpsc::channel();
709 let initial_permit = limiter.try_acquire().expect("hold the only slot");
710 let mut waiters = Vec::new();
711
712 for (request_id, class) in [
713 ("inspect-request", ColdBuildAdmissionClass::InspectTriggered),
714 ("maintenance-request", ColdBuildAdmissionClass::Maintenance),
715 ] {
716 let limiter = Arc::clone(&limiter);
717 let cancelled = Arc::clone(&cancelled);
718 let permit_tx = permit_tx.clone();
719 waiters.push(std::thread::spawn(move || {
720 while !cancelled.load(Ordering::SeqCst) {
721 let permit = acquire_blocking_while_cancellable_with_limiter(
722 &limiter,
723 "fixed-release-test",
724 ColdBuildAdmissionRequest::new(request_id, class),
725 || true,
726 || cancelled.load(Ordering::SeqCst),
727 );
728 let Some(permit) = permit else {
729 return;
730 };
731 if permit_tx.send(permit).is_err() {
732 return;
733 }
734 }
735 }));
736 }
737 drop(permit_tx);
738
739 wait_for_waiters(&limiter);
740 let mut released_permit = Some(initial_permit);
741 for release in 1..RELEASE_COUNT {
742 wait_for_waiters(&limiter);
743 drop(released_permit.take());
744 released_permit = Some(
745 permit_rx
746 .recv_timeout(Duration::from_secs(3))
747 .unwrap_or_else(|error| {
748 panic!("release {release} must admit a waiter: {error}")
749 }),
750 );
751 }
752 wait_for_waiters(&limiter);
753 drop(released_permit);
754 let consumed_by_build = permit_rx
755 .recv_timeout(Duration::from_secs(3))
756 .unwrap_or_else(|error| panic!("release {RELEASE_COUNT} must admit a waiter: {error}"));
757
758 cancelled.store(true, Ordering::SeqCst);
759 for waiter in waiters {
760 waiter.join().expect("cancelled waiter joins");
761 }
762
763 let events = limiter.admission_events();
764 assert_eq!(events.len(), RELEASE_COUNT);
765 assert!(events
766 .iter()
767 .any(|event| event.class == ColdBuildAdmissionClass::InspectTriggered));
768 assert!(events
769 .iter()
770 .any(|event| event.class == ColdBuildAdmissionClass::Maintenance));
771 assert!(events.iter().all(|event| matches!(
772 event.request_id.as_str(),
773 "inspect-request" | "maintenance-request"
774 )));
775 assert!(events
776 .iter()
777 .enumerate()
778 .all(|(index, event)| event.admission_order == index as u64 + 1));
779
780 assert_eq!(
781 limiter.available.load(Ordering::Acquire),
782 0,
783 "the final acquired permit must remain accounted for by the consumed build"
784 );
785 drop(consumed_by_build);
786 assert_eq!(
787 limiter.available.load(Ordering::Acquire),
788 1,
789 "releasing the consumed build permit must restore the limiter slot"
790 );
791 }
792}