/* Chrysalide - Outil d'analyse de fichiers binaires
* workqueue.c - gestion des travaux différés
*
* Copyright (C) 2009-2024 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 "workqueue.h"
#include
#include
#include "workqueue-int.h"
/* 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, guint);
/* Fournit le groupe de travail correspondant à un identifiant. */
static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *, wgroup_id_t);
/* 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_clear_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. *
* count = quantité de threads à allouer (0 pour un défaut). *
* *
* 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, guint count)
{
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 = realloc(queue->groups, queue->groups_count * sizeof(GWorkGroup *));
group = g_work_group_new(id, count);
queue->groups[queue->groups_count - 1] = group;
}
return !found;
}
/******************************************************************************
* *
* Paramètres : queue = gestionnaire de l'ensemble des groupes de travail. *
* count = quantité de threads à allouer (0 pour un défaut). *
* *
* 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_group(GWorkQueue *queue, guint count)
{
wgroup_id_t result; /* Valeur à retourner */
bool created; /* Bilan d'une tentative */
g_mutex_lock(&queue->mutex);
do
{
result = ++queue->generator;
if (result == INVALID_GROUP_ID)
continue;
created = g_work_queue_ensure_group_exists(queue, result, count);
}
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_group(GWorkQueue *queue, wgroup_id_t id)
{
size_t i; /* Boucle de parcours */
GWorkGroup *group; /* Groupe de travail manipulé */
#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++)
{
group = queue->groups[i];
if (g_work_group_get_id(group) == id)
{
unref_object(group);
memmove(&queue->groups[i], &queue->groups[i + 1],
(queue->groups_count - i - 1) * sizeof(GWorkGroup *));
queue->groups_count--;
queue->groups = 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 : Bilan, qui correspond à l'existence du groupe ciblé. *
* *
* Remarques : - *
* *
******************************************************************************/
bool g_work_queue_schedule(GWorkQueue *queue, GGenericWork *work, wgroup_id_t id)
{
bool result; /* Bilan à retourner */
GWorkGroup *group; /* Groupe de travail à attendre*/
group = g_work_queue_find_group_for_id(queue, id);
assert(group != NULL);
result = (group != NULL);
if (result)
{
g_work_group_schedule(group, work);
unref_object(group);
}
return result;
}
/******************************************************************************
* *
* 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];
ref_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);
unref_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);
unref_object(group);
}
}
/******************************************************************************
* *
* Paramètres : queue = gestionnaire de l'ensemble des groupes de travail. *
* id = identifiant d'un groupe de travail. *
* rel = durée relative à patienter au max. en microsecondes. *
* *
* Description : Attend que toutes les tâches d'un groupe soient traitées. *
* *
* Retour : Bilan de l'attente : false en cas d'expiration, true sinon. *
* *
* Remarques : Cette fonction est originellement dédiée à un usage Python. *
* *
******************************************************************************/
bool g_work_queue_wait_timed_for_completion(GWorkQueue *queue, wgroup_id_t id, gint64 rel)
{
bool result; /* Bilan d'attente à renvoyer */
GWorkGroup *group; /* Groupe de travail à attendre*/
group = g_work_queue_find_group_for_id(queue, id);
if (group != NULL)
{
result = g_work_group_wait_timed_for_completion(group, rel);
unref_object(group);
}
else
result = true;
return result;
}
/******************************************************************************
* *
* 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. *
* *
* 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);
unref_object(group);
}
}