1use crate::{backend::backend, FlamsExtension};
2
3use super::{
4 queue_manager::{QueueId, Semaphore},
5 BuildTask, BuildTaskId, Eta, QueueMessage, TaskRef, TaskState,
6};
7use flams_math_archives::{
8 artifacts::FileOrString,
9 backend::{AnyBackend, LocalBackend},
10 formats::{BuildResult, BuildTargetId, FormatOrTargets},
11 manager::ArchiveOrGroup,
12 source_files::{SourceEntry, SourceEntryRef},
13 utils::path_ext::RelPath,
14 Archive, BuildableArchive, LocallyBuilt, MathArchive,
15};
16use flams_utils::{
17 change_listener::{ChangeListener, ChangeSender},
18 prelude::HMap,
19 triomphe::Arc,
20};
21use ftml_ontology::utils::{time::Timestamp, RefTree};
22use ftml_uris::{ArchiveId, UriPath, UriWithArchive};
23use parking_lot::RwLock;
24use std::{collections::VecDeque, num::NonZeroU32};
25use tracing::{instrument, Instrument};
26
27#[derive(Debug)]
28pub(super) struct TaskMap {
29 pub(super) map: HMap<(ArchiveId, UriPath), BuildTask>,
30 pub(super) dependents: HMap<TaskRef, Vec<(BuildTask, BuildTargetId)>>,
31 pub(super) counter: NonZeroU32,
32 pub(super) total: usize,
33}
34
35impl Default for TaskMap {
36 fn default() -> Self {
37 Self {
38 map: HMap::default(),
39 dependents: HMap::default(),
40 counter: NonZeroU32::new(1).unwrap_or_else(|| unreachable!()),
41 total: 0,
42 }
43 }
44}
45
46#[derive(Debug)]
47pub enum QueueState {
48 Running(RunningQueue),
49 Idle,
50 Finished(FinishedQueue),
51}
52
53#[derive(Debug, Clone)]
54pub enum QueueName {
55 Global,
56 Sandbox { name: std::sync::Arc<str>, idx: u16 },
57}
58impl std::fmt::Display for QueueName {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 Self::Global => f.write_str("global"),
62 Self::Sandbox { name, idx } => {
63 f.write_str(name)?;
64 idx.fmt(f)
65 }
66 }
67 }
68}
69
70#[derive(Debug)]
71pub(super) struct QueueI {
72 backend: AnyBackend,
73 name: QueueName,
74 pub id: QueueId,
75 span: tracing::Span,
76 pub(super) map: RwLock<TaskMap>,
77 pub(super) sender: ChangeSender<QueueMessage>,
78 pub(super) state: RwLock<QueueState>,
79}
80
81#[derive(Debug, Clone)]
82pub struct Queue(pub(super) Arc<QueueI>);
83
84impl Queue {
85 pub(crate) fn new(id: QueueId, name: QueueName, backend: AnyBackend) -> Self {
86 Self(Arc::new(QueueI {
87 id,
88 name,
89 backend,
90 span: tracing::Span::current(),
91 map: RwLock::default(),
92 sender: ChangeSender::new(32),
93 state: RwLock::new(QueueState::Idle),
94 }))
95 }
96
97 #[inline]
98 #[must_use]
99 pub fn backend(&self) -> &AnyBackend {
100 &self.0.backend
101 }
102
103 #[must_use]
104 pub fn listener(&self) -> ChangeListener<QueueMessage> {
105 self.0.sender.listener()
106 }
107
108 #[instrument(level="info",parent=&self.0.span,skip_all,name="Collecting queue state")]
109 pub fn state_message(&self) -> QueueMessage {
110 match &*self.0.state.read() {
111 QueueState::Running(RunningQueue {
112 running,
113 queue,
114 blocked,
115 failed,
116 done,
117 ..
118 }) => QueueMessage::Started {
119 running: running.iter().map(BuildTask::as_message).collect(),
120 queue: queue.iter().map(BuildTask::as_message).collect(),
121 blocked: blocked.iter().map(BuildTask::as_message).collect(),
122 failed: failed.iter().map(BuildTask::as_message).collect(),
123 done: done.iter().map(BuildTask::as_message).collect(),
124 },
125 QueueState::Idle => QueueMessage::Idle(
126 self.0
127 .map
128 .read()
129 .map
130 .values()
131 .map(BuildTask::as_message)
132 .collect(),
133 ),
134 QueueState::Finished(FinishedQueue { done, failed }) => QueueMessage::Finished {
135 failed: failed.iter().map(BuildTask::as_message).collect(),
136 done: done.iter().map(BuildTask::as_message).collect(),
137 },
138 }
139 }
140
141 #[inline]
142 #[must_use]
143 pub fn name(&self) -> &QueueName {
144 &self.0.name
145 }
146
147 #[instrument(level = "info",
148 parent=&self.0.span,
149 target = "buildqueue",
150 name = "Running buildqueue",
151 skip_all
152 )]
153 pub fn start(&self, sem: Semaphore) {
154 let mut state = self.0.state.write();
155 if matches!(&*state, QueueState::Running(_)) {
156 return;
157 }
158 let map = self.0.map.read();
159 let mut running = RunningQueue::new(map.total);
160 tracing::info_span!("sorting...").in_scope(|| {
161 Self::sort(&map, &mut running);
162 tracing::info!("Done");
163 });
164 self.0.sender.lazy_send(|| QueueMessage::Started {
165 running: Vec::new(),
166 queue: running.queue.iter().map(BuildTask::as_message).collect(),
167 blocked: Vec::new(),
168 failed: Vec::new(),
169 done: Vec::new(),
170 });
171 *state = QueueState::Running(running);
172 drop(map);
173 drop(state);
174 match sem {
175 Semaphore::Linear => self.run_sync(),
176 #[cfg(feature = "tokio")]
177 Semaphore::Counting { inner: sem, .. } => {
178 tokio::task::spawn(self.clone().run_async(sem).in_current_span());
179 } }
181 }
182
183 #[inline]
184 fn run_sync(&self) {
185 while let Some((task, id)) = self.get_next() {
186 self.run_task(&task, id);
187 }
188 self.finish();
189 }
190
191 #[cfg(feature = "tokio")]
192 async fn run_async(self, sem: std::sync::Arc<tokio::sync::Semaphore>) {
193 loop {
194 let Ok(permit) = tokio::sync::Semaphore::acquire_owned(sem.clone()).await else {
195 break;
196 };
197 let Some((task, id)) = self.get_next_async().await else {
198 break;
199 };
200 let selfclone = self.clone();
201 let span = tracing::Span::current();
202 tokio::task::spawn_blocking(move || {
203 span.in_scope(move || selfclone.run_task_async(&task, id, permit));
204 });
205 }
206 loop {
207 if matches!(&*self.0.state.read(),QueueState::Running(RunningQueue{running,..}) if !running.is_empty())
208 {
209 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
210 } else {
211 break;
212 }
213 }
214 self.finish();
215 }
216
217 fn finish(&self) {
218 let state = &mut *self.0.state.write();
219 let QueueState::Running(RunningQueue { done, failed, .. }) = state else {
220 unreachable!()
221 };
222 let done = std::mem::take(done);
223 let failed = std::mem::take(failed);
224 self.0.sender.lazy_send(|| QueueMessage::Finished {
225 failed: failed.iter().map(BuildTask::as_message).collect(),
226 done: done.iter().map(BuildTask::as_message).collect(),
227 });
228 *state = QueueState::Finished(FinishedQueue { done, failed });
229 }
230
231 #[instrument(level="info",parent=&self.0.span,skip_all,name="Requeueing failed")]
232 pub fn requeue_failed(&self) {
233 let mut state = self.0.state.write();
234 let QueueState::Finished(FinishedQueue { failed, .. }) = &mut *state else {
235 return;
236 };
237 let failed = std::mem::take(failed);
238 *state = QueueState::Idle;
239 drop(state);
240 if failed.is_empty() {
241 return;
242 }
243 let map = &mut *self.0.map.write();
244 map.dependents.clear();
245 map.counter = unsafe { NonZeroU32::new_unchecked(1) };
246 map.total = failed.iter().map(|t| t.0.steps.len()).sum();
247 map.map.clear();
248 for t in failed {
249 for s in &t.0.steps {
250 s.0.state.set(TaskState::None);
251 }
252 map.map.insert(
253 (t.archive().archive_id().clone(), t.0.rel_path.clone()),
254 BuildTask(Arc::new(super::BuildTaskI {
255 id: BuildTaskId(map.counter),
256 rel_path: t.0.rel_path.clone(),
257 uri: t.0.uri.clone(),
258 steps: t.0.steps.clone(),
259 source: t.0.source.clone(),
260 })),
261 );
262 map.counter = map.counter.saturating_add(1);
263 }
264 self.0.sender.lazy_send(|| {
265 QueueMessage::Idle(map.map.values().map(BuildTask::as_message).collect())
266 });
267 }
268
269 #[cfg(feature = "tokio")]
270 #[inline]
271 fn run_task_async(
272 &self,
273 task: &BuildTask,
274 target: BuildTargetId,
275 permit: tokio::sync::OwnedSemaphorePermit,
276 ) {
277 self.run_task(task, target);
278 drop(permit);
279 }
280
281 #[allow(clippy::cast_possible_truncation)]
282 #[allow(clippy::significant_drop_tightening)]
283 fn run_task(&self, task: &BuildTask, target: BuildTargetId) {
284 self.0.sender.lazy_send(|| QueueMessage::TaskStarted {
285 id: task.0.id,
286 target,
287 });
288 let spec = task.as_build_spec(&self.0.backend);
289 let BuildResult { log, result } = tracing::info_span!(target:"buildqueue","Running task",
291 archive = %task.0.uri.archive_id(),
292 rel_path = %task.0.rel_path,
293 format = %target
294 )
295 .in_scope(|| (target.run)(spec));
296 let mut lock = self.0.state.write();
304 let QueueState::Running(ref mut state) = &mut *lock else {
305 unreachable!()
306 };
307 state.running.retain(|t| t != task);
308 let eta = state.timer.update(1);
309
310 let (mut err, res) = match result {
311 Ok(res) => (None, res),
312 Err(e) => (Some(e), None),
313 };
314
315 if let Err(e) =
316 self.0
317 .backend
318 .save(task.document_uri(), Some(task.rel_path()), log, target, res)
319 {
320 tracing::error!("Error saving build result: {e}");
321 if err.is_none() {
322 err = Some(Vec::new());
323 }
324 }
325
326 match err {
327 None => {
328 let mut found = false;
329 let mut requeue = false;
330 for s in task.steps() {
331 if s.0.target == target {
332 found = true;
333 s.0.state.set(TaskState::Done);
334 } else if found {
335 s.0.state.set(TaskState::Queued);
336 requeue = true;
337 break;
338 }
339 }
340 if requeue {
341 state.queue.push_front(task.clone());
342 } else {
343 state.done.push(task.clone());
344 }
345 drop(lock);
346
347 self.0.sender.lazy_send(|| QueueMessage::TaskSuccess {
348 id: task.0.id,
349 target,
350 eta,
351 });
352 }
353 Some(deps) => {
354 let mut found = false;
372 if deps.is_empty() || state.queue.is_empty() {
373 for s in task.steps() {
374 if s.0.target == target {
375 found = true;
376 }
377 if found {
378 s.0.state.set(TaskState::Failed);
379 }
380 }
381 state.failed.push(task.clone());
382 self.0.sender.lazy_send(|| QueueMessage::TaskFailed {
383 id: task.0.id,
384 target,
385 eta,
386 });
387 } else {
388 let mut found = false;
390 for s in task.steps() {
391 if s.0.target == target {
392 found = true;
393 }
394 if found {
395 s.0.state.set(TaskState::Blocked);
396 }
397 }
398 state.blocked.push(task.clone());
399 self.0.sender.lazy_send(|| QueueMessage::TaskBlocked {
400 id: task.0.id,
401 target,
402 eta,
403 });
404 }
405 drop(lock);
406 }
407 }
408 }
409
410 fn maybe_restart(&self) {
411 let mut state = self.0.state.write();
412 if let QueueState::Finished(_) = &mut *state {
413 drop(state);
414 self.requeue_failed();
415 }
416 }
417
418 #[instrument(level = "info",
419 parent=&self.0.span,
420 target = "buildqueue",
421 name = "Queueing tasks",
422 skip_all
423 )]
424 #[deprecated(note = "needs refactoring: archive need not be local, etc.")]
425 pub fn enqueue_all(&self, target: FormatOrTargets<'_>, stale_only: bool, clean: bool) -> usize {
426 self.maybe_restart();
427 if let AnyBackend::Sandbox(b) = &self.0.backend {
428 b.clear();
429 backend().with_archives(|archives| {
430 for a in archives {
431 let Archive::Local(archive) = a else { continue };
432 let matches = archive.is_meta()
433 || (match target {
434 FormatOrTargets::Format(f) => archive.formats().contains(&f),
435 FormatOrTargets::Targets(targets) => archive
436 .formats()
437 .iter()
438 .flat_map(|fmt| fmt.targets)
439 .any(|t| targets.contains(t)),
440 });
441 if matches {
442 b.maybe_copy(archive);
443 if clean {
444 let _ =
445 std::fs::remove_dir_all(b.path_for(archive.id()).join(".flams"));
446 }
447 }
448 }
449 b.load_all();
450 });
451 }
452 let mut acc = 0;
453 self.0.backend.with_archives(|archives| {
454 for a in archives {
455 let Archive::Local(archive) = a else { continue };
456 acc += archive.with_sources(|d| {
457 let d = d.dfs();
458 let map = &mut *self.0.map.write();
459 Self::enqueue(
460 map,
461 &self.0.backend,
462 a,
463 target,
464 stale_only,
465 d.filter_map(|e| match e {
466 SourceEntry::Dir(_) => None,
467 SourceEntry::File(f) => Some(f),
468 }),
469 )
470 });
471 }
472 });
473 acc
474 }
475
476 #[instrument(level = "debug",
477 parent=&self.0.span,
478 target = "buildqueue",
479 name = "Queueing tasks",
480 skip_all
481 )]
482 #[deprecated(note = "needs refactoring: assumes LocalArchive everywhere")]
483 pub fn enqueue_group(
484 &self,
485 id: &ArchiveId,
486 target: FormatOrTargets,
487 stale_only: bool,
488 clean: bool,
489 ) -> usize {
490 self.maybe_restart();
491 if let AnyBackend::Sandbox(b) = &self.0.backend {
492 b.require(id, false);
493 }
494 self.0.backend.with_archive_or_group(id, |g| match g {
495 None => 0,
496 Some(ArchiveOrGroup::Archive(id)) => self.0.backend.with_archive(id, |a| {
497 let Some(archive) = a else { return 0 };
498 if let Archive::Local(a) = archive {
499 let matches = match target {
500 FormatOrTargets::Format(f) => a.formats().contains(&f),
501 FormatOrTargets::Targets(targets) => a
502 .formats()
503 .iter()
504 .flat_map(|fmt| fmt.targets)
505 .any(|t| targets.contains(t)),
506 };
507 if matches {
508 if clean {
509 if let AnyBackend::Sandbox(b) = &self.0.backend {
510 let _ = std::fs::remove_dir_all(
511 b.path_for(archive.id()).join(".flams"),
512 );
513 } else if let Archive::Local(a) = archive {
514 let _ = std::fs::remove_dir_all(a.out_dir());
515 }
516 }
517 a.with_sources(|d| {
518 let map = &mut *self.0.map.write();
519 Self::enqueue(
520 map,
521 &self.0.backend,
522 archive,
523 target,
524 stale_only,
525 d.dfs().filter_map(|e| match e {
526 SourceEntry::Dir(_) => None,
527 SourceEntry::File(f) => Some(f),
528 }),
529 )
530 })
531 } else {
532 0
533 }
534 } else {
535 0
536 }
537 }),
538 Some(ArchiveOrGroup::Group(g)) => {
539 let map = &mut *self.0.map.write();
540 let mut ret = 0;
541 for id in g.dfs().filter_map(|e| match e {
542 ArchiveOrGroup::Archive(id) => Some(id),
543 ArchiveOrGroup::Group(_) => None,
544 }) {
545 ret += self.0.backend.with_archive(id, |a| {
546 let Some(archive) = a else { return 0 };
547 if let Archive::Local(a) = archive {
548 let matches = match target {
549 FormatOrTargets::Format(f) => a.formats().contains(&f),
550 FormatOrTargets::Targets(targets) => a
551 .formats()
552 .iter()
553 .flat_map(|fmt| fmt.targets)
554 .any(|t| targets.contains(t)),
555 };
556 if matches {
557 if clean {
558 if let AnyBackend::Sandbox(b) = &self.0.backend {
559 let _ = std::fs::remove_dir_all(
560 b.path_for(archive.id()).join(".flams"),
561 );
562 } else if let Archive::Local(a) = archive {
563 let _ = std::fs::remove_dir_all(a.out_dir());
564 }
565 }
566 a.with_sources(|d| {
567 Self::enqueue(
568 map,
569 &self.0.backend,
570 archive,
571 target,
572 stale_only,
573 d.dfs().filter_map(|e| match e {
574 SourceEntry::Dir(_) => None,
575 SourceEntry::File(f) => Some(f),
576 }),
577 )
578 })
579 } else {
580 0
581 }
582 } else {
583 0
584 }
585 });
586 }
587 ret
588 }
589 })
590 }
591
592 #[instrument(level = "info",
593 parent=&self.0.span,
594 target = "buildqueue",
595 name = "Queueing tasks",
596 skip_all
597 )]
598 #[deprecated(note = "needs refactoring: assumes LocalArchive everywhere")]
599 pub fn enqueue_archive(
600 &self,
601 id: &ArchiveId,
602 target: FormatOrTargets,
603 stale_only: bool,
604 rel_path: Option<RelPath<'_>>,
605 clean: bool,
606 ) -> usize {
607 self.maybe_restart();
608 if let AnyBackend::Sandbox(b) = &self.0.backend {
609 b.require(id, true);
610 }
611 self.0.backend.with_archive(id, |archive| {
612 let Some(archive) = archive else { return 0 };
613 if clean {
614 if let AnyBackend::Sandbox(b) = &self.0.backend {
615 let _ = std::fs::remove_dir_all(b.path_for(archive.id()).join(".flams"));
616 } else if let Archive::Local(a) = archive {
617 let _ = std::fs::remove_dir_all(a.out_dir());
618 }
619 }
620 if let Archive::Local(a) = archive {
621 a.with_sources(|d| match rel_path {
622 None => {
623 let map = &mut *self.0.map.write();
624 Self::enqueue(
625 map,
626 &self.0.backend,
627 archive,
628 target,
629 stale_only,
630 d.dfs().filter_map(|e| match e {
631 SourceEntry::Dir(_) => None,
632 SourceEntry::File(f) => Some(f),
633 }),
634 )
635 }
636 Some(p) => {
637 let Some(d) = d.find(p) else { return 0 };
638 match d {
639 SourceEntryRef::Dir(d) => {
640 let map = &mut *self.0.map.write();
641 Self::enqueue(
642 map,
643 &self.0.backend,
644 archive,
645 target,
646 stale_only,
647 d.dfs().filter_map(|e| match e {
648 SourceEntry::Dir(_) => None,
649 SourceEntry::File(f) => Some(f),
650 }),
651 )
652 }
653 SourceEntryRef::File(f) => {
654 let map = &mut *self.0.map.write();
655 Self::enqueue(
656 map,
657 &self.0.backend,
658 archive,
659 target,
660 stale_only,
661 std::iter::once(f),
662 )
663 }
664 }
665 }
666 })
667 } else {
668 0
669 }
670 })
671 }
672}
673
674#[derive(Debug)]
675pub struct RunningQueue {
676 pub(super) queue: VecDeque<BuildTask>,
677 pub(super) blocked: Vec<BuildTask>,
678 pub(super) done: Vec<BuildTask>,
679 pub(super) failed: Vec<BuildTask>,
680 pub(super) running: Vec<BuildTask>,
681 timer: Timer,
682}
683impl RunningQueue {
684 fn new(total: usize) -> Self {
685 Self {
686 queue: VecDeque::new(),
687 failed: Vec::new(),
688 blocked: Vec::new(),
689 done: Vec::new(),
690 running: Vec::new(),
691 timer: Timer::new(total),
692 }
693 }
694}
695
696#[derive(Debug)]
697pub struct FinishedQueue {
698 pub(super) done: Vec<BuildTask>,
699 pub(super) failed: Vec<BuildTask>,
700}
701
702#[derive(Debug)]
703struct Timer {
704 started: Timestamp,
705 steps: usize,
706 done: usize,
707}
708impl Timer {
709 fn new(total: usize) -> Self {
710 Self {
711 started: Timestamp::now(),
712 steps: total,
713 done: 0,
714 }
715 }
716 #[allow(clippy::cast_precision_loss)]
717 fn update(&mut self, dones: u8) -> Eta {
718 self.done += dones as usize;
719 let avg = self.started.since_now() * (1.0 / (self.done as f64));
720 let time_left = avg * ((self.steps - self.done) as f64);
721 Eta {
722 time_left,
723 done: self.done,
724 total: self.steps,
725 }
726 }
727}