Ajouter le chargement asynchrone du graphe d'enquête #60

Closed
opened 2026-07-21 08:59:07 +02:00 by fy59 · 0 comments
Owner

Ajouter le chargement asynchrone du graphe d'enquête

Contexte

Le projet possède désormais :

  • InvestigationGraphModel ;
  • InvestigationGraphLoader ;
  • un mécanisme BackgroundTask déjà utilisé dans l'application ;
  • une interface GTK destinée à ouvrir des enquêtes sans bloquer le thread principal.

Le chargeur synchrone construit correctement un graphe complet depuis SQLite,
mais il ne doit pas être appelé directement depuis un gestionnaire GTK lorsque
la base peut contenir beaucoup d'entités et de relations.

Objectif

Créer un service asynchrone chargé d'exécuter
investigation_graph_loader_load() en arrière-plan puis de restituer le
résultat sur le thread principal.

Le composant doit garantir :

  • aucune opération SQLite longue sur le thread GTK ;
  • un transfert clair de propriété du graphe ;
  • une propagation fidèle des erreurs ;
  • une annulation propre ;
  • une seule notification de fin ;
  • aucun accès concurrent à une connexion SQLite non prévue pour être partagée.

Point d'architecture important

Le thread de fond ne doit pas utiliser directement la connexion Database
ouverte par l'interface principale.

Chaque tâche doit ouvrir sa propre connexion SQLite à partir du chemin de la
base, puis :

ouvrir Database dans le worker
    ↓
créer InvestigationGraphLoader
    ↓
charger InvestigationGraphModel
    ↓
détruire le chargeur
    ↓
fermer Database
    ↓
transférer le graphe au thread principal

Cette règle évite le partage implicite d'une connexion SQLite entre threads.

Fichiers

Créer :

include/core/investigation_graph_load_task.h
src/core/investigation_graph_load_task.c
tests/test_investigation_graph_load_task.c

Type opaque

typedef struct InvestigationGraphLoadTask InvestigationGraphLoadTask;

Domaine d'erreur

Créer :

typedef enum
{
    INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INVALID_ARGUMENT,
    INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY,
    INVESTIGATION_GRAPH_LOAD_TASK_ERROR_ALREADY_RUNNING,
    INVESTIGATION_GRAPH_LOAD_TASK_ERROR_DATABASE_OPEN,
    INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD,
    INVESTIGATION_GRAPH_LOAD_TASK_ERROR_CANCELLED,
    INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INTERNAL
} InvestigationGraphLoadTaskError;

Ajouter :

#define INVESTIGATION_GRAPH_LOAD_TASK_ERROR \
    investigation_graph_load_task_error_quark()

GQuark investigation_graph_load_task_error_quark(void);

Callback de fin

Définir :

typedef void (*InvestigationGraphLoadTaskCallback)(
    InvestigationGraphLoadTask *load_task,
    InvestigationGraphModel *graph_model,
    const GError *error,
    gpointer user_data
);

Règles :

  • le callback est invoqué sur le thread principal ;
  • exactement un des deux éléments suivants est fourni :
    • graph_model non NULL et error == NULL ;
    • graph_model == NULL et error non NULL ;
  • le graphe appartient au destinataire du callback ;
  • l'erreur est empruntée et valable uniquement pendant le callback ;
  • le callback ne doit jamais être appelé deux fois pour une même exécution.

Construction

InvestigationGraphLoadTask *investigation_graph_load_task_new(
    const char *database_path,
    GError **error
);

Le constructeur doit :

  • refuser un chemin NULL ;
  • refuser une chaîne vide ;
  • copier le chemin ;
  • ne pas ouvrir immédiatement la base ;
  • initialiser l'état interne ;
  • accepter GError ** facultatif.

Destruction

void investigation_graph_load_task_free(
    InvestigationGraphLoadTask *load_task
);

La destruction doit :

  • accepter NULL ;
  • annuler une tâche encore active ;
  • attendre ou finaliser proprement le worker selon l'API de BackgroundTask ;
  • ne jamais invoquer le callback après la destruction ;
  • libérer le chemin copié ;
  • libérer les erreurs internes ;
  • libérer un graphe non encore remis au callback.

Démarrage

