lardon3d/docs/architecture/task_queue.md
fy59 b84f860d86 refactor(core): freeze global maintenance baseline
Complete the A-to-Z Lardon3D maintenance and coherence pass.

Generalize host resource policy, remove the global CPU12 ceiling, preserve
host CPU/RAM reserves, scale Task capabilities through the Resource Governor,
and validate deterministic parallel GV execution.

Migrate Project DB to v23 with data-driven camera, lens, optical configuration
and calibration profiles, including manual lenses without EXIF.

Integrate safe optional LARDON SSD swap/scratch control with Governor and F10
drain/safe-to-unplug semantics.

Refactor the ncurses TUI into a runtime observatory with durable progress,
elapsed time, smoothed ETA, throughput, resource telemetry, Governor state,
optics workflow, colors and compact/no-color fallbacks.

Reconcile Queue lifetime, persistence, concurrency, comments, tests,
README, AGENTS and canonical documentation.

GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN
2026-09-01 08:00:47 +02:00

6.9 KiB

File de tâches (Task Queue)

Objectif et responsabilités

Le module task_queue implémente la file d'attente FIFO des tâches de traitement. Il orchestre l'exécution séquentielle des tâches via un worker unique, gère la sélection de la prochaine tâche admissible et transmet les callbacks de résultat.

La file est le point central entre les producteurs et le worker d'exécution. Les responsabilités historiques de scheduler sont portées par cette Queue et le runtime existants : aucun second scheduler n'existe. La Queue ne décide jamais des ressources ; elle demande l'admission au Governor.

Fichiers

  • include/lardon3d/task_queue.h — types publics et API
  • src/task_queue.c — implémentation
  • tests/test_task_queue.c — tests unitaires

Types principaux

Le type public réel est l'opaque Lardon3DTaskQueue.

API publique

Fonction Description
lardon3d_task_queue_create() Crée une file bornée et son worker
lardon3d_task_queue_destroy() Arrête, annule, attend puis libère la file
lardon3d_task_queue_add() Ajoute avec backpressure bloquante
lardon3d_task_queue_try_add() Ajoute sans bloquer si une place existe
lardon3d_task_queue_try_add_ex() Distingue succès, saturation, arrêt, collision d'ID et erreur
lardon3d_task_queue_pause() / resume() Contrôle une tâche par son ID stable
lardon3d_task_queue_cancel() Demande l'annulation par ID
lardon3d_task_queue_remove() Retire un snapshot terminal retenu
lardon3d_task_queue_snapshot() Copie une vue bornée de la file
lardon3d_task_queue_observe() Copie la vue additive typée/durable/admission

Comportement FIFO avec bypass stable des WAIT

  1. lardon3d_task_queue_add() ajoute la tâche en fin de file.
  2. Le sélecteur interne parcourt la file du début vers la fin.
  3. La première tâche PENDING admissible (non terminale) est retournée.
  4. Si aucune tâche n'est admissible, le worker attend un changement.
  5. L'ordre de soumission est respecté entre tâches admissibles ; une tâche en WAIT ressources peut être dépassée sans être réordonnée ou supprimée.

Lorsqu'au moins une tâche PENDING reste en WAIT de ressources, cette attente est temporisée à 500 ms maximum. À l'expiration, le worker reprend le scan stable depuis la tête et le chemin d'admission capture de nouveaux snapshots. Enqueue, resume, resources_changed, annulation et arrêt continuent de réveiller immédiatement le worker ; le timeout n'impose donc jamais 500 ms après un signal. Cette règle utilise le worker unique existant et n'ajoute aucun thread.

Sélection adaptative

Le sélecteur saute les tâches pour lesquelles le governor répond WAIT et retourne la première tâche réellement admissible. Cela évite le blocage par la tête de file lorsqu'une tâche ne peut pas démarrer.

