/* Chrysalide - Outil d'analyse de fichiers binaires
* workgroup.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 "workgroup.h"
#include
#include
#include
#include
#include "workgroup-int.h"
#include "../common/dllist.h"
#include "../core/nproc.h"
/* 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 *);
/* Assure le traitement en différé. */
static void *g_work_group_process(GWorkGroup *);
/* 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)
{
DL_LIST_HEAD_INIT(group->works);
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 = NULL;
group->threads_count = 0;
group->force_exit = false;
}
/******************************************************************************
* *
* Paramètres : group = 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 */
GGenericWork *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;
g_generic_work_remove_from_list(work, &group->works);
unref_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 : id = identifiant accordé au nouveau groupe. *
* count = quantité de threads à allouer (0 pour un défaut). *
* *
* Description : Crée un nouveau thread dédié à un type de travaux donné. *
* *
* Retour : Structure associée au thread mise en place. *
* *
* Remarques : - *
* *
******************************************************************************/
GWorkGroup *g_work_group_new(wgroup_id_t id, guint count)
{
GWorkGroup *result; /* Traiteur à retourner */
guint i; /* Boucle de parcours */
int ret; /* Bilan d'un appel */
char *name; /* Désignation humaine */
result = g_object_new(G_TYPE_WORK_GROUP, NULL);
result->id = id;
if (count == 0)
count = get_max_online_threads();
result->threads_count = count;
result->threads = calloc(result->threads_count, sizeof(GThread *));
for (i = 0; i < result->threads_count; i++)
{
/**
* La documentation précise :
*
* Some systems restrict the length of name to 16 bytes.
*
* On laisse ces systèmes tronquer.
*/
ret = asprintf(&name, "wgrp_%" PRIu64 "-%u", id, i);
if (ret == -1) goto naming_error;
result->threads[i] = g_thread_new(name, (GThreadFunc)g_work_group_process, result);
free(name);
if (!result->threads[i])
goto start_error;
}
naming_error:
start_error:
result->threads_count = i;
assert(i > 0);
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 : - *
* *
******************************************************************************/
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 : - *
* *
******************************************************************************/
void g_work_group_schedule(GWorkGroup *group, GGenericWork *work)
{
g_mutex_lock(&group->mutex);
g_atomic_int_inc(&group->pending);
ref_object(work);
g_generic_work_add_to_list(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)
{
GGenericWork *work; /* Traitement à mener */
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;
g_generic_work_remove_from_list(work, &group->works);
g_mutex_unlock(&group->mutex);
g_generic_work_process(work);
unref_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 : - *
* *
******************************************************************************/
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. *
* *
* Description : Attend que toutes les tâches d'un groupe soient traitées. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_work_group_wait_for_completion(GWorkGroup *group)
{
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)
&& !group->force_exit)
{
g_cond_wait(&group->wait_cond, &group->mutex);
}
g_mutex_unlock(&group->mutex);
}
/******************************************************************************
* *
* Paramètres : group = groupe dont les conclusions sont attendues. *
* 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_group_wait_timed_for_completion(GWorkGroup *group, gint64 rel)
{
bool result; /* Bilan d'attente à renvoyer */
gint64 end_time; /* Borne de fin de l'attente */
result = true;
g_mutex_lock(&group->mutex);
end_time = g_get_monotonic_time() + rel;
/**
* 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)
&& !group->force_exit)
{
result = g_cond_wait_until(&group->wait_cond, &group->mutex, end_time);
if (!result) break;
}
g_mutex_unlock(&group->mutex);
return result;
}
/******************************************************************************
* *
* Paramètres : group = groupes de travail à manipuler. *
* *
* Description : Force un réveil d'une attente en cours pour la confirmer. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
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);
}