From 5710f9d5be56b427ccfa48f6a730d70396817efe Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Sat, 19 Dec 2015 21:22:14 +0100
Subject: Fixed several bugs when processing concurrent delayed works.

---
 ChangeLog                   |  17 ++++
 src/analysis/disass/fetch.c |  88 ++++++++++++++++---
 src/arch/context-int.h      |   2 +
 src/arch/context.c          |  55 +++++-------
 src/arch/context.h          |   6 +-
 src/glibext/delayed.c       | 203 ++++++++++++++++++++++++++++++++++++++++----
 src/glibext/delayed.h       |  11 +++
 7 files changed, 318 insertions(+), 64 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 31dca7f..a6c2c2c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+15-12-19  Cyrille Bagard <nocbos@gmail.com>
+
+	* src/analysis/disass/fetch.c:
+	Track the remaining drop points for the delayed work group.
+
+	* src/arch/context-int.h:
+	* src/arch/context.c:
+	* src/arch/context.h:
+	Use an external counter to track the added drop points.
+
+	* src/glibext/delayed.c:
+	* src/glibext/delayed.h:
+	Fix several bugs when processing concurrent delayed works. Count precisely
+	the remaining works to wait for. Avoid to generate group identifiers equal
+	to DEFAULT_WORK_GROUP. Fix a bug by handling the "no status bar" case. Fix
+	an Out-Of-Bound access when creating new groups.
+
 15-12-17  Cyrille Bagard <nocbos@gmail.com>
 
 	* src/arch/arm/context.c:
