From 4622aa3b93d6dccea5bc8f73ab31ce74adfb6b5b Mon Sep 17 00:00:00 2001 From: fy59 Date: Fri, 7 Aug 2026 07:23:57 +0200 Subject: [PATCH] new day --- .opencode/agents/lardon-explore.md | 38 +- .opencode/agents/lardon-orchestrator.md | 70 +++- .opencode/agents/lardon-tests.md | 2 +- .opencode/commands/lardon-plan.md | 127 +++++- .opencode/commands/lardon-resume-backup.md | 107 ++++- .opencode/commands/lardon-small.md | 75 +++- .opencode/commands/lardon-ticket-backup.md | 107 ++++- .opencode/commands/lardon-ticket.md | 88 +++- .opencode/context.md | 341 +++++++++++++++ .opencode/opencode.json | 3 +- .../scheduler_resource_integration.md | 41 +- docs/opencode_models.md | 77 +++- include/lardon3d/task.h | 11 + meson.build | 14 + src/task.c | 153 +++++++ src/task_queue.c | 4 + tests/test_sequential_task.c | 392 ++++++++++++++++++ tests/test_task.c | 6 +- 18 files changed, 1579 insertions(+), 77 deletions(-) create mode 100644 .opencode/context.md create mode 100644 tests/test_sequential_task.c diff --git a/.opencode/agents/lardon-explore.md b/.opencode/agents/lardon-explore.md index f7cc049..301aed8 100644 --- a/.opencode/agents/lardon-explore.md +++ b/.opencode/agents/lardon-explore.md @@ -1,7 +1,7 @@ --- description: Explore rapidement fichiers, symboles, appels et dépendances mode: subagent -model: opencode/north-mini-code-free +model: opencode/mimo-v2.5-free temperature: 0.1 permission: edit: deny @@ -16,3 +16,39 @@ Travaille en lecture seule. Localise exactement les fichiers, symboles, appelants, dépendances et documents strictement utiles. Ne lis pas toute l'architecture par défaut, ne propose pas de réécriture et retourne uniquement les chemins, conclusions et risques synthétiques. + +Tu reçois obligatoirement une liste de symboles, modules ou chemins précis. + +Si la demande ne fournit aucun élément précis, refuse l’exploration et demande +au parent de préciser la cible. + +Interdictions absolues : + +- pwd ; +- ls ; +- find ; +- glob global ; +- **/*.c ; +- **/*.h ; +- inventaire du dépôt ; +- lecture de toute la documentation. + +Utilise uniquement des recherches ciblées avec rg sur les chemins ou symboles +fournis. + +Lis au maximum : + +- cinq fichiers source ou headers ; +- deux fichiers de tests ; +- deux documents d’architecture. + +Retourne seulement : + +- fichiers pertinents ; +- symboles pertinents ; +- dépendances directes ; +- risques ; +- informations encore manquantes. + +Ne propose aucune nouvelle fonctionnalité. +Ne modifie aucun fichier. diff --git a/.opencode/agents/lardon-orchestrator.md b/.opencode/agents/lardon-orchestrator.md index 21bf1ce..9bacaad 100644 --- a/.opencode/agents/lardon-orchestrator.md +++ b/.opencode/agents/lardon-orchestrator.md @@ -1,7 +1,7 @@ --- description: Orchestre les tickets avec un contexte minimal et sans écrire les sources mode: primary -model: opencode/north-mini-code-free +model: opencode/mimo-v2.5-free temperature: 0.1 permission: edit: @@ -25,8 +25,66 @@ permission: "lardon-docs": allow --- -Orchestre sans modifier les sources. Charge AGENTS.md et l'overview déjà -injectés, puis utilise explore pour cibler le contexte. N'appelle que les agents -pertinents, sans sous-agent imbriqué. Transmets au seul implémenteur un résumé -court : objectif, contraintes, fichiers, API, invariants et tests. Tiens le -handoff concis à jour après chaque phase. Aucun modèle payant, commit ou push. +Le dépôt courant est déjà la racine de Lardon3D. + +Ne lance jamais de commande de découverte globale : + +- `pwd` +- `ls` ou `ls -la` +- `find` sur tout le dépôt +- glob `*` +- inventaire complet des fichiers +- lecture automatique de toute la documentation + +AGENTS.md et l’overview sont déjà injectés comme instructions projet. Ne les relis +pas intégralement sauf si une information précise manque. + +Pour chaque ticket : + +1. Lire `.opencode/work/current_ticket.md` s’il existe. +2. Identifier uniquement les modules, symboles et fichiers liés au ticket. +3. Utiliser `lardon-explore` avec une requête ciblée. +4. Charger uniquement les documents d’architecture directement pertinents. +5. Appeler seulement les agents nécessaires. + +Tout ticket touchant au moins un des éléments suivants exige obligatoirement +`lardon-concurrency` : + +- `task` +- `task_queue` +- scheduler +- `resource_governor` +- pthread +- mutex +- variable de condition +- pause ou reprise +- annulation +- réservation +- état ou durée de vie partagés entre threads + +Cette règle s’applique même si le ticket ne crée aucun nouveau thread. + +6. Transmettre au seul agent implémenteur un résumé court contenant : + - objectif ; + - contraintes ; + - fichiers concernés ; + - API et invariants ; + - tests requis. +7. Mettre à jour le handoff après chaque phase importante. + +Le répertoire `.opencode/work/` est préparé localement. Ne vérifie pas son +existence avec Bash. Crée ou mets à jour directement +`.opencode/work/current_ticket.md` avec l’outil d’édition autorisé. + +Ne modifie jamais les sources toi-même. + +N’utilise jamais : + +- de modèle payant ; +- de sous-agent imbriqué ; +- plusieurs agents d’écriture simultanément ; +- `git commit` ; +- `git push`. + +Les rapports intermédiaires doivent être courts et ne contenir que les +conclusions, risques, fichiers concernés, tests et prochaines actions. diff --git a/.opencode/agents/lardon-tests.md b/.opencode/agents/lardon-tests.md index 5fb82cf..b24a4f0 100644 --- a/.opencode/agents/lardon-tests.md +++ b/.opencode/agents/lardon-tests.md @@ -1,7 +1,7 @@ --- description: Exécute les validations Lardon3D sans modifier le code mode: subagent -model: opencode/north-mini-code-free +model: opencode/mimo-v2.5-free temperature: 0.1 permission: edit: deny diff --git a/.opencode/commands/lardon-plan.md b/.opencode/commands/lardon-plan.md index ef9b3de..baf086d 100644 --- a/.opencode/commands/lardon-plan.md +++ b/.opencode/commands/lardon-plan.md @@ -1,14 +1,125 @@ --- -description: Planifier en lecture seule avec un contexte minimal +description: Planifier un ticket en lecture seule avec un contexte minimal agent: lardon-orchestrator subtask: false --- -Planifie ce ticket sans modifier les sources : $ARGUMENTS +Planifie le ticket suivant sans modifier les sources : -Lis AGENTS.md et l'overview déjà chargés. Demande à lardon-explore uniquement -les modules concernés. Liste les fichiers et documents à charger, propose un plan -court et indique les agents réellement nécessaires. Écris dans -`.opencode/work/current_ticket.md` : titre, objectif, contraintes, fichiers, -documents utiles, plan, travail terminé/restant, fichiers modifiés, commandes, -tests passés/restants, décisions, erreurs, modèle et prochaine action sûre. +$ARGUMENTS + +Le contexte permanent est déjà injecté depuis `.opencode/context.md`. + +Pour cette commande : + +- n'appelle aucun sous-agent ; +- ne lance aucune commande Bash ; +- ne modifie aucun fichier sauf + `.opencode/work/current_ticket.md`. + +Ne lance jamais : + +- `pwd` +- `ls` +- `find` +- glob +- inventaire du dépôt +- compilation +- tests + +Réponds uniquement à partir : + +- du contexte permanent ; +- du texte du ticket ; +- de `.opencode/work/current_ticket.md` s'il existe et concerne déjà ce ticket. + +Si une information manque, indique simplement les deux ou trois fichiers qui +devront être lus lors d'une future exploration ciblée. + +Ne tente jamais de les découvrir toi-même. + +Choisis un seul ticket. + +Ne fusionne jamais plusieurs tickets. + +Ne propose jamais : + +- de métrique ; +- de compteur ; +- de diagnostic ; +- de nouvelle API ; +- d'état TUI ; +- de fonctionnalité annexe ; +- de refactoring non demandé. + +Le plan doit uniquement couvrir le périmètre demandé. + +Dans la section **Agents nécessaires** : + +- n'ajoute que les agents réellement utiles ; +- si le ticket touche `task`, `task_queue`, le scheduler, + `resource_governor`, pthread, mutex, variables de condition, + pause, reprise, annulation, réservations ou tout état partagé, + inclure obligatoirement `lardon-concurrency`, + même si aucun nouveau thread n'est créé. + +Écris `.opencode/work/current_ticket.md` +une seule fois avec exactement cette structure : + +Le handoff représente uniquement le ticket courant. + +Si un nouveau ticket est demandé, +remplacer entièrement `.opencode/work/current_ticket.md`. + +Ne jamais conserver plusieurs tickets simultanément. + +Ne jamais demander quel ticket utiliser. + +# Ticket + +## Objectif + +## Contraintes + +## Fichiers probablement concernés + +## Documents nécessaires + +## Plan + +## Agents nécessaires + +## Agents non nécessaires + +## Tests futurs + +## Risques + +## Prochaine action sûre + +Contraintes : + +- une seule occurrence de chaque rubrique ; +- aucun doublon ; +- aucun code inventé ; +- aucune API inventée ; +- aucune fonctionnalité hors périmètre ; +- aucune modification incrémentale répétée ; +- 80 lignes maximum. + +Après l'écriture : + +1. relire le fichier une seule fois ; +2. vérifier uniquement : + - structure ; + - absence de doublons ; + - cohérence ; +3. terminer immédiatement. + +Ne fais aucun commit. + +Ne fais aucun push. + +N'utilise aucun modèle payant. + +Ne modifie jamais `scan3d/`. diff --git a/.opencode/commands/lardon-resume-backup.md b/.opencode/commands/lardon-resume-backup.md index 5d49563..aa763d7 100644 --- a/.opencode/commands/lardon-resume-backup.md +++ b/.opencode/commands/lardon-resume-backup.md @@ -1,13 +1,104 @@ --- -description: Reprise rapide du handoff avec Nemotron +description: Reprendre rapidement un ticket avec Nemotron à partir du handoff agent: lardon-orchestrator subtask: false --- -Reprends le ticket avec lardon-build-backup. Lis uniquement le handoff, le diff -et les fichiers qu'ils citent. Ne refais ni exploration ni architecture si les -informations suffisent. Après l'implémentation, utilise lardon-build-light en -lecture seule pour une revue locale simple. N'appelle ni lardon-review ni -lardon-concurrency : signale l'absence de revue indépendante forte et diffère -toute revue de concurrence sensible jusqu'au prochain passage Codex. Mets le -handoff à jour avec la prochaine action sûre. +Reprends le ticket suivant : + +$ARGUMENTS + +Le contexte permanent de Lardon3D est déjà chargé via +`.opencode/context.md`. + +Commence toujours par : + +1. lire `.opencode/work/current_ticket.md` ; +2. lire le diff courant. + +Ne recharge jamais : + +- AGENTS.md ; +- l'overview ; +- la documentation générale ; +- les documents d'architecture. + +Ne relance jamais `lardon-explore` ou `lardon-architect` si le handoff contient +déjà : + +- l'objectif ; +- les contraintes ; +- les fichiers concernés ; +- les invariants ; +- les validations réalisées ; +- le travail restant. + +Ne lance une exploration supplémentaire que si une information indispensable est +absente. + +Transmets à `lardon-build-backup` uniquement : + +- l'objectif ; +- les contraintes ; +- les fichiers concernés ; +- les API concernées ; +- les invariants ; +- le travail restant ; +- la prochaine action sûre. + +Ne renvoie jamais tout le handoff au modèle d'implémentation. + +Après l'implémentation : + +1. appeler `lardon-tests` une seule fois ; +2. utiliser `lardon-build-light` uniquement pour une première revue locale + simple ; +3. mettre à jour le handoff. + +Ne pas appeler : + +- `lardon-review` ; +- `lardon-concurrency` ; + +car ils utilisent le même modèle que l'implémentation de secours. + +Si le ticket touche notamment : + +- pthread ; +- mutex ; +- condition variables ; +- scheduler ; +- task_queue ; +- resource_governor ; +- réservations ; +- synchronisation ; + +signaler explicitement : + +- qu'aucune revue indépendante forte n'a été réalisée ; +- que la revue de concurrence est différée au prochain passage Codex. + +Le handoff doit rester court et contenir uniquement : + +- objectif ; +- contraintes ; +- fichiers concernés ; +- travail effectué ; +- validations exécutées ; +- erreurs rencontrées ; +- limites restantes ; +- prochaine action sûre. + +Règles : + +- un seul agent écrit les sources ; +- aucun sous-agent imbriqué ; +- aucun modèle payant ; +- aucun commit ; +- aucun push ; +- aucune modification de `scan3d/`. + +Objectif principal : + +Reprendre un ticket en quelques secondes avec le plus petit contexte possible, +sans refaire l'analyse déjà présente dans le handoff. diff --git a/.opencode/commands/lardon-small.md b/.opencode/commands/lardon-small.md index 8d23275..dbd9192 100644 --- a/.opencode/commands/lardon-small.md +++ b/.opencode/commands/lardon-small.md @@ -4,11 +4,72 @@ agent: lardon-orchestrator subtask: false --- -Traite ce petit ticket : $ARGUMENTS +Traite le petit ticket suivant : -Utilise lardon-explore pour cibler les fichiers, puis un seul lardon-build-light -pour modifier. N'appelle architect, concurrency ou docs que si leur besoin est -avéré ; si le ticket est sensible, arrête et recommande le workflow normal. -Appelle ensuite lardon-tests pour compilation, tests ciblés et -`git diff --check`, puis une revue seulement si le diff le justifie. Maintiens -le handoff concis. Aucun commit, push ou fallback payant. +$ARGUMENTS + +Le contexte permanent de Lardon3D est déjà chargé via `.opencode/context.md`. + +Ne relis AGENTS.md, l’overview ou la documentation générale que si une information +précise manque. + +Ce workflow est réservé aux modifications locales, simples et clairement +délimitées. + +N’effectue jamais : + +- d’inventaire global du dépôt ; +- de `glob "*"` ; +- de `find` sur tout le dépôt ; +- de lecture complète de la documentation ; +- de refactoring d’architecture non demandé. + +Procédure : + +1. Lire `.opencode/work/current_ticket.md` s’il existe et s’il concerne ce ticket. +2. Utiliser `lardon-explore` avec une recherche strictement ciblée. +3. Identifier uniquement : + - les fichiers à modifier ; + - les dépendances directes ; + - les tests concernés ; + - les éventuels documents à mettre à jour. +4. Vérifier que le ticket convient au mode léger. +5. Utiliser un seul `lardon-build-light` pour l’implémentation. +6. Appeler `lardon-tests` une seule fois après l’implémentation pour : + - compiler avec Clang ; + - exécuter les tests ciblés ; + - exécuter `git diff --check`. +7. Demander une revue uniquement si le diff le justifie réellement. +8. Mettre à jour le handoff avec un résumé concis. + +N’appelle pas `lardon-architect`, `lardon-concurrency` ou `lardon-docs` sauf si +leur intervention est manifestement nécessaire. + +Arrête le workflow léger et recommande `/lardon-ticket` ou Codex si le ticket +touche notamment : + +- une API publique structurante ; +- `task`, `task_queue` ou `resource_governor` ; +- pthread, mutex ou condition variables ; +- la durée de vie complexe de ressources ; +- la persistance ou le rollback ; +- le scheduler, un futur DAG ou les réservations ; +- un refactoring réparti sur plusieurs modules ; +- une fondation sensible du projet. + +Dans ce cas, ne tente aucune modification risquée avec MiMo. + +Le handoff doit rester court et contenir seulement : + +- objectif ; +- fichiers concernés ; +- travail effectué ; +- validations ; +- erreurs ; +- limites ; +- prochaine action sûre. + +Ne fais aucun commit. +Ne fais aucun push. +N’utilise aucun modèle payant. +Ne modifie jamais `scan3d/`. diff --git a/.opencode/commands/lardon-ticket-backup.md b/.opencode/commands/lardon-ticket-backup.md index 3ef9f63..816f0e9 100644 --- a/.opencode/commands/lardon-ticket-backup.md +++ b/.opencode/commands/lardon-ticket-backup.md @@ -1,15 +1,102 @@ --- -description: Reprendre un ticket gratuit avec Nemotron après indisponibilité DeepSeek +description: Reprendre un ticket après indisponibilité de DeepSeek agent: lardon-orchestrator subtask: false --- -Lis d'abord `.opencode/work/current_ticket.md` et le diff. Si le handoff suffit, -ne refais pas l'exploration. Transmets seulement l'objectif, les fichiers, les -invariants, le travail restant et la prochaine action à lardon-build-backup. -Après reprise, appelle lardon-tests puis lardon-build-light en lecture seule pour -une première revue locale simple. N'appelle ni lardon-review ni -lardon-concurrency, car ils utilisent le même modèle que l'implémenteur. Si le -diff touche la concurrence, réserve sa revue au prochain passage Codex et -signale explicitement l'absence de revue indépendante forte. Mets à jour le -handoff après chaque phase. Aucun modèle payant. +Reprends le ticket suivant : + +$ARGUMENTS + +Le contexte permanent de Lardon3D est déjà chargé via +`.opencode/context.md`. + +Commence toujours par : + +1. lire `.opencode/work/current_ticket.md` ; +2. lire le diff courant. + +Si ces deux éléments décrivent correctement le ticket, ne relis pas : + +- AGENTS.md ; +- l'overview ; +- la documentation générale ; +- les documents d'architecture. + +Ne relance pas `lardon-explore` si le handoff contient déjà : + +- l'objectif ; +- les contraintes ; +- les fichiers concernés ; +- les invariants ; +- le travail restant. + +Relance `lardon-explore` uniquement lorsqu'une information indispensable est +absente. + +Transmets à `lardon-build-backup` uniquement : + +- l'objectif ; +- les contraintes ; +- les fichiers concernés ; +- les API concernées ; +- les invariants ; +- le travail restant ; +- la prochaine action sûre. + +Ne renvoie jamais tout le handoff au modèle d'implémentation. + +Après l'implémentation : + +1. appeler `lardon-tests` une seule fois ; +2. utiliser `lardon-build-light` uniquement pour une première revue locale + simple ; +3. mettre à jour le handoff. + +Ne pas appeler : + +- `lardon-review` ; +- `lardon-concurrency` ; + +car ils utilisent le même modèle que l'implémentation de secours. + +Si le ticket touche notamment : + +- pthread ; +- mutex ; +- condition variables ; +- scheduler ; +- task_queue ; +- resource_governor ; +- réservations ; +- synchronisation ; + +signaler explicitement : + +- qu'aucune revue indépendante forte n'a été réalisée ; +- que la revue de concurrence est différée au prochain passage Codex. + +Le handoff doit rester court et contenir uniquement : + +- objectif ; +- contraintes ; +- fichiers concernés ; +- travail effectué ; +- validations exécutées ; +- erreurs rencontrées ; +- limites restantes ; +- prochaine action sûre. + +Règles : + +- un seul agent écrit les sources ; +- aucun sous-agent imbriqué ; +- aucun modèle payant ; +- aucun commit ; +- aucun push ; +- aucune modification de `scan3d/`. + +Objectif principal : + +Reprendre un ticket en quelques secondes, avec le plus petit contexte possible, +sans refaire les analyses déjà présentes dans le handoff. diff --git a/.opencode/commands/lardon-ticket.md b/.opencode/commands/lardon-ticket.md index 74c2bcd..c12e1a4 100644 --- a/.opencode/commands/lardon-ticket.md +++ b/.opencode/commands/lardon-ticket.md @@ -1,19 +1,83 @@ --- -description: Traiter un ticket gratuit avec contexte progressif et handoff +description: Traiter un ticket complet avec contexte progressif et handoff agent: lardon-orchestrator subtask: false --- -Traite ce ticket : $ARGUMENTS +Traite le ticket suivant : -1. Initialise `.opencode/work/current_ticket.md` avec les rubriques prescrites. -2. Demande à lardon-explore les seuls fichiers et documents utiles. -3. Charge uniquement ce contexte. Appelle lardon-architect seulement pour une - abstraction importante, puis transmets un résumé concis à lardon-build. -4. Après l'implémentation, appelle lardon-tests une fois, puis lardon-review une - fois. Ajoute lardon-concurrency uniquement pour du code concurrent. -5. Mets le handoff à jour après chaque phase avec conclusions, commandes, - erreurs et prochaine action, sans documentation, sources ou logs complets. +$ARGUMENTS -Un seul agent écrit les sources. Aucun sous-agent imbriqué, commit, push, -fallback payant ou modification de scan3d/. +Le contexte permanent de Lardon3D est déjà chargé via +`.opencode/context.md`. + +Considère également AGENTS.md et le handoff courant comme déjà disponibles. +Ne les relis que lorsqu'une information précise manque. + +N'effectue jamais : + +- d'inventaire global du dépôt ; +- de `glob "*"` ; +- de `find` sur tout le dépôt ; +- de lecture complète de toute la documentation. + +Procédure : + +1. Initialiser `.opencode/work/current_ticket.md` avec les rubriques prescrites. +2. Utiliser `lardon-explore` avec une recherche strictement ciblée. +3. Identifier uniquement : + - les fichiers à modifier ; + - les dépendances directes ; + - les tests concernés ; + - les documents réellement utiles. +4. Charger uniquement ce contexte. +5. Appeler `lardon-architect` uniquement si une évolution d'architecture est + réellement nécessaire. +6. Transmettre à `lardon-build` un résumé concis contenant : + - objectif ; + - contraintes ; + - API concernées ; + - invariants ; + - fichiers ; + - validations attendues. +7. Après l'implémentation : + - appeler `lardon-tests` une seule fois ; + - appeler `lardon-review` une seule fois ; + - appeler `lardon-concurrency` uniquement si le ticket touche : + - pthread ; + - mutex ; + - condition variables ; + - scheduler ; + - task_queue ; + - resource_governor ; + - code concurrent. +8. Mettre à jour le handoff après chaque phase importante. + +Le handoff doit rester court et contenir uniquement : + +- objectif ; +- contraintes ; +- fichiers concernés ; +- travail effectué ; +- validations exécutées ; +- erreurs rencontrées ; +- limites restantes ; +- prochaine action sûre. + +Règles : + +- un seul agent écrit les sources ; +- aucun sous-agent imbriqué ; +- aucun agent inutile ; +- aucun modèle payant ; +- aucun commit ; +- aucun push ; +- aucune modification de `scan3d/`. + +Privilégier systématiquement : + +- le plus petit contexte possible ; +- le moins d'agents possible ; +- une seule compilation complète ; +- une seule revue ; +- une seule analyse de concurrence lorsque nécessaire. diff --git a/.opencode/context.md b/.opencode/context.md new file mode 100644 index 0000000..7ccbeb7 --- /dev/null +++ b/.opencode/context.md @@ -0,0 +1,341 @@ +# Lardon3D – Persistent Context + +## Project + +Lardon3D est un moteur de photogrammétrie Linux écrit en C17. + +Objectifs principaux : + +- stabilité absolue ; +- faible consommation mémoire ; +- traitement par lots adaptatifs ; +- aucune saturation du système ; +- TUI toujours réactive ; +- architecture modulaire. + +Le viewer Vulkan sera un processus séparé lisant uniquement des snapshots validés. + +--- + +## Build + +Compiler avec : + +CC=clang meson setup build --wipe +meson compile -C build -j8 + +Validation minimale : + +meson test -C build --print-errorlogs +git diff --check + +Utiliser ASan/UBSan et TSan uniquement lorsque le ticket touche la mémoire ou la concurrence. + +--- + +## Git + +Interdictions : + +- jamais git add -A +- jamais git commit +- jamais git push +- jamais modifier scan3d/ +- jamais modifier tri_photos.py + +Toujours fournir : + +- liste exacte des fichiers du ticket +- validations exécutées +- limites restantes +- message de commit recommandé + +--- + +## Architecture + +Séparation stricte : + +TUI +↓ + +Scheduler + +↓ + +Governor + +↓ + +Workers + +↓ + +Résultats atomiques + +Le thread ncurses ne fait jamais de travail métier. + +Le scheduler ne décide jamais des ressources. + +Le gouverneur est le seul propriétaire des budgets. + +--- + +## Resource Governor + +Estimate + +↓ + +Reservation + +↓ + +Scheduler + +↓ + +Worker + +Les réservations sont obligatoires avant toute exécution. + +RAM iGPU partagée = RAM système. + +La zram n'est jamais un budget de travail. + +--- + +## Coding rules + +C17 uniquement. + +Pas de variables globales d'état. + +Pas de system(). + +Pas de popen(). + +Pas d'allocation infinie. + +Toujours des buffers bornés. + +Sorties atomiques. + +Rollback complet en cas d'échec. + +--- + +## Current architecture + +Modules disponibles : + +- project +- import +- import_task +- image_catalog +- image_view +- task +- task_queue +- hardware_profile +- resource_snapshot +- resource_governor + +--- + +## Active workflow + +1. explorer uniquement les fichiers utiles + +2. architect uniquement si nécessaire + +3. un seul agent d'écriture + +4. tests + +5. revue + +6. handoff + +Ne jamais relire tout le dépôt. + +Ne jamais faire de glob "*". + +Ne jamais parcourir tous les fichiers. + +--- + +## Models + +Primary orchestration + +MiMo V2.5 Free + +Implementation + +DeepSeek V4 Flash Free + +Backup implementation + +Nemotron 3 Ultra Free + +Exploration + +North Mini Code Free + +Architecture / Review / Concurrency + +Nemotron 3 Ultra Free + +Documentation + +Ling 3.0 Flash Free + +Aucun modèle payant. + +--- + +## If DeepSeek fails + +Reprendre depuis : + +.opencode/work/current_ticket.md + +Ne jamais refaire : + +- exploration +- architecture +- revue + +si le handoff contient déjà ces informations. + +--- + +## Project roadmap + +Fondations terminées : + +- TUI modulaire +- projets persistants +- import asynchrone +- catalogue d'images +- image_view +- moteur de tâches +- scheduler FIFO +- gouverneur de ressources +- réservations +- intégration scheduler ↔ governor +- sélection de la première tâche admissible +- documentation d'architecture + +Ordre obligatoire des prochains tickets : + +1. séquences de traitement adaptatives pilotées par le Resource Governor +2. contre-pression (backpressure) et bornage des files +3. checkpoints et persistance des tâches +4. DAG minimal de dépendances +5. priorités +6. pools CPU / GPU / IO +7. migration de l'import vers le scheduler générique +8. publication incrémentale +9. viewer Vulkan + +Ne jamais proposer un ticket déjà terminé. + +Ne jamais sauter une étape de cette feuille de route sans justification explicite. + +--- + +## Mission + +Toujours privilégier : + +- stabilité +- simplicité +- déterminisme +- faible mémoire +- traitements séquencés +- protection du système + +avant les performances brutes. + +État actuel important : + +- Le scheduler sait déjà sauter une tâche en WAIT en tête de file. +- Il sélectionne la première tâche admissible dans l’ordre FIFO. +- Ce ticket est terminé et ne doit pas être reproposé. + +--- + +## Revue de concurrence obligatoire + +Tout ticket touchant `task`, `task_queue`, le scheduler, +`resource_governor`, pthread, mutex, variables de condition, pause, reprise, +annulation, réservations ou états partagés exige `lardon-concurrency`. + +Cette règle s’applique même si aucun nouveau thread n’est créé. + +--- + +## Design philosophy + +Chaque étape du pipeline doit être : + +- indépendante ; +- interruptible ; +- reprenable ; +- observable ; +- testable ; +- déterministe. + +Chaque tâche doit pouvoir être découpée en petits lots afin de : + +- limiter l'utilisation mémoire ; +- garder une TUI fluide ; +- permettre une adaptation dynamique des ressources ; +- éviter toute saturation du système. + +La stabilité du système hôte est prioritaire sur les performances brutes. + +--- + +## Long-term objective + +Lardon3D doit devenir un moteur de photogrammétrie entièrement piloté par un +scheduler adaptatif. + +Le scheduler décide uniquement : + +- quand lancer une étape ; +- avec quel lot ; +- dans quel ordre. + +Le Resource Governor décide uniquement : + +- si les ressources permettent l'exécution ; +- la taille optimale des lots ; +- quand différer une tâche. + +Le viewer Vulkan ne participe jamais aux calculs. + +Il affiche uniquement des snapshots validés produits par le pipeline. + +L'utilisateur doit voir le résultat apparaître progressivement pendant les +calculs, sans attendre la fin complète de la reconstruction. + +Toutes les étapes doivent pouvoir être interrompues puis reprises sans perdre +les résultats déjà validés. + +--- + +## Optimisation + +Privilégier toujours : + +- plusieurs petits traitements ; +- plusieurs petites allocations ; +- plusieurs petites copies ; + +plutôt qu'une grosse opération monolithique. + +Lardon3D doit être optimisé pour des machines disposant de peu de mémoire +(16 à 32 Go) et d'un iGPU partagé. diff --git a/.opencode/opencode.json b/.opencode/opencode.json index 3026efb..008a32b 100644 --- a/.opencode/opencode.json +++ b/.opencode/opencode.json @@ -1,10 +1,11 @@ { "$schema": "https://opencode.ai/config.json", "default_agent": "lardon-orchestrator", - "model": "opencode/north-mini-code-free", + "model": "opencode/mimo-v2.5-free", "subagent_depth": 1, "instructions": [ "AGENTS.md", + ".opencode/context.md", "docs/architecture/overview.md" ], "permission": { diff --git a/docs/architecture/scheduler_resource_integration.md b/docs/architecture/scheduler_resource_integration.md index 6bb4fff..ee5bbf6 100644 --- a/docs/architecture/scheduler_resource_integration.md +++ b/docs/architecture/scheduler_resource_integration.md @@ -44,10 +44,47 @@ pendant `PAUSED`. Ses ressources restent donc indisponibles pour les autres tâches. Ce choix évite de reprendre un callback avec un contrat qui aurait été attribué entre-temps à un autre travail. +## Exécution séquencée adaptative + +Une tâche peut s'exécuter en plusieurs séquences (lots) successives. Le +callback appelle `lardon3d_task_sequence_break()` pour libérer sa réservation +courante, capturer un nouvel instantané système, obtenir une nouvelle +réservation auprès du gouverneur et reprendre avec un contrat actualisé. + +Le cycle d'une séquence est strict : + +```text +Callback + → sequence_break + → Release Reservation courante + → Snapshot système + → Governor → Reservation + → Contrat actualisé + → Reprise du callback +``` + +Le task conserve une référence au gouverneur et à sa réservation courante. +`lardon3d_task_start` stocke la réservation initiale ; à la fin de l'exécution, +il libère la réservation courante si elle est encore détenue. Le worker de la +file libère ensuite la réservation d'origine, qui est déjà libérée si une +séquence a eu lieu : cet appel est alors sans effet. + +Invariants préservés : + +- Aucun callback ne démarre sans réservation active. +- Une seule réservation active par tâche à tout instant. +- La réservation est toujours valide avant et après un `sequence_break`. +- Le contrat est mis à jour atomiquement sous le mutex de la tâche. +- Si pause ou annulation est demandée pendant le `sequence_break`, la fonction + retourne `false` et la tâche est annulée. +- Si le gouverneur répond `WAIT` ou `REJECT`, la fonction retourne `false` et + la tâche passe en échec sans bloquer le callback. +- La progression est conservée entre les séquences. + ## Limites et extensions La file possède un seul worker, reste strictement FIFO et ne gère ni priorité ni dépendance. Une notification explicite est nécessaire lorsqu'un composant extérieur libère une réservation. Les prochaines étapes pourront ajouter un -DAG, des priorités, des séquences de lots adaptatives et des pools distincts -CPU, IO et GPU sans déplacer les décisions de ressources hors du gouverneur. +DAG, des priorités, des pools distincts CPU, IO et GPU sans déplacer les +décisions de ressources hors du gouverneur. diff --git a/docs/opencode_models.md b/docs/opencode_models.md index bc0b1a8..a015939 100644 --- a/docs/opencode_models.md +++ b/docs/opencode_models.md @@ -15,12 +15,11 @@ entrée, sortie et cache : recommandé `opencode/nemotron-3-ultra-free` en cas de saturation 503. - `opencode/nemotron-3-ultra-free` : architecture, concurrence, revue et repli de l'agent principal. -- `opencode/north-mini-code-free` : exploration rapide et validations. +- `opencode/mimo-v2.5-free` : exploration rapide et validations. - `opencode/laguna-s-2.1-free` : testé comme reviewer sur Lardon3D ; résultats jugés insuffisants. Non utilisé dans les rôles actifs. - `opencode/ling-3.0-flash-free` : documentation. - `opencode/longcat-2.0-free` : disponible, non configuré actuellement. -- `opencode/mimo-v2.5-free` : disponible, non configuré actuellement. ## Gratuité non confirmée @@ -63,24 +62,66 @@ OpenCode actuelle. ## Optimisation du quota -L'agent par défaut est `lardon-orchestrator` sur North Mini Code Free. Il charge -seulement `AGENTS.md` et l'overview, puis demande à North Mini Code une -exploration ciblée. DeepSeek est réservé à l'implémentation complexe ; il ne sert -ni à l'orchestration, ni aux tests, ni à la documentation. Nemotron traite les -reprises, revues et analyses d'architecture ou de concurrence, MiMo les petits -tickets et Ling la documentation. +L’agent par défaut est `lardon-orchestrator` sur MiMo V2.5 Free. -Les commandes `lardon-plan`, `lardon-small`, `lardon-ticket` et -`lardon-ticket-backup` chargent progressivement le contexte. Elles ne lisent pas -automatiquement toute l'architecture et transmettent uniquement objectif, -contraintes, fichiers, API, invariants, risques et tests. Les rapports de succès -restent courts. Le handoff ignoré `.opencode/work/current_ticket.md` permet une -reprise sans rejouer toute l'analyse. +Le contexte permanent est chargé depuis `.opencode/context.md`. L’orchestrateur +ne relit pas automatiquement AGENTS.md, l’overview ni toute la documentation +d’architecture. Il utilise uniquement le handoff courant et les documents +directement liés au ticket. -Nemotron ne relit jamais sa propre implémentation lorsqu'il a servi de backup. -MiMo fournit alors seulement une première revue locale simple. La revue de -concurrence sensible est différée jusqu'au prochain passage Codex, et le workflow -signale explicitement qu'aucune revue indépendante forte n'a eu lieu. +MiMo assure : + +- l’orchestration ; +- l’exploration ciblée ; +- les tests ; +- les petits tickets. + +DeepSeek V4 Flash Free est réservé à l’implémentation complexe. Il ne sert ni à +l’orchestration, ni à l’exploration, ni aux tests, ni à la documentation. + +Nemotron 3 Ultra Free assure : + +- les reprises après indisponibilité de DeepSeek ; +- l’architecture ; +- la concurrence ; +- la revue générale. + +Ling 3.0 Flash Free est réservé à la documentation. + +North Mini Code Free n’est plus utilisé dans les rôles actifs. Il reste mentionné +dans l’inventaire historique comme modèle testé puis écarté en raison de résultats +jugés insuffisants et trop erratiques sur Lardon3D. + +Les commandes `lardon-plan`, `lardon-small`, `lardon-ticket`, +`lardon-ticket-backup` et les commandes de reprise utilisent un contexte +progressif. + +Elles ne lisent jamais automatiquement tout le dépôt ni toute l’architecture. +Elles transmettent uniquement : + +- objectif ; +- contraintes ; +- fichiers concernés ; +- API ; +- invariants ; +- risques ; +- tests requis ; +- prochaine action sûre. + +Les rapports de succès restent courts. Le handoff ignoré +`.opencode/work/current_ticket.md` permet une reprise sans rejouer l’exploration +ou l’analyse déjà terminée. + +Nemotron ne relit jamais sa propre implémentation lorsqu’il a servi de backup. + +Dans ce cas : + +- MiMo fournit seulement une première revue locale simple ; +- la revue de concurrence sensible est différée jusqu’au prochain passage Codex ; +- le workflow signale explicitement qu’aucune revue indépendante forte n’a eu + lieu. + +Aucun modèle payant n’est utilisé automatiquement. ## Chaîne de secours diff --git a/include/lardon3d/task.h b/include/lardon3d/task.h index d771a57..445237e 100644 --- a/include/lardon3d/task.h +++ b/include/lardon3d/task.h @@ -81,6 +81,17 @@ bool lardon3d_task_execution_contract( const Lardon3DTask *task, Lardon3DTaskExecutionContract *contract ); +/* Libère la réservation courante, en obtient une nouvelle auprès du gouverneur + * et met à jour le contrat. À appeler uniquement depuis le callback en cours + * d'exécution. Retourne false si pause/annulation est demandée ou si le + * gouverneur répond WAIT/REJECT (la tâche passe alors en échec). */ +bool lardon3d_task_sequence_break( + Lardon3DTask *task, + Lardon3DResourceGovernor *governor, + Lardon3DResourceReservation **out_reservation, + Lardon3DTaskExecutionContract *out_contract +); +unsigned int lardon3d_task_sequence_count(const Lardon3DTask *task); bool lardon3d_task_reject(Lardon3DTask *task, const char *message); const char *lardon3d_task_state_name(Lardon3DTaskState state); diff --git a/meson.build b/meson.build index 6680500..15a04f0 100644 --- a/meson.build +++ b/meson.build @@ -111,6 +111,20 @@ task_test = executable( test('task', task_test, timeout: 30) +sequential_task_test = executable( + 'test-sequential-task', + sources: [ + 'tests/test_sequential_task.c', + 'src/task.c', + 'src/resource_governor.c', + 'src/resource_snapshot.c', + ], + include_directories: include_directories('include'), + dependencies: [threads], +) + +test('sequential-task', sequential_task_test, timeout: 30) + task_queue_test = executable( 'test-task-queue', sources: [ diff --git a/src/task.c b/src/task.c index c77113e..17400da 100644 --- a/src/task.c +++ b/src/task.c @@ -23,6 +23,9 @@ struct Lardon3DTask { bool pause_requested; bool cancel_requested; bool executing; + Lardon3DResourceGovernor *governor; + Lardon3DResourceReservation *current_reservation; + unsigned int sequence_count; }; static bool @@ -125,6 +128,8 @@ lardon3d_task_start( return false; } task->executing = true; + task->governor = governor; + task->current_reservation = (Lardon3DResourceReservation *)reservation; task->contract = (Lardon3DTaskExecutionContract) { .batch_size = information.batch_size, .memory_bytes = information.memory_bytes, @@ -143,6 +148,7 @@ lardon3d_task_start( while (task->pause_requested && !task->cancel_requested) { task->state = TASK_PAUSED; copy_text(task->message, sizeof(task->message), "Tâche en pause."); + (void)pthread_cond_broadcast(&task->condition); (void)pthread_cond_wait(&task->condition, &task->mutex); } if (task->cancel_requested) { @@ -169,6 +175,13 @@ lardon3d_task_start( finish_locked(task, TASK_COMPLETED, "Tâche terminée."); } (void)pthread_mutex_unlock(&task->mutex); + if (task->current_reservation) { + (void)lardon3d_resource_governor_release( + governor, + task->current_reservation + ); + task->current_reservation = NULL; + } return true; } @@ -265,6 +278,146 @@ lardon3d_task_checkpoint(Lardon3DTask *task) return continuing; } +bool +lardon3d_task_sequence_break( + Lardon3DTask *task, + Lardon3DResourceGovernor *governor, + Lardon3DResourceReservation **out_reservation, + Lardon3DTaskExecutionContract *out_contract +) +{ + if (!task || !governor || !out_reservation || !out_contract) { + return false; + } + *out_reservation = NULL; + (void)pthread_mutex_lock(&task->mutex); + if (!task->executing || is_terminal(task->state)) { + (void)pthread_mutex_unlock(&task->mutex); + return false; + } + while (task->pause_requested && !task->cancel_requested) { + task->state = TASK_PAUSED; + copy_text(task->message, sizeof(task->message), "Tâche en pause."); + (void)pthread_cond_broadcast(&task->condition); + (void)pthread_cond_wait(&task->condition, &task->mutex); + } + if (task->cancel_requested) { + (void)pthread_mutex_unlock(&task->mutex); + return false; + } + task->state = TASK_RUNNING; + Lardon3DResourceReservation *previous = task->current_reservation; + task->current_reservation = NULL; + (void)pthread_mutex_unlock(&task->mutex); + + if (previous) { + (void)lardon3d_resource_governor_release(governor, previous); + } + + Lardon3DResourceDecision decision; + Lardon3DResourceReservation *next = NULL; + bool reserved = lardon3d_resource_governor_reserve_available( + governor, + &task->estimate, + &decision, + &next + ); + + (void)pthread_mutex_lock(&task->mutex); + if (task->cancel_requested) { + if (next) { + (void)lardon3d_resource_governor_release(governor, next); + } + finish_locked( + task, + TASK_CANCELLED, + "Tâche annulée pendant sequence_break." + ); + (void)pthread_mutex_unlock(&task->mutex); + return false; + } + while (task->pause_requested && !task->cancel_requested) { + task->state = TASK_PAUSED; + copy_text(task->message, sizeof(task->message), "Tâche en pause."); + (void)pthread_cond_broadcast(&task->condition); + (void)pthread_cond_wait(&task->condition, &task->mutex); + } + if (task->cancel_requested) { + if (next) { + (void)lardon3d_resource_governor_release(governor, next); + } + finish_locked(task, TASK_CANCELLED, "Tâche annulée."); + (void)pthread_mutex_unlock(&task->mutex); + return false; + } + if (!reserved || !next) { + task->current_reservation = NULL; + finish_locked( + task, + TASK_FAILED, + "Impossible de réserver les ressources de la séquence." + ); + (void)pthread_mutex_unlock(&task->mutex); + return false; + } + if (decision.kind == LARDON3D_RESOURCE_WAIT + || decision.kind == LARDON3D_RESOURCE_REJECT) { + task->current_reservation = NULL; + finish_locked( + task, + TASK_FAILED, + decision.reason[0] ? decision.reason + : "Ressources impossibles à réserver." + ); + (void)pthread_mutex_unlock(&task->mutex); + return false; + } + Lardon3DResourceReservationInfo information; + if (!lardon3d_resource_reservation_get_active( + governor, + next, + &information + )) { + (void)lardon3d_resource_governor_release(governor, next); + task->current_reservation = NULL; + finish_locked( + task, + TASK_FAILED, + "Réservation de séquence invalide." + ); + (void)pthread_mutex_unlock(&task->mutex); + return false; + } + task->current_reservation = next; + task->contract = (Lardon3DTaskExecutionContract) { + .batch_size = information.batch_size, + .memory_bytes = information.memory_bytes, + .gpu_memory_bytes = information.gpu_memory_bytes, + .cpu_threads = information.cpu_threads, + .gpu_slots = information.gpu_slots, + .io_slots = information.io_slots, + }; + task->has_contract = true; + ++task->sequence_count; + *out_reservation = next; + *out_contract = task->contract; + (void)pthread_mutex_unlock(&task->mutex); + return true; +} + +unsigned int +lardon3d_task_sequence_count(const Lardon3DTask *task) +{ + if (!task) { + return 0; + } + Lardon3DTask *mutable_task = (Lardon3DTask *)task; + (void)pthread_mutex_lock(&mutable_task->mutex); + unsigned int count = task->sequence_count; + (void)pthread_mutex_unlock(&mutable_task->mutex); + return count; +} + bool lardon3d_task_set_progress( Lardon3DTask *task, diff --git a/src/task_queue.c b/src/task_queue.c index 83e9f29..fdf7f65 100644 --- a/src/task_queue.c +++ b/src/task_queue.c @@ -139,6 +139,10 @@ queue_worker(void *context) "Réservation de ressources invalide." ); } + /* La tâche peut avoir libéré et re-réservé via sequence_break pendant + * son callback. Dans ce cas la réservation d'origine est déjà libérée + * et cet appel est sans effet ; la réservation courante de la tâche a + * été libérée par lardon3d_task_start. */ (void)lardon3d_resource_governor_release( queue->governor, reservation diff --git a/tests/test_sequential_task.c b/tests/test_sequential_task.c new file mode 100644 index 0000000..94d153f --- /dev/null +++ b/tests/test_sequential_task.c @@ -0,0 +1,392 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + (void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \ + return false; \ + } \ + } while (0) + +typedef struct { + Lardon3DResourceGovernor *governor; + Lardon3DResourceReservation *initial_reservation; + unsigned int expected_breaks; + unsigned int breaks_done; + size_t batch_sizes[8]; + Lardon3DTaskExecutionContract initial_contract; + bool got_initial_contract; +} SeqContext; + +typedef struct { + Lardon3DResourceGovernor *governor; +} WaitContext; + +typedef struct { + Lardon3DResourceGovernor *governor; + pthread_mutex_t sync_mutex; + pthread_cond_t sync_cond; + bool about_to_break; + bool cancel_ready; +} CancelContext; + +typedef struct { + Lardon3DTask *task; + Lardon3DResourceGovernor *governor; + Lardon3DResourceReservation *reservation; +} StartContext; + +static void +short_pause(long nanoseconds) +{ + struct timespec duration = {.tv_sec = 0, .tv_nsec = nanoseconds}; + (void)nanosleep(&duration, NULL); +} + +static bool +seq_callback(Lardon3DTask *task, void *userdata) +{ + SeqContext *ctx = userdata; + if (!lardon3d_task_execution_contract(task, &ctx->initial_contract)) { + return false; + } + ctx->got_initial_contract = true; + Lardon3DResourceReservation *previous = ctx->initial_reservation; + for (unsigned int index = 0; index < ctx->expected_breaks; ++index) { + if (!lardon3d_task_checkpoint(task)) { + return false; + } + /* Vérifier que la réservation précédente est encore valide AVANT le break */ + if (previous && !lardon3d_resource_governor_reservation_is_valid( + ctx->governor, + previous + )) { + return false; + } + Lardon3DResourceReservation *reservation = NULL; + Lardon3DTaskExecutionContract contract; + if (!lardon3d_task_sequence_break( + task, + ctx->governor, + &reservation, + &contract + )) { + return false; + } + if (!lardon3d_resource_governor_reservation_is_valid( + ctx->governor, + reservation + )) { + return false; + } + ctx->batch_sizes[index] = contract.batch_size; + previous = reservation; + ++ctx->breaks_done; + unsigned int progress = (unsigned int)( + ((index + 1) * 100) / ctx->expected_breaks + ); + if (!lardon3d_task_set_progress(task, progress, "Séquence.")) { + return false; + } + short_pause(2000000); + } + return true; +} + +static bool +wait_callback(Lardon3DTask *task, void *userdata) +{ + WaitContext *ctx = userdata; + Lardon3DResourceReservation *reservation = NULL; + Lardon3DTaskExecutionContract contract; + return lardon3d_task_sequence_break( + task, + ctx->governor, + &reservation, + &contract + ); +} + +static bool +cancel_callback(Lardon3DTask *task, void *userdata) +{ + CancelContext *ctx = userdata; + if (!lardon3d_task_checkpoint(task)) { + return false; + } + (void)pthread_mutex_lock(&ctx->sync_mutex); + ctx->about_to_break = true; + (void)pthread_cond_broadcast(&ctx->sync_cond); + while (!ctx->cancel_ready) { + (void)pthread_cond_wait(&ctx->sync_cond, &ctx->sync_mutex); + } + (void)pthread_mutex_unlock(&ctx->sync_mutex); + Lardon3DResourceReservation *reservation = NULL; + Lardon3DTaskExecutionContract contract; + return lardon3d_task_sequence_break( + task, + ctx->governor, + &reservation, + &contract + ); +} + +static void * +start_task(void *context) +{ + StartContext *start = context; + return (void *)(uintptr_t)(lardon3d_task_start( + start->task, + start->governor, + start->reservation + ) ? 1 : 0); +} + +static bool +wait_for_state(Lardon3DTask *task, Lardon3DTaskState expected) +{ + for (size_t attempt = 0; attempt < 5000; ++attempt) { + Lardon3DTaskSnapshot snapshot; + if (!lardon3d_task_snapshot(task, &snapshot)) { + return false; + } + if (snapshot.state == expected) { + return true; + } + short_pause(1000000); + } + return false; +} + +static bool +run_sequential_test(void) +{ + const Lardon3DResourceEstimate estimate = { + .memory_bytes_per_item = 1000, + .minimum_batch_size = 1, + .maximum_batch_size = 1000, + .desired_cpu_threads = 1, + }; + Lardon3DHardwareProfile profile = { + .logical_cpu_count = 16, + .page_size_bytes = 4096, + .memory_total_bytes = UINT64_MAX, + .cpu_architecture = "test", + }; + Lardon3DResourcePolicy policy = { + .maximum_cpu_load_ratio = 1.0, + .maximum_io_pressure_avg10 = 100.0, + .io_slot_capacity = 1, + }; + Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( + &profile, + &policy + ); + CHECK(governor); + Lardon3DResourceSnapshot resource_snapshot = { + .memory_available_bytes = 20000, + .cpu_load_1m = 0.0, + }; + Lardon3DResourceDecision decision; + Lardon3DResourceReservation *reservation; + CHECK(lardon3d_resource_governor_reserve( + governor, + &resource_snapshot, + &estimate, + &decision, + &reservation + )); + CHECK(decision.kind == LARDON3D_RESOURCE_START + || decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH); + CHECK(decision.batch_size == 20); + + SeqContext ctx = { + .governor = governor, + .initial_reservation = reservation, + .expected_breaks = 3, + }; + Lardon3DTask *task = lardon3d_task_create( + "Séquences", + &estimate, + seq_callback, + &ctx + ); + CHECK(task); + StartContext start = {task, governor, reservation}; + pthread_t thread; + CHECK(pthread_create(&thread, NULL, start_task, &start) == 0); + CHECK(wait_for_state(task, TASK_RUNNING)); + CHECK(lardon3d_task_join(task)); + void *thread_result; + CHECK(pthread_join(thread, &thread_result) == 0); + CHECK((uintptr_t)thread_result == 1); + CHECK(ctx.got_initial_contract); + CHECK(ctx.breaks_done == 3); + CHECK(ctx.initial_contract.batch_size == 20); + CHECK(ctx.batch_sizes[0] == 1000); + CHECK(ctx.batch_sizes[1] == 1000); + CHECK(ctx.batch_sizes[2] == 1000); + CHECK(ctx.batch_sizes[0] != ctx.initial_contract.batch_size); + CHECK(lardon3d_task_sequence_count(task) == 3); + Lardon3DTaskSnapshot snapshot; + CHECK(lardon3d_task_snapshot(task, &snapshot)); + CHECK(snapshot.state == TASK_COMPLETED); + CHECK(snapshot.progress == 100); + CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); + lardon3d_task_destroy(task); + lardon3d_resource_governor_destroy(governor); + return true; +} + +static bool +run_wait_test(void) +{ + const Lardon3DResourceEstimate estimate = { + .minimum_batch_size = 1, + .maximum_batch_size = 100, + .desired_cpu_threads = 1, + .desired_io_slots = 1, + }; + Lardon3DHardwareProfile profile = { + .logical_cpu_count = 16, + .page_size_bytes = 4096, + .memory_total_bytes = UINT64_MAX, + .cpu_architecture = "test", + }; + Lardon3DResourcePolicy policy = { + .maximum_cpu_load_ratio = 1.0, + .maximum_io_pressure_avg10 = 0.0, + .io_slot_capacity = 1, + }; + Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( + &profile, + &policy + ); + CHECK(governor); + Lardon3DResourceSnapshot resource_snapshot = { + .memory_available_bytes = UINT64_MAX, + .cpu_load_1m = 0.0, + .io_pressure_known = false, + }; + Lardon3DResourceDecision decision; + Lardon3DResourceReservation *reservation; + CHECK(lardon3d_resource_governor_reserve( + governor, + &resource_snapshot, + &estimate, + &decision, + &reservation + )); + WaitContext ctx = {.governor = governor}; + Lardon3DTask *task = lardon3d_task_create( + "Attente", + &estimate, + wait_callback, + &ctx + ); + CHECK(task); + CHECK(lardon3d_task_start(task, governor, reservation)); + Lardon3DTaskSnapshot snapshot; + CHECK(lardon3d_task_snapshot(task, &snapshot)); + CHECK(snapshot.state == TASK_FAILED); + CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); + lardon3d_task_destroy(task); + lardon3d_resource_governor_destroy(governor); + return true; +} + +static bool +run_cancel_test(void) +{ + const Lardon3DResourceEstimate estimate = { + .minimum_batch_size = 1, + .maximum_batch_size = 100, + .desired_cpu_threads = 1, + }; + Lardon3DHardwareProfile profile = { + .logical_cpu_count = 16, + .page_size_bytes = 4096, + .memory_total_bytes = UINT64_MAX, + .cpu_architecture = "test", + }; + Lardon3DResourcePolicy policy = { + .maximum_cpu_load_ratio = 1.0, + .maximum_io_pressure_avg10 = 100.0, + .io_slot_capacity = 1, + }; + Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create( + &profile, + &policy + ); + CHECK(governor); + Lardon3DResourceSnapshot resource_snapshot = { + .memory_available_bytes = UINT64_MAX, + .cpu_load_1m = 0.0, + }; + Lardon3DResourceDecision decision; + Lardon3DResourceReservation *reservation; + CHECK(lardon3d_resource_governor_reserve( + governor, + &resource_snapshot, + &estimate, + &decision, + &reservation + )); + CancelContext ctx = {.governor = governor}; + CHECK(pthread_mutex_init(&ctx.sync_mutex, NULL) == 0); + CHECK(pthread_cond_init(&ctx.sync_cond, NULL) == 0); + Lardon3DTask *task = lardon3d_task_create( + "Annulation", + &estimate, + cancel_callback, + &ctx + ); + CHECK(task); + StartContext start = {task, governor, reservation}; + pthread_t thread; + CHECK(pthread_create(&thread, NULL, start_task, &start) == 0); + (void)pthread_mutex_lock(&ctx.sync_mutex); + while (!ctx.about_to_break) { + (void)pthread_cond_wait(&ctx.sync_cond, &ctx.sync_mutex); + } + (void)pthread_mutex_unlock(&ctx.sync_mutex); + lardon3d_task_request_cancel(task); + (void)pthread_mutex_lock(&ctx.sync_mutex); + ctx.cancel_ready = true; + (void)pthread_cond_broadcast(&ctx.sync_cond); + (void)pthread_mutex_unlock(&ctx.sync_mutex); + CHECK(lardon3d_task_join(task)); + CHECK(pthread_join(thread, NULL) == 0); + Lardon3DTaskSnapshot snapshot; + CHECK(lardon3d_task_snapshot(task, &snapshot)); + CHECK(snapshot.state == TASK_CANCELLED); + CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); + lardon3d_task_destroy(task); + (void)pthread_cond_destroy(&ctx.sync_cond); + (void)pthread_mutex_destroy(&ctx.sync_mutex); + lardon3d_resource_governor_destroy(governor); + return true; +} + +int +main(void) +{ + if (!run_sequential_test()) { + return EXIT_FAILURE; + } + if (!run_wait_test()) { + return EXIT_FAILURE; + } + if (!run_cancel_test()) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/test_task.c b/tests/test_task.c index 3629ed3..dfe5a16 100644 --- a/tests/test_task.c +++ b/tests/test_task.c @@ -157,7 +157,7 @@ run_test(void) void *thread_result; CHECK(pthread_join(thread, &thread_result) == 0); CHECK((uintptr_t)thread_result == 1); - CHECK(lardon3d_resource_governor_release(governor, reservation)); + CHECK(!lardon3d_resource_governor_release(governor, reservation)); CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_COMPLETED); CHECK(snapshot.progress == 100); @@ -178,7 +178,7 @@ run_test(void) lardon3d_task_request_cancel(task); CHECK(lardon3d_task_join(task)); CHECK(pthread_join(thread, NULL) == 0); - CHECK(lardon3d_resource_governor_release(governor, reservation)); + CHECK(!lardon3d_resource_governor_release(governor, reservation)); CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_CANCELLED); CHECK(snapshot.progress < 100); @@ -190,7 +190,7 @@ run_test(void) governor, &resource_snapshot, &estimate, &decision, &reservation )); CHECK(lardon3d_task_start(task, governor, reservation)); - CHECK(lardon3d_resource_governor_release(governor, reservation)); + CHECK(!lardon3d_resource_governor_release(governor, reservation)); CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(snapshot.state == TASK_FAILED); CHECK(strcmp(snapshot.message, "Erreur contrôlée.") == 0);