gboolean investigation_graph_load_task_start(
    InvestigationGraphLoadTask *load_task,
    InvestigationGraphLoadTaskCallback callback,
    gpointer user_data,
    GDestroyNotify user_data_destroy,
    GError **error
);

La fonction doit :

  • refuser un objet NULL ;
  • refuser un callback NULL ;
  • refuser un second démarrage pendant une exécution active ;
  • stocker user_data ;
  • appeler user_data_destroy une seule fois lorsque les données ne sont plus
    nécessaires ;
  • lancer le travail avec le mécanisme BackgroundTask existant ;
  • retourner immédiatement au thread appelant.

Annulation

void investigation_graph_load_task_cancel(
    InvestigationGraphLoadTask *load_task
);

La fonction doit :

  • accepter NULL ;
  • être idempotente ;
  • demander l'annulation au worker ;
  • produire une erreur CANCELLED si l'annulation intervient avant la remise
    du résultat ;
  • ne pas transformer un résultat déjà livré en annulation ;
  • ne jamais appeler le callback plusieurs fois.

Le worker doit vérifier l'annulation au minimum :

  1. avant l'ouverture de la base ;
  2. après l'ouverture de la base ;
  3. après le chargement du graphe ;
  4. avant la remise du résultat au thread principal.

État public

Ajouter :

gboolean investigation_graph_load_task_is_running(
    const InvestigationGraphLoadTask *load_task
);

La fonction doit :

  • retourner FALSE pour NULL ;
  • retourner TRUE uniquement entre le démarrage accepté et la finalisation ;
  • être sûre lorsqu'elle est appelée depuis le thread principal.

Travail du worker

Le worker doit :

  1. vérifier l'annulation ;
  2. ouvrir la base avec database_open(database_path) ;
  3. contextualiser un échec d'ouverture avec DATABASE_OPEN ;
  4. créer InvestigationGraphLoader ;
  5. appeler investigation_graph_loader_load() ;
  6. contextualiser l'échec avec LOAD ;
  7. détruire le chargeur ;
  8. fermer la connexion locale ;
  9. remettre le graphe ou l'erreur au thread principal.

Au moindre échec :

  • aucun graphe partiel ne doit être livré ;
  • la connexion locale doit être fermée ;
  • le chargeur doit être détruit ;
  • l'erreur d'origine doit apparaître dans le message final.

Intégration avec BackgroundTask

Le composant doit utiliser l'infrastructure BackgroundTask déjà présente
dans le projet plutôt que créer une seconde abstraction concurrente.

Avant l'implémentation, vérifier l'API actuelle de BackgroundTask et respecter
ses règles de cycle de vie.

Le nouveau composant peut encapsuler BackgroundTask, mais ne doit pas
dupliquer :

  • la création de thread ;
  • le retour vers la boucle principale ;
  • la synchronisation ;
  • la gestion de fin de tâche.

Propriété du résultat

Succès avant callback

Tant que le callback n'a pas commencé :

  • InvestigationGraphLoadTask possède le graphe.

Entrée dans le callback

À l'entrée du callback :

  • la propriété du graphe est transférée au destinataire ;
  • le composant met immédiatement son pointeur interne à NULL.

Callback absent pour cause de destruction

Si l'objet est détruit avant la remise :

  • le graphe doit être libéré par la tâche ;
  • aucun callback ne doit être invoqué.

Tests

Créer :

tests/test_investigation_graph_load_task.c

Les tests doivent utiliser une base SQLite temporaire initialisée avec le
schéma réel.

La boucle principale GLib doit être utilisée pour attendre proprement les
callbacks.

Scénarios minimaux

  1. refus d'un chemin NULL ;
  2. refus d'un chemin vide ;
  3. création et destruction sans démarrage ;
  4. destruction avec NULL ;
  5. état initial non actif ;
  6. démarrage valide ;
  7. état actif pendant l'exécution ;
  8. callback exécuté sur le thread principal ;
  9. chargement asynchrone d'une base vide ;
  10. chargement d'une entité ;
  11. chargement d'un graphe complet ;
  12. vérification des index du graphe reçu ;
  13. transfert de propriété du graphe au callback ;
  14. callback appelé une seule fois ;
  15. user_data_destroy appelé une seule fois ;
  16. refus d'un callback NULL ;
  17. refus d'un second démarrage simultané ;
  18. possibilité d'une nouvelle exécution après finalisation si l'objet est
    conçu comme réutilisable ;
  19. échec d'ouverture d'un chemin inexistant ;
  20. propagation du message d'ouverture ;
  21. échec de chargement avec une base sans schéma ;
  22. propagation du message de InvestigationGraphLoader ;
  23. annulation avant ouverture ;
  24. annulation pendant le chargement ;
  25. annulation idempotente ;
  26. aucun graphe remis après annulation ;
  27. destruction pendant une tâche active ;
  28. aucun callback après destruction ;
  29. fermeture de la connexion créée par le worker ;
  30. deux tâches simultanées utilisant deux connexions distinctes ;
  31. aucun blocage du thread principal ;
  32. GError ** facultatif ;
  33. tous les anciens tests restent valides.

