/* Chrysalide - Outil d'analyse de fichiers binaires * workqueue.c - prototypes pour l'équivalent Python du fichier "glibext/workqueue.c" * * Copyright (C) 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "workqueue.h" #include #include #include #include "work.h" #include "../access.h" #include "../helpers.h" /* ------------------------ GLUE POUR CREATION DEPUIS PYTHON ------------------------ */ CREATE_DYN_CONSTRUCTOR(work_queue, G_TYPE_WORK_QUEUE); /* Initialise une instance sur la base du dérivé de GObject. */ static int py_work_queue_init(PyObject *, PyObject *, PyObject *); /* ------------------------- CONNEXION AVEC L'API DE PYTHON ------------------------- */ /* Constitue un nouveau groupe de travail. */ static PyObject *py_work_queue_define_group(PyObject *, PyObject *); /* Dissout un groupe de travail existant. */ static PyObject *py_work_queue_delete_group(PyObject *, PyObject *); /* Place une nouvelle tâche en attente. */ static PyObject *py_work_queue_schedule(PyObject *, PyObject *); /* Détermine si un groupe est vide de toute programmation. */ static PyObject *py_work_queue_is_empty(PyObject *, PyObject *); /* Attend que toutes les tâches d'un groupe soient traitées. */ static PyObject *py_work_queue_wait_for_completion(PyObject *, PyObject *); /* Force un réveil d'une attente en cours pour la confirmer. */ static PyObject *py_work_queue_wake_up_waiters(PyObject *, PyObject *); /* ---------------------------------------------------------------------------------- */ /* GLUE POUR CREATION DEPUIS PYTHON */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : self = objet à initialiser (théoriquement). * * args = arguments fournis à l'appel. * * kwds = arguments de type key=val fournis. * * * * Description : Initialise une instance sur la base du dérivé de GObject. * * * * Retour : 0. * * * * Remarques : - * * * ******************************************************************************/ static int py_work_queue_init(PyObject *self, PyObject *args, PyObject *kwds) { int ret; /* Bilan de lecture des args. */ #define WORK_QUEUE_DOC \ "WorkQueue defines a basic work aimed to get processed in a" \ " thread setup by a pychrysalide.glibext.WorkQueue instance.\n" \ "\n" \ "Instances can be created using the following constructor:\n" \ "\n" \ " WorkQueue()" /* Initialisation d'un objet GLib */ ret = forward_pygobjet_init(self); if (ret == -1) return -1; return 0; } /* ---------------------------------------------------------------------------------- */ /* CONNEXION AVEC L'API DE PYTHON */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : self = élément d'appel à consulter. * * args = arguments fournis pour l'opération. * * * * Description : Constitue un nouveau groupe de travail. * * * * Retour : Nouvel identifiant unique d'un nouveau groupe de travail. * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_work_queue_define_group(PyObject *self, PyObject *args) { PyObject *result; /* Bilan à retourner */ unsigned int count; /* Nombre de threads à activer */ int ret; /* Bilan de lecture des args. */ GWorkQueue *queue; /* Version native */ wgroup_id_t id; /* Identifiant de groupe créé */ #define WORK_QUEUE_DEFINE_GROUP_METHOD PYTHON_METHOD_DEF \ ( \ define_group, "$self, /, count=0", \ METH_VARARGS, py_work_queue, \ "Create a new work group for a work queue.\n" \ "\n" \ "The *count* argument states the number of threads which will" \ " be activated for the processing. If the provided value is" \ " zero, then a default value based on the current hardware is" \ " selected.\n" \ "\n" \ "The result is a work group identifier." \ ) count = 0; ret = PyArg_ParseTuple(args, "|I", &count); if (!ret) return NULL; queue = G_WORK_QUEUE(pygobject_get(self)); id = g_work_queue_define_group(queue, count); result = PyLong_FromUnsignedLong(id); return result; } /****************************************************************************** * * * Paramètres : self = élément d'appel à consulter. * * args = arguments fournis pour l'opération. * * * * Description : Dissout un groupe de travail existant. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_work_queue_delete_group(PyObject *self, PyObject *args) { PyObject *result; /* Bilan à retourner */ unsigned long long id; /* Identifiant de groupe ciblé */ int ret; /* Bilan de lecture des args. */ GWorkQueue *queue; /* Version native */ #define WORK_QUEUE_DELETE_GROUP_METHOD PYTHON_METHOD_DEF \ ( \ delete_group, "$self, id", \ METH_VARARGS, py_work_queue, \ "Destroy a work group previously registered inside a queue.\n" \ "\n" \ "The *id* argument refers to a work group identifier. It" \ " should have been provided by a previous call to" \ " pychrysalide.glibext.WorkQueue.g_work_queue_define_group()." \ ) ret = PyArg_ParseTuple(args, "K", &id); if (!ret) return NULL; queue = G_WORK_QUEUE(pygobject_get(self)); g_work_queue_delete_group(queue, id); result = Py_None; Py_INCREF(result); return result; } /****************************************************************************** * * * Paramètres : self = élément d'appel à consulter. * * args = arguments fournis pour l'opération. * * * * Description : Place une nouvelle tâche en attente. * * * * Retour : Bilan, qui correspond à l'existence du groupe ciblé. * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_work_queue_schedule(PyObject *self, PyObject *args) { PyObject *result; /* Bilan à retourner */ GGenericWork *work; /* Tâche à programmer */ unsigned long long id; /* Identifiant de groupe ciblé */ int ret; /* Bilan de lecture des args. */ GWorkQueue *queue; /* Version native */ bool status; /* Bilan de programmation */ #define WORK_QUEUE_SCHEDULE_METHOD PYTHON_METHOD_DEF \ ( \ schedule, "$self, work, id", \ METH_VARARGS, py_work_queue, \ "Schedule a work into the queue processing.\n" \ "\n" \ "The *work* argument has to point to a" \ " pychrysalide.glibext.GenericWork instance, and the *id*" \ " argument refers to a work group identifier which should have" \ " been provided by a previous call to" \ " pychrysalide.glibext.WorkQueue.g_work_queue_define_group().\n"\ "\n" \ "The result is a boolean value: *True* in case of succes (does" \ " the provided identifier exist?), *False* otherwise." \ ) ret = PyArg_ParseTuple(args, "O&K", convert_to_generic_work, &work, &id); if (!ret) return NULL; queue = G_WORK_QUEUE(pygobject_get(self)); status = g_work_queue_schedule(queue, work, id); result = status ? Py_True : Py_False; Py_INCREF(result); return result; } /****************************************************************************** * * * Paramètres : self = élément d'appel à consulter. * * args = arguments fournis pour l'opération. * * * * Description : Détermine si un groupe est vide de toute programmation. * * * * Retour : Etat du groupe de travail. * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_work_queue_is_empty(PyObject *self, PyObject *args) { PyObject *result; /* Bilan à retourner */ unsigned long long id; /* Identifiant de groupe ciblé */ int ret; /* Bilan de lecture des args. */ GWorkQueue *queue; /* Version native */ bool status; /* Bilan de consultation */ #define WORK_QUEUE_IS_EMPTY_METHOD PYTHON_METHOD_DEF \ ( \ is_empty, "$self, id", \ METH_VARARGS, py_work_queue, \ "Signal if there is some remaining work to get processed by" \ " the queue or not.\n" \ "\n" \ "The *id* argument refers to a work group identifier. It" \ " should have been provided by a previous call to" \ " pychrysalide.glibext.WorkQueue.g_work_queue_define_group().\n"\ "\n" \ "The result is a boolean value: *True* if the group does not" \ " contain any works or does not exist, *False* otherwise." \ ) ret = PyArg_ParseTuple(args, "K", &id); if (!ret) return NULL; queue = G_WORK_QUEUE(pygobject_get(self)); status = g_work_queue_is_empty(queue, id); result = status ? Py_True : Py_False; Py_INCREF(result); return result; } /****************************************************************************** * * * Paramètres : self = élément d'appel à consulter. * * args = arguments fournis pour l'opération. * * * * Description : Attend que toutes les tâches d'un groupe soient traitées. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_work_queue_wait_for_completion(PyObject *self, PyObject *args) { PyObject *result; /* Bilan à retourner */ unsigned long long rel; /* Durée d'attente */ unsigned long long id; /* Identifiant de groupe ciblé */ int ret; /* Bilan de lecture des args. */ GWorkQueue *queue; /* Version native */ bool status; /* Bilan de l'attente menée */ /** * Python appelle cette fonction, lorsqu'elle est sollicitée dans des scripts, * via la fonction PyObject_Vectorcall(), qui obtient la structure de suivi * du thread courant avec _PyThreadState_GET(). * * Un commentaire associé à cette dernière fonction précise : * * The caller must hold the GIL. * * Comme le verrou GIL est posé par Python au moment de l'exécution de * py_work_queue_wait_for_completion(), aucune attente bloquante ne peut * alors être engagée ici : elle bloquerait la tâche _run() d'un travail * réalisé en Python(), comme son déférencement via unref_ojbect() qui * implique une exécution de la fonction pyg_toggle_notify() appelant * PyGILState_Ensure() dans python3-gi. * * Aussi une attente bornée dans le temps est la seule option possible * ici. */ #define WORK_QUEUE_WAIT_FOR_COMPLETION_METHOD PYTHON_METHOD_DEF \ ( \ wait_for_completion, "$self, id, /, rel=100000", \ METH_VARARGS, py_work_queue, \ "Wait until all the works from a given group have been" \ " processed.\n" \ "\n" \ "The *id* argument refers to a work group identifier. It" \ " should have been provided by a previous call to" \ " pychrysalide.glibext.WorkQueue.g_work_queue_define_group()." \ " The *rel* range provide a relative time to wait (100ms by" \ " default).\n" \ "\n" \ "The returned value is a boolean status: *False* on a timeout," \ " *True* otherwise." \ "\n" \ "_Important note:_\n" \ "As this function is called by Python with its Global" \ " Interpreter Lock (GIL) held, an endless wait is impossible" \ " because it would keep the GIL unreleased whereas others" \ " threads (including those which may end the wait) may need to" \ " acquire it.\n" \ "\n" \ "So an unoptimized timed wait is performed here instead, and" \ " the returned status has to be checked by the caller in order" \ " to schedule another wait if needed." \ ) rel = 100 * G_TIME_SPAN_MILLISECOND; ret = PyArg_ParseTuple(args, "K|K", &id, &rel); if (!ret) return NULL; queue = G_WORK_QUEUE(pygobject_get(self)); status = g_work_queue_wait_timed_for_completion(queue, id, rel); result = status ? Py_True : Py_False; Py_INCREF(result); return result; } /****************************************************************************** * * * Paramètres : self = élément d'appel à consulter. * * args = arguments fournis pour l'opération. * * * * Description : Force un réveil d'une attente en cours pour la confirmer. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_work_queue_wake_up_waiters(PyObject *self, PyObject *args) { PyObject *result; /* Bilan à retourner */ unsigned long long id; /* Identifiant de groupe ciblé */ int ret; /* Bilan de lecture des args. */ GWorkQueue *queue; /* Version native */ #define WORK_QUEUE_WAKE_UP_WAITERS_METHOD PYTHON_METHOD_DEF \ ( \ wake_up_waiters, "$self, id", \ METH_VARARGS, py_work_queue, \ "Force a wake up of all threads waiting for processing a given" \ " queue work group.\n" \ "\n" \ "The *id* argument refers to a work group identifier. It" \ " should have been provided by a previous call to" \ " pychrysalide.glibext.WorkQueue.g_work_queue_define_group()." \ ) ret = PyArg_ParseTuple(args, "K", &id); if (!ret) return NULL; queue = G_WORK_QUEUE(pygobject_get(self)); g_work_queue_wake_up_waiters(queue, id); result = Py_None; Py_INCREF(result); return result; } /****************************************************************************** * * * Paramètres : - * * * * Description : Fournit un accès à une définition de type à diffuser. * * * * Retour : Définition d'objet pour Python. * * * * Remarques : - * * * ******************************************************************************/ PyTypeObject *get_python_work_queue_type(void) { static PyMethodDef py_work_queue_methods[] = { WORK_QUEUE_DEFINE_GROUP_METHOD, WORK_QUEUE_DELETE_GROUP_METHOD, WORK_QUEUE_SCHEDULE_METHOD, WORK_QUEUE_IS_EMPTY_METHOD, WORK_QUEUE_WAIT_FOR_COMPLETION_METHOD, WORK_QUEUE_WAKE_UP_WAITERS_METHOD, { NULL } }; static PyGetSetDef py_work_queue_getseters[] = { { NULL } }; static PyTypeObject py_work_queue_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "pychrysalide.glibext.WorkQueue", .tp_basicsize = sizeof(PyGObject), .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = WORK_QUEUE_DOC, .tp_methods = py_work_queue_methods, .tp_getset = py_work_queue_getseters, .tp_init = py_work_queue_init, .tp_new = py_work_queue_new, }; return &py_work_queue_type; } /****************************************************************************** * * * Paramètres : module = module dont la définition est à compléter. * * * * Description : Prend en charge l'objet 'pychrysalide.glibext.WorkQueue'. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool ensure_python_work_queue_is_registered(void) { PyTypeObject *type; /* Type Python 'WorkQueue' */ PyObject *module; /* Module à recompléter */ PyObject *dict; /* Dictionnaire du module */ type = get_python_work_queue_type(); if (!PyType_HasFeature(type, Py_TPFLAGS_READY)) { module = get_access_to_python_module("pychrysalide.glibext"); dict = PyModule_GetDict(module); if (!register_class_for_pygobject(dict, G_TYPE_WORK_QUEUE, type)) return false; } return true; } /****************************************************************************** * * * Paramètres : arg = argument quelconque à tenter de convertir. * * dst = destination des valeurs récupérées en cas de succès. * * * * Description : Tente de convertir en gestionnaire de travaux différés. * * * * Retour : Bilan de l'opération, voire indications supplémentaires. * * * * Remarques : - * * * ******************************************************************************/ int convert_to_work_queue(PyObject *arg, void *dst) { int result; /* Bilan à retourner */ result = PyObject_IsInstance(arg, (PyObject *)get_python_work_queue_type()); switch (result) { case -1: /* L'exception est déjà fixée par Python */ result = 0; break; case 0: PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to work queue"); break; case 1: *((GWorkQueue **)dst) = G_WORK_QUEUE(pygobject_get(arg)); break; default: assert(false); break; } return result; }