/* Chrysalide - Outil d'analyse de fichiers binaires
* delayed.c - gestion des travaux différés
*
* Copyright (C) 2009-2017 Cyrille Bagard
*
* This file is part of Chrysalide.
*
* Chrysalide is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Chrysalide is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Chrysalide. If not, see .
*/
#include "delayed.h"
#include
#include
#include
#include
#include "delayed-int.h"
#include "../gui/core/global.h"
/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
/* Initialise la classe des travaux différés. */
static void g_delayed_work_class_init(GDelayedWorkClass *);
/* Initialise une instance de travail différé. */
static void g_delayed_work_init(GDelayedWork *);
/* Supprime toutes les références externes. */
static void g_delayed_work_dispose(GDelayedWork *);
/* Procède à la libération totale de la mémoire. */
static void g_delayed_work_finalize(GDelayedWork *);
/* Mène l'opération programmée. */
static void g_delayed_work_process(GDelayedWork *, GtkStatusStack *);
/* -------------------------- THREAD DE TRAITEMENTS DEDIES -------------------------- */
#define G_TYPE_WORK_GROUP g_work_group_get_type()
#define G_WORK_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), g_work_group_get_type(), GWorkGroup))
#define G_IS_WORK_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_work_group_get_type()))
#define G_WORK_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_WORK_GROUP, GWorkGroupClass))
#define G_IS_WORK_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_WORK_GROUP))
#define G_WORK_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_WORK_GROUP, GWorkGroupClass))
/* File de traitement pour un type donné (instance) */
typedef struct _GWorkGroup
{
GObject parent; /* A laisser en premier */
wgroup_id_t id; /* Identifiant de travaux menés*/
GDelayedWork *works; /* Tâches à mener à bien */
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) */
typedef struct _GWorkGroupClass
{
GObjectClass parent; /* A laisser en premier */
} GWorkGroupClass;
/* Indique le type défini pour les groupes de travail. */
static GType g_work_group_get_type(void);
/* Initialise la classe des groupes de travail. */
static void g_work_group_class_init(GWorkGroupClass *);
/* Initialise une instance de groupe de travail. */
static void g_work_group_init(GWorkGroup *);
/* Supprime toutes les références externes. */
static void g_work_group_dispose(GWorkGroup *);
/* Procède à la libération totale de la mémoire. */
static void g_work_group_finalize(GWorkGroup *);
/* Crée un nouveau thread dédié à un type de travaux donné. */
static GWorkGroup *g_work_group_new(wgroup_id_t);
/* Fournit l'identifiant associé à un groupe de travail. */
static wgroup_id_t g_work_group_get_id(const GWorkGroup *);
/* Place une nouvelle tâche en attente dans une file dédiée. */
static void g_work_group_schedule(GWorkGroup *, GDelayedWork *);
/* Assure le traitement en différé. */
static void *g_work_group_process(GWorkGroup *);
/* Détermine si le groupe est vide de toute programmation. */
static bool g_work_group_is_empty(GWorkGroup *);
/* Attend que toutes les tâches d'un groupe soient traitées. */
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 *);
/* ------------------------- TRAITEMENT DE TACHES DIFFEREES ------------------------- */
/* Gestionnaire des travaux différés (instance) */
struct _GWorkQueue
{
GObject parent; /* A laisser en premier */
wgroup_id_t generator; /* Générateur d'identifiants */
GWorkGroup **groups; /* Files de traitement */
size_t groups_count; /* Nombre de files internes */
GMutex mutex; /* Verrou pour l'accès */
GCond wait_all; /* Réveil d'attente globale */
};
/* Gestionnaire des travaux différés (classe) */
struct _GWorkQueueClass
{
GObjectClass parent; /* A laisser en premier */
};
/* Initialise la classe des travaux différés. */
static void g_work_queue_class_init(GWorkQueueClass *);
/* Initialise une instance de gestionnaire de travaux différés. */
static void g_work_queue_init(GWorkQueue *);
/* Supprime toutes les références externes. */
static void g_work_queue_dispose(GWorkQueue *);
/* Procède à la libération totale de la mémoire. */
static void g_work_queue_finalize(GWorkQueue *);
/* Donne l'assurance de l'existence d'un groupe de travail. */
static bool 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);
/* ---------------------------------------------------------------------------------- */
/* TACHE DIFFEREE DANS LE TEMPS */
/* ---------------------------------------------------------------------------------- */
/* Indique le type défini pour les travaux différés. */
G_DEFINE_TYPE(GDelayedWork, g_delayed_work, G_TYPE_OBJECT);
/******************************************************************************
* *
* Paramètres : klass = classe à initialiser. *
* *
* Description : Initialise la classe des travaux différés. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_delayed_work_class_init(GDelayedWorkClass *klass)
{
GObjectClass *object; /* Autre version de la classe */
object = G_OBJECT_CLASS(klass);
object->dispose = (GObjectFinalizeFunc/* ! */)g_delayed_work_dispose;
object->finalize = (GObjectFinalizeFunc)g_delayed_work_finalize;
g_signal_new("work-completed",
G_TYPE_DELAYED_WORK,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GDelayedWorkClass, work_completed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
/******************************************************************************
* *
* Paramètres : work = instance à initialiser. *
* *
* Description : Initialise une instance de travail différé. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_delayed_work_init(GDelayedWork *work)
{
work->completed = false;
g_mutex_init(&work->mutex);
g_cond_init(&work->cond);
}
/******************************************************************************
* *
* Paramètres : work = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_delayed_work_dispose(GDelayedWork *work)
{
g_mutex_clear(&work->mutex);
g_cond_clear(&work->cond);
G_OBJECT_CLASS(g_delayed_work_parent_class)->dispose(G_OBJECT(work));
}
/******************************************************************************
* *
* Paramètres : work = instance d'objet GLib à traiter. *
* *
* Description : Procède à la libération totale de la mémoire. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_delayed_work_finalize(GDelayedWork *work)
{
G_OBJECT_CLASS(g_delayed_work_parent_class)->finalize(G_OBJECT(work));
}
/******************************************************************************
* *
* Paramètres : work = travail à effectuer. *
* status = barre de statut à tenir informée. *
* *
* Description : Mène l'opération programmée. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_delayed_work_process(GDelayedWork *work, GtkStatusStack *status)
{
G_DELAYED_WORK_GET_CLASS(work)->run(work, status);
g_mutex_lock(&work->mutex);
work->completed = true;
g_cond_signal(&work->cond);
g_mutex_unlock(&work->mutex);
g_signal_emit_by_name(work, "work-completed");
}
/******************************************************************************
* *
* Paramètres : work = travail à surveiller. *
* *
* Description : Attend la fin de l'exécution d'une tâche donnée. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_delayed_work_wait_for_completion(GDelayedWork *work)
{
g_mutex_lock(&work->mutex);
while (!work->completed)
g_cond_wait(&work->cond, &work->mutex);
g_mutex_unlock(&work->mutex);
}
/* ---------------------------------------------------------------------------------- */
/* THREADS DES TRAITEMENTS DEDIES */
/* ---------------------------------------------------------------------------------- */
/* Indique le type défini pour les groupes de travail. */
G_DEFINE_TYPE(GWorkGroup, g_work_group, G_TYPE_OBJECT);
/******************************************************************************
* *
* Paramètres : klass = classe à initialiser. *
* *
* Description : Initialise la classe des groupes de travail. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_work_group_class_init(GWorkGroupClass *klass)
{
GObjectClass *object; /* Autre version de la classe */
object = G_OBJECT_CLASS(klass);
object->dispose = (GObjectFinalizeFunc/* ! */)g_work_group_dispose;
object->finalize = (GObjectFinalizeFunc)g_work_group_finalize;
}
/******************************************************************************
* *
* Paramètres : group = instance à initialiser. *
* *
* Description : Initialise une instance de groupe de travail. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_work_group_init(GWorkGroup *group)
{
guint i; /* Boucle de parcours */
char name[16]; /* Désignation humaine */
g_mutex_init(&group->mutex);
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 *));
for (i = 0; i < group->threads_count; i++)
{
snprintf(name, sizeof(name), "work_group_%u", i);
group->threads[i] = g_thread_new(name, (GThreadFunc)g_work_group_process, group);
if (!group->threads[i])
goto gwgi_error;
}
gwgi_error:
group->threads_count = i;
assert(i > 0);
group->force_exit = false;
group->callback = NULL;
group->data = NULL;
}
/******************************************************************************
* *
* Paramètres : queue = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_work_group_dispose(GWorkGroup *group)
{
guint i; /* Boucle de parcours */
GDelayedWork *work; /* Travail à oublier */
group->force_exit = true;
/**
* Concernant la pose du verrou, se référer aux commentaires de la
* fonction g_work_group_process().
*/
g_mutex_lock(&group->mutex);
g_cond_broadcast(&group->cond);
g_mutex_unlock(&group->mutex);
for (i = 0; i < group->threads_count; i++)
g_thread_join(group->threads[i]);
while (!dl_list_empty(group->works))
{
work = group->works;
delayed_work_list_del(work, &group->works);
g_object_unref(G_OBJECT(work));
}
g_mutex_clear(&group->mutex);
g_cond_clear(&group->cond);
g_cond_clear(&group->wait_cond);
G_OBJECT_CLASS(g_work_group_parent_class)->dispose(G_OBJECT(group));
}
/******************************************************************************
* *
* Paramètres : group = instance d'objet GLib à traiter. *
* *
* Description : Procède à la libération totale de la mémoire. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_work_group_finalize(GWorkGroup *group)
{
if (group->threads != NULL)
free(group->threads);
G_OBJECT_CLASS(g_work_group_parent_class)->finalize(G_OBJECT(group));
}
/******************************************************************************
* *
* Paramètres : type = type dont seront marqués tous les travaux donnés. *
* *
* Description : Crée un nouveau thread dédié à un type de travaux donné. *
* *
* Retour : Structure associée au thread mise en place. *
* *
* Remarques : - *
* *
******************************************************************************/
static GWorkGroup *g_work_group_new(wgroup_id_t id)
{
GWorkGroup *result; /* Traiteur à retourner */
result = g_object_new(G_TYPE_WORK_GROUP, NULL);
result->id = id;
return result;
}
/******************************************************************************
* *
* Paramètres : group = gestionnaire des actions à mener. *
* *
* Description : Fournit l'identifiant associé à un groupe de travail. *
* *
* Retour : Identifiant unique attribué au groupe de travail. *
* *
* Remarques : - *
* *
******************************************************************************/
static wgroup_id_t g_work_group_get_id(const GWorkGroup *group)
{
return group->id;
}
/******************************************************************************
* *
* Paramètres : group = gestionnaire des actions à mener. *
* work = nouvelle tâche à programmer, puis effectuer. *
* *
* Description : Place une nouvelle tâche en attente dans une file dédiée. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
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);
g_mutex_unlock(&group->mutex);
}
/******************************************************************************
* *
* Paramètres : group = gestionnaire des actions à mener. *
* *
* Description : Assure le traitement en différé. *
* *
* Retour : Bilan de l'opération. *
* *
* Remarques : - *
* *
******************************************************************************/
static void *g_work_group_process(GWorkGroup *group)
{
GDelayedWork *work; /* Traitement à mener */
GtkStatusStack *status; /* Zone d'info éventuelle */
while (1)
{
g_mutex_lock(&group->mutex);
while (dl_list_empty(group->works) && !group->force_exit)
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);
g_mutex_unlock(&group->mutex);
status = get_global_status();
g_delayed_work_process(work, status);
g_object_unref(G_OBJECT(work));
/**
* Verrou ou pas verrou ?
*
* La documentation de la GLib indique que ce n'est pas nécessaire :
*
* '''
* It is good practice to lock the same mutex as the waiting threads
* while calling this function, though not required.
* '''
*
* Ce conseil se trouve verbatim à l'adresse :
*
* https://developer.gnome.org/glib/stable/glib-Threads.html#g-cond-broadcast
*
* Dans la pratique, il peut arriver que l'attente de la fonction
* g_work_group_wait_for_completion() ne soit jamais interrompue.
*
* La documentation POSIX est un peu plus orientée :
*
* '''
* The pthread_cond_broadcast() functions may be called by a thread
* whether or not it currently owns the mutex that threads calling
* pthread_cond_wait() have associated with the condition variable
* during their waits; however, if predictable scheduling behavior is
* required, then that mutex shall be locked by the thread calling
* pthread_cond_broadcast().
* '''
*
* Ce passage complet est consultable à l'adresse :
*
* http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_broadcast.html
*
* La page de manuel pthread_cond_broadcast(3) est quant à elle plus
* directrice : aucun complément d'information sur le sujet n'est fourni
* et les exemples associés utilisent implicement un verrou pendant
* sont appel.
*/
g_mutex_lock(&group->mutex);
if (g_atomic_int_dec_and_test(&group->pending))
g_cond_broadcast(&group->wait_cond);
g_mutex_unlock(&group->mutex);
}
return NULL;
}
/******************************************************************************
* *
* Paramètres : group = gestionnaire des actions à consulter. *
* *
* Description : Détermine si le groupe est vide de toute programmation. *
* *
* Retour : Etat du groupe de travail. *
* *
* Remarques : - *
* *
******************************************************************************/
static bool g_work_group_is_empty(GWorkGroup *group)
{
bool result; /* Etat à retourner */
/**
* Pour que le résultat soit exploitable, il ne doit pas varier
* en dehors de la zone couverte par le verrou du groupe avant
* son utilisation par l'appelant.
*
* Il doit donc logiquement y avoir un autre verrou en amont et,
* comme à priori on ne devrait pas bloquer les groupes principaux
* pour un traitement particulier, cette procédure ne devrait concerner
* que des groupes dynamiques.
*/
g_mutex_lock(&group->mutex);
result = dl_list_empty(group->works);
g_mutex_unlock(&group->mutex);
return result;
}
/******************************************************************************
* *
* 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. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
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);
/**
* 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)
{
/**
* Concernant la pose du verrou, se référer aux commentaires de la
* fonction g_work_group_process().
*/
g_mutex_lock(&group->mutex);
g_cond_broadcast(&group->wait_cond);
g_mutex_unlock(&group->mutex);
}
/* ---------------------------------------------------------------------------------- */
/* TRAITEMENT DE TACHES DIFFEREES */
/* ---------------------------------------------------------------------------------- */
/* Indique le type défini pour le gestionnaire des travaux différés. */
G_DEFINE_TYPE(GWorkQueue, g_work_queue, G_TYPE_OBJECT);
/******************************************************************************
* *
* Paramètres : klass = classe à initialiser. *
* *
* Description : Initialise la classe des travaux différés. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_work_queue_class_init(GWorkQueueClass *klass)
{
GObjectClass *object; /* Autre version de la classe */
object = G_OBJECT_CLASS(klass);
object->dispose = (GObjectFinalizeFunc/* ! */)g_work_queue_dispose;
object->finalize = (GObjectFinalizeFunc)g_work_queue_finalize;
}
/******************************************************************************
* *
* Paramètres : queue = instance à initialiser. *
* *
* Description : Initialise une instance de gestionnaire de travaux différés. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_work_queue_init(GWorkQueue *queue)
{
queue->generator = 0;
queue->groups = NULL;
queue->groups_count = 0;
g_mutex_init(&queue->mutex);
g_cond_init(&queue->wait_all);
}
/******************************************************************************
* *
* Paramètres : queue = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_work_queue_dispose(GWorkQueue *queue)
{
size_t i; /* Boucle de parcours */
g_mutex_lock(&queue->mutex);
for (i = 0; i < queue->groups_count; i++)
g_object_unref(G_OBJECT(queue->groups[i]));
g_mutex_unlock(&queue->mutex);
g_mutex_clear(&queue->mutex);
g_cond_clear(&queue->wait_all);
G_OBJECT_CLASS(g_work_queue_parent_class)->dispose(G_OBJECT(queue));
}
/******************************************************************************
* *
* Paramètres : queue = instance d'objet GLib à traiter. *
* *
* Description : Procède à la libération totale de la mémoire. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_work_queue_finalize(GWorkQueue *queue)
{
if (queue->groups != NULL)
free(queue->groups);
G_OBJECT_CLASS(g_work_queue_parent_class)->finalize(G_OBJECT(queue));
}
/******************************************************************************
* *
* Paramètres : - *
* *
* Description : Créé un nouveau gestionnaire de tâches parallèles. *
* *
* Retour : Gestionnaire de traitements mis en place. *
* *
* Remarques : - *
* *
******************************************************************************/
GWorkQueue *g_work_queue_new(void)
{
GWorkQueue *result; /* Instance à retourner */
result = g_object_new(G_TYPE_WORK_QUEUE, NULL);
return result;
}
/******************************************************************************
* *
* Paramètres : queue = gestionnaire de l'ensemble des groupes de travail. *
* id = identifiant d'un groupe de travail. *
* *
* Description : Donne l'assurance de l'existence d'un groupe de travail. *
* *
* Retour : true si un nouveau groupe a été constitué, false sinon. *
* *
* Remarques : Le verrou d'accès doit être posé par l'appelant. *
* *
******************************************************************************/
static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id)
{
bool found; /* Bilan des recherches */
size_t i; /* Boucle de parcours */
GWorkGroup *group; /* Groupe à consulter */
assert(!g_mutex_trylock(&queue->mutex));
found = false;
for (i = 0; i < queue->groups_count && !found; i++)
{
group = queue->groups[i];
found = (g_work_group_get_id(group) == id);
}
if (!found)
{
queue->groups_count++;
queue->groups = (GWorkGroup **)realloc(queue->groups,
queue->groups_count * sizeof(GWorkGroup *));
group = g_work_group_new(id);
queue->groups[queue->groups_count - 1] = group;
}
return !found;
}
/******************************************************************************
* *
* Paramètres : queue = gestionnaire de l'ensemble des groupes de travail. *
* *
* Description : Constitue un nouveau groupe de travail. *
* *
* Retour : Nouvel identifiant unique d'un nouveau groupe de travail. *
* *
* Remarques : - *
* *
******************************************************************************/
wgroup_id_t g_work_queue_define_work_group(GWorkQueue *queue)
{
wgroup_id_t result; /* Valeur à retourner */
bool created; /* Bilan d'une tentative */
g_mutex_lock(&queue->mutex);
do
{
result = queue->generator++;
created = g_work_queue_ensure_group_exists(queue, result);
}
while (!created);
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 : Dissout un groupe de travail existant. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_work_queue_delete_work_group(GWorkQueue *queue, wgroup_id_t id)
{
size_t i; /* Boucle de parcours */
#ifndef NDEBUG
bool found; /* Repérage du groupe visé */
#endif
#ifndef NDEBUG
found = false;
#endif
g_mutex_lock(&queue->mutex);
for (i = 0; i < queue->groups_count; i++)
if (g_work_group_get_id(queue->groups[i]) == id)
{
memmove(&queue->groups[i], &queue->groups[i + 1],
(queue->groups_count - i - 1) * sizeof(GWorkGroup *));
queue->groups_count--;
queue->groups = (GWorkGroup **)realloc(queue->groups,
queue->groups_count * sizeof(GWorkGroup *));
#ifndef NDEBUG
found = true;
#endif
break;
}
assert(found);
g_cond_broadcast(&queue->wait_all);
g_mutex_unlock(&queue->mutex);
}
/******************************************************************************
* *
* Paramètres : queue = gestionnaire des actions à mener. *
* work = nouvelle tâche à programmer, puis effectuer. *
* id = identifiant du groupe de travail d'affectation. *
* *
* Description : Place une nouvelle tâche en attente. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_work_queue_schedule_work(GWorkQueue *queue, GDelayedWork *work, wgroup_id_t id)
{
GWorkGroup *group; /* Groupe de travail à attendre*/
group = g_work_queue_find_group_for_id(queue, id);
assert(group != NULL);
g_work_group_schedule(group, work);
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 : Fournit le groupe de travail correspondant à un identifiant. *
* *
* Retour : Eventuel groupe existant trouvé ou NULL si aucun. *
* *
* Remarques : - *
* *
******************************************************************************/
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 */
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)
{
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 : Détermine si un groupe est vide de toute programmation. *
* *
* Retour : Etat du groupe de travail. *
* *
* Remarques : - *
* *
******************************************************************************/
bool g_work_queue_is_empty(GWorkQueue *queue, wgroup_id_t id)
{
bool result; /* Etat à retourner */
GWorkGroup *group; /* Groupe de travail à attendre*/
group = g_work_queue_find_group_for_id(queue, id);
if (group != NULL)
{
result = g_work_group_is_empty(group);
g_object_unref(G_OBJECT(group));
}
else
result = true;
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.*
* gb_ids = identifiants de groupes globaux. *
* gb_count = nombre de ces groupes globaux. *
* *
* Description : Attend que toutes les tâches de tout groupe soient traitées. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_work_queue_wait_for_all_completions(GWorkQueue *queue, const wgroup_id_t *gb_ids, size_t gb_count)
{
size_t i; /* Boucle de parcours */
g_mutex_lock(&queue->mutex);
wait_again:
/**
* Attente d'éventuels groupes isolés.
*/
while (queue->groups_count > gb_count)
g_cond_wait(&queue->wait_all, &queue->mutex);
g_mutex_unlock(&queue->mutex);
/**
* Attente des groupes principaux.
*/
for (i = 0; i < gb_count; i++)
g_work_queue_wait_for_completion(queue, gb_ids[i]);
/**
* Si le groupe par défaut a généré de nouveaux groupes, on recommence !
*/
g_mutex_lock(&queue->mutex);
if (queue->groups_count > gb_count)
goto wait_again;
g_mutex_unlock(&queue->mutex);
}
/******************************************************************************
* *
* 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_wake_up_waiters(group);
g_object_unref(G_OBJECT(group));
}
}