Vérification du thread principal

Le test doit enregistrer le thread appelant avant le démarrage :

GThread *main_thread = g_thread_self();

Dans le callback :

assert(g_thread_self() == main_thread);

Vérification de l'absence de blocage

Préparer une tâche de test contrôlée ou utiliser les points de synchronisation
de test de BackgroundTask.

Pendant que le worker est volontairement suspendu :

  • une source idle de la boucle principale doit encore être exécutée ;
  • investigation_graph_load_task_start() doit déjà avoir rendu la main.

Le test ne doit pas dépendre d'une durée arbitraire fragile.

Hooks de test

Lorsque nécessaire, ajouter des hooks compilés uniquement avec :

-DINVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS

Ils peuvent permettre de :

  • suspendre le worker avant l'ouverture ;
  • suspendre le worker après l'ouverture ;
  • détecter la remise du résultat ;
  • simuler une annulation à un point précis.

Les hooks ne doivent pas apparaître dans la compilation de production.

Créer, uniquement si nécessaire :

include/core/investigation_graph_load_task_test.h

Makefile

Ajouter :

INVESTIGATION_GRAPH_LOAD_TASK_TEST_CFLAGS := \
    $(TEST_CFLAGS) \
    -Wpedantic \
    -DINVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS

TEST_INVESTIGATION_GRAPH_LOAD_TASK := \
    tests/test_investigation_graph_load_task

La cible doit compiler :

  • tests/test_investigation_graph_load_task.c ;
  • src/core/investigation_graph_load_task.c ;
  • src/core/investigation_graph_loader.c ;
  • le composant BackgroundTask ;
  • EntityDao ;
  • RelationDao ;
  • InvestigationGraphModel ;
  • EntityRecord ;
  • RelationRecord ;
  • les modules database nécessaires.

Elle ne doit inclure aucun fichier GTK si BackgroundTask n'en dépend pas.

Ajouter l'exécutable :

  • aux dépendances de make test ;
  • aux commandes exécutées par make test ;
  • aux fichiers supprimés par make clean.

La compilation doit conserver :

-std=c17 -Wall -Wextra -Wpedantic -Werror

Hors périmètre

Ce ticket ne couvre pas :

  • l'affichage du graphe ;
  • le tableau blanc GTK ;
  • Cairo ;
  • les coordonnées des nœuds ;
  • le zoom ;
  • le déplacement des nœuds ;
  • la barre de progression ;
  • le pourcentage de chargement ;
  • la synchronisation après modification SQLite ;
  • le rechargement automatique ;
  • les preuves liées aux relations ;
  • le choix de l'enquête dans l'interface ;
  • les messages visuels d'erreur ;
  • la fermeture automatique de la fenêtre.

L'intégration du résultat dans la fenêtre principale fera l'objet du ticket
suivant.

Critères d'acceptation

  • Le composant est opaque.
  • Le chemin SQLite est copié.
  • Le thread GTK n'est jamais bloqué par le chargement.
  • Une connexion SQLite propre au worker est utilisée.
  • La connexion principale n'est jamais partagée avec le worker.
  • BackgroundTask est réutilisé.
  • Le callback revient sur le thread principal.
  • Le callback est invoqué au maximum une fois.
  • Le graphe est transféré sans fuite.
  • Les erreurs sont contextualisées.
  • L'annulation est idempotente.
  • Aucun callback ne survient après destruction.
  • user_data_destroy est invoqué exactement une fois.
  • Deux tâches simultanées restent indépendantes.
  • Aucun changement GTK n'est introduit.
  • Les tests ciblés passent.
  • make clean && make && make test réussit.
  • git diff --check ne retourne aucune erreur.