Invariants

  1. Worker unique : une seule tâche s'exécute à la fois. Pas de parallélisme interne à la file.
  2. FIFO stable : le scan commence à la tête et ne contourne que les WAIT d'admission. Il n'existe ni priorité cachée ni réordonnancement.
  3. Réservation obligatoire : aucune tâche n'est exécutée sans réservation validée par le gouverneur.
  4. Notification terminale unique : le callback terminé de la Task retourne avant la destruction de son userdata. Les callbacks métier peuvent avoir plusieurs séquences admises.
  5. Annulation sûre : annuler une tâche en cours la met en état CANCELLED sans interrompre brutalement le worker.
  6. Retraite prompte et bornée : après retour du callback terminé, la vraie Task est détruite hors mutex et seule une copie de snapshot demeure. Les 64 terminaisons les plus récentes sont retenues ; les plus anciennes sont évincées sans conserver userdata ni Task.
  7. Fermeture coordonnée : destroy() ferme d'abord l'ingress, attend le worker et tout appel enregistré avant la fermeture, puis libère mutex et mémoire exactement une fois. Le propriétaire doit interdire tout nouvel appel API dès que la destruction commence.
  8. IDs non réutilisés : la génération est monotone. Après consommation ou génération de UINT64_MAX, l'épuisement est permanent pour la vie de la Queue ; un ID restauré plus petit ou une éviction d'historique ne la réarme jamais.
  9. Observation complète bornée : la Queue de production accepte au plus 64 pending, possède au plus une active et retient 64 terminaux. Une capacité additive de 129 observe donc tout l'état courant. Les Tasks vivantes sont copiées d'abord en ordre de soumission décroissant, puis l'histoire en ordre de terminaison décroissant ; l'actif ne peut pas être caché par l'histoire.

Interactions

  • task : chaque entrée de la file est un Lardon3DTask avec son état et sa progression.
  • runtime / producteurs : soumettent les Tasks ; la Queue applique FIFO et orchestre l'exécution via le worker.
  • resource_governor : la file consulte directement le gouverneur avant d'exécuter chaque tâche.
  • hardware_profile / resource_snapshot : informations matériel utilisées par le gouverneur pour les réservations.

Statut

GATE G — PASS / FROZEN — file FIFO stable avec worker unique, sélection adaptative, réévaluation autonome des WAIT, pause, annulation coopérative et accueil des tâches restaurées avec ID préassigné.

La reprise projet utilise try_add_ex() et ne bloque jamais project_open(). À saturation, elle arrête sa fenêtre : les tâches non transférées restent PENDING en DB et seront réévaluées lors d'une ouverture ultérieure. Ce n'est pas un second scheduler.

Ouvrir, fermer ou changer de projet détruit/joint cette Queue avant fermeture de Project DB, puis recrée une unique Queue vide. L'histoire et son namespace d'IDs ne traversent donc jamais une frontière projet ; ce reset ne modifie pas les Task IDs durables conservés dans chaque DB.

Les callbacks terminés sont appelés sans le mutex Queue. Tant que le propriétaire maintient la Queue vivante, ils peuvent employer les lectures get, get_at, count et snapshot. Ils ne doivent pas appeler synchroniquement remove() sur leur propre record, destroy() sur la même Queue, ni une opération dont l'achèvement dépend de leur propre retour.

Limites

  • Worker unique : pas de parallélisme inter-Tasks. Un kind peut posséder un parallélisme interne borné et chargé dans sa réservation.
  • Pas de DAG ni de dépendances inter-tâches.
  • Pas de priorités (FIFO stable avec bypass des seuls WAIT).
  • La reprise ne possède pas encore de DAG ni de déclenchement différé lorsque une place se libère pendant la session courante.
  • Pas de pool de workers CPU/IO/GPU.
  • La backpressure borne les producteurs à la capacité configurée.
  • Les 500 ms concernent uniquement l'admission initiale PENDING. Le polling existant à une rupture de séquence reste 50 ms.