feat(task): add durable task kind registry
This commit is contained in:
parent
efa556a824
commit
2c122e495f
19 changed files with 925 additions and 86 deletions
|
|
@ -27,7 +27,8 @@ Lardon3D ne vise pas simplement "dossier de photos → objet 3D", mais "ensemble
|
|||
- **Image View** : vues triées et filtrées pour la TUI
|
||||
- **Task** : moteur de tâches avec pause/reprise, annulation et séquences
|
||||
- **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre
|
||||
- **Project Database v1** : identité, tâches/checkpoints et inventaire d'artefacts SQLite
|
||||
- **Project Database v2** : identité, tâches typées/checkpoints et inventaire d'artefacts SQLite
|
||||
- **Task Kind Registry** : identité métier durable et reconstruction runtime explicite
|
||||
- **Task Queue** : file FIFO avec sélection adaptative et backpressure
|
||||
- **Hardware Profile** : détection des capacités matérielles
|
||||
- **Resource Snapshot** : capture instantanée des ressources
|
||||
|
|
@ -40,7 +41,7 @@ Lardon3D ne vise pas simplement "dossier de photos → objet 3D", mais "ensemble
|
|||
|
||||
### Briques prévues (PLANNED)
|
||||
|
||||
- Reconstruction des callbacks métier et resoumission des tâches récupérables
|
||||
- Premiers types métier reconstructibles et resoumission contrôlée des tâches récupérables
|
||||
- DAG de dépendances
|
||||
- Pools de workers multiples (CPU/GPU/IO)
|
||||
- Publication live validée
|
||||
|
|
@ -94,6 +95,7 @@ Acquisitions
|
|||
- [Vue d'ensemble](docs/architecture/overview.md)
|
||||
- [Runtime](docs/architecture/runtime.md)
|
||||
- [Système de tâches](docs/architecture/task_system.md)
|
||||
- [Registry des types de tâches](docs/architecture/task_kind_registry.md)
|
||||
- [File de tâches](docs/architecture/task_queue.md)
|
||||
- [Resource Governor](docs/architecture/resource_governor.md)
|
||||
- [Intégration Scheduler ↔ Governor](docs/architecture/scheduler_resource_integration.md)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ par le `time_t` local avant conversion. Le format reste donc lisible entre
|
|||
plateformes uniquement pour les valeurs communes à leurs domaines `size_t` et
|
||||
`time_t`.
|
||||
|
||||
## Project Database v1
|
||||
## Project Database v2
|
||||
|
||||
SQLite contient l'état logique interrogable et les références aux fichiers ;
|
||||
les checkpoints et artefacts volumineux restent externes. L'enregistrement du
|
||||
|
|
@ -135,19 +135,29 @@ L'inventaire distingue checkpoint récupérable durable, récupérable mais publ
|
|||
non durable, absent, invalide, version inconnue et erreur d'I/O. Aucune réparation
|
||||
ou suppression silencieuse n'est effectuée.
|
||||
|
||||
Le format checkpoint reste en version 1 et ne contient pas de `task_kind`. Le
|
||||
schéma SQLite v2 conserve `task_kind` et `task_kind_version` dans le résumé
|
||||
logique interrogable. La migration v1→v2 laisse ces deux colonnes à `NULL` : une
|
||||
tâche legacy reste inspectable mais ne peut pas être reconstruite ou resoumise.
|
||||
Un kind inconnu ou une version non supportée est diagnostiqué sans exécuter de
|
||||
code.
|
||||
|
||||
## Statut
|
||||
|
||||
**IMPLEMENTED** — modèle durable, codec v1, lecture validée, publication
|
||||
atomique et restauration sûre d'une tâche isolée.
|
||||
|
||||
**IMPLEMENTED** — Project Database v1 pour identité, résumés de tâches,
|
||||
**IMPLEMENTED** — Project Database v2 pour identité, résumés de tâches typées,
|
||||
références checkpoint et artefacts génériques.
|
||||
|
||||
**IMPLEMENTED** — registry statique bornée et reconstruction explicite avec
|
||||
ownership du userdata.
|
||||
|
||||
**IMPLEMENTED** — API projet de sauvegarde fichier+DB et inventaire validé au
|
||||
redémarrage.
|
||||
|
||||
**NOT_YET_WIRED** — autosave complet, reconstruction des callbacks métier,
|
||||
**NOT_YET_WIRED** — autosave complet, premier type métier de production,
|
||||
resoumission scheduler et réconciliation des fichiers orphelins.
|
||||
|
||||
**PLANNED** — catalogue d'artefacts photogrammétriques réels, migrations v2+ et
|
||||
**PLANNED** — catalogue d'artefacts photogrammétriques réels, migrations v3+ et
|
||||
reprise globale.
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ La base de données projet stocke les métadonnées de reconstruction et les rel
|
|||
## Frontière avec les checkpoints de tâche
|
||||
|
||||
Le modèle durable v1 et son codec fichier sont implémentés indépendamment du
|
||||
stockage. La future base réutilisera les mêmes règles de normalisation et de
|
||||
stockage. La base réutilise les mêmes règles de normalisation et de
|
||||
validation ; elle ne stockera jamais les objets pthread, callbacks, pointeurs,
|
||||
contrats ou réservations. Le fichier par tâche est une fondation, pas une
|
||||
Project Database miniature.
|
||||
|
|
@ -167,12 +167,12 @@ référence vers le fichier checkpoint. Le fichier checkpoint validé reste la
|
|||
source complète pour `lardon3d_task_restore()` ; la DB seule ne reconstruit
|
||||
jamais une tâche. Un écart ou un fichier invalide interdit la reprise.
|
||||
|
||||
## Schéma v1 implémenté
|
||||
## Schéma v2 implémenté
|
||||
|
||||
- `metadata(key PRIMARY KEY, value)` contient uniquement `schema_version=1`.
|
||||
- `metadata(key PRIMARY KEY, value)` contient uniquement `schema_version=2`.
|
||||
- `project(singleton=1, stable_id UNIQUE, name, created_at, updated_at)` décrit
|
||||
l'unique identité logique de la DB.
|
||||
- `tasks(task_id PRIMARY KEY, name, saved_state, recovery_state, progress,
|
||||
- `tasks(task_id PRIMARY KEY, name, task_kind, task_kind_version, saved_state, recovery_state, progress,
|
||||
sequence_count, started_sec/nsec, finished_sec/nsec, updated_at)` contient le
|
||||
résumé durable. Les IDs v1 sont compris entre 1 et `INT64_MAX`.
|
||||
- `checkpoints(task_id PRIMARY KEY REFERENCES tasks ON DELETE CASCADE, path,
|
||||
|
|
@ -187,13 +187,26 @@ Les indexes portent uniquement sur `tasks(recovery_state, task_id)`,
|
|||
|
||||
## Ouverture et migrations
|
||||
|
||||
Une DB vide reçoit le schéma v1 dans une transaction `BEGIN IMMEDIATE`. Une DB
|
||||
v1 est validée puis ouverte. Une version future est refusée et une DB contenant
|
||||
Une DB vide reçoit directement le schéma v2 dans une transaction
|
||||
`BEGIN IMMEDIATE`. Une DB v1 reçoit transactionnellement les colonnes nullable
|
||||
`task_kind` et `task_kind_version`, puis passe à v2. Les anciennes lignes restent
|
||||
`NULL/NULL`, sans type inventé et sans perte des projets, tâches, checkpoints ou
|
||||
artefacts. Une interruption ou erreur provoque un rollback complet. Une DB v2
|
||||
est validée puis ouverte. Une version future est refusée et une DB contenant
|
||||
des tables sans métadonnée de version est considérée corrompue. La fonction
|
||||
interne de migration ne connaît que `0 → 1`; aucune migration fictive ou
|
||||
destructive n'est présente.
|
||||
interne de migration ne connaît que `0 → 2` et `1 → 2`.
|
||||
|
||||
Configuration v1 : `foreign_keys=ON`, `journal_mode=DELETE`,
|
||||
Migration v1→v2 exacte, exécutée entre `BEGIN IMMEDIATE` et `COMMIT` :
|
||||
|
||||
```sql
|
||||
ALTER TABLE tasks ADD COLUMN task_kind TEXT;
|
||||
ALTER TABLE tasks ADD COLUMN task_kind_version INTEGER
|
||||
CHECK(task_kind_version IS NULL OR task_kind_version > 0);
|
||||
UPDATE metadata SET value=2
|
||||
WHERE key='schema_version' AND value=1;
|
||||
```
|
||||
|
||||
Configuration v2 : `foreign_keys=ON`, `journal_mode=DELETE`,
|
||||
`synchronous=FULL`, `busy_timeout=5000`. Le mode DELETE convient au propriétaire
|
||||
unique actuel, évite les fichiers WAL/SHM durables et conserve la synchronisation
|
||||
forte. Le timeout borne l'attente d'un verrou externe à cinq secondes.
|
||||
|
|
@ -235,17 +248,25 @@ Les checkpoints sont référencés par chemins relatifs portables :
|
|||
résout ce chemin sous la racine, charge le checkpoint et vérifie la cohérence du
|
||||
snapshot avec le résumé DB.
|
||||
|
||||
Après copie du record hors mutex SQLite, l'inventaire consulte la registry. Il
|
||||
distingue `LEGACY_UNTYPED`, `UNKNOWN_TASK_KIND` et
|
||||
`UNSUPPORTED_TASK_KIND_VERSION`. Aucun reconstructeur métier n'est appelé sous
|
||||
le mutex DB. Un upsert ne peut pas changer le couple kind/version d'un task ID.
|
||||
|
||||
## Statut
|
||||
|
||||
**IMPLEMENTED** — SQLite système, schéma/migration v1, identité projet,
|
||||
**IMPLEMENTED** — SQLite système, schéma v2 et migration v1→v2, identité projet,
|
||||
transactions tâche+checkpoint, pagination de reprise et artefacts génériques.
|
||||
|
||||
**IMPLEMENTED** — ouverture/fermeture avec le projet, identité INI/DB cohérente,
|
||||
publication de checkpoints par le projet et inventaire de reprise validé.
|
||||
|
||||
**NOT_YET_WIRED** — reconstruction des callbacks/userdata métier, resoumission
|
||||
scheduler, autosave à toutes les transitions et réconciliation des checkpoints
|
||||
orphelins, ScanSet et catalogue image persistants, Feature Store et Visual Index.
|
||||
**IMPLEMENTED** — kinds persistants, classification par registry et
|
||||
reconstruction explicite testée hors scheduler.
|
||||
|
||||
**PLANNED** — migrations v2+, dépendances d'artefacts, graphe géométrique et
|
||||
**NOT_YET_WIRED** — type métier de production, resoumission scheduler, autosave
|
||||
à toutes les transitions et réconciliation des checkpoints orphelins, ScanSet
|
||||
et catalogue image persistants, Feature Store et Visual Index.
|
||||
|
||||
**PLANNED** — migrations v3+, dépendances d'artefacts, graphe géométrique et
|
||||
reconstruction incrémentale.
|
||||
|
|
|
|||
|
|
@ -89,7 +89,10 @@ les opérations multi-tables sont transactionnelles et bornées.
|
|||
l'identité et ferme la connexion. L'application arrête la task queue avant la
|
||||
fermeture finale du projet.
|
||||
|
||||
**NOT_YET_WIRED** — aucune reconstruction de callback/userdata ni resoumission
|
||||
**IMPLEMENTED** — la registry reconstruit explicitement callback/userdata hors
|
||||
mutex DB pour un kind connu ; elle ne soumet aucune tâche.
|
||||
|
||||
**NOT_YET_WIRED** — aucun type métier de production ni resoumission
|
||||
automatique. Une future resoumission devra aussi permettre à la queue de
|
||||
conserver l'identifiant stable restauré au lieu d'en assigner un nouveau.
|
||||
|
||||
|
|
|
|||
44
docs/architecture/task_kind_registry.md
Normal file
44
docs/architecture/task_kind_registry.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Registry des types métier de tâches
|
||||
|
||||
## Contrat
|
||||
|
||||
Une instance possède un **task ID** stable. Son **task kind** décrit son
|
||||
comportement métier, son **task state** décrit son état d'avancement, la
|
||||
**checkpoint version** décrit le codec générique et la **task kind version**
|
||||
versionne les paramètres nécessaires au reconstructeur. Ces identités ne sont
|
||||
pas interchangeables.
|
||||
|
||||
Le kind v1 est une chaîne ASCII de 1 à 64 caractères au format
|
||||
`[a-z0-9][a-z0-9._-]*`. La version est un entier non nul. Aucun kind n'est
|
||||
déduit d'un nom, callback ou pointeur et aucune normalisation n'est effectuée.
|
||||
|
||||
## Registry et ownership
|
||||
|
||||
La registry est une vue bornée à 64 descriptors sur un tableau statique
|
||||
immutable. Le lookup est linéaire, déterministe, sans allocation et sûr en
|
||||
lecture concurrente. Elle ne charge aucun code dynamiquement.
|
||||
|
||||
Un descriptor contient exactement le kind, sa version et un reconstructeur.
|
||||
Le reconstructeur produit callback, userdata et destructeur optionnel. Avant le
|
||||
transfert, la registry nettoie le userdata sur toute erreur ; après restauration
|
||||
réussie, `Lardon3DTask` en devient propriétaire et le détruit une fois après la
|
||||
fin de l'exécution. Le constructeur métier n'est jamais appelé sous mutex DB.
|
||||
|
||||
## Persistance et legacy
|
||||
|
||||
Le checkpoint générique reste en version 1. Project Database v2 conserve le
|
||||
kind/version ; les lignes migrées depuis v1 restent `NULL/NULL` et sont classées
|
||||
`LEGACY_UNTYPED`. Un kind inconnu ou une version non supportée reste inspectable
|
||||
mais inexécutable. Aucun type n'est inventé et aucun code n'est sélectionné par
|
||||
adresse persistée.
|
||||
|
||||
## Statut
|
||||
|
||||
**IMPLEMENTED** — identité typée immutable, registry statique, lookup,
|
||||
migration DB v1→v2, classification recovery et restauration explicite testée.
|
||||
|
||||
**NOT_YET_WIRED** — type métier de production, paramètres métier persistants,
|
||||
autosave complet, resoumission automatique et réconciliation orpheline.
|
||||
|
||||
**PLANNED** — kinds des tâches ScanSet, Image Catalog, Feature Store, Visual
|
||||
Index et reconstruction lorsque ces traitements existeront réellement.
|
||||
|
|
@ -39,6 +39,8 @@ une opération de réadmission, pas un état supplémentaire.
|
|||
|---|---|
|
||||
| `lardon3d_task_durable_snapshot()` | Copie les champs durables sous mutex |
|
||||
| `lardon3d_task_restore()` | Reconstruit une tâche sans état d'exécution vivant |
|
||||
| `lardon3d_task_create_typed()` | Crée une tâche persistable avec kind/version immuables |
|
||||
| `lardon3d_task_restore_typed()` | Restaure une tâche typée et transfère l'ownership du userdata |
|
||||
| `lardon3d_task_checkpoint_save()` | Publie atomiquement un snapshot v1 |
|
||||
| `lardon3d_task_checkpoint_load()` | Lit et valide un checkpoint borné |
|
||||
|
||||
|
|
@ -73,9 +75,14 @@ mais sa durabilité après crash n'est pas confirmée.
|
|||
**IMPLEMENTED** — cycle de vie, pause/annulation coopératives, séquences
|
||||
adaptatives et fondation de checkpoints persistants isolés.
|
||||
|
||||
**NOT_YET_WIRED** — sauvegarde automatique et restauration par la file.
|
||||
**IMPLEMENTED** — une registry statique peut reconstruire explicitement le
|
||||
callback et le userdata d'un kind connu. Le destructeur du userdata est détenu
|
||||
par la tâche restaurée et exécuté après arrêt de son exécution.
|
||||
|
||||
La Project Database v1 peut enregistrer transactionnellement un résumé
|
||||
**NOT_YET_WIRED** — sauvegarde automatique, types métier de production et
|
||||
restauration par la file.
|
||||
|
||||
La Project Database v2 peut enregistrer transactionnellement un résumé
|
||||
`Lardon3DTaskDurableSnapshot` et la référence de son checkpoint. Elle ne stocke
|
||||
ni estimation sérialisée complète, ni callback, ni réservation, et ne remplace
|
||||
pas la validation du fichier checkpoint avant `task_restore()`.
|
||||
|
|
@ -83,8 +90,7 @@ pas la validation du fichier checkpoint avant `task_restore()`.
|
|||
`lardon3d_project_checkpoint_task()` est la frontière runtime : elle capture le
|
||||
snapshot, publie le fichier hors mutex de tâche, puis met à jour la DB. L'API
|
||||
d'inventaire retourne des snapshots durables validés, mais ne peut pas appeler
|
||||
`task_restore()` tant que le type métier ne sait pas reconstruire callback et
|
||||
userdata.
|
||||
`task_restore()` que via un descriptor connu ; aucun pointeur n'est persistant.
|
||||
|
||||
## Limites
|
||||
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@ Lardon3D suit une feuille de route ordonnée qui privilégie la stabilité et la
|
|||
|
||||
### Phase 3 : Persistance
|
||||
- ✅ Fondation versionnée des checkpoints de tâches
|
||||
- ✅ Project Database v1 (SQLite)
|
||||
- ✅ Project Database v2 (SQLite, migration v1 et tâches typées)
|
||||
- ✅ Branchement Project Database au cycle de vie projet et inventaire de reprise
|
||||
- 📋 Reconstruction métier et resoumission des tâches récupérables
|
||||
- ✅ Registry durable des types métier de tâches
|
||||
- 📋 Premier type métier reconstructible et resoumission des tâches récupérables
|
||||
- 📋 ScanSet et Image Catalog persistants
|
||||
|
||||
### Phase 4 : Pipeline avancé
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <lardon3d/app_state.h>
|
||||
#include <lardon3d/project_db.h>
|
||||
#include <lardon3d/task_kind_registry.h>
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_PROJECT_TASK_CHECKPOINT_OK = 0,
|
||||
|
|
@ -22,12 +23,17 @@ typedef enum {
|
|||
LARDON3D_PROJECT_RECOVERY_MISSING_CHECKPOINT,
|
||||
LARDON3D_PROJECT_RECOVERY_INVALID_CHECKPOINT,
|
||||
LARDON3D_PROJECT_RECOVERY_UNSUPPORTED_CHECKPOINT,
|
||||
LARDON3D_PROJECT_RECOVERY_CHECKPOINT_IO_ERROR
|
||||
LARDON3D_PROJECT_RECOVERY_CHECKPOINT_IO_ERROR,
|
||||
LARDON3D_PROJECT_RECOVERY_LEGACY_UNTYPED,
|
||||
LARDON3D_PROJECT_RECOVERY_UNKNOWN_TASK_KIND,
|
||||
LARDON3D_PROJECT_RECOVERY_UNSUPPORTED_TASK_KIND_VERSION
|
||||
} Lardon3DProjectRecoveryStatus;
|
||||
|
||||
typedef struct {
|
||||
uint64_t task_id;
|
||||
char name[LARDON3D_TASK_NAME_CAPACITY];
|
||||
char task_kind[LARDON3D_TASK_KIND_CAPACITY];
|
||||
uint32_t task_kind_version;
|
||||
Lardon3DProjectRecoveryStatus status;
|
||||
Lardon3DProjectDbCheckpointDurability durability;
|
||||
Lardon3DTaskDurableSnapshot snapshot;
|
||||
|
|
@ -50,6 +56,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_task(
|
|||
);
|
||||
Lardon3DProjectDbResult lardon3d_project_list_recoverable(
|
||||
Lardon3DAppState *state,
|
||||
const Lardon3DTaskKindRegistry *registry,
|
||||
uint64_t after_task_id,
|
||||
Lardon3DProjectRecoveryEntry *entries,
|
||||
size_t capacity,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include <lardon3d/task.h>
|
||||
|
||||
enum {
|
||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 1,
|
||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 2,
|
||||
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
||||
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
||||
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
||||
|
|
@ -57,6 +57,9 @@ typedef struct {
|
|||
typedef struct {
|
||||
uint64_t task_id;
|
||||
char name[LARDON3D_TASK_NAME_CAPACITY];
|
||||
bool has_task_kind;
|
||||
char task_kind[LARDON3D_TASK_KIND_CAPACITY];
|
||||
uint32_t task_kind_version;
|
||||
Lardon3DTaskState saved_state;
|
||||
Lardon3DTaskState recovery_state;
|
||||
unsigned int progress;
|
||||
|
|
@ -103,6 +106,8 @@ Lardon3DProjectDbResult lardon3d_project_db_get_project(
|
|||
Lardon3DProjectDbResult lardon3d_project_db_record_task(
|
||||
Lardon3DProjectDb *database,
|
||||
const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
const char *task_kind,
|
||||
uint32_t task_kind_version,
|
||||
const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||
int64_t updated_at
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
enum {
|
||||
LARDON3D_TASK_NAME_CAPACITY = 128,
|
||||
LARDON3D_TASK_MESSAGE_CAPACITY = 256,
|
||||
LARDON3D_TASK_KIND_CAPACITY = 65,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
|
@ -23,6 +24,7 @@ typedef enum {
|
|||
|
||||
typedef struct Lardon3DTask Lardon3DTask;
|
||||
typedef bool (*Lardon3DTaskCallback)(Lardon3DTask *task, void *userdata);
|
||||
typedef void (*Lardon3DTaskUserdataDestroy)(void *userdata);
|
||||
|
||||
typedef struct {
|
||||
uint64_t id;
|
||||
|
|
@ -62,6 +64,15 @@ Lardon3DTask *lardon3d_task_create(
|
|||
Lardon3DTaskCallback callback,
|
||||
void *userdata
|
||||
);
|
||||
Lardon3DTask *lardon3d_task_create_typed(
|
||||
const char *name,
|
||||
const Lardon3DResourceEstimate *estimate,
|
||||
const char *task_kind,
|
||||
uint32_t task_kind_version,
|
||||
Lardon3DTaskCallback callback,
|
||||
void *userdata,
|
||||
Lardon3DTaskUserdataDestroy userdata_destroy
|
||||
);
|
||||
void lardon3d_task_destroy(Lardon3DTask *task);
|
||||
/* Exécute le callback dans le thread appelant. */
|
||||
bool lardon3d_task_start(
|
||||
|
|
@ -93,6 +104,22 @@ Lardon3DTask *lardon3d_task_restore(
|
|||
Lardon3DTaskCallback callback,
|
||||
void *userdata
|
||||
);
|
||||
Lardon3DTask *lardon3d_task_restore_typed(
|
||||
const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
const char *task_kind,
|
||||
uint32_t task_kind_version,
|
||||
Lardon3DTaskCallback callback,
|
||||
void *userdata,
|
||||
Lardon3DTaskUserdataDestroy userdata_destroy
|
||||
);
|
||||
/* Une restauration typée réussie transfère userdata/userdata_destroy à la
|
||||
* tâche. En cas d'échec, l'appelant en reste propriétaire. */
|
||||
bool lardon3d_task_kind_is_valid(const char *task_kind);
|
||||
bool lardon3d_task_kind(
|
||||
const Lardon3DTask *task,
|
||||
char task_kind[LARDON3D_TASK_KIND_CAPACITY],
|
||||
uint32_t *task_kind_version
|
||||
);
|
||||
uint64_t lardon3d_task_id(const Lardon3DTask *task);
|
||||
bool lardon3d_task_assign_id(Lardon3DTask *task, uint64_t id);
|
||||
bool lardon3d_task_resource_estimate(
|
||||
|
|
|
|||
65
include/lardon3d/task_kind_registry.h
Normal file
65
include/lardon3d/task_kind_registry.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#ifndef LARDON3D_TASK_KIND_REGISTRY_H
|
||||
#define LARDON3D_TASK_KIND_REGISTRY_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lardon3d/task.h>
|
||||
|
||||
enum {
|
||||
LARDON3D_TASK_KIND_REGISTRY_MAX = 64,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Lardon3DTaskCallback callback;
|
||||
void *userdata;
|
||||
Lardon3DTaskUserdataDestroy userdata_destroy;
|
||||
} Lardon3DTaskKindBinding;
|
||||
|
||||
typedef bool (*Lardon3DTaskKindReconstruct)(
|
||||
const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
void *context,
|
||||
Lardon3DTaskKindBinding *binding
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
const char *kind;
|
||||
uint32_t kind_version;
|
||||
Lardon3DTaskKindReconstruct reconstruct;
|
||||
} Lardon3DTaskKindDescriptor;
|
||||
|
||||
typedef struct {
|
||||
const Lardon3DTaskKindDescriptor *descriptors;
|
||||
size_t count;
|
||||
} Lardon3DTaskKindRegistry;
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_TASK_KIND_OK = 0,
|
||||
LARDON3D_TASK_KIND_INVALID_ARGUMENT,
|
||||
LARDON3D_TASK_KIND_UNKNOWN,
|
||||
LARDON3D_TASK_KIND_UNSUPPORTED_VERSION,
|
||||
LARDON3D_TASK_KIND_RECONSTRUCTION_FAILED,
|
||||
LARDON3D_TASK_KIND_RESTORE_FAILED
|
||||
} Lardon3DTaskKindResult;
|
||||
|
||||
bool lardon3d_task_kind_registry_init(
|
||||
Lardon3DTaskKindRegistry *registry,
|
||||
const Lardon3DTaskKindDescriptor *descriptors,
|
||||
size_t count
|
||||
);
|
||||
Lardon3DTaskKindResult lardon3d_task_kind_registry_lookup(
|
||||
const Lardon3DTaskKindRegistry *registry,
|
||||
const char *kind,
|
||||
uint32_t kind_version,
|
||||
const Lardon3DTaskKindDescriptor **descriptor
|
||||
);
|
||||
Lardon3DTaskKindResult lardon3d_task_kind_registry_restore(
|
||||
const Lardon3DTaskKindRegistry *registry,
|
||||
const char *kind,
|
||||
uint32_t kind_version,
|
||||
const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
void *context,
|
||||
Lardon3DTask **task
|
||||
);
|
||||
|
||||
#endif
|
||||
20
meson.build
20
meson.build
|
|
@ -38,6 +38,7 @@ executable(
|
|||
'src/project_db.c',
|
||||
'src/task.c',
|
||||
'src/task_checkpoint.c',
|
||||
'src/task_kind_registry.c',
|
||||
'src/task_queue.c',
|
||||
'src/resource_governor.c',
|
||||
'src/resource_snapshot.c',
|
||||
|
|
@ -133,6 +134,9 @@ project_db_test = executable(
|
|||
sources: [
|
||||
'tests/test_project_db.c',
|
||||
'src/project_db.c',
|
||||
'src/task.c',
|
||||
'src/resource_governor.c',
|
||||
'src/resource_snapshot.c',
|
||||
],
|
||||
c_args: ['-DLARDON3D_PROJECT_DB_TESTING'],
|
||||
include_directories: include_directories('include'),
|
||||
|
|
@ -150,6 +154,7 @@ project_test = executable(
|
|||
'src/project_db.c',
|
||||
'src/task.c',
|
||||
'src/task_checkpoint.c',
|
||||
'src/task_kind_registry.c',
|
||||
'src/image_catalog.c',
|
||||
'src/image_view.c',
|
||||
'src/resource_governor.c',
|
||||
|
|
@ -165,6 +170,21 @@ project_test = executable(
|
|||
|
||||
test('project', project_test, timeout: 30)
|
||||
|
||||
task_kind_registry_test = executable(
|
||||
'test-task-kind-registry',
|
||||
sources: [
|
||||
'tests/test_task_kind_registry.c',
|
||||
'src/task_kind_registry.c',
|
||||
'src/task.c',
|
||||
'src/resource_governor.c',
|
||||
'src/resource_snapshot.c',
|
||||
],
|
||||
include_directories: include_directories('include'),
|
||||
dependencies: [threads],
|
||||
)
|
||||
|
||||
test('task-kind-registry', task_kind_registry_test, timeout: 30)
|
||||
|
||||
sequential_task_test = executable(
|
||||
'test-sequential-task',
|
||||
sources: [
|
||||
|
|
|
|||
|
|
@ -861,6 +861,11 @@ lardon3d_project_checkpoint_task(
|
|||
if (!lardon3d_task_durable_snapshot(task, &snapshot) || snapshot.id == 0) {
|
||||
return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
}
|
||||
char task_kind[LARDON3D_TASK_KIND_CAPACITY];
|
||||
uint32_t task_kind_version = 0;
|
||||
if (!lardon3d_task_kind(task, task_kind, &task_kind_version)) {
|
||||
return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
}
|
||||
char relative[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
||||
char absolute[PATH_MAX];
|
||||
if (!checkpoint_paths(state, snapshot.id, relative, absolute)) {
|
||||
|
|
@ -892,6 +897,8 @@ lardon3d_project_checkpoint_task(
|
|||
Lardon3DProjectDbResult recorded = lardon3d_project_db_record_task(
|
||||
state->project_db,
|
||||
&snapshot,
|
||||
task_kind,
|
||||
task_kind_version,
|
||||
&checkpoint,
|
||||
now.tv_sec
|
||||
);
|
||||
|
|
@ -923,6 +930,7 @@ coherent_recovery(
|
|||
Lardon3DProjectDbResult
|
||||
lardon3d_project_list_recoverable(
|
||||
Lardon3DAppState *state,
|
||||
const Lardon3DTaskKindRegistry *registry,
|
||||
uint64_t after_task_id,
|
||||
Lardon3DProjectRecoveryEntry *entries,
|
||||
size_t capacity,
|
||||
|
|
@ -932,7 +940,7 @@ lardon3d_project_list_recoverable(
|
|||
if (count) {
|
||||
*count = 0;
|
||||
}
|
||||
if (!state || !state->project_loaded || !state->project_db || !entries
|
||||
if (!state || !state->project_loaded || !state->project_db || !registry || !entries
|
||||
|| !count || capacity == 0
|
||||
|| capacity > LARDON3D_PROJECT_DB_RECOVERY_PAGE_MAX) {
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
|
|
@ -962,9 +970,28 @@ lardon3d_project_list_recoverable(
|
|||
entry->task_id = tasks[index].task_id;
|
||||
entry->durability = tasks[index].checkpoint.durability;
|
||||
(void)snprintf(entry->name, sizeof(entry->name), "%s", tasks[index].name);
|
||||
if (tasks[index].has_task_kind) {
|
||||
(void)snprintf(entry->task_kind, sizeof(entry->task_kind), "%s",
|
||||
tasks[index].task_kind);
|
||||
entry->task_kind_version = tasks[index].task_kind_version;
|
||||
}
|
||||
char relative[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
||||
char absolute[PATH_MAX];
|
||||
if (!checkpoint_paths(state, entry->task_id, relative, absolute)
|
||||
const Lardon3DTaskKindDescriptor *descriptor = NULL;
|
||||
Lardon3DTaskKindResult kind_result = tasks[index].has_task_kind
|
||||
? lardon3d_task_kind_registry_lookup(
|
||||
registry, tasks[index].task_kind,
|
||||
tasks[index].task_kind_version, &descriptor)
|
||||
: LARDON3D_TASK_KIND_UNKNOWN;
|
||||
if (!tasks[index].has_task_kind) {
|
||||
entry->status = LARDON3D_PROJECT_RECOVERY_LEGACY_UNTYPED;
|
||||
} else if (kind_result == LARDON3D_TASK_KIND_UNKNOWN) {
|
||||
entry->status = LARDON3D_PROJECT_RECOVERY_UNKNOWN_TASK_KIND;
|
||||
} else if (kind_result == LARDON3D_TASK_KIND_UNSUPPORTED_VERSION) {
|
||||
entry->status = LARDON3D_PROJECT_RECOVERY_UNSUPPORTED_TASK_KIND_VERSION;
|
||||
} else if (kind_result != LARDON3D_TASK_KIND_OK) {
|
||||
entry->status = LARDON3D_PROJECT_RECOVERY_UNKNOWN_TASK_KIND;
|
||||
} else if (!checkpoint_paths(state, entry->task_id, relative, absolute)
|
||||
|| strcmp(relative, tasks[index].checkpoint.path) != 0) {
|
||||
entry->status = LARDON3D_PROJECT_RECOVERY_INVALID_CHECKPOINT;
|
||||
} else {
|
||||
|
|
|
|||
120
src/project_db.c
120
src/project_db.c
|
|
@ -17,17 +17,19 @@ struct Lardon3DProjectDb {
|
|||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||
};
|
||||
|
||||
static const char schema_v1[] =
|
||||
static const char schema_v2[] =
|
||||
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
||||
"INSERT INTO metadata(key,value) VALUES('schema_version',1);"
|
||||
"INSERT INTO metadata(key,value) VALUES('schema_version',2);"
|
||||
"CREATE TABLE project(singleton INTEGER PRIMARY KEY CHECK(singleton=1),"
|
||||
"stable_id TEXT NOT NULL UNIQUE,name TEXT NOT NULL,created_at INTEGER NOT NULL,updated_at INTEGER NOT NULL);"
|
||||
"CREATE TABLE tasks(task_id INTEGER PRIMARY KEY CHECK(task_id>0),name TEXT NOT NULL,"
|
||||
"task_kind TEXT,task_kind_version INTEGER,"
|
||||
"saved_state INTEGER NOT NULL CHECK(saved_state BETWEEN 0 AND 5),"
|
||||
"recovery_state INTEGER NOT NULL CHECK(recovery_state BETWEEN 0 AND 5),"
|
||||
"progress INTEGER NOT NULL CHECK(progress BETWEEN 0 AND 100),sequence_count INTEGER NOT NULL CHECK(sequence_count>=0),"
|
||||
"started_sec INTEGER NOT NULL,started_nsec INTEGER NOT NULL CHECK(started_nsec BETWEEN 0 AND 999999999),"
|
||||
"finished_sec INTEGER NOT NULL,finished_nsec INTEGER NOT NULL CHECK(finished_nsec BETWEEN 0 AND 999999999),updated_at INTEGER NOT NULL);"
|
||||
"finished_sec INTEGER NOT NULL,finished_nsec INTEGER NOT NULL CHECK(finished_nsec BETWEEN 0 AND 999999999),updated_at INTEGER NOT NULL,"
|
||||
"CHECK((task_kind IS NULL AND task_kind_version IS NULL) OR (task_kind IS NOT NULL AND task_kind_version>0)));"
|
||||
"CREATE INDEX tasks_recovery_state_idx ON tasks(recovery_state,task_id);"
|
||||
"CREATE TABLE checkpoints(task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
|
||||
"path TEXT NOT NULL,format_version INTEGER NOT NULL CHECK(format_version>0),"
|
||||
|
|
@ -122,10 +124,35 @@ migrate(Lardon3DProjectDb *database, unsigned int from_version)
|
|||
copy_error(database->error, "Version de schéma future non supportée.");
|
||||
return LARDON3D_PROJECT_DB_UNSUPPORTED_SCHEMA;
|
||||
}
|
||||
if (from_version == 1) return LARDON3D_PROJECT_DB_OK;
|
||||
if (from_version != 0) return LARDON3D_PROJECT_DB_CORRUPT;
|
||||
if (from_version == LARDON3D_PROJECT_DB_SCHEMA_VERSION) {
|
||||
return LARDON3D_PROJECT_DB_OK;
|
||||
}
|
||||
if (from_version != 0 && from_version != 1) {
|
||||
return LARDON3D_PROJECT_DB_CORRUPT;
|
||||
}
|
||||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
|
||||
if (result == LARDON3D_PROJECT_DB_OK) result = execute(database, schema_v1, "create schema v1");
|
||||
if (result == LARDON3D_PROJECT_DB_OK && from_version == 0) {
|
||||
result = execute(database, schema_v2, "create schema v2");
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK && from_version == 1) {
|
||||
result = execute(database,
|
||||
"ALTER TABLE tasks ADD COLUMN task_kind TEXT;"
|
||||
"ALTER TABLE tasks ADD COLUMN task_kind_version INTEGER CHECK(task_kind_version IS NULL OR task_kind_version>0)",
|
||||
"migrate schema v1 to v2");
|
||||
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2");
|
||||
if (result == LARDON3D_PROJECT_DB_OK && forced_failure
|
||||
&& strcmp(forced_failure, "1") == 0) {
|
||||
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
||||
"forced migration failure");
|
||||
}
|
||||
#endif
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
result = execute(database,
|
||||
"UPDATE metadata SET value=2 WHERE key='schema_version' AND value=1",
|
||||
"finish schema v2 migration");
|
||||
}
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK) result = execute(database, "COMMIT", "commit migration");
|
||||
if (result != LARDON3D_PROJECT_DB_OK) (void)execute(database, "ROLLBACK", "rollback migration");
|
||||
return result;
|
||||
|
|
@ -187,7 +214,7 @@ lardon3d_project_db_open(const char *path, Lardon3DProjectDb **output, char erro
|
|||
unsigned int version = 0;
|
||||
if (result == LARDON3D_PROJECT_DB_OK) result = read_schema_version(database, &version);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) result = migrate(database, version);
|
||||
if (result == LARDON3D_PROJECT_DB_OK && version == 1) {
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
const char *required[] = {"project", "tasks", "checkpoints", "artifacts"};
|
||||
for (size_t index = 0; index < 4 && result == LARDON3D_PROJECT_DB_OK; ++index) {
|
||||
if (!table_exists(database->connection, required[index])) {
|
||||
|
|
@ -223,7 +250,7 @@ lardon3d_project_db_last_error(Lardon3DProjectDb *database, char error[LARDON3D_
|
|||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return true;
|
||||
}
|
||||
unsigned int lardon3d_project_db_schema_version(Lardon3DProjectDb *database) { return database ? 1U : 0U; }
|
||||
unsigned int lardon3d_project_db_schema_version(Lardon3DProjectDb *database) { return database ? LARDON3D_PROJECT_DB_SCHEMA_VERSION : 0U; }
|
||||
|
||||
static Lardon3DProjectDbResult
|
||||
step_done(Lardon3DProjectDb *database, sqlite3_stmt *statement, const char *context)
|
||||
|
|
@ -323,9 +350,15 @@ valid_durable_task(const Lardon3DTaskDurableSnapshot *snapshot, int64_t updated_
|
|||
|
||||
Lardon3DProjectDbResult
|
||||
lardon3d_project_db_record_task(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
const char *task_kind, uint32_t task_kind_version,
|
||||
const Lardon3DProjectDbCheckpoint *checkpoint, int64_t updated_at)
|
||||
{
|
||||
if (!database || !valid_durable_task(snapshot, updated_at) || (checkpoint && !valid_checkpoint(checkpoint))) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
bool typed = task_kind != NULL;
|
||||
if (!database || !valid_durable_task(snapshot, updated_at)
|
||||
|| (typed && (!lardon3d_task_kind_is_valid(task_kind)
|
||||
|| task_kind_version == 0))
|
||||
|| (!typed && task_kind_version != 0)
|
||||
|| (checkpoint && !valid_checkpoint(checkpoint))) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||
const char *forced_busy = getenv("LARDON3D_TEST_PROJECT_DB_BUSY_CHECKPOINT");
|
||||
if (forced_busy && checkpoint && strcmp(forced_busy, "1") == 0) {
|
||||
|
|
@ -337,17 +370,29 @@ lardon3d_project_db_record_task(Lardon3DProjectDb *database, const Lardon3DTaskD
|
|||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin task record");
|
||||
sqlite3_stmt *statement = NULL;
|
||||
if (result == LARDON3D_PROJECT_DB_OK) result = prepare(database,
|
||||
"INSERT INTO tasks(task_id,name,saved_state,recovery_state,progress,sequence_count,started_sec,started_nsec,finished_sec,finished_nsec,updated_at)"
|
||||
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11) ON CONFLICT(task_id) DO UPDATE SET name=excluded.name,saved_state=excluded.saved_state,"
|
||||
"INSERT INTO tasks(task_id,name,task_kind,task_kind_version,saved_state,recovery_state,progress,sequence_count,started_sec,started_nsec,finished_sec,finished_nsec,updated_at)"
|
||||
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13) ON CONFLICT(task_id) DO UPDATE SET name=excluded.name,saved_state=excluded.saved_state,"
|
||||
"recovery_state=excluded.recovery_state,progress=excluded.progress,sequence_count=excluded.sequence_count,started_sec=excluded.started_sec,"
|
||||
"started_nsec=excluded.started_nsec,finished_sec=excluded.finished_sec,finished_nsec=excluded.finished_nsec,updated_at=excluded.updated_at", &statement);
|
||||
"started_nsec=excluded.started_nsec,finished_sec=excluded.finished_sec,finished_nsec=excluded.finished_nsec,updated_at=excluded.updated_at "
|
||||
"WHERE (tasks.task_kind IS NULL AND excluded.task_kind IS NULL) OR (tasks.task_kind=excluded.task_kind AND tasks.task_kind_version=excluded.task_kind_version)", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)snapshot->id); (void)sqlite3_bind_text(statement, 2, snapshot->name, -1, SQLITE_TRANSIENT);
|
||||
(void)sqlite3_bind_int(statement, 3, (int)snapshot->saved_state); (void)sqlite3_bind_int(statement, 4, (int)snapshot->recovery_state);
|
||||
(void)sqlite3_bind_int(statement, 5, (int)snapshot->progress); (void)sqlite3_bind_int(statement, 6, (int)snapshot->sequence_count);
|
||||
(void)sqlite3_bind_int64(statement, 7, snapshot->started_at.tv_sec); (void)sqlite3_bind_int64(statement, 8, snapshot->started_at.tv_nsec);
|
||||
(void)sqlite3_bind_int64(statement, 9, snapshot->finished_at.tv_sec); (void)sqlite3_bind_int64(statement, 10, snapshot->finished_at.tv_nsec);
|
||||
(void)sqlite3_bind_int64(statement, 11, updated_at); result = step_done(database, statement, "upsert task");
|
||||
if (typed) {
|
||||
(void)sqlite3_bind_text(statement, 3, task_kind, -1, SQLITE_TRANSIENT);
|
||||
(void)sqlite3_bind_int64(statement, 4, task_kind_version);
|
||||
} else {
|
||||
(void)sqlite3_bind_null(statement, 3); (void)sqlite3_bind_null(statement, 4);
|
||||
}
|
||||
(void)sqlite3_bind_int(statement, 5, (int)snapshot->saved_state); (void)sqlite3_bind_int(statement, 6, (int)snapshot->recovery_state);
|
||||
(void)sqlite3_bind_int(statement, 7, (int)snapshot->progress); (void)sqlite3_bind_int(statement, 8, (int)snapshot->sequence_count);
|
||||
(void)sqlite3_bind_int64(statement, 9, snapshot->started_at.tv_sec); (void)sqlite3_bind_int64(statement, 10, snapshot->started_at.tv_nsec);
|
||||
(void)sqlite3_bind_int64(statement, 11, snapshot->finished_at.tv_sec); (void)sqlite3_bind_int64(statement, 12, snapshot->finished_at.tv_nsec);
|
||||
(void)sqlite3_bind_int64(statement, 13, updated_at); result = step_done(database, statement, "upsert task");
|
||||
if (result == LARDON3D_PROJECT_DB_OK
|
||||
&& sqlite3_changes(database->connection) != 1) {
|
||||
copy_error(database->error, "Type métier de tâche immuable.");
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK && checkpoint) {
|
||||
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||
|
|
@ -377,34 +422,45 @@ read_task(sqlite3_stmt *statement, Lardon3DProjectDbTask *task)
|
|||
{
|
||||
memset(task, 0, sizeof(*task));
|
||||
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
||||
int progress = sqlite3_column_int(statement, 4);
|
||||
sqlite3_int64 sequence_count = sqlite3_column_int64(statement, 5);
|
||||
sqlite3_int64 started_nsec = sqlite3_column_int64(statement, 7);
|
||||
sqlite3_int64 finished_nsec = sqlite3_column_int64(statement, 9);
|
||||
bool has_kind = sqlite3_column_type(statement, 2) != SQLITE_NULL;
|
||||
bool has_kind_version = sqlite3_column_type(statement, 3) != SQLITE_NULL;
|
||||
int progress = sqlite3_column_int(statement, 6);
|
||||
sqlite3_int64 sequence_count = sqlite3_column_int64(statement, 7);
|
||||
sqlite3_int64 started_nsec = sqlite3_column_int64(statement, 9);
|
||||
sqlite3_int64 finished_nsec = sqlite3_column_int64(statement, 11);
|
||||
if (id <= 0 || !copy_column(statement, 1, task->name, sizeof(task->name))) return false;
|
||||
task->task_id = (uint64_t)id; task->saved_state = (Lardon3DTaskState)sqlite3_column_int(statement, 2);
|
||||
task->recovery_state = (Lardon3DTaskState)sqlite3_column_int(statement, 3);
|
||||
if (has_kind != has_kind_version) return false;
|
||||
if (has_kind) {
|
||||
sqlite3_int64 version = sqlite3_column_int64(statement, 3);
|
||||
if (!copy_column(statement, 2, task->task_kind, sizeof(task->task_kind))
|
||||
|| !lardon3d_task_kind_is_valid(task->task_kind)
|
||||
|| version <= 0 || version > UINT32_MAX) return false;
|
||||
task->has_task_kind = true;
|
||||
task->task_kind_version = (uint32_t)version;
|
||||
}
|
||||
task->task_id = (uint64_t)id; task->saved_state = (Lardon3DTaskState)sqlite3_column_int(statement, 4);
|
||||
task->recovery_state = (Lardon3DTaskState)sqlite3_column_int(statement, 5);
|
||||
if (progress < 0 || progress > 100 || sequence_count < 0 || sequence_count > UINT_MAX
|
||||
|| started_nsec < 0 || started_nsec >= 1000000000 || finished_nsec < 0 || finished_nsec >= 1000000000
|
||||
|| !database_time(sqlite3_column_int64(statement, 6), &task->started_at.tv_sec)
|
||||
|| !database_time(sqlite3_column_int64(statement, 8), &task->finished_at.tv_sec)) return false;
|
||||
|| !database_time(sqlite3_column_int64(statement, 8), &task->started_at.tv_sec)
|
||||
|| !database_time(sqlite3_column_int64(statement, 10), &task->finished_at.tv_sec)) return false;
|
||||
task->progress = (unsigned int)progress; task->sequence_count = (unsigned int)sequence_count;
|
||||
task->started_at.tv_nsec = (long)started_nsec; task->finished_at.tv_nsec = (long)finished_nsec; task->updated_at = sqlite3_column_int64(statement, 10);
|
||||
if (sqlite3_column_type(statement, 11) != SQLITE_NULL) {
|
||||
task->started_at.tv_nsec = (long)started_nsec; task->finished_at.tv_nsec = (long)finished_nsec; task->updated_at = sqlite3_column_int64(statement, 12);
|
||||
if (sqlite3_column_type(statement, 13) != SQLITE_NULL) {
|
||||
task->has_checkpoint = true;
|
||||
if (!copy_column(statement, 11, task->checkpoint.path, sizeof(task->checkpoint.path))) return false;
|
||||
sqlite3_int64 format_version = sqlite3_column_int64(statement, 12);
|
||||
if (!copy_column(statement, 13, task->checkpoint.path, sizeof(task->checkpoint.path))) return false;
|
||||
sqlite3_int64 format_version = sqlite3_column_int64(statement, 14);
|
||||
if (format_version <= 0 || format_version > UINT32_MAX) return false;
|
||||
task->checkpoint.format_version = (uint32_t)format_version;
|
||||
task->checkpoint.durability = (Lardon3DProjectDbCheckpointDurability)sqlite3_column_int(statement, 13);
|
||||
task->checkpoint.updated_at = sqlite3_column_int64(statement, 14);
|
||||
task->checkpoint.durability = (Lardon3DProjectDbCheckpointDurability)sqlite3_column_int(statement, 15);
|
||||
task->checkpoint.updated_at = sqlite3_column_int64(statement, 16);
|
||||
if (task->checkpoint.durability < LARDON3D_DB_CHECKPOINT_DURABLE
|
||||
|| task->checkpoint.durability > LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE) return false;
|
||||
}
|
||||
return valid_state(task->saved_state) && valid_state(task->recovery_state) && task->progress <= 100;
|
||||
}
|
||||
|
||||
static const char task_select[] = "SELECT t.task_id,t.name,t.saved_state,t.recovery_state,t.progress,t.sequence_count,t.started_sec,t.started_nsec,"
|
||||
static const char task_select[] = "SELECT t.task_id,t.name,t.task_kind,t.task_kind_version,t.saved_state,t.recovery_state,t.progress,t.sequence_count,t.started_sec,t.started_nsec,"
|
||||
"t.finished_sec,t.finished_nsec,t.updated_at,c.path,c.format_version,c.durability,c.updated_at FROM tasks t LEFT JOIN checkpoints c ON c.task_id=t.task_id ";
|
||||
|
||||
Lardon3DProjectDbResult
|
||||
|
|
|
|||
101
src/task.c
101
src/task.c
|
|
@ -18,6 +18,9 @@ struct Lardon3DTask {
|
|||
struct timespec finished_at;
|
||||
Lardon3DTaskCallback callback;
|
||||
void *userdata;
|
||||
Lardon3DTaskUserdataDestroy userdata_destroy;
|
||||
char task_kind[LARDON3D_TASK_KIND_CAPACITY];
|
||||
uint32_t task_kind_version;
|
||||
Lardon3DResourceEstimate estimate;
|
||||
Lardon3DTaskExecutionContract contract;
|
||||
bool has_contract;
|
||||
|
|
@ -42,6 +45,28 @@ valid_state(Lardon3DTaskState state)
|
|||
return state >= TASK_PENDING && state <= TASK_COMPLETED;
|
||||
}
|
||||
|
||||
bool
|
||||
lardon3d_task_kind_is_valid(const char *task_kind)
|
||||
{
|
||||
if (!task_kind) {
|
||||
return false;
|
||||
}
|
||||
size_t length = strnlen(task_kind, LARDON3D_TASK_KIND_CAPACITY);
|
||||
if (length == 0 || length >= LARDON3D_TASK_KIND_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
for (size_t index = 0; index < length; ++index) {
|
||||
char character = task_kind[index];
|
||||
if (!((character >= 'a' && character <= 'z')
|
||||
|| (character >= '0' && character <= '9')
|
||||
|| (index > 0 && (character == '.' || character == '_'
|
||||
|| character == '-')))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Lardon3DTaskState
|
||||
recovery_state(Lardon3DTaskState state)
|
||||
{
|
||||
|
|
@ -81,7 +106,27 @@ lardon3d_task_create(
|
|||
void *userdata
|
||||
)
|
||||
{
|
||||
if (!name || !name[0] || !estimate || !callback) {
|
||||
return lardon3d_task_create_typed(
|
||||
name, estimate, NULL, 0, callback, userdata, NULL
|
||||
);
|
||||
}
|
||||
|
||||
Lardon3DTask *
|
||||
lardon3d_task_create_typed(
|
||||
const char *name,
|
||||
const Lardon3DResourceEstimate *estimate,
|
||||
const char *task_kind,
|
||||
uint32_t task_kind_version,
|
||||
Lardon3DTaskCallback callback,
|
||||
void *userdata,
|
||||
Lardon3DTaskUserdataDestroy userdata_destroy
|
||||
)
|
||||
{
|
||||
bool typed = task_kind != NULL;
|
||||
if (!name || !name[0] || !estimate || !callback
|
||||
|| (typed && (!lardon3d_task_kind_is_valid(task_kind)
|
||||
|| task_kind_version == 0))
|
||||
|| (!typed && (task_kind_version != 0 || userdata_destroy))) {
|
||||
return NULL;
|
||||
}
|
||||
Lardon3DTask *task = calloc(1, sizeof(*task));
|
||||
|
|
@ -102,6 +147,11 @@ lardon3d_task_create(
|
|||
task->state = TASK_PENDING;
|
||||
task->callback = callback;
|
||||
task->userdata = userdata;
|
||||
task->userdata_destroy = userdata_destroy;
|
||||
if (typed) {
|
||||
(void)snprintf(task->task_kind, sizeof(task->task_kind), "%s", task_kind);
|
||||
task->task_kind_version = task_kind_version;
|
||||
}
|
||||
task->estimate = *estimate;
|
||||
copy_text(task->message, sizeof(task->message), "En attente.");
|
||||
return task;
|
||||
|
|
@ -117,6 +167,9 @@ lardon3d_task_destroy(Lardon3DTask *task)
|
|||
(void)lardon3d_task_join(task);
|
||||
(void)pthread_cond_destroy(&task->condition);
|
||||
(void)pthread_mutex_destroy(&task->mutex);
|
||||
if (task->userdata_destroy) {
|
||||
task->userdata_destroy(task->userdata);
|
||||
}
|
||||
free(task);
|
||||
}
|
||||
|
||||
|
|
@ -605,6 +658,21 @@ lardon3d_task_restore(
|
|||
Lardon3DTaskCallback callback,
|
||||
void *userdata
|
||||
)
|
||||
{
|
||||
return lardon3d_task_restore_typed(
|
||||
snapshot, NULL, 0, callback, userdata, NULL
|
||||
);
|
||||
}
|
||||
|
||||
Lardon3DTask *
|
||||
lardon3d_task_restore_typed(
|
||||
const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
const char *task_kind,
|
||||
uint32_t task_kind_version,
|
||||
Lardon3DTaskCallback callback,
|
||||
void *userdata,
|
||||
Lardon3DTaskUserdataDestroy userdata_destroy
|
||||
)
|
||||
{
|
||||
if (!snapshot || snapshot->id == 0 || !snapshot->name[0] || !callback
|
||||
|| memchr(snapshot->name, '\0', sizeof(snapshot->name)) == NULL
|
||||
|
|
@ -625,11 +693,14 @@ lardon3d_task_restore(
|
|||
&& snapshot->estimate.desired_gpu_slots == 0)) {
|
||||
return NULL;
|
||||
}
|
||||
Lardon3DTask *task = lardon3d_task_create(
|
||||
Lardon3DTask *task = lardon3d_task_create_typed(
|
||||
snapshot->name,
|
||||
&snapshot->estimate,
|
||||
task_kind,
|
||||
task_kind_version,
|
||||
callback,
|
||||
userdata
|
||||
userdata,
|
||||
userdata_destroy
|
||||
);
|
||||
if (!task) {
|
||||
return NULL;
|
||||
|
|
@ -646,6 +717,30 @@ lardon3d_task_restore(
|
|||
return task;
|
||||
}
|
||||
|
||||
bool
|
||||
lardon3d_task_kind(
|
||||
const Lardon3DTask *task,
|
||||
char task_kind[LARDON3D_TASK_KIND_CAPACITY],
|
||||
uint32_t *task_kind_version
|
||||
)
|
||||
{
|
||||
if (!task || !task_kind || !task_kind_version) {
|
||||
return false;
|
||||
}
|
||||
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
|
||||
(void)pthread_mutex_lock(&mutable_task->mutex);
|
||||
bool typed = task->task_kind[0] != '\0';
|
||||
if (typed) {
|
||||
copy_text(task_kind, LARDON3D_TASK_KIND_CAPACITY, task->task_kind);
|
||||
*task_kind_version = task->task_kind_version;
|
||||
} else {
|
||||
task_kind[0] = '\0';
|
||||
*task_kind_version = 0;
|
||||
}
|
||||
(void)pthread_mutex_unlock(&mutable_task->mutex);
|
||||
return typed;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
lardon3d_task_id(const Lardon3DTask *task)
|
||||
{
|
||||
|
|
|
|||
116
src/task_kind_registry.c
Normal file
116
src/task_kind_registry.c
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <lardon3d/task_kind_registry.h>
|
||||
|
||||
bool
|
||||
lardon3d_task_kind_registry_init(
|
||||
Lardon3DTaskKindRegistry *registry,
|
||||
const Lardon3DTaskKindDescriptor *descriptors,
|
||||
size_t count
|
||||
)
|
||||
{
|
||||
if (!registry || count > LARDON3D_TASK_KIND_REGISTRY_MAX
|
||||
|| (count > 0 && !descriptors)) {
|
||||
return false;
|
||||
}
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
if (!lardon3d_task_kind_is_valid(descriptors[index].kind)
|
||||
|| descriptors[index].kind_version == 0
|
||||
|| !descriptors[index].reconstruct) {
|
||||
return false;
|
||||
}
|
||||
for (size_t previous = 0; previous < index; ++previous) {
|
||||
if (strcmp(descriptors[index].kind,
|
||||
descriptors[previous].kind) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
*registry = (Lardon3DTaskKindRegistry) {
|
||||
.descriptors = descriptors,
|
||||
.count = count,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
Lardon3DTaskKindResult
|
||||
lardon3d_task_kind_registry_lookup(
|
||||
const Lardon3DTaskKindRegistry *registry,
|
||||
const char *kind,
|
||||
uint32_t kind_version,
|
||||
const Lardon3DTaskKindDescriptor **descriptor
|
||||
)
|
||||
{
|
||||
if (descriptor) {
|
||||
*descriptor = NULL;
|
||||
}
|
||||
if (!registry || !descriptor || !lardon3d_task_kind_is_valid(kind)
|
||||
|| kind_version == 0 || registry->count > LARDON3D_TASK_KIND_REGISTRY_MAX
|
||||
|| (registry->count > 0 && !registry->descriptors)) {
|
||||
return LARDON3D_TASK_KIND_INVALID_ARGUMENT;
|
||||
}
|
||||
bool kind_known = false;
|
||||
for (size_t index = 0; index < registry->count; ++index) {
|
||||
const Lardon3DTaskKindDescriptor *candidate =
|
||||
®istry->descriptors[index];
|
||||
if (strcmp(candidate->kind, kind) == 0) {
|
||||
kind_known = true;
|
||||
if (candidate->kind_version == kind_version) {
|
||||
*descriptor = candidate;
|
||||
return LARDON3D_TASK_KIND_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
return kind_known ? LARDON3D_TASK_KIND_UNSUPPORTED_VERSION
|
||||
: LARDON3D_TASK_KIND_UNKNOWN;
|
||||
}
|
||||
|
||||
Lardon3DTaskKindResult
|
||||
lardon3d_task_kind_registry_restore(
|
||||
const Lardon3DTaskKindRegistry *registry,
|
||||
const char *kind,
|
||||
uint32_t kind_version,
|
||||
const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
void *context,
|
||||
Lardon3DTask **task
|
||||
)
|
||||
{
|
||||
if (task) {
|
||||
*task = NULL;
|
||||
}
|
||||
if (!snapshot || !task) {
|
||||
return LARDON3D_TASK_KIND_INVALID_ARGUMENT;
|
||||
}
|
||||
const Lardon3DTaskKindDescriptor *descriptor = NULL;
|
||||
Lardon3DTaskKindResult result = lardon3d_task_kind_registry_lookup(
|
||||
registry, kind, kind_version, &descriptor
|
||||
);
|
||||
if (result != LARDON3D_TASK_KIND_OK) {
|
||||
return result;
|
||||
}
|
||||
Lardon3DTaskKindBinding binding = {0};
|
||||
if (!descriptor->reconstruct(snapshot, context, &binding)
|
||||
|| !binding.callback) {
|
||||
if (binding.userdata_destroy) {
|
||||
binding.userdata_destroy(binding.userdata);
|
||||
}
|
||||
return LARDON3D_TASK_KIND_RECONSTRUCTION_FAILED;
|
||||
}
|
||||
*task = lardon3d_task_restore_typed(
|
||||
snapshot,
|
||||
kind,
|
||||
kind_version,
|
||||
binding.callback,
|
||||
binding.userdata,
|
||||
binding.userdata_destroy
|
||||
);
|
||||
if (!*task) {
|
||||
if (binding.userdata_destroy) {
|
||||
binding.userdata_destroy(binding.userdata);
|
||||
}
|
||||
return LARDON3D_TASK_KIND_RESTORE_FAILED;
|
||||
}
|
||||
return LARDON3D_TASK_KIND_OK;
|
||||
}
|
||||
|
|
@ -23,6 +23,25 @@ unused_callback(Lardon3DTask *task, void *userdata)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
reconstruct_test_task(
|
||||
const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
void *context,
|
||||
Lardon3DTaskKindBinding *binding
|
||||
)
|
||||
{
|
||||
(void)snapshot;
|
||||
binding->callback = unused_callback;
|
||||
binding->userdata = context;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const Lardon3DTaskKindDescriptor test_descriptors[] = {{
|
||||
.kind = "test.persisted",
|
||||
.kind_version = 1,
|
||||
.reconstruct = reconstruct_test_task,
|
||||
}};
|
||||
|
||||
static bool
|
||||
write_ini(const char *path, const char *name, const char *stable_id, unsigned int version)
|
||||
{
|
||||
|
|
@ -56,6 +75,8 @@ run_test(void)
|
|||
CHECK(setenv("LARDON3D_PROJECTS_ROOT", root, 1) == 0);
|
||||
Lardon3DAppState state;
|
||||
lardon3d_app_state_init(&state);
|
||||
Lardon3DTaskKindRegistry registry;
|
||||
CHECK(lardon3d_task_kind_registry_init(®istry, test_descriptors, 1));
|
||||
CHECK(lardon3d_project_create(&state, "Projet Cycle"));
|
||||
CHECK(state.project_loaded && state.project_db && strlen(state.project_stable_id) == 32);
|
||||
|
||||
|
|
@ -97,7 +118,9 @@ run_test(void)
|
|||
.maximum_batch_size = 1,
|
||||
.desired_cpu_threads = 1,
|
||||
};
|
||||
Lardon3DTask *task = lardon3d_task_create("Persistée", &estimate, unused_callback, NULL);
|
||||
Lardon3DTask *task = lardon3d_task_create_typed(
|
||||
"Persistée", &estimate, "test.persisted", 1,
|
||||
unused_callback, NULL, NULL);
|
||||
CHECK(task && lardon3d_task_assign_id(task, 1));
|
||||
CHECK(lardon3d_task_set_progress(task, 10, "frontière 10"));
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, task) == LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
|
|
@ -113,7 +136,7 @@ run_test(void)
|
|||
CHECK(unsetenv("LARDON3D_TEST_CHECKPOINT_SYNC_DIRECTORY_FAILURE") == 0);
|
||||
CHECK(lardon3d_project_db_load_task(state.project_db, 1, &db_task) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(db_task.checkpoint.durability == LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(count == 1 && entries[0].status == LARDON3D_PROJECT_RECOVERABLE_PUBLISHED_NOT_DURABLE);
|
||||
|
||||
CHECK(lardon3d_task_set_progress(task, 20, "frontière 20"));
|
||||
|
|
@ -121,7 +144,9 @@ run_test(void)
|
|||
CHECK(lardon3d_project_checkpoint_task(&state, task) == LARDON3D_PROJECT_TASK_CHECKPOINT_IO_ERROR);
|
||||
CHECK(unsetenv("LARDON3D_TEST_CHECKPOINT_PREPUBLICATION_FAILURE") == 0);
|
||||
CHECK(lardon3d_project_db_load_task(state.project_db, 1, &db_task) == LARDON3D_PROJECT_DB_OK && db_task.progress == 10);
|
||||
Lardon3DTask *unpublished = lardon3d_task_create("Non publiée", &estimate, unused_callback, NULL);
|
||||
Lardon3DTask *unpublished = lardon3d_task_create_typed(
|
||||
"Non publiée", &estimate, "test.persisted", 1,
|
||||
unused_callback, NULL, NULL);
|
||||
CHECK(unpublished && lardon3d_task_assign_id(unpublished, 3));
|
||||
CHECK(setenv("LARDON3D_TEST_CHECKPOINT_PREPUBLICATION_FAILURE", "1", 1) == 0);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, unpublished) == LARDON3D_PROJECT_TASK_CHECKPOINT_IO_ERROR);
|
||||
|
|
@ -146,25 +171,35 @@ run_test(void)
|
|||
CHECK(lardon3d_project_db_load_task(state.project_db, 1, &db_task) == LARDON3D_PROJECT_DB_OK && db_task.progress == 10);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, task) == LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
|
||||
CHECK(lardon3d_project_list_recoverable(&state, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(count == 1 && entries[0].status == LARDON3D_PROJECT_RECOVERABLE && entries[0].snapshot.progress == 20);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, 0, entries, 257, &count) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 257, &count) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||
|
||||
CHECK(unlink(checkpoint_path) == 0);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(count == 1 && entries[0].status == LARDON3D_PROJECT_RECOVERY_MISSING_CHECKPOINT);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, task) == LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
int descriptor = open(checkpoint_path, O_WRONLY | O_TRUNC); CHECK(descriptor >= 0);
|
||||
CHECK(write(descriptor, "corrupt", 7) == 7 && close(descriptor) == 0);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(entries[0].status == LARDON3D_PROJECT_RECOVERY_INVALID_CHECKPOINT);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, task) == LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
descriptor = open(checkpoint_path, O_RDWR); CHECK(descriptor >= 0);
|
||||
unsigned char future_version[4] = {2, 0, 0, 0};
|
||||
CHECK(pwrite(descriptor, future_version, sizeof(future_version), 8) == 4 && close(descriptor) == 0);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(entries[0].status == LARDON3D_PROJECT_RECOVERY_UNSUPPORTED_CHECKPOINT);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, task) == LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
CHECK(lardon3d_task_checkpoint_load(checkpoint_path, &disk_snapshot, NULL)
|
||||
== LARDON3D_TASK_CHECKPOINT_OK);
|
||||
disk_snapshot.id = 99;
|
||||
CHECK(lardon3d_task_checkpoint_save(checkpoint_path, &disk_snapshot)
|
||||
== LARDON3D_TASK_CHECKPOINT_OK);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 2,
|
||||
&count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(entries[0].status == LARDON3D_PROJECT_RECOVERY_INVALID_CHECKPOINT);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, task)
|
||||
== LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
|
||||
CheckpointThread contexts[2] = {{.state = &state, .task = task}, {.state = &state, .task = task}};
|
||||
pthread_t threads[2]; CHECK(pthread_create(&threads[0], NULL, checkpoint_thread, &contexts[0]) == 0);
|
||||
|
|
@ -172,17 +207,77 @@ run_test(void)
|
|||
CHECK(pthread_join(threads[0], NULL) == 0 && pthread_join(threads[1], NULL) == 0);
|
||||
CHECK(contexts[0].result == LARDON3D_PROJECT_TASK_CHECKPOINT_OK && contexts[1].result == LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
|
||||
Lardon3DTask *terminal = lardon3d_task_create("Terminale", &estimate, unused_callback, NULL);
|
||||
Lardon3DTask *terminal = lardon3d_task_create_typed(
|
||||
"Terminale", &estimate, "test.persisted", 1,
|
||||
unused_callback, NULL, NULL);
|
||||
CHECK(terminal && lardon3d_task_assign_id(terminal, 2)); lardon3d_task_request_cancel(terminal);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, terminal) == LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK && count == 1);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK && count == 1);
|
||||
lardon3d_task_destroy(terminal);
|
||||
|
||||
lardon3d_project_close(&state);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, task) == LARDON3D_PROJECT_TASK_CHECKPOINT_NO_PROJECT);
|
||||
CHECK(lardon3d_project_open(&state, "Projet Cycle"));
|
||||
CHECK(lardon3d_project_list_recoverable(&state, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0, entries, 2, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(count == 1 && entries[0].status == LARDON3D_PROJECT_RECOVERABLE);
|
||||
CHECK(strcmp(entries[0].task_kind, "test.persisted") == 0
|
||||
&& entries[0].task_kind_version == 1);
|
||||
Lardon3DTask *restored = NULL;
|
||||
CHECK(lardon3d_task_kind_registry_restore(
|
||||
®istry, entries[0].task_kind, entries[0].task_kind_version,
|
||||
&entries[0].snapshot, NULL, &restored) == LARDON3D_TASK_KIND_OK);
|
||||
Lardon3DTaskSnapshot restored_snapshot;
|
||||
CHECK(restored && lardon3d_task_snapshot(restored, &restored_snapshot));
|
||||
CHECK(restored_snapshot.id == 1 && restored_snapshot.state == TASK_PENDING
|
||||
&& restored_snapshot.progress == 20
|
||||
&& lardon3d_task_sequence_count(restored) == 0);
|
||||
lardon3d_task_destroy(restored);
|
||||
|
||||
Lardon3DTask *unknown = lardon3d_task_create_typed(
|
||||
"Unknown", &estimate, "unknown.work", 1, unused_callback, NULL, NULL);
|
||||
Lardon3DTask *future_kind = lardon3d_task_create_typed(
|
||||
"Future kind", &estimate, "test.persisted", 2,
|
||||
unused_callback, NULL, NULL);
|
||||
CHECK(unknown && future_kind && lardon3d_task_assign_id(unknown, 4)
|
||||
&& lardon3d_task_assign_id(future_kind, 5));
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, unknown)
|
||||
== LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, future_kind)
|
||||
== LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
Lardon3DTask *legacy = lardon3d_task_create(
|
||||
"Legacy", &estimate, unused_callback, NULL);
|
||||
CHECK(legacy && lardon3d_task_assign_id(legacy, 6));
|
||||
CHECK(lardon3d_project_checkpoint_task(&state, legacy)
|
||||
== LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK);
|
||||
Lardon3DTaskDurableSnapshot legacy_snapshot;
|
||||
CHECK(lardon3d_task_durable_snapshot(legacy, &legacy_snapshot));
|
||||
char legacy_path[512];
|
||||
CHECK(snprintf(legacy_path, sizeof(legacy_path),
|
||||
"%s/.lardon3d/checkpoints/6.chk", project_path) > 0);
|
||||
CHECK(lardon3d_task_checkpoint_save(legacy_path, &legacy_snapshot)
|
||||
== LARDON3D_TASK_CHECKPOINT_OK);
|
||||
Lardon3DProjectDbCheckpoint legacy_checkpoint = {
|
||||
.format_version = LARDON3D_TASK_CHECKPOINT_VERSION,
|
||||
.durability = LARDON3D_DB_CHECKPOINT_DURABLE,
|
||||
.updated_at = 500,
|
||||
};
|
||||
(void)snprintf(legacy_checkpoint.path, sizeof(legacy_checkpoint.path),
|
||||
".lardon3d/checkpoints/6.chk");
|
||||
CHECK(lardon3d_project_db_record_task(state.project_db, &legacy_snapshot,
|
||||
NULL, 0, &legacy_checkpoint, 500) == LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DProjectRecoveryEntry negative_entries[4];
|
||||
CHECK(lardon3d_project_list_recoverable(&state, ®istry, 0,
|
||||
negative_entries, 4, &count) == LARDON3D_PROJECT_DB_OK && count == 4);
|
||||
CHECK(negative_entries[1].task_id == 4
|
||||
&& negative_entries[1].status == LARDON3D_PROJECT_RECOVERY_UNKNOWN_TASK_KIND);
|
||||
CHECK(negative_entries[2].task_id == 5
|
||||
&& negative_entries[2].status
|
||||
== LARDON3D_PROJECT_RECOVERY_UNSUPPORTED_TASK_KIND_VERSION);
|
||||
CHECK(negative_entries[3].task_id == 6
|
||||
&& negative_entries[3].status == LARDON3D_PROJECT_RECOVERY_LEGACY_UNTYPED);
|
||||
lardon3d_task_destroy(legacy);
|
||||
lardon3d_task_destroy(future_kind);
|
||||
lardon3d_task_destroy(unknown);
|
||||
lardon3d_project_close(&state);
|
||||
|
||||
CHECK(write_ini(ini_path, "Projet Cycle", "00000000000000000000000000000000", 2));
|
||||
|
|
@ -196,7 +291,12 @@ run_test(void)
|
|||
lardon3d_task_destroy(task);
|
||||
|
||||
char terminal_checkpoint[512]; CHECK(snprintf(terminal_checkpoint, sizeof(terminal_checkpoint), "%s/.lardon3d/checkpoints/2.chk", project_path) > 0);
|
||||
char unknown_checkpoint[512], future_kind_checkpoint[512];
|
||||
CHECK(snprintf(unknown_checkpoint, sizeof(unknown_checkpoint), "%s/.lardon3d/checkpoints/4.chk", project_path) > 0);
|
||||
CHECK(snprintf(future_kind_checkpoint, sizeof(future_kind_checkpoint), "%s/.lardon3d/checkpoints/5.chk", project_path) > 0);
|
||||
CHECK(unlink(checkpoint_path) == 0); CHECK(unlink(terminal_checkpoint) == 0);
|
||||
CHECK(unlink(unknown_checkpoint) == 0); CHECK(unlink(future_kind_checkpoint) == 0);
|
||||
CHECK(unlink(legacy_path) == 0);
|
||||
CHECK(unlink(database_path) == 0); CHECK(unlink(ini_path) == 0);
|
||||
char path[512];
|
||||
CHECK(snprintf(path, sizeof(path), "%s/.lardon3d/checkpoints", project_path) > 0 && rmdir(path) == 0);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,31 @@ create_future_database(const char *path)
|
|||
sqlite3 *connection = NULL;
|
||||
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||
bool ok = sqlite3_exec(connection, "CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
||||
"INSERT INTO metadata VALUES('schema_version',2);", NULL, NULL, NULL) == SQLITE_OK;
|
||||
"INSERT INTO metadata VALUES('schema_version',3);", NULL, NULL, NULL) == SQLITE_OK;
|
||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||
}
|
||||
|
||||
static bool
|
||||
create_v1_database(const char *path)
|
||||
{
|
||||
static const char sql[] =
|
||||
"PRAGMA foreign_keys=ON;"
|
||||
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
||||
"INSERT INTO metadata VALUES('schema_version',1);"
|
||||
"CREATE TABLE project(singleton INTEGER PRIMARY KEY CHECK(singleton=1),stable_id TEXT NOT NULL UNIQUE,name TEXT NOT NULL,created_at INTEGER NOT NULL,updated_at INTEGER NOT NULL);"
|
||||
"CREATE TABLE tasks(task_id INTEGER PRIMARY KEY CHECK(task_id>0),name TEXT NOT NULL,saved_state INTEGER NOT NULL CHECK(saved_state BETWEEN 0 AND 5),recovery_state INTEGER NOT NULL CHECK(recovery_state BETWEEN 0 AND 5),progress INTEGER NOT NULL CHECK(progress BETWEEN 0 AND 100),sequence_count INTEGER NOT NULL CHECK(sequence_count>=0),started_sec INTEGER NOT NULL,started_nsec INTEGER NOT NULL CHECK(started_nsec BETWEEN 0 AND 999999999),finished_sec INTEGER NOT NULL,finished_nsec INTEGER NOT NULL CHECK(finished_nsec BETWEEN 0 AND 999999999),updated_at INTEGER NOT NULL);"
|
||||
"CREATE INDEX tasks_recovery_state_idx ON tasks(recovery_state,task_id);"
|
||||
"CREATE TABLE checkpoints(task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,path TEXT NOT NULL,format_version INTEGER NOT NULL CHECK(format_version>0),durability INTEGER NOT NULL CHECK(durability BETWEEN 0 AND 1),updated_at INTEGER NOT NULL);"
|
||||
"CREATE TABLE artifacts(artifact_id TEXT PRIMARY KEY,kind TEXT NOT NULL,path TEXT NOT NULL,state INTEGER NOT NULL CHECK(state BETWEEN 0 AND 1),size_bytes INTEGER NOT NULL CHECK(size_bytes>=0),producer_task_id INTEGER REFERENCES tasks(task_id),created_at INTEGER NOT NULL,updated_at INTEGER NOT NULL);"
|
||||
"CREATE INDEX artifacts_state_idx ON artifacts(state,artifact_id);"
|
||||
"CREATE INDEX artifacts_producer_idx ON artifacts(producer_task_id);"
|
||||
"INSERT INTO project VALUES(1,'legacy-project','Legacy',1,1);"
|
||||
"INSERT INTO tasks VALUES(9,'Legacy task',1,0,12,3,1,0,0,0,2);"
|
||||
"INSERT INTO checkpoints VALUES(9,'legacy.chk',1,0,2);"
|
||||
"INSERT INTO artifacts VALUES('legacy-artifact','legacy','legacy.bin',0,0,9,2,2);";
|
||||
sqlite3 *connection = NULL;
|
||||
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||
bool ok = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
|
||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||
}
|
||||
|
||||
|
|
@ -85,15 +109,18 @@ run_test(void)
|
|||
char directory[] = "/tmp/lardon3d-project-db-XXXXXX";
|
||||
CHECK(mkdtemp(directory));
|
||||
char database_path[512], artifact_path[512], future_path[512], corrupt_path[512];
|
||||
char legacy_path[512], failed_migration_path[512];
|
||||
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db", directory) > 0);
|
||||
CHECK(snprintf(artifact_path, sizeof(artifact_path), "%s/artifact.bin", directory) > 0);
|
||||
CHECK(snprintf(future_path, sizeof(future_path), "%s/future.db", directory) > 0);
|
||||
CHECK(snprintf(corrupt_path, sizeof(corrupt_path), "%s/corrupt.db", directory) > 0);
|
||||
CHECK(snprintf(legacy_path, sizeof(legacy_path), "%s/legacy.db", directory) > 0);
|
||||
CHECK(snprintf(failed_migration_path, sizeof(failed_migration_path), "%s/failed-migration.db", directory) > 0);
|
||||
|
||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||
Lardon3DProjectDb *database = NULL;
|
||||
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(database && lardon3d_project_db_schema_version(database) == 1);
|
||||
CHECK(database && lardon3d_project_db_schema_version(database) == 2);
|
||||
|
||||
Lardon3DProjectDbProject project = {.created_at = 100, .updated_at = 100};
|
||||
(void)snprintf(project.stable_id, sizeof(project.stable_id), "project-0001");
|
||||
|
|
@ -115,22 +142,30 @@ run_test(void)
|
|||
Lardon3DTaskDurableSnapshot running = task_snapshot(1, TASK_RUNNING);
|
||||
Lardon3DProjectDbCheckpoint checkpoint = {.format_version = 1, .durability = LARDON3D_DB_CHECKPOINT_DURABLE, .updated_at = 200};
|
||||
(void)snprintf(checkpoint.path, sizeof(checkpoint.path), "%s/checkpoints/task-1.chk", directory);
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, &checkpoint, 200) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, "test.work", 1, &checkpoint, 200) == LARDON3D_PROJECT_DB_OK);
|
||||
running.progress = 30; running.sequence_count = 3;
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, &checkpoint, 201) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, "test.work", 1, &checkpoint, 201) == LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DProjectDbTask task;
|
||||
CHECK(lardon3d_project_db_load_task(database, 1, &task) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(task.recovery_state == TASK_PENDING && task.progress == 30 && task.sequence_count == 3);
|
||||
CHECK(task.has_task_kind && strcmp(task.task_kind, "test.work") == 0
|
||||
&& task.task_kind_version == 1);
|
||||
CHECK(task.has_checkpoint && strcmp(task.checkpoint.path, checkpoint.path) == 0);
|
||||
checkpoint.durability = LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE;
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, &checkpoint, 201) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, "test.work", 1, &checkpoint, 201) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_task(database, 1, &task) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(task.checkpoint.durability == LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE);
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, "test.other", 1,
|
||||
&checkpoint, 202) == LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, "Test.invalid", 1,
|
||||
&checkpoint, 202) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||
CHECK(lardon3d_project_db_record_task(database, &running, "test.work", 0,
|
||||
&checkpoint, 202) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||
|
||||
Lardon3DTaskDurableSnapshot completed = task_snapshot(2, TASK_COMPLETED);
|
||||
CHECK(lardon3d_project_db_record_task(database, &completed, NULL, 202) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_task(database, &completed, "test.work", 1, NULL, 202) == LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DTaskDurableSnapshot no_checkpoint = task_snapshot(4, TASK_PENDING);
|
||||
CHECK(lardon3d_project_db_record_task(database, &no_checkpoint, NULL, 202) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_task(database, &no_checkpoint, NULL, 0, NULL, 202) == LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DProjectDbTask page[1]; size_t count = 0;
|
||||
CHECK(lardon3d_project_db_list_recoverable(database, 0, page, 1, &count) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(count == 1 && page[0].task_id == 1);
|
||||
|
|
@ -139,7 +174,7 @@ run_test(void)
|
|||
|
||||
Lardon3DTaskDurableSnapshot rollback_task = task_snapshot(3, TASK_PENDING);
|
||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_CHECKPOINT", "1", 1) == 0);
|
||||
CHECK(lardon3d_project_db_record_task(database, &rollback_task, &checkpoint, 203) == LARDON3D_PROJECT_DB_IO_ERROR);
|
||||
CHECK(lardon3d_project_db_record_task(database, &rollback_task, "test.work", 1, &checkpoint, 203) == LARDON3D_PROJECT_DB_IO_ERROR);
|
||||
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_CHECKPOINT") == 0);
|
||||
CHECK(lardon3d_project_db_load_task(database, 3, &task) == LARDON3D_PROJECT_DB_NOT_FOUND);
|
||||
CHECK(lardon3d_project_db_test_orphan_checkpoint(database) == LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||
|
|
@ -175,7 +210,7 @@ run_test(void)
|
|||
char too_long[LARDON3D_PROJECT_DB_PATH_CAPACITY + 1]; memset(too_long, 'x', sizeof(too_long)); too_long[sizeof(too_long) - 1] = '\0';
|
||||
CHECK(lardon3d_project_db_open(too_long, &database, error) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||
lardon3d_project_db_close(contexts[0].database); database = NULL;
|
||||
CHECK(query_integer(database_path, "SELECT value FROM metadata WHERE key='schema_version'", 1));
|
||||
CHECK(query_integer(database_path, "SELECT value FROM metadata WHERE key='schema_version'", 2));
|
||||
CHECK(query_integer(database_path, "SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
||||
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_task(database, 1, &task) == LARDON3D_PROJECT_DB_OK);
|
||||
|
|
@ -188,7 +223,34 @@ run_test(void)
|
|||
CHECK(write(descriptor, "not sqlite", 10) == 10 && close(descriptor) == 0);
|
||||
CHECK(lardon3d_project_db_open(corrupt_path, &database, error) == LARDON3D_PROJECT_DB_CORRUPT);
|
||||
|
||||
CHECK(create_v1_database(legacy_path));
|
||||
CHECK(lardon3d_project_db_open(legacy_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 2);
|
||||
CHECK(lardon3d_project_db_get_project(database, &loaded_project) == LARDON3D_PROJECT_DB_OK
|
||||
&& strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(!task.has_task_kind && task.has_checkpoint
|
||||
&& strcmp(task.checkpoint.path, "legacy.chk") == 0);
|
||||
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact",
|
||||
&loaded_artifact) == LARDON3D_PROJECT_DB_OK);
|
||||
lardon3d_project_db_close(database); database = NULL;
|
||||
CHECK(query_integer(legacy_path, "SELECT value FROM metadata WHERE key='schema_version'", 2));
|
||||
|
||||
CHECK(create_v1_database(failed_migration_path));
|
||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
|
||||
CHECK(lardon3d_project_db_open(failed_migration_path, &database, error)
|
||||
== LARDON3D_PROJECT_DB_IO_ERROR);
|
||||
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2") == 0);
|
||||
CHECK(query_integer(failed_migration_path,
|
||||
"SELECT value FROM metadata WHERE key='schema_version'", 1));
|
||||
CHECK(lardon3d_project_db_open(failed_migration_path, &database, error)
|
||||
== LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK
|
||||
&& !task.has_task_kind);
|
||||
lardon3d_project_db_close(database); database = NULL;
|
||||
|
||||
CHECK(unlink(artifact_path) == 0); CHECK(unlink(database_path) == 0); CHECK(unlink(future_path) == 0); CHECK(unlink(corrupt_path) == 0);
|
||||
CHECK(unlink(legacy_path) == 0); CHECK(unlink(failed_migration_path) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
172
tests/test_task_kind_registry.c
Normal file
172
tests/test_task_kind_registry.c
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <lardon3d/task_kind_registry.h>
|
||||
|
||||
#define CHECK(condition) do { if (!(condition)) { \
|
||||
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); return false; \
|
||||
} } while (0)
|
||||
|
||||
typedef struct {
|
||||
int *destroyed;
|
||||
uint64_t expected_id;
|
||||
} TestUserdata;
|
||||
|
||||
typedef struct {
|
||||
int *destroyed;
|
||||
bool fail;
|
||||
} ReconstructContext;
|
||||
|
||||
static bool
|
||||
test_callback(Lardon3DTask *task, void *userdata)
|
||||
{
|
||||
TestUserdata *data = userdata;
|
||||
return data && lardon3d_task_id(task) == data->expected_id;
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_userdata(void *userdata)
|
||||
{
|
||||
TestUserdata *data = userdata;
|
||||
if (data) {
|
||||
++*data->destroyed;
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
reconstruct(
|
||||
const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
void *context,
|
||||
Lardon3DTaskKindBinding *binding
|
||||
)
|
||||
{
|
||||
ReconstructContext *settings = context;
|
||||
TestUserdata *data = malloc(sizeof(*data));
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
*data = (TestUserdata) {
|
||||
.destroyed = settings->destroyed,
|
||||
.expected_id = snapshot->id,
|
||||
};
|
||||
binding->callback = test_callback;
|
||||
binding->userdata = data;
|
||||
binding->userdata_destroy = destroy_userdata;
|
||||
return !settings->fail;
|
||||
}
|
||||
|
||||
static Lardon3DTaskDurableSnapshot
|
||||
snapshot(void)
|
||||
{
|
||||
Lardon3DTaskDurableSnapshot result = {
|
||||
.id = 42,
|
||||
.estimate = {
|
||||
.minimum_batch_size = 1,
|
||||
.maximum_batch_size = 1,
|
||||
.desired_cpu_threads = 1,
|
||||
.task_class = LARDON3D_RESOURCE_TASK_GENERAL,
|
||||
},
|
||||
.progress = 23,
|
||||
.saved_state = TASK_RUNNING,
|
||||
.recovery_state = TASK_PENDING,
|
||||
.sequence_count = 4,
|
||||
};
|
||||
(void)snprintf(result.name, sizeof(result.name), "Registry test");
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
run_test(void)
|
||||
{
|
||||
static const Lardon3DTaskKindDescriptor descriptors[] = {
|
||||
{.kind = "test.recovery", .kind_version = 1, .reconstruct = reconstruct},
|
||||
{.kind = "test.other", .kind_version = 1, .reconstruct = reconstruct},
|
||||
};
|
||||
Lardon3DTaskKindRegistry registry;
|
||||
CHECK(lardon3d_task_kind_registry_init(®istry, descriptors, 2));
|
||||
CHECK(lardon3d_task_kind_is_valid("test.recovery-1"));
|
||||
CHECK(!lardon3d_task_kind_is_valid(""));
|
||||
char too_long[LARDON3D_TASK_KIND_CAPACITY + 1];
|
||||
memset(too_long, 'a', sizeof(too_long));
|
||||
too_long[sizeof(too_long) - 1] = '\0';
|
||||
CHECK(!lardon3d_task_kind_is_valid(too_long));
|
||||
CHECK(!lardon3d_task_kind_is_valid("Test.recovery"));
|
||||
CHECK(!lardon3d_task_kind_is_valid("test/recovery"));
|
||||
CHECK(!lardon3d_task_kind_is_valid(".test"));
|
||||
|
||||
const Lardon3DTaskKindDescriptor *found = NULL;
|
||||
CHECK(lardon3d_task_kind_registry_lookup(®istry, "test.recovery", 1,
|
||||
&found) == LARDON3D_TASK_KIND_OK && found == &descriptors[0]);
|
||||
found = NULL;
|
||||
CHECK(lardon3d_task_kind_registry_lookup(®istry, "test.recovery", 1,
|
||||
&found) == LARDON3D_TASK_KIND_OK && found == &descriptors[0]);
|
||||
CHECK(lardon3d_task_kind_registry_lookup(®istry, "unknown.kind", 1,
|
||||
&found) == LARDON3D_TASK_KIND_UNKNOWN);
|
||||
CHECK(lardon3d_task_kind_registry_lookup(®istry, "test.recovery", 3,
|
||||
&found) == LARDON3D_TASK_KIND_UNSUPPORTED_VERSION);
|
||||
|
||||
Lardon3DTaskKindDescriptor duplicate[] = {descriptors[0], descriptors[0]};
|
||||
CHECK(!lardon3d_task_kind_registry_init(®istry, duplicate, 2));
|
||||
CHECK(lardon3d_task_kind_registry_init(®istry, descriptors, 2));
|
||||
|
||||
int destroyed = 0;
|
||||
ReconstructContext context = {.destroyed = &destroyed};
|
||||
Lardon3DTaskDurableSnapshot durable = snapshot();
|
||||
Lardon3DTask *task = NULL;
|
||||
CHECK(lardon3d_task_kind_registry_restore(®istry, "test.recovery", 1,
|
||||
&durable, &context, &task) == LARDON3D_TASK_KIND_OK);
|
||||
CHECK(task && lardon3d_task_id(task) == durable.id
|
||||
&& lardon3d_task_sequence_count(task) == durable.sequence_count);
|
||||
char kind[LARDON3D_TASK_KIND_CAPACITY]; uint32_t version = 0;
|
||||
CHECK(lardon3d_task_kind(task, kind, &version));
|
||||
CHECK(strcmp(kind, "test.recovery") == 0 && version == 1);
|
||||
Lardon3DHardwareProfile profile = {
|
||||
.logical_cpu_count = 2,
|
||||
.page_size_bytes = 4096,
|
||||
.memory_total_bytes = 1024 * 1024,
|
||||
.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);
|
||||
Lardon3DResourceSnapshot resources = {
|
||||
.memory_available_bytes = profile.memory_total_bytes,
|
||||
.cpu_load_1m = 0.0,
|
||||
};
|
||||
Lardon3DResourceDecision decision;
|
||||
Lardon3DResourceReservation *reservation = NULL;
|
||||
CHECK(governor && lardon3d_resource_governor_reserve(
|
||||
governor, &resources, &durable.estimate, &decision, &reservation));
|
||||
CHECK(lardon3d_task_start(task, governor, reservation));
|
||||
Lardon3DTaskSnapshot runtime;
|
||||
CHECK(lardon3d_task_snapshot(task, &runtime)
|
||||
&& runtime.state == TASK_COMPLETED);
|
||||
lardon3d_task_destroy(task);
|
||||
lardon3d_resource_governor_destroy(governor);
|
||||
CHECK(destroyed == 1);
|
||||
|
||||
context.fail = true;
|
||||
CHECK(lardon3d_task_kind_registry_restore(®istry, "test.recovery", 1,
|
||||
&durable, &context, &task) == LARDON3D_TASK_KIND_RECONSTRUCTION_FAILED);
|
||||
CHECK(!task && destroyed == 2);
|
||||
context.fail = false;
|
||||
durable.progress = 101;
|
||||
CHECK(lardon3d_task_kind_registry_restore(®istry, "test.recovery", 1,
|
||||
&durable, &context, &task) == LARDON3D_TASK_KIND_RESTORE_FAILED);
|
||||
CHECK(!task && destroyed == 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Loading…
Reference in a new issue