# Ajouter le chargement asynchrone du graphe d'enquête ## Contexte Le projet possède désormais : - `InvestigationGraphModel` ; - `InvestigationGraphLoader` ; - un mécanisme `BackgroundTask` déjà utilisé dans l'application ; - une interface GTK destinée à ouvrir des enquêtes sans bloquer le thread principal. Le chargeur synchrone construit correctement un graphe complet depuis SQLite, mais il ne doit pas être appelé directement depuis un gestionnaire GTK lorsque la base peut contenir beaucoup d'entités et de relations. ## Objectif Créer un service asynchrone chargé d'exécuter `investigation_graph_loader_load()` en arrière-plan puis de restituer le résultat sur le thread principal. Le composant doit garantir : - aucune opération SQLite longue sur le thread GTK ; - un transfert clair de propriété du graphe ; - une propagation fidèle des erreurs ; - une annulation propre ; - une seule notification de fin ; - aucun accès concurrent à une connexion SQLite non prévue pour être partagée. ## Point d'architecture important Le thread de fond ne doit pas utiliser directement la connexion `Database` ouverte par l'interface principale. Chaque tâche doit ouvrir sa propre connexion SQLite à partir du chemin de la base, puis : ```text ouvrir Database dans le worker ↓ créer InvestigationGraphLoader ↓ charger InvestigationGraphModel ↓ détruire le chargeur ↓ fermer Database ↓ transférer le graphe au thread principal ``` Cette règle évite le partage implicite d'une connexion SQLite entre threads. ## Fichiers Créer : ```text include/core/investigation_graph_load_task.h src/core/investigation_graph_load_task.c tests/test_investigation_graph_load_task.c ``` ## Type opaque ```c typedef struct InvestigationGraphLoadTask InvestigationGraphLoadTask; ``` ## Domaine d'erreur Créer : ```c typedef enum { INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INVALID_ARGUMENT, INVESTIGATION_GRAPH_LOAD_TASK_ERROR_MEMORY, INVESTIGATION_GRAPH_LOAD_TASK_ERROR_ALREADY_RUNNING, INVESTIGATION_GRAPH_LOAD_TASK_ERROR_DATABASE_OPEN, INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD, INVESTIGATION_GRAPH_LOAD_TASK_ERROR_CANCELLED, INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INTERNAL } InvestigationGraphLoadTaskError; ``` Ajouter : ```c #define INVESTIGATION_GRAPH_LOAD_TASK_ERROR \ investigation_graph_load_task_error_quark() GQuark investigation_graph_load_task_error_quark(void); ``` ## Callback de fin Définir : ```c typedef void (*InvestigationGraphLoadTaskCallback)( InvestigationGraphLoadTask *load_task, InvestigationGraphModel *graph_model, const GError *error, gpointer user_data ); ``` Règles : - le callback est invoqué sur le thread principal ; - exactement un des deux éléments suivants est fourni : - `graph_model` non `NULL` et `error == NULL` ; - `graph_model == NULL` et `error` non `NULL` ; - le graphe appartient au destinataire du callback ; - l'erreur est empruntée et valable uniquement pendant le callback ; - le callback ne doit jamais être appelé deux fois pour une même exécution. ## Construction ```c InvestigationGraphLoadTask *investigation_graph_load_task_new( const char *database_path, GError **error ); ``` Le constructeur doit : - refuser un chemin `NULL` ; - refuser une chaîne vide ; - copier le chemin ; - ne pas ouvrir immédiatement la base ; - initialiser l'état interne ; - accepter `GError **` facultatif. ## Destruction ```c void investigation_graph_load_task_free( InvestigationGraphLoadTask *load_task ); ``` La destruction doit : - accepter `NULL` ; - annuler une tâche encore active ; - attendre ou finaliser proprement le worker selon l'API de `BackgroundTask` ; - ne jamais invoquer le callback après la destruction ; - libérer le chemin copié ; - libérer les erreurs internes ; - libérer un graphe non encore remis au callback. ## Démarrage ```c gboolean investigation_graph_load_task_start( InvestigationGraphLoadTask *load_task, InvestigationGraphLoadTaskCallback callback, gpointer user_data, GDestroyNotify user_data_destroy, GError **error ); ``` La fonction doit : - refuser un objet `NULL` ; - refuser un callback `NULL` ; - refuser un second démarrage pendant une exécution active ; - stocker `user_data` ; - appeler `user_data_destroy` une seule fois lorsque les données ne sont plus nécessaires ; - lancer le travail avec le mécanisme `BackgroundTask` existant ; - retourner immédiatement au thread appelant. ## Annulation ```c void investigation_graph_load_task_cancel( InvestigationGraphLoadTask *load_task ); ``` La fonction doit : - accepter `NULL` ; - être idempotente ; - demander l'annulation au worker ; - produire une erreur `CANCELLED` si l'annulation intervient avant la remise du résultat ; - ne pas transformer un résultat déjà livré en annulation ; - ne jamais appeler le callback plusieurs fois. Le worker doit vérifier l'annulation au minimum : 1. avant l'ouverture de la base ; 2. après l'ouverture de la base ; 3. après le chargement du graphe ; 4. avant la remise du résultat au thread principal. ## État public Ajouter : ```c gboolean investigation_graph_load_task_is_running( const InvestigationGraphLoadTask *load_task ); ``` La fonction doit : - retourner `FALSE` pour `NULL` ; - retourner `TRUE` uniquement entre le démarrage accepté et la finalisation ; - être sûre lorsqu'elle est appelée depuis le thread principal. ## Travail du worker Le worker doit : 1. vérifier l'annulation ; 2. ouvrir la base avec `database_open(database_path)` ; 3. contextualiser un échec d'ouverture avec `DATABASE_OPEN` ; 4. créer `InvestigationGraphLoader` ; 5. appeler `investigation_graph_loader_load()` ; 6. contextualiser l'échec avec `LOAD` ; 7. détruire le chargeur ; 8. fermer la connexion locale ; 9. remettre le graphe ou l'erreur au thread principal. Au moindre échec : - aucun graphe partiel ne doit être livré ; - la connexion locale doit être fermée ; - le chargeur doit être détruit ; - l'erreur d'origine doit apparaître dans le message final. ## Intégration avec BackgroundTask Le composant doit utiliser l'infrastructure `BackgroundTask` déjà présente dans le projet plutôt que créer une seconde abstraction concurrente. Avant l'implémentation, vérifier l'API actuelle de `BackgroundTask` et respecter ses règles de cycle de vie. Le nouveau composant peut encapsuler `BackgroundTask`, mais ne doit pas dupliquer : - la création de thread ; - le retour vers la boucle principale ; - la synchronisation ; - la gestion de fin de tâche. ## Propriété du résultat ### Succès avant callback Tant que le callback n'a pas commencé : - `InvestigationGraphLoadTask` possède le graphe. ### Entrée dans le callback À l'entrée du callback : - la propriété du graphe est transférée au destinataire ; - le composant met immédiatement son pointeur interne à `NULL`. ### Callback absent pour cause de destruction Si l'objet est détruit avant la remise : - le graphe doit être libéré par la tâche ; - aucun callback ne doit être invoqué. ## Tests Créer : ```text tests/test_investigation_graph_load_task.c ``` Les tests doivent utiliser une base SQLite temporaire initialisée avec le schéma réel. La boucle principale GLib doit être utilisée pour attendre proprement les callbacks. ### Scénarios minimaux 1. refus d'un chemin `NULL` ; 2. refus d'un chemin vide ; 3. création et destruction sans démarrage ; 4. destruction avec `NULL` ; 5. état initial non actif ; 6. démarrage valide ; 7. état actif pendant l'exécution ; 8. callback exécuté sur le thread principal ; 9. chargement asynchrone d'une base vide ; 10. chargement d'une entité ; 11. chargement d'un graphe complet ; 12. vérification des index du graphe reçu ; 13. transfert de propriété du graphe au callback ; 14. callback appelé une seule fois ; 15. `user_data_destroy` appelé une seule fois ; 16. refus d'un callback `NULL` ; 17. refus d'un second démarrage simultané ; 18. possibilité d'une nouvelle exécution après finalisation si l'objet est conçu comme réutilisable ; 19. échec d'ouverture d'un chemin inexistant ; 20. propagation du message d'ouverture ; 21. échec de chargement avec une base sans schéma ; 22. propagation du message de `InvestigationGraphLoader` ; 23. annulation avant ouverture ; 24. annulation pendant le chargement ; 25. annulation idempotente ; 26. aucun graphe remis après annulation ; 27. destruction pendant une tâche active ; 28. aucun callback après destruction ; 29. fermeture de la connexion créée par le worker ; 30. deux tâches simultanées utilisant deux connexions distinctes ; 31. aucun blocage du thread principal ; 32. `GError **` facultatif ; 33. tous les anciens tests restent valides. ## Vérification du thread principal Le test doit enregistrer le thread appelant avant le démarrage : ```c GThread *main_thread = g_thread_self(); ``` Dans le callback : ```c assert(g_thread_self() == main_thread); ``` ## Vérification de l'absence de blocage Préparer une tâche de test contrôlée ou utiliser les points de synchronisation de test de `BackgroundTask`. Pendant que le worker est volontairement suspendu : - une source idle de la boucle principale doit encore être exécutée ; - `investigation_graph_load_task_start()` doit déjà avoir rendu la main. Le test ne doit pas dépendre d'une durée arbitraire fragile. ## Hooks de test Lorsque nécessaire, ajouter des hooks compilés uniquement avec : ```text -DINVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS ``` Ils peuvent permettre de : - suspendre le worker avant l'ouverture ; - suspendre le worker après l'ouverture ; - détecter la remise du résultat ; - simuler une annulation à un point précis. Les hooks ne doivent pas apparaître dans la compilation de production. Créer, uniquement si nécessaire : ```text include/core/investigation_graph_load_task_test.h ``` ## Makefile Ajouter : ```make INVESTIGATION_GRAPH_LOAD_TASK_TEST_CFLAGS := \ $(TEST_CFLAGS) \ -Wpedantic \ -DINVESTIGATION_GRAPH_LOAD_TASK_ENABLE_TEST_HOOKS TEST_INVESTIGATION_GRAPH_LOAD_TASK := \ tests/test_investigation_graph_load_task ``` La cible doit compiler : - `tests/test_investigation_graph_load_task.c` ; - `src/core/investigation_graph_load_task.c` ; - `src/core/investigation_graph_loader.c` ; - le composant `BackgroundTask` ; - `EntityDao` ; - `RelationDao` ; - `InvestigationGraphModel` ; - `EntityRecord` ; - `RelationRecord` ; - les modules `database` nécessaires. Elle ne doit inclure aucun fichier GTK si `BackgroundTask` n'en dépend pas. Ajouter l'exécutable : - aux dépendances de `make test` ; - aux commandes exécutées par `make test` ; - aux fichiers supprimés par `make clean`. La compilation doit conserver : ```text -std=c17 -Wall -Wextra -Wpedantic -Werror ``` ## Hors périmètre Ce ticket ne couvre pas : - l'affichage du graphe ; - le tableau blanc GTK ; - Cairo ; - les coordonnées des nœuds ; - le zoom ; - le déplacement des nœuds ; - la barre de progression ; - le pourcentage de chargement ; - la synchronisation après modification SQLite ; - le rechargement automatique ; - les preuves liées aux relations ; - le choix de l'enquête dans l'interface ; - les messages visuels d'erreur ; - la fermeture automatique de la fenêtre. L'intégration du résultat dans la fenêtre principale fera l'objet du ticket suivant. ## Critères d'acceptation - [x] Le composant est opaque. - [x] Le chemin SQLite est copié. - [x] Le thread GTK n'est jamais bloqué par le chargement. - [x] Une connexion SQLite propre au worker est utilisée. - [x] La connexion principale n'est jamais partagée avec le worker. - [x] `BackgroundTask` est réutilisé. - [x] Le callback revient sur le thread principal. - [x] Le callback est invoqué au maximum une fois. - [x] Le graphe est transféré sans fuite. - [x] Les erreurs sont contextualisées. - [x] L'annulation est idempotente. - [x] Aucun callback ne survient après destruction. - [x] `user_data_destroy` est invoqué exactement une fois. - [x] Deux tâches simultanées restent indépendantes. - [x] Aucun changement GTK n'est introduit. - [x] Les tests ciblés passent. - [x] `make clean && make && make test` réussit. - [x] `git diff --check` ne retourne aucune erreur.
fy59 closed this issue 2026-07-21 09:40:12 +02:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: fy59/labfy-investigation#60
No description provided.