/* 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 : 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 */ 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 : 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_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); }