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

138 lines
6.9 KiB
Markdown

# 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.