1mod single_flight;
2
3#[cfg(test)]
4mod tests;
5
6use std::{
7 collections::{HashMap, HashSet, VecDeque},
8 sync::{
9 atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering},
10 Arc,
11 },
12 thread::{self, JoinHandle},
13 time::{Duration, Instant},
14};
15
16use crossbeam_channel::{Receiver, RecvError, RecvTimeoutError, Sender};
17use parking_lot::{Condvar, Mutex, RwLock};
18use tokio::sync::oneshot;
19
20use crate::{context::AppContext, path_identity::ProjectRootId, protocol::Response};
21
22pub use single_flight::SingleFlight;
23
24const JOB_COST: isize = 1;
25const SCHEDULER_EVENT_BATCH_CAP: usize = 64;
29pub(crate) const MAINTENANCE_QUEUE_CAP: usize = 512;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum Lane {
34 PureRead,
37 SerialLspStatus,
40 HeavyInit,
44 Mutating,
49 MaintenanceCommit,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub enum JobClass {
61 Interactive,
62 Maintenance,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub(crate) enum MaintenanceCoalesceKey {
68 WatcherDrain,
69 LspDrain,
70 StandingPass,
74}
75
76pub type ExecutorJob = Box<dyn FnOnce(&AppContext) -> Response + Send + 'static>;
77
78const INTERACTIVE_WRITER_PROMOTION_AGE: Duration = Duration::from_secs(6);
85const READER_STUCK_CENSUS_AGE: Duration = Duration::from_secs(60);
88
89#[derive(Debug, Clone)]
90pub struct ExecutorConfig {
91 pub pool_size: usize,
92 pub read_cap: usize,
93 pub actor_cap: usize,
94 pub heavy_permits: usize,
95 pub drr_quantum: isize,
96}
97
98impl Default for ExecutorConfig {
99 fn default() -> Self {
100 let available = thread::available_parallelism()
101 .map(usize::from)
102 .unwrap_or(2);
103 let pool_size = available.saturating_sub(1).clamp(2, 8);
104 let actor_cap = pool_size.saturating_sub(1).clamp(1, 4);
105 let read_cap = actor_cap.clamp(1, 4);
106 let heavy_permits = pool_size.saturating_sub(1).clamp(2, 3);
107
108 Self {
109 pool_size,
110 read_cap,
111 actor_cap,
112 heavy_permits,
113 drr_quantum: 1,
114 }
115 }
116}
117
118#[derive(Debug, Clone)]
119struct EffectiveConfig {
120 pool_size: usize,
121 read_cap: usize,
122 actor_cap: usize,
123 heavy_permits: usize,
124 drr_quantum: isize,
125 deficit_cap: isize,
126 interactive_reserve: usize,
127 maintenance_cap: usize,
128}
129
130impl ExecutorConfig {
131 fn effective(&self) -> EffectiveConfig {
132 let pool_size = self.pool_size.clamp(2, 8);
133 let max_actor_cap = pool_size.saturating_sub(1).max(1);
134 let actor_cap = self.actor_cap.max(1).min(max_actor_cap);
135 let read_cap = self.read_cap.max(1).min(actor_cap).min(4);
136 let heavy_permits = self
140 .heavy_permits
141 .max(1)
142 .min(pool_size.saturating_sub(1).max(1))
143 .min(3);
144 let drr_quantum = self.drr_quantum.max(1);
145 let deficit_cap = (actor_cap.max(1) as isize) * 4;
146 let interactive_reserve = if pool_size >= 4 { 2 } else { 1 };
147 let maintenance_cap = pool_size.saturating_sub(interactive_reserve).max(1);
148
149 EffectiveConfig {
150 pool_size,
151 read_cap,
152 actor_cap,
153 heavy_permits,
154 drr_quantum,
155 deficit_cap,
156 interactive_reserve,
157 maintenance_cap,
158 }
159 }
160}
161
162pub struct CompletionHandle {
165 rx: Receiver<Response>,
166}
167
168impl CompletionHandle {
169 pub fn recv(self) -> Result<Response, RecvError> {
170 self.rx.recv()
171 }
172
173 pub fn recv_timeout(&self, timeout: Duration) -> Result<Response, RecvTimeoutError> {
174 self.rx.recv_timeout(timeout)
175 }
176
177 pub fn into_receiver(self) -> Receiver<Response> {
178 self.rx
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct DispatchClassQueueSnapshot {
184 pub queued: usize,
185 pub oldest_age_ms: Option<u64>,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct DispatchRunningSnapshot {
190 pub interactive: usize,
191 pub maintenance: usize,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct DispatchLivenessSnapshot {
196 pub interactive: DispatchClassQueueSnapshot,
197 pub maintenance: DispatchClassQueueSnapshot,
198 pub running: DispatchRunningSnapshot,
199 pub interactive_reserve: usize,
200 pub maintenance_cap: usize,
201}
202
203struct DispatchLivenessAtomics {
207 origin: Instant,
208 interactive_queued: AtomicUsize,
209 interactive_oldest_enqueued_ms_plus_one: AtomicU64,
210 maintenance_queued: AtomicUsize,
211 maintenance_oldest_enqueued_ms_plus_one: AtomicU64,
212 interactive_running: AtomicUsize,
213 maintenance_running: AtomicUsize,
214}
215
216impl DispatchLivenessAtomics {
217 fn new() -> Self {
218 Self {
219 origin: Instant::now(),
220 interactive_queued: AtomicUsize::new(0),
221 interactive_oldest_enqueued_ms_plus_one: AtomicU64::new(0),
222 maintenance_queued: AtomicUsize::new(0),
223 maintenance_oldest_enqueued_ms_plus_one: AtomicU64::new(0),
224 interactive_running: AtomicUsize::new(0),
225 maintenance_running: AtomicUsize::new(0),
226 }
227 }
228
229 fn now_ms(&self) -> u64 {
230 duration_millis_u64(self.origin.elapsed())
231 }
232
233 fn record(&self, snapshot: &DispatchLivenessSnapshot) {
234 let now_ms = self.now_ms();
235 let encode_oldest = |queued: usize, oldest_age_ms: Option<u64>| {
236 if queued == 0 {
237 0
238 } else {
239 now_ms
240 .saturating_sub(oldest_age_ms.unwrap_or(0))
241 .saturating_add(1)
242 }
243 };
244 self.interactive_oldest_enqueued_ms_plus_one.store(
245 encode_oldest(
246 snapshot.interactive.queued,
247 snapshot.interactive.oldest_age_ms,
248 ),
249 Ordering::Relaxed,
250 );
251 self.maintenance_oldest_enqueued_ms_plus_one.store(
252 encode_oldest(
253 snapshot.maintenance.queued,
254 snapshot.maintenance.oldest_age_ms,
255 ),
256 Ordering::Relaxed,
257 );
258 self.interactive_running
259 .store(snapshot.running.interactive, Ordering::Release);
260 self.maintenance_running
261 .store(snapshot.running.maintenance, Ordering::Release);
262 self.interactive_queued
263 .store(snapshot.interactive.queued, Ordering::Release);
264 self.maintenance_queued
265 .store(snapshot.maintenance.queued, Ordering::Release);
266 }
267
268 fn snapshot(&self, config: &EffectiveConfig) -> DispatchLivenessSnapshot {
269 let now_ms = self.now_ms();
270 let decode_oldest = |queued: usize, encoded: u64| {
271 (queued > 0 && encoded > 0).then(|| now_ms.saturating_sub(encoded - 1))
272 };
273 let interactive_queued = self.interactive_queued.load(Ordering::Acquire);
274 let maintenance_queued = self.maintenance_queued.load(Ordering::Acquire);
275 DispatchLivenessSnapshot {
276 interactive: DispatchClassQueueSnapshot {
277 queued: interactive_queued,
278 oldest_age_ms: decode_oldest(
279 interactive_queued,
280 self.interactive_oldest_enqueued_ms_plus_one
281 .load(Ordering::Relaxed),
282 ),
283 },
284 maintenance: DispatchClassQueueSnapshot {
285 queued: maintenance_queued,
286 oldest_age_ms: decode_oldest(
287 maintenance_queued,
288 self.maintenance_oldest_enqueued_ms_plus_one
289 .load(Ordering::Relaxed),
290 ),
291 },
292 running: DispatchRunningSnapshot {
293 interactive: self.interactive_running.load(Ordering::Acquire),
294 maintenance: self.maintenance_running.load(Ordering::Acquire),
295 },
296 interactive_reserve: config.interactive_reserve,
297 maintenance_cap: config.maintenance_cap,
298 }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub struct MutatingLaneSnapshot {
304 pub root_id: ProjectRootId,
305 pub request_id: String,
306 pub command: String,
307 pub started_age_ms: u64,
308}
309
310#[derive(Debug, Clone, PartialEq, Eq)]
312pub struct BindBlockerReaderSnapshot {
313 pub request_id: String,
314 pub command: String,
315 pub lane: Lane,
316 pub started_age_ms: u64,
317 pub started_before_oldest_writer: bool,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub struct BindBlockerSnapshot {
322 pub configure_state: &'static str,
323 pub configure_phase_timings: Option<String>,
324 pub blockers: Vec<String>,
325 pub oldest_queued_writer_age_ms: Option<u64>,
326 pub in_flight_readers: Vec<BindBlockerReaderSnapshot>,
327 pub reader_admissions_while_promoted_writer_waited: u64,
328}
329
330#[derive(Debug, Clone)]
341pub struct JobCancellation {
342 inner: Arc<JobCancellationInner>,
343}
344
345#[derive(Debug)]
346struct JobCancellationInner {
347 state: AtomicU8,
348 wait_lock: Mutex<()>,
349 wake: Condvar,
350}
351
352const JOB_CANCEL_STATE_PENDING: u8 = 0;
353const JOB_CANCEL_STATE_RUNNING: u8 = 1;
354const JOB_CANCEL_STATE_COMMITTED: u8 = 2;
355const JOB_CANCEL_STATE_CANCELLED: u8 = 3;
356
357impl JobCancellation {
358 pub fn new() -> Self {
359 Self {
360 inner: Arc::new(JobCancellationInner {
361 state: AtomicU8::new(JOB_CANCEL_STATE_PENDING),
362 wait_lock: Mutex::new(()),
363 wake: Condvar::new(),
364 }),
365 }
366 }
367
368 fn mark_running(&self) {
369 let _ = self.inner.state.compare_exchange(
370 JOB_CANCEL_STATE_PENDING,
371 JOB_CANCEL_STATE_RUNNING,
372 Ordering::SeqCst,
373 Ordering::SeqCst,
374 );
375 }
376
377 #[must_use]
381 pub fn try_seal_committed(&self) -> bool {
382 loop {
383 let current = self.state();
384 match current {
385 JOB_CANCEL_STATE_CANCELLED => return false,
386 JOB_CANCEL_STATE_COMMITTED => return true,
387 _ => {
388 if self
389 .inner
390 .state
391 .compare_exchange(
392 current,
393 JOB_CANCEL_STATE_COMMITTED,
394 Ordering::SeqCst,
395 Ordering::SeqCst,
396 )
397 .is_ok()
398 {
399 return true;
400 }
401 }
402 }
403 }
404 }
405
406 fn signal_cancel(&self) -> u8 {
409 let observed = loop {
410 let current = self.state();
411 match current {
412 JOB_CANCEL_STATE_COMMITTED | JOB_CANCEL_STATE_CANCELLED => break current,
413 _ => {
414 if self
415 .inner
416 .state
417 .compare_exchange(
418 current,
419 JOB_CANCEL_STATE_CANCELLED,
420 Ordering::SeqCst,
421 Ordering::SeqCst,
422 )
423 .is_ok()
424 {
425 break current;
426 }
427 }
428 }
429 };
430 if observed != JOB_CANCEL_STATE_COMMITTED {
431 let _wait_guard = self.inner.wait_lock.lock();
432 self.inner.wake.notify_all();
433 }
434 observed
435 }
436
437 fn state(&self) -> u8 {
438 self.inner.state.load(Ordering::SeqCst)
439 }
440
441 pub fn request_cancel(&self) {
445 self.signal_cancel();
446 }
447
448 pub fn cancel_requested_before_commit(&self) -> bool {
450 self.state() == JOB_CANCEL_STATE_CANCELLED
451 }
452
453 pub fn wait_for_cancellation(&self, timeout: Duration) -> bool {
458 if self.cancel_requested_before_commit() {
459 return true;
460 }
461 let mut guard = self.inner.wait_lock.lock();
462 if self.cancel_requested_before_commit() {
463 return true;
464 }
465 self.inner.wake.wait_for(&mut guard, timeout);
466 self.cancel_requested_before_commit()
467 }
468
469 fn same_token(&self, other: &JobCancellation) -> bool {
470 Arc::ptr_eq(&self.inner, &other.inner)
471 }
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq)]
476pub enum JobCancelOutcome {
477 QueuedRemoved,
480 RunningSignalled,
483 RunningCommitted,
486 NotFound,
488}
489
490thread_local! {
491 static CURRENT_JOB_CANCELLATION: std::cell::RefCell<Option<JobCancellation>> =
492 const { std::cell::RefCell::new(None) };
493}
494
495pub fn current_job_cancellation() -> Option<JobCancellation> {
498 CURRENT_JOB_CANCELLATION.with(|slot| slot.borrow().clone())
499}
500
501pub fn current_job_cancelled() -> bool {
502 current_job_cancellation().is_some_and(|token| token.cancel_requested_before_commit())
503}
504
505pub struct JobCancellationContextGuard {
506 previous: Option<JobCancellation>,
507}
508
509impl JobCancellationContextGuard {
510 fn install(token: Option<JobCancellation>) -> Self {
511 let previous = CURRENT_JOB_CANCELLATION.with(|slot| slot.replace(token));
512 Self { previous }
513 }
514}
515
516impl Drop for JobCancellationContextGuard {
517 fn drop(&mut self) {
518 CURRENT_JOB_CANCELLATION.with(|slot| {
519 *slot.borrow_mut() = self.previous.take();
520 });
521 }
522}
523
524pub fn install_job_cancellation(token: JobCancellation) -> JobCancellationContextGuard {
530 JobCancellationContextGuard::install(Some(token))
531}
532
533#[derive(Debug, Clone)]
534struct RunningJob {
535 root_id: ProjectRootId,
536 request_id: String,
537 command: String,
538 job_class: JobClass,
539 lane: Lane,
540 started_at: Instant,
541 occupancy_reported: bool,
542}
543
544#[derive(Debug, Clone)]
545pub(crate) struct LongRunningInteractiveJobSnapshot {
546 pub(crate) root_id: ProjectRootId,
547 pub(crate) request_id: String,
548 pub(crate) command: String,
549 pub(crate) lane: Lane,
550 pub(crate) age: Duration,
551}
552
553#[derive(Debug, Clone)]
554struct RunningMutatingJob {
555 request_id: String,
556 command: String,
557 started_at: Instant,
558}
559
560pub struct Executor {
562 inner: Arc<ExecutorInner>,
563}
564
565impl Executor {
566 pub fn new() -> Self {
567 Self::with_config(ExecutorConfig::default())
568 }
569
570 pub fn with_config(config: ExecutorConfig) -> Self {
571 let effective = config.effective();
572 let state = Arc::new(Mutex::new(SchedulerState::new(effective.clone())));
573 let heavy = Arc::new(HeavySemaphore::new(effective.heavy_permits));
574 let nonrunnable_dispatches = Arc::new(AtomicUsize::new(0));
575 let completed_interactive = Arc::new(AtomicU64::new(0));
576 let completed_maintenance = Arc::new(AtomicU64::new(0));
577 let dispatch_liveness = Arc::new(DispatchLivenessAtomics::new());
578 let (run_tx, run_rx) = crossbeam_channel::unbounded();
579 let (event_tx, event_rx) = crossbeam_channel::unbounded();
580
581 let scheduler_state = Arc::clone(&state);
582 let scheduler_heavy = Arc::clone(&heavy);
583 let scheduler_violations = Arc::clone(&nonrunnable_dispatches);
584 let scheduler_completed_interactive = Arc::clone(&completed_interactive);
585 let scheduler_completed_maintenance = Arc::clone(&completed_maintenance);
586 let scheduler_dispatch_liveness = Arc::clone(&dispatch_liveness);
587 let scheduler_handle = thread::Builder::new()
588 .name("aft-executor-scheduler".to_string())
589 .spawn(move || {
590 scheduler_loop(
591 scheduler_state,
592 scheduler_heavy,
593 run_tx,
594 event_rx,
595 scheduler_violations,
596 scheduler_completed_interactive,
597 scheduler_completed_maintenance,
598 scheduler_dispatch_liveness,
599 );
600 })
601 .expect("spawn AFT executor scheduler");
602
603 let mut worker_handles = Vec::with_capacity(effective.pool_size);
604 for worker_id in 0..effective.pool_size {
605 let worker_rx = run_rx.clone();
606 let worker_events = event_tx.clone();
607 let handle = thread::Builder::new()
608 .name(format!("aft-executor-worker-{worker_id}"))
609 .spawn(move || worker_loop(worker_rx, worker_events))
610 .expect("spawn AFT executor worker");
611 worker_handles.push(handle);
612 }
613
614 Self {
615 inner: Arc::new(ExecutorInner {
616 state,
617 event_tx,
618 scheduler_handle: Mutex::new(Some(scheduler_handle)),
619 worker_handles: Mutex::new(worker_handles),
620 config: effective,
621 nonrunnable_dispatches,
622 completed_interactive,
623 completed_maintenance,
624 dispatch_liveness,
625 }),
626 }
627 }
628
629 pub fn register_actor(&self, root_id: ProjectRootId, ctx: Arc<AppContext>) -> bool {
636 let memory_root = root_id.as_path().to_path_buf();
637 let inserted = {
638 let mut state = self.inner.state.lock();
639 if state.actors.contains_key(&root_id) {
640 false
641 } else {
642 state.actor_order.push(root_id.clone());
643 state
644 .actors
645 .insert(root_id, ActorState::new(Arc::clone(&ctx)));
646 true
647 }
648 };
649 if inserted {
650 let app = ctx.app();
651 crate::root_cache::register_live_scope(&ctx.storage_dir(), &memory_root);
652 app.register_memory_context(memory_root, &ctx);
653 app.actor_root_registered();
654 }
655 self.wake_scheduler();
656 inserted
657 }
658
659 pub fn remove_actor(&self, root_id: &ProjectRootId) {
667 let removed = {
668 let mut state = self.inner.state.lock();
669 state.actor_order.retain(|actor_root| actor_root != root_id);
670 state.actors.remove(root_id)
671 };
672 if let Some(actor) = removed.as_ref() {
673 let app = actor.ctx.app();
674 crate::root_cache::unregister_live_scope(&actor.ctx.storage_dir(), root_id.as_path());
675 app.unregister_memory_context(root_id.as_path(), &actor.ctx);
676 app.actor_root_unregistered();
677 }
678 drop(removed);
679 self.wake_scheduler();
680 }
681
682 pub fn actor_is_idle(&self, root_id: &ProjectRootId) -> bool {
684 let state = self.inner.state.lock();
685 state.actors.get(root_id).is_some_and(ActorState::is_idle)
686 }
687
688 pub fn try_actor_is_idle(&self, root_id: &ProjectRootId) -> Option<bool> {
692 let state = self.inner.state.try_lock()?;
693 Some(state.actors.get(root_id).is_some_and(ActorState::is_idle))
694 }
695
696 pub fn retire_idle_actor_in_background(&self, root_id: &ProjectRootId) -> bool {
700 let removed = {
701 let mut state = self.inner.state.lock();
702 if !state.actors.get(root_id).is_some_and(ActorState::is_idle) {
703 return false;
704 }
705 state.actor_order.retain(|actor_root| actor_root != root_id);
706 state.actors.remove(root_id)
707 };
708 let Some(actor) = removed else {
709 return false;
710 };
711 let app = actor.ctx.app();
712 crate::root_cache::unregister_live_scope(&actor.ctx.storage_dir(), root_id.as_path());
713 app.unregister_memory_context(root_id.as_path(), &actor.ctx);
714 app.actor_root_unregistered();
715 std::thread::spawn(move || {
716 actor.ctx.teardown_deleted_root();
717 drop(actor);
718 });
719 self.wake_scheduler();
720 true
721 }
722
723 pub fn cancel_queued_maintenance(&self, root_id: &ProjectRootId) -> usize {
729 let cancelled = {
730 let mut state = self.inner.state.lock();
731 state
732 .actors
733 .get_mut(root_id)
734 .map(|actor| actor.maintenance.cancel_queued_jobs())
735 .unwrap_or(0)
736 };
737 if cancelled > 0 {
738 self.wake_scheduler();
739 }
740 cancelled
741 }
742
743 pub fn actor_registered(&self, root_id: &ProjectRootId) -> bool {
745 let state = self.inner.state.lock();
746 state.actors.contains_key(root_id)
747 }
748
749 pub fn actor_context(&self, root_id: &ProjectRootId) -> Option<Arc<AppContext>> {
752 let state = self.inner.state.lock();
753 state
754 .actors
755 .get(root_id)
756 .map(|actor| Arc::clone(&actor.ctx))
757 }
758
759 pub fn actor_contexts(&self) -> Vec<Arc<AppContext>> {
764 let state = self.inner.state.lock();
765 state
766 .actors
767 .values()
768 .map(|actor_state| Arc::clone(&actor_state.ctx))
769 .collect()
770 }
771
772 pub fn actor_entries(&self) -> Vec<(ProjectRootId, Arc<AppContext>)> {
774 let state = self.inner.state.lock();
775 state
776 .actors
777 .iter()
778 .map(|(root_id, actor_state)| (root_id.clone(), Arc::clone(&actor_state.ctx)))
779 .collect()
780 }
781
782 pub fn try_actor_entries(&self) -> Option<Vec<(ProjectRootId, Arc<AppContext>)>> {
786 let state = self.inner.state.try_lock()?;
787 Some(
788 state
789 .actors
790 .iter()
791 .map(|(root_id, actor_state)| (root_id.clone(), Arc::clone(&actor_state.ctx)))
792 .collect(),
793 )
794 }
795
796 pub fn try_actor_count(&self) -> Option<usize> {
798 self.inner.state.try_lock().map(|state| state.actors.len())
799 }
800
801 pub fn submit(
802 &self,
803 root_id: ProjectRootId,
804 lane: Lane,
805 request_id: String,
806 job: ExecutorJob,
807 ) -> CompletionHandle {
808 let (completion_tx, completion_rx) = crossbeam_channel::bounded(1);
809 self.submit_with_completion(
810 root_id,
811 JobClass::Interactive,
812 lane,
813 request_id,
814 job,
815 CompletionSender::Sync(completion_tx),
816 );
817 CompletionHandle { rx: completion_rx }
818 }
819
820 pub fn submit_async(
821 &self,
822 root_id: ProjectRootId,
823 lane: Lane,
824 request_id: String,
825 job: ExecutorJob,
826 ) -> oneshot::Receiver<Response> {
827 let (completion_tx, completion_rx) = oneshot::channel();
828 self.submit_with_completion(
829 root_id,
830 JobClass::Interactive,
831 lane,
832 request_id,
833 job,
834 CompletionSender::Async(completion_tx),
835 );
836 completion_rx
837 }
838
839 pub fn submit_cancellable_async(
845 &self,
846 root_id: ProjectRootId,
847 lane: Lane,
848 request_id: String,
849 job: ExecutorJob,
850 ) -> (oneshot::Receiver<Response>, JobCancellation) {
851 let cancellation = JobCancellation::new();
852 let (completion_tx, completion_rx) = oneshot::channel();
853 self.submit_with_completion_cancellable(
854 root_id,
855 JobClass::Interactive,
856 lane,
857 request_id,
858 job,
859 CompletionSender::Async(completion_tx),
860 Some(cancellation.clone()),
861 None,
862 );
863 (completion_rx, cancellation)
864 }
865
866 pub fn cancel_job(&self, root_id: &ProjectRootId, token: &JobCancellation) -> JobCancelOutcome {
872 let observed = token.signal_cancel();
879 let (outcome, settled) = {
880 let mut state = self.inner.state.lock();
881 let Some(actor) = state.actors.get_mut(root_id) else {
882 return match observed {
883 JOB_CANCEL_STATE_COMMITTED => JobCancelOutcome::RunningCommitted,
884 JOB_CANCEL_STATE_RUNNING | JOB_CANCEL_STATE_PENDING => {
885 JobCancelOutcome::RunningSignalled
886 }
887 _ => JobCancelOutcome::NotFound,
888 };
889 };
890 match actor.remove_queued_cancellable(token) {
891 Some(queued) => (JobCancelOutcome::QueuedRemoved, Some(queued)),
892 None => match observed {
893 JOB_CANCEL_STATE_COMMITTED => (JobCancelOutcome::RunningCommitted, None),
895 JOB_CANCEL_STATE_RUNNING | JOB_CANCEL_STATE_PENDING => {
899 (JobCancelOutcome::RunningSignalled, None)
900 }
901 _ => (JobCancelOutcome::NotFound, None),
903 },
904 }
905 };
906 if let Some(queued) = settled {
907 queued.completion.send(Response::error(
908 queued.request_id,
909 "request_cancelled",
910 "request cancelled before execution",
911 ));
912 self.wake_scheduler();
913 }
914 outcome
915 }
916
917 pub fn submit_maintenance_async(
918 &self,
919 root_id: ProjectRootId,
920 lane: Lane,
921 request_id: String,
922 job: ExecutorJob,
923 ) -> oneshot::Receiver<Response> {
924 self.submit_maintenance_async_with_key(root_id, lane, request_id, job, None)
925 }
926
927 pub(crate) fn submit_coalescable_maintenance_async(
928 &self,
929 root_id: ProjectRootId,
930 lane: Lane,
931 request_id: String,
932 coalesce_key: MaintenanceCoalesceKey,
933 job: ExecutorJob,
934 ) -> oneshot::Receiver<Response> {
935 self.submit_maintenance_async_with_key(root_id, lane, request_id, job, Some(coalesce_key))
936 }
937
938 fn submit_maintenance_async_with_key(
939 &self,
940 root_id: ProjectRootId,
941 lane: Lane,
942 request_id: String,
943 job: ExecutorJob,
944 coalesce_key: Option<MaintenanceCoalesceKey>,
945 ) -> oneshot::Receiver<Response> {
946 let (completion_tx, completion_rx) = oneshot::channel();
947 self.submit_with_completion_cancellable(
948 root_id,
949 JobClass::Maintenance,
950 lane,
951 request_id,
952 job,
953 CompletionSender::Async(completion_tx),
954 None,
955 coalesce_key,
956 );
957 completion_rx
958 }
959
960 fn submit_with_completion(
961 &self,
962 root_id: ProjectRootId,
963 job_class: JobClass,
964 lane: Lane,
965 request_id: String,
966 job: ExecutorJob,
967 completion: CompletionSender,
968 ) {
969 self.submit_with_completion_cancellable(
970 root_id, job_class, lane, request_id, job, completion, None, None,
971 );
972 }
973
974 #[allow(clippy::too_many_arguments)]
975 fn submit_with_completion_cancellable(
976 &self,
977 root_id: ProjectRootId,
978 job_class: JobClass,
979 lane: Lane,
980 request_id: String,
981 job: ExecutorJob,
982 completion: CompletionSender,
983 cancellation: Option<JobCancellation>,
984 maintenance_coalesce_key: Option<MaintenanceCoalesceKey>,
985 ) {
986 let command = job_command(job_class, lane);
987 let mut job = Some(job);
988 let mut completion = Some(completion);
989 let mut duplicate_victims = Vec::new();
990
991 let response = {
992 let mut state = self.inner.state.lock();
993 match state.actors.get_mut(&root_id) {
994 Some(actor) if actor.fatal => Some(actor_fatal_response(request_id.clone())),
995 Some(actor) => {
996 let mut admission_error = None;
997 if job_class == JobClass::Maintenance {
998 if maintenance_coalesce_key
999 .is_some_and(|key| actor.maintenance.has_maintenance_coalesce_key(key))
1000 {
1001 admission_error = Some(maintenance_cancelled_response(
1002 request_id.clone(),
1003 "maintenance drain coalesced behind an identical queued drain",
1004 ));
1005 } else {
1006 if actor.maintenance.queued_count() >= MAINTENANCE_QUEUE_CAP {
1007 duplicate_victims =
1008 actor.maintenance.remove_duplicate_maintenance_jobs();
1009 }
1010 if actor.maintenance.queued_count() >= MAINTENANCE_QUEUE_CAP {
1011 admission_error =
1012 Some(maintenance_backpressure_response(request_id.clone()));
1013 }
1014 }
1015 }
1016
1017 if admission_error.is_none() {
1018 actor.push_job(
1019 job_class,
1020 lane,
1021 QueuedJob {
1022 job: job.take().expect("executor job already queued"),
1023 completion: completion
1024 .take()
1025 .expect("executor completion already queued"),
1026 request_id: request_id.clone(),
1027 command,
1028 queued_at: Instant::now(),
1029 cancellation: cancellation.clone(),
1030 maintenance_coalesce_key,
1031 },
1032 );
1033 }
1034 admission_error
1035 }
1036 None => Some(Response::error(
1037 request_id.clone(),
1038 "actor_not_registered",
1039 "executor actor is not registered",
1040 )),
1041 }
1042 };
1043
1044 for victim in duplicate_victims {
1045 victim.completion.send(maintenance_cancelled_response(
1046 victim.request_id,
1047 "duplicate maintenance drain removed to preserve queue capacity",
1048 ));
1049 }
1050
1051 if let Some(response) = response {
1052 if let Some(completion) = completion {
1053 completion.send(response);
1054 }
1055 return;
1056 }
1057
1058 self.wake_scheduler();
1059 }
1060
1061 pub fn pool_size(&self) -> usize {
1062 self.inner.config.pool_size
1063 }
1064
1065 pub fn actor_cap(&self) -> usize {
1066 self.inner.config.actor_cap
1067 }
1068
1069 pub fn read_cap(&self) -> usize {
1070 self.inner.config.read_cap
1071 }
1072
1073 pub fn heavy_permits(&self) -> usize {
1074 self.inner.config.heavy_permits
1075 }
1076
1077 pub fn interactive_reserve(&self) -> usize {
1078 self.inner.config.interactive_reserve
1079 }
1080
1081 pub fn maintenance_cap(&self) -> usize {
1082 self.inner.config.maintenance_cap
1083 }
1084
1085 pub fn try_dispatch_liveness_snapshot(&self) -> Option<DispatchLivenessSnapshot> {
1086 Some(self.inner.dispatch_liveness.snapshot(&self.inner.config))
1087 }
1088
1089 pub fn try_mutating_lane_snapshots(&self) -> Option<Vec<MutatingLaneSnapshot>> {
1090 self.inner
1091 .state
1092 .try_lock()
1093 .map(|state| state.mutating_lane_snapshots())
1094 }
1095
1096 pub fn try_mutating_job_state_label(
1097 &self,
1098 root_id: &ProjectRootId,
1099 request_id: &str,
1100 ) -> Option<&'static str> {
1101 self.inner
1102 .state
1103 .try_lock()
1104 .map(|state| state.mutating_job_state_label(root_id, request_id))
1105 }
1106
1107 pub fn try_bind_blocker_snapshot(
1111 &self,
1112 root_id: &ProjectRootId,
1113 request_id: &str,
1114 ) -> Option<BindBlockerSnapshot> {
1115 self.inner
1116 .state
1117 .try_lock()
1118 .map(|state| state.bind_blocker_snapshot(root_id, request_id))
1119 }
1120
1121 pub(crate) fn try_take_long_running_interactive_jobs(
1125 &self,
1126 minimum_age: Duration,
1127 ) -> Option<Vec<LongRunningInteractiveJobSnapshot>> {
1128 let now = Instant::now();
1129 let mut state = self.inner.state.try_lock()?;
1130 let mut snapshots = state
1131 .running_jobs
1132 .values_mut()
1133 .filter_map(|job| {
1134 let age = now.saturating_duration_since(job.started_at);
1135 if job.job_class != JobClass::Interactive
1136 || job.occupancy_reported
1137 || age < minimum_age
1138 {
1139 return None;
1140 }
1141 job.occupancy_reported = true;
1142 Some(LongRunningInteractiveJobSnapshot {
1143 root_id: job.root_id.clone(),
1144 request_id: job.request_id.clone(),
1145 command: job.command.clone(),
1146 lane: job.lane,
1147 age,
1148 })
1149 })
1150 .collect::<Vec<_>>();
1151 snapshots.sort_by(|left, right| {
1152 left.root_id
1153 .as_path()
1154 .cmp(right.root_id.as_path())
1155 .then_with(|| left.request_id.cmp(&right.request_id))
1156 });
1157 Some(snapshots)
1158 }
1159
1160 pub fn nonrunnable_dispatch_count(&self) -> usize {
1161 self.inner.nonrunnable_dispatches.load(Ordering::Acquire)
1162 }
1163
1164 pub fn completion_counts(&self) -> (u64, u64) {
1165 (
1166 self.inner.completed_interactive.load(Ordering::Relaxed),
1167 self.inner.completed_maintenance.load(Ordering::Relaxed),
1168 )
1169 }
1170
1171 pub fn actor_is_fatal(&self, root_id: &ProjectRootId) -> bool {
1172 self.inner
1173 .state
1174 .lock()
1175 .actors
1176 .get(root_id)
1177 .map(|actor| actor.fatal)
1178 .unwrap_or(false)
1179 }
1180
1181 fn wake_scheduler(&self) {
1182 let _ = self.inner.event_tx.send(SchedulerEvent::Wake);
1183 }
1184}
1185
1186impl Default for Executor {
1187 fn default() -> Self {
1188 Self::new()
1189 }
1190}
1191
1192struct ExecutorInner {
1193 state: Arc<Mutex<SchedulerState>>,
1194 event_tx: Sender<SchedulerEvent>,
1195 scheduler_handle: Mutex<Option<JoinHandle<()>>>,
1196 worker_handles: Mutex<Vec<JoinHandle<()>>>,
1197 config: EffectiveConfig,
1198 nonrunnable_dispatches: Arc<AtomicUsize>,
1199 completed_interactive: Arc<AtomicU64>,
1200 completed_maintenance: Arc<AtomicU64>,
1201 dispatch_liveness: Arc<DispatchLivenessAtomics>,
1202}
1203
1204impl Drop for ExecutorInner {
1205 fn drop(&mut self) {
1206 let _ = self.event_tx.send(SchedulerEvent::Shutdown);
1207
1208 if let Some(handle) = self.scheduler_handle.lock().take() {
1209 let _ = handle.join();
1210 }
1211
1212 let mut workers = self.worker_handles.lock();
1213 for handle in workers.drain(..) {
1214 let _ = handle.join();
1215 }
1216 }
1217}
1218
1219struct SchedulerState {
1220 actors: HashMap<ProjectRootId, ActorState>,
1221 actor_order: Vec<ProjectRootId>,
1222 cursor: usize,
1223 idle_workers: usize,
1224 interactive_inflight: usize,
1225 maintenance_inflight: usize,
1226 config: EffectiveConfig,
1227 running_jobs: HashMap<(ProjectRootId, String), RunningJob>,
1228}
1229
1230impl SchedulerState {
1231 fn new(config: EffectiveConfig) -> Self {
1232 Self {
1233 actors: HashMap::new(),
1234 actor_order: Vec::new(),
1235 cursor: 0,
1236 idle_workers: config.pool_size,
1237 interactive_inflight: 0,
1238 maintenance_inflight: 0,
1239 config,
1240 running_jobs: HashMap::new(),
1241 }
1242 }
1243
1244 fn dispatch_liveness_snapshot(&self) -> DispatchLivenessSnapshot {
1245 let now = Instant::now();
1246 let mut interactive = QueueSnapshotAccumulator::default();
1247 let mut maintenance = QueueSnapshotAccumulator::default();
1248 for actor in self.actors.values() {
1249 interactive.add(actor.class_queues(JobClass::Interactive), now);
1250 maintenance.add(actor.class_queues(JobClass::Maintenance), now);
1251 }
1252
1253 DispatchLivenessSnapshot {
1254 interactive: interactive.finish(),
1255 maintenance: maintenance.finish(),
1256 running: DispatchRunningSnapshot {
1257 interactive: self.interactive_inflight,
1258 maintenance: self.maintenance_inflight,
1259 },
1260 interactive_reserve: self.config.interactive_reserve,
1261 maintenance_cap: self.config.maintenance_cap,
1262 }
1263 }
1264
1265 fn mutating_lane_snapshots(&self) -> Vec<MutatingLaneSnapshot> {
1266 let now = Instant::now();
1267 let mut snapshots: Vec<_> = self
1268 .actors
1269 .iter()
1270 .filter_map(|(root_id, actor)| {
1271 actor
1272 .mutating_inflight
1273 .as_ref()
1274 .map(|job| MutatingLaneSnapshot {
1275 root_id: root_id.clone(),
1276 request_id: job.request_id.clone(),
1277 command: job.command.clone(),
1278 started_age_ms: duration_millis_u64(
1279 now.saturating_duration_since(job.started_at),
1280 ),
1281 })
1282 })
1283 .collect();
1284 snapshots.sort_by(|left, right| left.root_id.as_path().cmp(right.root_id.as_path()));
1285 snapshots
1286 }
1287
1288 fn mutating_job_state_label(&self, root_id: &ProjectRootId, request_id: &str) -> &'static str {
1289 let Some(actor) = self.actors.get(root_id) else {
1290 return "actor_missing";
1291 };
1292 if actor
1293 .mutating_inflight
1294 .as_ref()
1295 .is_some_and(|job| job.request_id == request_id)
1296 {
1297 return "running";
1298 }
1299 if actor.has_queued_mutating_job(request_id) {
1300 return "queued";
1301 }
1302 if actor.writer_inflight {
1303 return "blocked_by_other_mutating";
1304 }
1305 "not_found"
1306 }
1307
1308 fn bind_blocker_snapshot(
1309 &self,
1310 root_id: &ProjectRootId,
1311 request_id: &str,
1312 ) -> BindBlockerSnapshot {
1313 let configure_state = self.mutating_job_state_label(root_id, request_id);
1314 let now = Instant::now();
1315 let actor = self.actors.get(root_id);
1316 let oldest_queued_writer_at = actor.and_then(ActorState::oldest_queued_writer_at);
1317 let oldest_queued_writer_age_ms = oldest_queued_writer_at
1318 .map(|queued_at| duration_millis_u64(now.saturating_duration_since(queued_at)));
1319 let mut in_flight_readers = self
1320 .running_jobs
1321 .values()
1322 .filter(|job| {
1323 job.root_id == *root_id
1324 && matches!(job.lane, Lane::PureRead | Lane::SerialLspStatus)
1325 })
1326 .map(|job| BindBlockerReaderSnapshot {
1327 request_id: job.request_id.clone(),
1328 command: job.command.clone(),
1329 lane: job.lane,
1330 started_age_ms: duration_millis_u64(now.saturating_duration_since(job.started_at)),
1331 started_before_oldest_writer: oldest_queued_writer_at
1332 .is_some_and(|queued_at| job.started_at <= queued_at),
1333 })
1334 .collect::<Vec<_>>();
1335 in_flight_readers.sort_by(|left, right| left.request_id.cmp(&right.request_id));
1336
1337 let mut blockers = Vec::new();
1338 if let Some(actor) = actor {
1339 if configure_state == "queued" {
1340 let configure_count = actor.pending_configure_count();
1341 if configure_count > 0 {
1342 blockers.push(format!("queued_behind_configure({configure_count})"));
1343 }
1344 if actor.read_inflight > 0 || actor.lsp_inflight {
1345 let stuck_readers = in_flight_readers
1346 .iter()
1347 .filter(|reader| {
1348 reader.started_age_ms >= duration_millis_u64(READER_STUCK_CENSUS_AGE)
1349 })
1350 .map(format_bind_blocker_reader)
1351 .collect::<Vec<_>>();
1352 if stuck_readers.is_empty() {
1353 blockers.push("waiting_on_readers".to_string());
1354 } else {
1355 blockers.push(format!(
1356 "waiting_on_readers_stuck({})",
1357 stuck_readers.join("; ")
1358 ));
1359 }
1360 }
1361 }
1362 }
1363
1364 if configure_state == "queued" {
1365 let maintenance: Vec<_> = self
1366 .running_jobs
1367 .values()
1368 .filter(|job| job.job_class == JobClass::Maintenance)
1369 .collect();
1370 if !maintenance.is_empty() {
1371 blockers.push(format!(
1372 "queued_behind_maintenance({})",
1373 format_running_jobs(&maintenance)
1374 ));
1375 }
1376 }
1377
1378 if self.idle_workers == 0 {
1379 let running: Vec<_> = self.running_jobs.values().collect();
1380 blockers.push(format!(
1381 "idle_workers==0({})",
1382 format_running_jobs(&running)
1383 ));
1384 }
1385
1386 BindBlockerSnapshot {
1387 configure_state,
1388 configure_phase_timings: actor.map(|actor| actor.ctx.configure_ack_phase_snapshot()),
1389 blockers,
1390 oldest_queued_writer_age_ms,
1391 in_flight_readers,
1392 reader_admissions_while_promoted_writer_waited: actor
1393 .map(|actor| actor.reader_admissions_while_promoted_writer_waited)
1394 .unwrap_or(0),
1395 }
1396 }
1397}
1398
1399fn is_configure_request(request_id: &str) -> bool {
1400 request_id.starts_with("subc-bind-")
1401}
1402
1403fn format_bind_blocker_reader(reader: &BindBlockerReaderSnapshot) -> String {
1404 format!(
1405 "job={} command={} lane={:?} age_ms={} started_before_oldest_writer={}",
1406 reader.request_id,
1407 reader.command,
1408 reader.lane,
1409 reader.started_age_ms,
1410 reader.started_before_oldest_writer
1411 )
1412}
1413
1414fn format_running_jobs(jobs: &[&RunningJob]) -> String {
1415 let now = Instant::now();
1416 let mut labels: Vec<_> = jobs
1417 .iter()
1418 .map(|job| {
1419 format!(
1420 "job={} command={} lane={:?} root={} age_ms={}",
1421 job.request_id,
1422 job.command,
1423 job.lane,
1424 job.root_id.as_path().display(),
1425 duration_millis_u64(now.saturating_duration_since(job.started_at))
1426 )
1427 })
1428 .collect();
1429 labels.sort();
1430 labels.truncate(4);
1431 labels.join("; ")
1432}
1433
1434#[derive(Default)]
1435struct QueueSnapshotAccumulator {
1436 queued: usize,
1437 oldest_age_ms: Option<u64>,
1438}
1439
1440impl QueueSnapshotAccumulator {
1441 fn add(&mut self, queues: &ClassQueues, now: Instant) {
1442 self.queued += queues.queued_count();
1443 if let Some(queued_at) = queues.oldest_queued_at() {
1444 let age_ms = duration_millis_u64(now.saturating_duration_since(queued_at));
1445 self.oldest_age_ms = Some(
1446 self.oldest_age_ms
1447 .map_or(age_ms, |oldest| oldest.max(age_ms)),
1448 );
1449 }
1450 }
1451
1452 fn finish(self) -> DispatchClassQueueSnapshot {
1453 DispatchClassQueueSnapshot {
1454 queued: self.queued,
1455 oldest_age_ms: self.oldest_age_ms,
1456 }
1457 }
1458}
1459
1460fn duration_millis_u64(duration: Duration) -> u64 {
1461 duration.as_millis().min(u128::from(u64::MAX)) as u64
1462}
1463
1464struct ActorState {
1465 ctx: Arc<AppContext>,
1466 epoch: Arc<RwLock<()>>,
1467 read_inflight: usize,
1468 lsp_inflight: bool,
1469 actor_total_inflight: usize,
1470 writer_inflight: bool,
1471 maintenance_commit_inflight: bool,
1472 mutating_inflight: Option<RunningMutatingJob>,
1473 reader_admissions_while_promoted_writer_waited: u64,
1474 deficit: isize,
1475 interactive: ClassQueues,
1476 maintenance: ClassQueues,
1477 fatal: bool,
1478}
1479
1480impl ActorState {
1481 fn new(ctx: Arc<AppContext>) -> Self {
1482 Self {
1483 ctx,
1484 epoch: Arc::new(RwLock::new(())),
1485 read_inflight: 0,
1486 lsp_inflight: false,
1487 actor_total_inflight: 0,
1488 writer_inflight: false,
1489 maintenance_commit_inflight: false,
1490 mutating_inflight: None,
1491 reader_admissions_while_promoted_writer_waited: 0,
1492 deficit: 0,
1493 interactive: ClassQueues::new(),
1494 maintenance: ClassQueues::new(),
1495 fatal: false,
1496 }
1497 }
1498
1499 fn push_job(&mut self, job_class: JobClass, lane: Lane, job: QueuedJob) {
1500 self.class_queues_mut(job_class).push_job(lane, job);
1501 }
1502
1503 fn has_queued_jobs(&self) -> bool {
1504 self.interactive.has_queued_jobs() || self.maintenance.has_queued_jobs()
1505 }
1506
1507 fn is_idle(&self) -> bool {
1508 self.actor_total_inflight == 0 && !self.has_queued_jobs()
1509 }
1510
1511 fn has_queued_jobs_for(&self, job_class: JobClass) -> bool {
1512 self.class_queues(job_class).has_queued_jobs()
1513 }
1514
1515 fn front_lane(&self, job_class: JobClass) -> Option<Lane> {
1516 self.class_queues(job_class).front_lane()
1517 }
1518
1519 fn pop_front_job(&mut self, job_class: JobClass, lane: Lane) -> Option<QueuedJob> {
1520 self.class_queues_mut(job_class).pop_front_job(lane)
1521 }
1522
1523 fn higher_priority_writer_barrier_blocks(&self, job_class: JobClass) -> bool {
1524 matches!(job_class, JobClass::Maintenance)
1528 && !self.interactive.queue(Lane::Mutating).is_empty()
1529 }
1530
1531 fn class_queues(&self, job_class: JobClass) -> &ClassQueues {
1532 match job_class {
1533 JobClass::Interactive => &self.interactive,
1534 JobClass::Maintenance => &self.maintenance,
1535 }
1536 }
1537
1538 fn class_queues_mut(&mut self, job_class: JobClass) -> &mut ClassQueues {
1539 match job_class {
1540 JobClass::Interactive => &mut self.interactive,
1541 JobClass::Maintenance => &mut self.maintenance,
1542 }
1543 }
1544
1545 fn fail_queued_jobs(&mut self) {
1546 self.interactive.fail_queued_jobs();
1547 self.maintenance.fail_queued_jobs();
1548 }
1549
1550 fn has_queued_mutating_job(&self, request_id: &str) -> bool {
1551 self.interactive.has_queued_mutating_job(request_id)
1552 || self.maintenance.has_queued_mutating_job(request_id)
1553 }
1554
1555 fn remove_queued_cancellable(&mut self, token: &JobCancellation) -> Option<QueuedJob> {
1556 self.interactive
1557 .remove_cancellable(token)
1558 .or_else(|| self.maintenance.remove_cancellable(token))
1559 }
1560
1561 fn oldest_queued_writer_at(&self) -> Option<Instant> {
1562 [
1563 self.interactive.queue(Lane::Mutating).front(),
1564 self.maintenance.queue(Lane::Mutating).front(),
1565 ]
1566 .into_iter()
1567 .flatten()
1568 .map(|job| job.queued_at)
1569 .min()
1570 }
1571
1572 fn pending_configure_count(&self) -> usize {
1573 usize::from(
1574 self.mutating_inflight
1575 .as_ref()
1576 .is_some_and(|job| is_configure_request(&job.request_id)),
1577 ) + self.interactive.queued_configure_count()
1578 + self.maintenance.queued_configure_count()
1579 }
1580}
1581
1582struct ClassQueues {
1583 order: VecDeque<Lane>,
1584 pure_reads: VecDeque<QueuedJob>,
1585 lsp_status: VecDeque<QueuedJob>,
1586 heavy_init: VecDeque<QueuedJob>,
1587 mutating: VecDeque<QueuedJob>,
1588 maintenance_commit: VecDeque<QueuedJob>,
1589}
1590
1591impl ClassQueues {
1592 fn new() -> Self {
1593 Self {
1594 order: VecDeque::new(),
1595 pure_reads: VecDeque::new(),
1596 lsp_status: VecDeque::new(),
1597 heavy_init: VecDeque::new(),
1598 mutating: VecDeque::new(),
1599 maintenance_commit: VecDeque::new(),
1600 }
1601 }
1602
1603 fn push_job(&mut self, lane: Lane, job: QueuedJob) {
1604 self.order.push_back(lane);
1605 self.queue_mut(lane).push_back(job);
1606 }
1607
1608 fn has_queued_jobs(&self) -> bool {
1609 !self.order.is_empty()
1610 }
1611
1612 fn front_lane(&self) -> Option<Lane> {
1613 self.order.front().copied()
1614 }
1615
1616 fn next_interactive_lane(&self, now: Instant) -> Option<Lane> {
1622 let starved_writer = self.mutating.iter().any(|job| {
1623 now.saturating_duration_since(job.queued_at) >= INTERACTIVE_WRITER_PROMOTION_AGE
1624 });
1625 if starved_writer {
1626 return Some(Lane::Mutating);
1629 }
1630 if !self.pure_reads.is_empty() {
1631 return Some(Lane::PureRead);
1632 }
1633 self.order
1634 .iter()
1635 .copied()
1636 .find(|lane| *lane != Lane::PureRead)
1637 }
1638
1639 fn pop_front_job(&mut self, lane: Lane) -> Option<QueuedJob> {
1640 let position = self.order.iter().position(|queued| *queued == lane)?;
1644 self.order.remove(position);
1645 self.queue_mut(lane).pop_front()
1646 }
1647
1648 fn queued_count(&self) -> usize {
1649 self.order.len()
1650 }
1651
1652 fn has_maintenance_coalesce_key(&self, key: MaintenanceCoalesceKey) -> bool {
1653 [
1654 Lane::PureRead,
1655 Lane::SerialLspStatus,
1656 Lane::HeavyInit,
1657 Lane::Mutating,
1658 Lane::MaintenanceCommit,
1659 ]
1660 .into_iter()
1661 .any(|lane| {
1662 self.queue(lane)
1663 .iter()
1664 .any(|job| job.maintenance_coalesce_key == Some(key))
1665 })
1666 }
1667
1668 fn remove_duplicate_maintenance_jobs(&mut self) -> Vec<QueuedJob> {
1670 let mut seen = HashSet::new();
1671 let mut lane_positions = [0usize; 5];
1672 let mut duplicates = Vec::new();
1673
1674 for (order_position, lane) in self.order.iter().copied().enumerate() {
1675 let lane_index = lane_index(lane);
1676 let queue_position = lane_positions[lane_index];
1677 lane_positions[lane_index] += 1;
1678 let Some(key) = self
1679 .queue(lane)
1680 .get(queue_position)
1681 .and_then(|job| job.maintenance_coalesce_key)
1682 else {
1683 continue;
1684 };
1685 if !seen.insert(key) {
1686 duplicates.push((order_position, lane, queue_position));
1687 }
1688 }
1689
1690 let mut removed = Vec::with_capacity(duplicates.len());
1691 for (order_position, lane, queue_position) in duplicates.into_iter().rev() {
1692 self.order.remove(order_position);
1693 if let Some(job) = self.queue_mut(lane).remove(queue_position) {
1694 removed.push(job);
1695 }
1696 }
1697 removed
1698 }
1699
1700 fn oldest_queued_at(&self) -> Option<Instant> {
1701 self.front_lane()
1702 .and_then(|lane| self.queue(lane).front().map(|job| job.queued_at))
1703 }
1704
1705 fn fail_queued_jobs(&mut self) {
1706 self.order.clear();
1707 fail_queued_job_queue(&mut self.pure_reads);
1708 fail_queued_job_queue(&mut self.lsp_status);
1709 fail_queued_job_queue(&mut self.heavy_init);
1710 fail_queued_job_queue(&mut self.mutating);
1711 fail_queued_job_queue(&mut self.maintenance_commit);
1712 }
1713
1714 fn cancel_queued_jobs(&mut self) -> usize {
1715 self.order.clear();
1716 cancel_queued_job_queue(&mut self.pure_reads)
1717 + cancel_queued_job_queue(&mut self.lsp_status)
1718 + cancel_queued_job_queue(&mut self.heavy_init)
1719 + cancel_queued_job_queue(&mut self.mutating)
1720 + cancel_queued_job_queue(&mut self.maintenance_commit)
1721 }
1722
1723 fn has_queued_mutating_job(&self, request_id: &str) -> bool {
1724 self.mutating.iter().any(|job| job.request_id == request_id)
1725 }
1726
1727 fn remove_cancellable(&mut self, token: &JobCancellation) -> Option<QueuedJob> {
1736 for lane in [
1737 Lane::PureRead,
1738 Lane::SerialLspStatus,
1739 Lane::HeavyInit,
1740 Lane::Mutating,
1741 Lane::MaintenanceCommit,
1742 ] {
1743 let queue = self.queue_mut(lane);
1744 let position = queue.iter().position(|queued| {
1745 queued
1746 .cancellation
1747 .as_ref()
1748 .is_some_and(|candidate| candidate.same_token(token))
1749 });
1750 if let Some(position) = position {
1751 let removed = queue.remove(position);
1752 let mut occurrence = 0usize;
1753 if let Some(order_position) = self.order.iter().position(|entry| {
1754 if *entry != lane {
1755 return false;
1756 }
1757 let matched = occurrence == position;
1758 occurrence += 1;
1759 matched
1760 }) {
1761 self.order.remove(order_position);
1762 }
1763 return removed;
1764 }
1765 }
1766 None
1767 }
1768
1769 fn queued_configure_count(&self) -> usize {
1770 self.mutating
1771 .iter()
1772 .filter(|job| is_configure_request(&job.request_id))
1773 .count()
1774 }
1775
1776 fn queue(&self, lane: Lane) -> &VecDeque<QueuedJob> {
1777 match lane {
1778 Lane::PureRead => &self.pure_reads,
1779 Lane::SerialLspStatus => &self.lsp_status,
1780 Lane::HeavyInit => &self.heavy_init,
1781 Lane::Mutating => &self.mutating,
1782 Lane::MaintenanceCommit => &self.maintenance_commit,
1783 }
1784 }
1785
1786 fn queue_mut(&mut self, lane: Lane) -> &mut VecDeque<QueuedJob> {
1787 match lane {
1788 Lane::PureRead => &mut self.pure_reads,
1789 Lane::SerialLspStatus => &mut self.lsp_status,
1790 Lane::HeavyInit => &mut self.heavy_init,
1791 Lane::Mutating => &mut self.mutating,
1792 Lane::MaintenanceCommit => &mut self.maintenance_commit,
1793 }
1794 }
1795}
1796
1797struct QueuedJob {
1798 job: ExecutorJob,
1799 completion: CompletionSender,
1800 request_id: String,
1801 command: String,
1802 queued_at: Instant,
1803 cancellation: Option<JobCancellation>,
1804 maintenance_coalesce_key: Option<MaintenanceCoalesceKey>,
1805}
1806
1807fn lane_index(lane: Lane) -> usize {
1808 match lane {
1809 Lane::PureRead => 0,
1810 Lane::SerialLspStatus => 1,
1811 Lane::HeavyInit => 2,
1812 Lane::Mutating => 3,
1813 Lane::MaintenanceCommit => 4,
1814 }
1815}
1816
1817fn fail_queued_job_queue(queue: &mut VecDeque<QueuedJob>) {
1818 for queued in queue.drain(..) {
1819 queued
1820 .completion
1821 .send(actor_fatal_response(queued.request_id));
1822 }
1823}
1824
1825fn cancel_queued_job_queue(queue: &mut VecDeque<QueuedJob>) -> usize {
1826 let cancelled = queue.len();
1827 for queued in queue.drain(..) {
1828 queued.completion.send(Response::error(
1829 queued.request_id,
1830 "maintenance_cancelled",
1831 "maintenance cancelled because the actor has no bound routes",
1832 ));
1833 }
1834 cancelled
1835}
1836
1837fn job_command(job_class: JobClass, lane: Lane) -> String {
1838 format!("executor::{job_class:?}::{lane:?}")
1839}
1840
1841fn maintenance_cancelled_response(
1842 request_id: impl Into<String>,
1843 message: impl Into<String>,
1844) -> Response {
1845 Response::error(request_id, "maintenance_cancelled", message)
1846}
1847
1848fn maintenance_backpressure_response(request_id: impl Into<String>) -> Response {
1849 Response::error_with_data(
1850 request_id,
1851 "maintenance_backpressure",
1852 format!("maintenance queue reached its per-actor capacity of {MAINTENANCE_QUEUE_CAP} jobs"),
1853 serde_json::json!({
1854 "retryable": true,
1855 "queue_cap": MAINTENANCE_QUEUE_CAP,
1856 }),
1857 )
1858}
1859
1860fn actor_fatal_response(request_id: impl Into<String>) -> Response {
1861 Response::error(
1862 request_id,
1863 "actor_fatal",
1864 "executor actor is fatal after a mutating job panic",
1865 )
1866}
1867
1868fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String {
1869 if let Some(message) = payload.downcast_ref::<&'static str>() {
1870 (*message).to_string()
1871 } else if let Some(message) = payload.downcast_ref::<String>() {
1872 message.clone()
1873 } else {
1874 "unknown panic payload".to_string()
1875 }
1876}
1877
1878fn panic_response(
1879 request_id: impl Into<String>,
1880 command: &str,
1881 payload: &(dyn std::any::Any + Send),
1882) -> Response {
1883 let panic_message = panic_payload_message(payload);
1884 Response::error(
1885 request_id,
1886 "actor_fatal",
1887 format!("command '{command}' panicked: {panic_message}"),
1888 )
1889}
1890
1891enum CompletionSender {
1892 Sync(Sender<Response>),
1893 Async(oneshot::Sender<Response>),
1894}
1895
1896impl CompletionSender {
1897 fn send(self, response: Response) {
1898 match self {
1899 Self::Sync(tx) => {
1900 let _ = tx.send(response);
1901 }
1902 Self::Async(tx) => {
1903 let _ = tx.send(response);
1904 }
1905 }
1906 }
1907}
1908
1909struct RunJob {
1910 root_id: ProjectRootId,
1911 job_class: JobClass,
1912 lane: Lane,
1913 ctx: Arc<AppContext>,
1914 epoch: Arc<RwLock<()>>,
1915 job: ExecutorJob,
1916 completion: Option<CompletionSender>,
1917 request_id: String,
1918 command: String,
1919 heavy_permit: Option<HeavyPermit>,
1920 cancellation: Option<JobCancellation>,
1921}
1922
1923struct CompletionEvent {
1924 root_id: ProjectRootId,
1925 request_id: String,
1926 job_class: JobClass,
1927 lane: Lane,
1928 heavy_permit: Option<HeavyPermit>,
1929 panicked: bool,
1930}
1931
1932enum SchedulerEvent {
1933 Wake,
1934 Completed(CompletionEvent),
1935 Shutdown,
1936}
1937
1938fn scheduler_loop(
1939 state: Arc<Mutex<SchedulerState>>,
1940 heavy: Arc<HeavySemaphore>,
1941 run_tx: Sender<RunJob>,
1942 event_rx: Receiver<SchedulerEvent>,
1943 nonrunnable_dispatches: Arc<AtomicUsize>,
1944 completed_interactive: Arc<AtomicU64>,
1945 completed_maintenance: Arc<AtomicU64>,
1946 dispatch_liveness: Arc<DispatchLivenessAtomics>,
1947) {
1948 while let Ok(event) = event_rx.recv() {
1949 let shutdown;
1950 {
1951 let mut state = state.lock();
1952 shutdown = process_scheduler_event_batch(
1953 event,
1954 &event_rx,
1955 &mut state,
1956 &completed_interactive,
1957 &completed_maintenance,
1958 );
1959
1960 if !shutdown {
1961 dispatch_runnable(&mut state, &heavy, &run_tx, &nonrunnable_dispatches);
1962 }
1963 dispatch_liveness.record(&state.dispatch_liveness_snapshot());
1964 }
1965
1966 if shutdown {
1967 break;
1968 }
1969 }
1970}
1971
1972fn process_scheduler_event_batch(
1973 first: SchedulerEvent,
1974 event_rx: &Receiver<SchedulerEvent>,
1975 state: &mut SchedulerState,
1976 completed_interactive: &AtomicU64,
1977 completed_maintenance: &AtomicU64,
1978) -> bool {
1979 let mut event = Some(first);
1980 for _ in 0..SCHEDULER_EVENT_BATCH_CAP {
1981 let Some(current) = event.take().or_else(|| event_rx.try_recv().ok()) else {
1982 break;
1983 };
1984 note_completion_event(¤t, completed_interactive, completed_maintenance);
1985 if process_scheduler_event(current, state) {
1986 return true;
1987 }
1988 }
1989 false
1990}
1991
1992fn note_completion_event(
1993 event: &SchedulerEvent,
1994 completed_interactive: &AtomicU64,
1995 completed_maintenance: &AtomicU64,
1996) {
1997 let SchedulerEvent::Completed(event) = event else {
1998 return;
1999 };
2000 match event.job_class {
2001 JobClass::Interactive => {
2002 completed_interactive.fetch_add(1, Ordering::Relaxed);
2003 }
2004 JobClass::Maintenance => {
2005 completed_maintenance.fetch_add(1, Ordering::Relaxed);
2006 }
2007 }
2008}
2009
2010fn process_scheduler_event(event: SchedulerEvent, state: &mut SchedulerState) -> bool {
2011 match event {
2012 SchedulerEvent::Wake => false,
2013 SchedulerEvent::Completed(event) => {
2014 complete_job(state, event);
2015 false
2016 }
2017 SchedulerEvent::Shutdown => true,
2018 }
2019}
2020
2021fn complete_job(state: &mut SchedulerState, event: CompletionEvent) {
2022 let CompletionEvent {
2023 root_id,
2024 request_id,
2025 job_class,
2026 lane,
2027 heavy_permit,
2028 panicked,
2029 } = event;
2030 state.running_jobs.remove(&(root_id.clone(), request_id));
2031
2032 match job_class {
2033 JobClass::Interactive => {
2034 state.interactive_inflight = state.interactive_inflight.saturating_sub(1);
2035 }
2036 JobClass::Maintenance => {
2037 state.maintenance_inflight = state.maintenance_inflight.saturating_sub(1);
2038 }
2039 }
2040
2041 if let Some(actor) = state.actors.get_mut(&root_id) {
2042 actor.actor_total_inflight = actor.actor_total_inflight.saturating_sub(1);
2043 match lane {
2044 Lane::PureRead => {
2045 actor.read_inflight = actor.read_inflight.saturating_sub(1);
2046 }
2047 Lane::SerialLspStatus => {
2048 actor.lsp_inflight = false;
2049 }
2050 Lane::HeavyInit => {}
2051 Lane::Mutating => {
2052 actor.writer_inflight = false;
2053 actor.mutating_inflight = None;
2054 }
2055 Lane::MaintenanceCommit => {
2056 actor.maintenance_commit_inflight = false;
2057 }
2058 }
2059
2060 if panicked && lane == Lane::Mutating {
2061 actor.fatal = true;
2062 actor.fail_queued_jobs();
2063 }
2064 }
2065
2066 drop(heavy_permit);
2067 state.idle_workers += 1;
2068}
2069
2070fn dispatch_runnable(
2071 state: &mut SchedulerState,
2072 heavy: &Arc<HeavySemaphore>,
2073 run_tx: &Sender<RunJob>,
2074 nonrunnable_dispatches: &AtomicUsize,
2075) {
2076 while state.idle_workers > 0 && !state.actor_order.is_empty() {
2077 let mut made_progress = false;
2078 let mut dispatch_failed = false;
2079
2080 made_progress |= dispatch_runnable_class(
2081 state,
2082 JobClass::Interactive,
2083 heavy,
2084 run_tx,
2085 nonrunnable_dispatches,
2086 &mut dispatch_failed,
2087 );
2088 if dispatch_failed || state.idle_workers == 0 {
2089 return;
2090 }
2091
2092 if can_dispatch_class(state, JobClass::Maintenance) {
2093 made_progress |= dispatch_runnable_class(
2094 state,
2095 JobClass::Maintenance,
2096 heavy,
2097 run_tx,
2098 nonrunnable_dispatches,
2099 &mut dispatch_failed,
2100 );
2101 if dispatch_failed {
2102 return;
2103 }
2104 }
2105
2106 if !made_progress {
2107 break;
2108 }
2109 }
2110}
2111
2112fn dispatch_runnable_class(
2113 state: &mut SchedulerState,
2114 job_class: JobClass,
2115 heavy: &Arc<HeavySemaphore>,
2116 run_tx: &Sender<RunJob>,
2117 nonrunnable_dispatches: &AtomicUsize,
2118 dispatch_failed: &mut bool,
2119) -> bool {
2120 if !can_dispatch_class(state, job_class) || state.actor_order.is_empty() {
2121 return false;
2122 }
2123
2124 let actor_count = state.actor_order.len();
2125 let mut made_progress = false;
2126
2127 for _ in 0..actor_count {
2128 if !can_dispatch_class(state, job_class) || state.actor_order.is_empty() {
2129 break;
2130 }
2131
2132 if state.cursor >= state.actor_order.len() {
2133 state.cursor = 0;
2134 }
2135 let root_id = state.actor_order[state.cursor].clone();
2136 state.cursor = (state.cursor + 1) % state.actor_order.len();
2137
2138 let run_job = {
2139 let Some(actor) = state.actors.get_mut(&root_id) else {
2140 continue;
2141 };
2142
2143 if actor.fatal {
2144 actor.fail_queued_jobs();
2145 actor.deficit = 0;
2146 continue;
2147 }
2148
2149 if !actor.has_queued_jobs() {
2150 actor.deficit = 0;
2151 continue;
2152 }
2153
2154 if !actor.has_queued_jobs_for(job_class) {
2155 continue;
2156 }
2157
2158 actor.deficit =
2159 (actor.deficit + state.config.drr_quantum).min(state.config.deficit_cap);
2160 if actor.deficit < JOB_COST {
2161 continue;
2162 }
2163
2164 try_admit_actor(&root_id, actor, job_class, &state.config, heavy)
2165 };
2166
2167 if let Some(run_job) = run_job {
2168 state.running_jobs.insert(
2169 (run_job.root_id.clone(), run_job.request_id.clone()),
2170 RunningJob {
2171 root_id: run_job.root_id.clone(),
2172 request_id: run_job.request_id.clone(),
2173 command: run_job.command.clone(),
2174 job_class: run_job.job_class,
2175 lane: run_job.lane,
2176 started_at: Instant::now(),
2177 occupancy_reported: false,
2178 },
2179 );
2180 state.idle_workers -= 1;
2181 match job_class {
2182 JobClass::Interactive => state.interactive_inflight += 1,
2183 JobClass::Maintenance => state.maintenance_inflight += 1,
2184 }
2185 made_progress = true;
2186 if run_tx.send(run_job).is_err() {
2187 nonrunnable_dispatches.fetch_add(1, Ordering::AcqRel);
2188 *dispatch_failed = true;
2189 return made_progress;
2190 }
2191 }
2192 }
2193
2194 made_progress
2195}
2196
2197fn can_dispatch_class(state: &SchedulerState, job_class: JobClass) -> bool {
2198 if state.idle_workers == 0 {
2199 return false;
2200 }
2201 match job_class {
2202 JobClass::Interactive => true,
2203 JobClass::Maintenance => {
2204 state.maintenance_inflight < state.config.maintenance_cap
2205 && state.idle_workers > state.config.interactive_reserve
2206 }
2207 }
2208}
2209
2210fn try_admit_actor(
2211 root_id: &ProjectRootId,
2212 actor: &mut ActorState,
2213 job_class: JobClass,
2214 config: &EffectiveConfig,
2215 heavy: &Arc<HeavySemaphore>,
2216) -> Option<RunJob> {
2217 let lane = match job_class {
2218 JobClass::Interactive => actor
2219 .class_queues(JobClass::Interactive)
2220 .next_interactive_lane(Instant::now())?,
2221 JobClass::Maintenance => actor.front_lane(job_class)?,
2222 };
2223 let mut heavy_permit = None;
2224
2225 if actor.writer_inflight || actor.higher_priority_writer_barrier_blocks(job_class) {
2226 return None;
2227 }
2228
2229 let has_epoch_reader =
2230 actor.read_inflight > 0 || actor.lsp_inflight || actor.maintenance_commit_inflight;
2231 let actor_interactive_inflight = actor
2237 .actor_total_inflight
2238 .saturating_sub(usize::from(actor.maintenance_commit_inflight));
2239 let actor_has_interactive_capacity = actor_interactive_inflight < config.actor_cap;
2240 let runnable = match lane {
2241 Lane::PureRead => actor.read_inflight < config.read_cap && actor_has_interactive_capacity,
2242 Lane::SerialLspStatus => !actor.lsp_inflight && actor_has_interactive_capacity,
2243 Lane::HeavyInit => {
2244 if !actor_has_interactive_capacity {
2245 false
2246 } else if let Some(permit) = heavy.try_acquire() {
2247 heavy_permit = Some(permit);
2248 true
2249 } else {
2250 false
2251 }
2252 }
2253 Lane::Mutating => !has_epoch_reader && actor_has_interactive_capacity,
2254 Lane::MaintenanceCommit => !actor.maintenance_commit_inflight,
2258 };
2259
2260 if !runnable {
2261 return None;
2262 }
2263
2264 let promoted_writer_waiting = actor.oldest_queued_writer_at().is_some_and(|queued_at| {
2265 Instant::now().saturating_duration_since(queued_at) >= INTERACTIVE_WRITER_PROMOTION_AGE
2266 });
2267 if promoted_writer_waiting && matches!(lane, Lane::PureRead | Lane::SerialLspStatus) {
2268 actor.reader_admissions_while_promoted_writer_waited = actor
2269 .reader_admissions_while_promoted_writer_waited
2270 .saturating_add(1);
2271 }
2272
2273 let queued = actor.pop_front_job(job_class, lane)?;
2274 actor.deficit -= JOB_COST;
2275 if let Some(cancellation) = queued.cancellation.as_ref() {
2276 cancellation.mark_running();
2277 }
2278 if lane == Lane::Mutating {
2279 actor.mutating_inflight = Some(RunningMutatingJob {
2280 request_id: queued.request_id.clone(),
2281 command: queued.command.clone(),
2282 started_at: Instant::now(),
2283 });
2284 }
2285 match lane {
2286 Lane::PureRead => {
2287 actor.read_inflight += 1;
2288 actor.actor_total_inflight += 1;
2289 }
2290 Lane::SerialLspStatus => {
2291 actor.lsp_inflight = true;
2292 actor.actor_total_inflight += 1;
2293 }
2294 Lane::HeavyInit => {
2295 actor.actor_total_inflight += 1;
2296 }
2297 Lane::Mutating => {
2298 actor.writer_inflight = true;
2299 actor.actor_total_inflight += 1;
2300 }
2301 Lane::MaintenanceCommit => {
2302 actor.maintenance_commit_inflight = true;
2303 actor.actor_total_inflight += 1;
2304 }
2305 }
2306
2307 Some(RunJob {
2308 root_id: root_id.clone(),
2309 job_class,
2310 lane,
2311 ctx: Arc::clone(&actor.ctx),
2312 epoch: Arc::clone(&actor.epoch),
2313 job: queued.job,
2314 completion: Some(queued.completion),
2315 request_id: queued.request_id,
2316 command: queued.command,
2317 heavy_permit,
2318 cancellation: queued.cancellation,
2319 })
2320}
2321
2322fn worker_loop(run_rx: Receiver<RunJob>, event_tx: Sender<SchedulerEvent>) {
2323 while let Ok(mut run_job) = run_rx.recv() {
2324 let response =
2325 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| run_lane_job(&mut run_job)));
2326 let panicked = response.is_err();
2327 let response = match response {
2328 Ok(response) => response,
2329 Err(payload) => panic_response(
2330 run_job.request_id.clone(),
2331 &run_job.command,
2332 payload.as_ref(),
2333 ),
2334 };
2335
2336 if let Some(completion) = run_job.completion.take() {
2337 completion.send(response);
2338 }
2339 let completion = CompletionEvent {
2340 root_id: run_job.root_id,
2341 request_id: run_job.request_id,
2342 job_class: run_job.job_class,
2343 lane: run_job.lane,
2344 heavy_permit: run_job.heavy_permit.take(),
2345 panicked,
2346 };
2347 let _ = event_tx.send(SchedulerEvent::Completed(completion));
2348 }
2349}
2350
2351fn run_lane_job(run_job: &mut RunJob) -> Response {
2352 let _cancellation_ctx = JobCancellationContextGuard::install(run_job.cancellation.clone());
2353 let missing_request_id = run_job.request_id.clone();
2354 let job = std::mem::replace(
2355 &mut run_job.job,
2356 Box::new(move |_| {
2357 Response::error(
2358 missing_request_id,
2359 "job_missing",
2360 "executor job already taken",
2361 )
2362 }),
2363 );
2364
2365 match run_job.lane {
2366 Lane::PureRead | Lane::SerialLspStatus => {
2367 let _epoch = run_job.epoch.read();
2368 job(&run_job.ctx)
2369 }
2370 Lane::HeavyInit => {
2371 let response = job(&run_job.ctx);
2372 {
2373 let _install = run_job.epoch.write();
2374 }
2375 response
2376 }
2377 Lane::Mutating => {
2378 let _epoch = run_job.epoch.write();
2379 job(&run_job.ctx)
2380 }
2381 Lane::MaintenanceCommit => {
2382 let _epoch = run_job.epoch.read();
2386 job(&run_job.ctx)
2387 }
2388 }
2389}
2390
2391#[derive(Debug)]
2392struct HeavySemaphore {
2393 available: AtomicUsize,
2394 max: usize,
2395}
2396
2397impl HeavySemaphore {
2398 fn new(permits: usize) -> Self {
2399 Self {
2400 available: AtomicUsize::new(permits),
2401 max: permits,
2402 }
2403 }
2404
2405 fn try_acquire(self: &Arc<Self>) -> Option<HeavyPermit> {
2406 loop {
2407 let available = self.available.load(Ordering::Acquire);
2408 if available == 0 {
2409 return None;
2410 }
2411 if self
2412 .available
2413 .compare_exchange(
2414 available,
2415 available - 1,
2416 Ordering::AcqRel,
2417 Ordering::Acquire,
2418 )
2419 .is_ok()
2420 {
2421 return Some(HeavyPermit {
2422 semaphore: Arc::clone(self),
2423 });
2424 }
2425 }
2426 }
2427}
2428
2429struct HeavyPermit {
2430 semaphore: Arc<HeavySemaphore>,
2431}
2432
2433impl Drop for HeavyPermit {
2434 fn drop(&mut self) {
2435 let previous = self.semaphore.available.fetch_add(1, Ordering::Release);
2436 debug_assert!(previous < self.semaphore.max);
2437 }
2438}