diff --git a/src/analysis/disass/fetch.c b/src/analysis/disass/fetch.c
index 5caf573..29d8923 100644
--- a/src/analysis/disass/fetch.c
+++ b/src/analysis/disass/fetch.c
@@ -309,7 +309,8 @@ static void g_delayed_fetching_process(GDelayedFetching *fetching, GtkExtStatusB
 /* Poursuit l'analyse à partir des points d'entrée découverts. */
 static void follow_execution_flow_v2(GProcContext *, const GDelayedFetching *);
 
-
+/* Etudie le besoin d'attendre d'avantage de prochaines tâches. */
+static bool check_if_extra_wait_is_needed(GWorkQueue *, wgroup_id_t, GProcContext *);
 
 
 
@@ -331,11 +332,14 @@ static void follow_execution_flow_v2(GProcContext *, const GDelayedFetching *);
 static void follow_execution_flow_v2(GProcContext *ctx, const GDelayedFetching *template)
 {
     GWorkQueue *queue;                      /* Gestionnaire de différés    */
+    gint *remaining_counter;                /* Compteur à considérer       */
     virt_t virt;                            /* Adresse de départ dépilée   */
     GDelayedFetching *fetching;             /* Récupération à mener        */
 
     queue = get_work_queue();
 
+    remaining_counter = (gint *)g_object_get_data(G_OBJECT(ctx), "remaining_counter");
+
     while (g_proc_context_pop_drop_point(ctx, &virt))
     {
         fetching = g_delayed_fetching_new(template, virt);
@@ -351,6 +355,16 @@ static void follow_execution_flow_v2(GProcContext *ctx, const GDelayedFetching *
 
         g_work_queue_schedule_work(queue, G_DELAYED_WORK(fetching), template->gid);
 
+        /**
+         * Le décompte n'est réalisé qu'après la programmation de la tâche.
+         * Ainsi, lors de l'attente de la fin des traitements, on a la garantie
+         * de ne pas avoir de trou entre le dépilement des points et la programmation
+         * des tâches de traitement associées.
+         */
+
+        if (g_atomic_int_dec_and_test(remaining_counter))
+            g_work_queue_wake_up_waiters(queue, template->gid);
+
     }
 
 }
@@ -358,6 +372,45 @@ static void follow_execution_flow_v2(GProcContext *ctx, const GDelayedFetching *
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
+*                id    = identifiant d'un groupe de travail.                  *
+*                ctx   = contexte de désass. avec une nouvelle entrée.        *
+*                                                                             *
+*  Description : Etudie le besoin d'attendre d'avantage de prochaines tâches. *
+*                                                                             *
+*  Retour      : true pour attendre d'avantage, false sinon.                  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool check_if_extra_wait_is_needed(GWorkQueue *queue, wgroup_id_t id, GProcContext *ctx)
+{
+    bool result;                            /* Bilan à retourner           */
+    gint *remaining_counter;                /* Compteur à considérer       */
+
+    remaining_counter = (gint *)g_object_get_data(G_OBJECT(ctx), "remaining_counter");
+
+    result = (g_atomic_int_get(remaining_counter) > 0);
+
+    return result;
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : binary = représentation de binaire chargé.                   *
 *                ctx    = contexte offert en soutien à un désassemblage.      *
 *                areas  = liste de zones contenant des données à traiter.     *
@@ -435,15 +488,9 @@ GArchInstruction *disassemble_binary_content(const GLoadedBinary *binary, wgroup
     GBinContent *content;                   /* Contenu binaire à manipuler */
     phys_t length;                          /* Taille des données à lire   */
     GWorkQueue *queue;                      /* Gestionnaire de différés    */
+    gint remaining_counter;                 /* Quantité de points restants */
     double done;                            /* Portion de travail accompli */
 
-
-
-
-
-
-
-
     /* Constitution du modèle de référence */
 
     template.gid = gid;
@@ -463,24 +510,33 @@ GArchInstruction *disassemble_binary_content(const GLoadedBinary *binary, wgroup
 
     /* Amorce des traitements */
 
-    g_signal_connect(template.ctx, "drop-point-pushed", G_CALLBACK(follow_execution_flow_v2), &template);
-
     queue = get_work_queue();
 
+    g_atomic_int_set(&remaining_counter, 0);
+
+    g_object_set_data(G_OBJECT(template.ctx), "remaining_counter", &remaining_counter);
+
+    g_proc_context_attach_counter(template.ctx, &remaining_counter);
+
     /**
      * Première phase de désassemblage : suivi des chemins tracés.
      */
 
+
+    g_work_queue_set_extra_wait_callback(queue, gid,
+                                         (wait_for_incoming_works_cb)check_if_extra_wait_is_needed,
+                                         template.ctx);
+
+    g_signal_connect(template.ctx, "drop-point-pushed", G_CALLBACK(follow_execution_flow_v2), &template);
+
     template.info = init_progessive_status(statusbar,
                                            _("Disassembling following the execution flow..."),
                                            0, length);
-    
+
     g_binary_format_setup_disassembling_context(format, template.ctx);
 
     g_work_queue_wait_for_completion(queue, gid);
 
-    printf("===================== DONE !\n");
-
     done = get_current_progessive_status(template.info);
 
     fini_progessive_status(template.info);
@@ -495,6 +551,12 @@ GArchInstruction *disassemble_binary_content(const GLoadedBinary *binary, wgroup
 
     ensure_all_mem_areas_are_filled(template.areas, template.count, template.ctx, template.info);
 
+    g_work_queue_wait_for_completion(queue, gid);
+
+    g_work_queue_set_extra_wait_callback(queue, gid, NULL, NULL);
+
+    g_object_set_data(G_OBJECT(template.ctx), "remaining_counter", NULL);
+
     fini_progessive_status(template.info);
 
     /**
diff --git a/src/arch/context-int.h b/src/arch/context-int.h
index 885c2f6..59a06bb 100644
--- a/src/arch/context-int.h
+++ b/src/arch/context-int.h
@@ -54,6 +54,8 @@ struct _GProcContext
     size_t esyms_count;                     /* Nombres de nouveautés       */
     GMutex es_access;                       /* Accès à cette même liste    */
 
+    gint *counter;
+
 };
 
 
diff --git a/src/arch/context.c b/src/arch/context.c
index bb1d80a..98eebb2 100644
--- a/src/arch/context.c
+++ b/src/arch/context.c
@@ -104,6 +104,26 @@ static void g_proc_context_init(GProcContext *ctx)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : ctx     = contexte de désassemblage à mettre à jour.         *
+*                counter = adresse du compteur à modifier.                    *
+*                                                                             *
+*  Description : Enregistre un compteur pour le décompte des points à traiter.*
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_proc_context_attach_counter(GProcContext *ctx, gint *counter)
+{
+    ctx->counter = counter;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : ctx   = contexte de désassemblage à compléter.               *
 *                level = indication de priorité et d'origine de l'adresse.    *
 *                addr  = adresse d'un nouveau point de départ à traiter.      *
@@ -155,6 +175,9 @@ void g_proc_context_push_drop_point(GProcContext *ctx, unsigned int level, virt_
 
     g_mutex_lock(&ctx->dp_access);
 
+    if (ctx->counter != NULL)
+        g_atomic_int_inc(ctx->counter);
+
     G_PROC_CONTEXT_GET_CLASS(ctx)->push_point(ctx, level, addr, ap);
 
     g_mutex_unlock(&ctx->dp_access);
@@ -168,38 +191,6 @@ void g_proc_context_push_drop_point(GProcContext *ctx, unsigned int level, virt_
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : ctx  = contexte de désassemblage à consulter.                *
-*                addr = adresse de mémoire virtuelle à rechercher.            *
-*                                                                             *
-*  Description : Précise si une adresse donnée figure comme point de départ.  *
-*                                                                             *
-*  Retour      : true si l'adresse est connue en interne, false sinon.        *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_proc_context_has_addr_as_drop_points(GProcContext *ctx, virt_t addr)
-{
-    bool result;                            /* Bilan à retourner           */
-    size_t i;                               /* Boucle de parcours          */
-
-    result = false;
-
-    g_mutex_lock(&ctx->dp_access);
-
-    for (i = 0; i < ctx->dp_count && !result; i++)
-        result = (ctx->drop_points[i] == addr);
-
-    g_mutex_unlock(&ctx->dp_access);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
 *  Paramètres  : ctx  = contexte de désassemblage à compléter.                *
 *                virt = adresse d'un point de départ de code à traiter.       *
 *                                                                             *
diff --git a/src/arch/context.h b/src/arch/context.h
index 5dffd6a..334eaf1 100644
--- a/src/arch/context.h
+++ b/src/arch/context.h
@@ -51,12 +51,12 @@ typedef struct _GProcContextClass GProcContextClass;
 /* Indique le type définit par la GLib pour le contexte de processeur. */
 GType g_proc_context_get_type(void);
 
+/* Enregistre un compteur pour le décompte des points à traiter. */
+void g_proc_context_attach_counter(GProcContext *, gint *);
+
 /* Ajoute une adresse virtuelle comme point de départ de code. */
 void g_proc_context_push_drop_point(GProcContext *, unsigned int, virt_t, ...);
 
-/* Précise si une adresse donnée figure comme point de départ. */
-bool g_proc_context_has_addr_as_drop_points(GProcContext *, virt_t);
-
 /* Fournit une adresse virtuelle comme point de départ de code. */
 bool g_proc_context_pop_drop_point(GProcContext *, virt_t *);
 
diff --git a/src/glibext/delayed.c b/src/glibext/delayed.c
index 967865b..a83fae4 100644
--- a/src/glibext/delayed.c
+++ b/src/glibext/delayed.c
@@ -78,11 +78,15 @@ typedef struct _GWorkGroup
     GMutex mutex;                           /* Verrou pour l'accès         */
     GCond cond;                             /* Réveil pour un traitement   */
     GCond wait_cond;                        /* Réveil d'attente de fin     */
+    gint pending;                           /* Tâches en cours d'exécution */
 
     GThread **threads;                      /* Procédure de traitement     */
     guint threads_count;                    /* Nombre de procédures        */
     bool force_exit;                        /* Procédure d'arrêt           */
 
+    wait_for_incoming_works_cb callback;    /* Encadre les attentes de fin */
+    void *data;                             /* Données à associer          */
+
 } GWorkGroup;
 
 /* File de traitement pour un type donné (classe) */
@@ -121,7 +125,13 @@ static void g_work_group_schedule(GWorkGroup *, GDelayedWork *);
 static void *g_work_group_process(GWorkGroup *);
 
 /* Attend que toutes les tâches d'un groupe soient traitées. */
-static void g_work_group_wait_for_completion(GWorkGroup *);
+static void g_work_group_wait_for_completion(GWorkGroup *, GWorkQueue *);
+
+/* Modifie les conditions d'attente des fins d'exécutions. */
+static void g_work_group_set_extra_wait_callback(GWorkGroup *, wait_for_incoming_works_cb, void *);
+
+/* Force un réveil d'une attente en cours pour la confirmer. */
+static void g_work_group_wake_up_waiters(GWorkGroup *);
 
 
 
@@ -166,6 +176,9 @@ static void g_work_queue_finalize(GWorkQueue *);
 /* Donne l'assurance de l'existence d'un groupe de travail. */
 static GWorkGroup *g_work_queue_ensure_group_exists(GWorkQueue *, wgroup_id_t);
 
+/* Fournit le groupe de travail correspondant à un identifiant. */
+static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *, wgroup_id_t);
+
 
 
 /* ---------------------------------------------------------------------------------- */
@@ -379,6 +392,8 @@ static void g_work_group_init(GWorkGroup *group)
     g_cond_init(&group->cond);
     g_cond_init(&group->wait_cond);
 
+    g_atomic_int_set(&group->pending, 0);
+
     group->threads_count = g_get_num_processors();
 
     group->threads = (GThread **)calloc(group->threads_count, sizeof(GThread *));
@@ -401,6 +416,9 @@ static void g_work_group_init(GWorkGroup *group)
 
     group->force_exit = false;
 
+    group->callback = NULL;
+    group->data = NULL;
+
 }
 
 
@@ -491,8 +509,11 @@ static GWorkGroup *g_work_group_new(wgroup_id_t id, GtkExtStatusBar *statusbar)
 
     result->id = id;
 
-    result->statusbar = statusbar;
-    g_object_ref(statusbar);
+    if (statusbar != NULL)
+    {
+        result->statusbar = statusbar;
+        g_object_ref(statusbar);
+    }
 
     return result;
 
@@ -535,6 +556,8 @@ static void g_work_group_schedule(GWorkGroup *group, GDelayedWork *work)
 {
     g_mutex_lock(&group->mutex);
 
+    g_atomic_int_inc(&group->pending);
+
     delayed_work_list_add_tail(work, &group->works);
 
     g_cond_signal(&group->cond);
@@ -568,7 +591,10 @@ static void *g_work_group_process(GWorkGroup *group)
             g_cond_wait(&group->cond, &group->mutex);
 
         if (group->force_exit)
+        {
+            g_mutex_unlock(&group->mutex);
             break;
+        }
 
         work = group->works;
         delayed_work_list_del(work, &group->works);
@@ -579,7 +605,8 @@ static void *g_work_group_process(GWorkGroup *group)
 
         g_object_unref(G_OBJECT(work));
 
-        g_cond_broadcast(&group->wait_cond);
+        if (g_atomic_int_dec_and_test(&group->pending))
+            g_cond_broadcast(&group->wait_cond);
 
     }
 
@@ -591,6 +618,7 @@ static void *g_work_group_process(GWorkGroup *group)
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : group = groupe dont les conclusions sont attendues.          *
+*                queue = queue d'appartenance pour les appels externes.       *
 *                                                                             *
 *  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
 *                                                                             *
@@ -600,18 +628,78 @@ static void *g_work_group_process(GWorkGroup *group)
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_group_wait_for_completion(GWorkGroup *group)
+static void g_work_group_wait_for_completion(GWorkGroup *group, GWorkQueue *queue)
 {
+    wait_for_incoming_works_cb callback;    /* Procédure complémentaire    */
+
+    bool no_extra_check(GWorkQueue *_q, wgroup_id_t _id, void *_data)
+    {
+        return false;
+    }
+
+    callback = group->callback != NULL ? group->callback : no_extra_check;
+
     g_mutex_lock(&group->mutex);
 
-    while (dl_list_empty(group->works) && !group->force_exit)
+    /**
+     * On attend que :
+     *  - la liste des tâches programmées soit vide.
+     *  - il n'existe plus de tâche en cours.
+     *  - rien n'indique que de nouvelles tâches supplémentaires vont arriver.
+     */
+
+    while ((g_atomic_int_get(&group->pending) > 0 || callback(queue, group->id, group->data))
+           && !group->force_exit)
+    {
         g_cond_wait(&group->wait_cond, &group->mutex);
+    }
 
     g_mutex_unlock(&group->mutex);
 
 }
 
 
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : group    = groupe dont les paramètres sont à modifier.       *
+*                callback = éventuelle fonction à appeler ou NULL.            *
+*                data     = données devant accompagner l'appel.               *
+*                                                                             *
+*  Description : Modifie les conditions d'attente des fins d'exécutions.      *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_work_group_set_extra_wait_callback(GWorkGroup *group, wait_for_incoming_works_cb callback, void *data)
+{
+    group->callback = callback;
+    group->data = data;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
+*                id       = identifiant d'un groupe de travail.               *
+*                                                                             *
+*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_work_group_wake_up_waiters(GWorkGroup *group)
+{
+    g_cond_broadcast(&group->wait_cond);
+
+}
+
 
 
 /* ---------------------------------------------------------------------------------- */
@@ -816,7 +904,7 @@ static GWorkGroup *g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id
                                                queue->groups_count * sizeof(GWorkGroup *));
 
         result = g_work_group_new(id, queue->statusbar);
-        queue->groups[i] = result;
+        queue->groups[queue->groups_count - 1] = result;
 
     }
 
@@ -843,7 +931,7 @@ wgroup_id_t g_work_queue_define_work_group(GWorkQueue *queue)
 
     g_mutex_lock(&queue->mutex);
 
-    result = queue->generator++;
+    result = ++queue->generator;
 
     g_work_queue_ensure_group_exists(queue, result);
 
@@ -932,35 +1020,118 @@ void g_work_queue_schedule_work(GWorkQueue *queue, GDelayedWork *work, wgroup_id
 *  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
 *                id    = identifiant d'un groupe de travail.                  *
 *                                                                             *
-*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
+*  Description : Fournit le groupe de travail correspondant à un identifiant. *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Eventuel groupe existant trouvé ou NULL si aucun.            *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-void g_work_queue_wait_for_completion(GWorkQueue *queue, wgroup_id_t id)
+static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *queue, wgroup_id_t id)
 {
+    GWorkGroup *result;                     /* Trouvaille à retourner      */
     size_t i;                               /* Boucle de parcours          */
-    GWorkGroup *group;                      /* Groupe de travail à attendre*/
 
-    group = NULL;
+    result = NULL;
 
     g_mutex_lock(&queue->mutex);
 
     for (i = 0; i < queue->groups_count; i++)
         if (g_work_group_get_id(queue->groups[i]) == id)
         {
-            group = queue->groups[i];
-            g_object_ref(G_OBJECT(group));
+            result = queue->groups[i];
+            g_object_ref(G_OBJECT(result));
+            break;
         }
 
     g_mutex_unlock(&queue->mutex);
 
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
+*                id    = identifiant d'un groupe de travail.                  *
+*                                                                             *
+*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_work_queue_wait_for_completion(GWorkQueue *queue, wgroup_id_t id)
+{
+    GWorkGroup *group;                      /* Groupe de travail à attendre*/
+
+    group = g_work_queue_find_group_for_id(queue, id);
+
+    if (group != NULL)
+    {
+        g_work_group_wait_for_completion(group, queue);
+        g_object_unref(G_OBJECT(group));
+    }
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
+*                id       = identifiant d'un groupe de travail.               *
+*                callback = éventuelle fonction à appeler ou NULL.            *
+*                data     = données devant accompagner l'appel.               *
+*                                                                             *
+*  Description : Modifie les conditions d'attente des fins d'exécutions.      *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_work_queue_set_extra_wait_callback(GWorkQueue *queue, wgroup_id_t id, wait_for_incoming_works_cb callback, void *data)
+{
+    GWorkGroup *group;                      /* Groupe de travail à traiter */
+
+    group = g_work_queue_find_group_for_id(queue, id);
+
+    if (group != NULL)
+    {
+        g_work_group_set_extra_wait_callback(group, callback, data);
+        g_object_unref(G_OBJECT(group));
+    }
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
+*                id       = identifiant d'un groupe de travail.               *
+*                                                                             *
+*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_work_queue_wake_up_waiters(GWorkQueue *queue, wgroup_id_t id)
+{
+    GWorkGroup *group;                      /* Groupe de travail à traiter */
+
+    group = g_work_queue_find_group_for_id(queue, id);
+
     if (group != NULL)
     {
-        g_work_group_wait_for_completion(group);
+        g_work_group_wake_up_waiters(group);
         g_object_unref(G_OBJECT(group));
     }
 
diff --git a/src/glibext/delayed.h b/src/glibext/delayed.h
index 0a27c93..4902e68 100644
--- a/src/glibext/delayed.h
+++ b/src/glibext/delayed.h
@@ -106,5 +106,16 @@ void g_work_queue_schedule_work(GWorkQueue *, GDelayedWork *, wgroup_id_t);
 void g_work_queue_wait_for_completion(GWorkQueue *, wgroup_id_t);
 
 
+/* Etudie le besoin d'attendre d'avantage de prochaines tâches. */
+typedef bool (* wait_for_incoming_works_cb) (GWorkQueue *, wgroup_id_t, void *);
+
+
+/* Modifie les conditions d'attente des fins d'exécutions. */
+void g_work_queue_set_extra_wait_callback(GWorkQueue *, wgroup_id_t, wait_for_incoming_works_cb, void *);
+
+/* Force un réveil d'une attente en cours pour la confirmer. */
+void g_work_queue_wake_up_waiters(GWorkQueue *, wgroup_id_t);
+
+
 
 #endif  /* _GLIBEXT_DELAYED_H */
-- 
cgit v0.11.2-87-g4458