PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 16.4 » Programmation serveur » Processus en tâche de fond (background worker)

Chapitre 48. Processus en tâche de fond (background worker)

PostgreSQL peut être étendu pour lancer du code utilisateur dans des processus séparés. Ces processus sont démarrés, arrêtés et supervisés par postgres, ce qui leur permet d'avoir un cycle de vie étroitement lié au statut du serveur. Ces processus sont attachés à la zone de mémoire partagée de PostgreSQL et ont l'option de se connecter aux bases de manière interne ; ils peuvent également exécuter de multiples transactions séquentiellement, comme n'importe quel processus client standard connecté au serveur. De plus, en se liant avec la bibliothèque libpq, ils peuvent se connecter au serveur et se comporter comme une application cliente standard.

Avertissement

Il y a de considérables risques de robustesse et sécurité lorsque l'on utilise des processus background worker. En effet, ceux-ci étant écrit en langage C, ils ont un accès total aux données. Les administrateurs désirant activer des modules incluant des processus background worker devraient prendre énormément de précautions. Seuls les modules soigneusement testés devraient être autorisés à lancer des processus background worker.

Les processus en tâche de fond peuvent être initialisés au moment où PostgreSQL est démarré en incluant le nom du module dans shared_preload_libraries. Un module qui souhaite fonctionner comme un processus en tâche de fond peut s'enregistrer en appelant RegisterBackgroundWorker(BackgroundWorker *worker) dans son _PG_init(). Les processus en tâche de fond peuvent également être démarrés après que le système ait démarré et soit en fonctionnement en appelant la fonction RegisterDynamicBackgroundWorker( BackgroundWorker *worker, BackgroundWorkerHandle **handle). À la différence de RegisterBackgroundWorker, qui ne peut être appelée que depuis le processus postmaster,RegisterDynamicBackgroundWorker doit être appelée depuis un processus client standard ou un processus en tâche de fond.

La structure BackgroundWorker est définie ainsi :

typedef void (*bgworker_main_type)(Datum main_arg);
typedef struct BackgroundWorker
{
    char        bgw_name[BGW_MAXLEN];
    char        bgw_type[BGW_MAXLEN];
    int         bgw_flags;
    BgWorkerStartTime bgw_start_time;
    int         bgw_restart_time;       /* in seconds, or BGW_NEVER_RESTART */
    char        bgw_library_name[BGW_MAXLEN];
    char        bgw_function_name[BGW_MAXLEN];
    Datum       bgw_main_arg;
    char        bgw_extra[BGW_EXTRALEN];
    pid_t       bgw_notify_pid;
} BackgroundWorker;
  

bgw_name et bgw_type sont des chaînes de caractères à utiliser dans les messages de trace, liste de processus et autres listes similaires. bgw_type devrait être identique pour tous les processus en tâche de fond du même type pour qu'il soit possibl de grouper ces processus avec une liste des processus par exemple. Par contre, bgw_name peut contenir des informations supplémentaires sur ce processus spécifique. (Typiquement, la chaîne de bgw_name contiendra le type en quelque sort, mais ce n'est pas requis strictement.)

bgw_flags est un masque de bit OR indiquant les capacités que veut le module. Les valeurs possibles sont

BGWORKER_SHMEM_ACCESS

Réclame un accès à la mémoire partagée. C'est requis.

BGWORKER_BACKEND_DATABASE_CONNECTION

Réclame la capacité à établir une connexion à une base à partir de laquelle il peut ensuite exécuter des transactions et des requêtes. Un processus en tâche de fond utilisant BGWORKER_BACKEND_DATABASE_CONNECTION pour se connecter à une base doit aussi s'attacher à la mémoire partagée en utilisant BGWORKER_SHMEM_ACCESS. Dans le cas contraire, son démarrage échouera.

bgw_start_time spécifie l'état du serveur dans lequel postgres devrait démarrer le processus ; les valeurs possibles sont BgWorkerStart_PostmasterStart (démarrer dès que postgres lui-même a fini sa propre initialisation ; les processus réclamant celà ne sont pas éligibles à une connexion à la base de données), BgWorkerStart_ConsistentState (démarrer dès qu'un état cohérent a été atteint sur un serveur esclave en lecture seule, permettant aux processus de se connecter aux bases et d'exécuter des requêtes en lecture seule), et BgWorkerStart_RecoveryFinished (démarrer dès que le système est entré dans un état de lecture-écriture normal). Notez que les deux dernières valeurs sont équivalentes sur un serveur qui n'est pas un esclave en lecture seule. Notez également que ces valeurs indiquent uniquement quand les processus doivent être démarrés ; ils ne s'arrêtent pas quand un état différent est atteint.

bgw_restart_time est un intervalle, en secondes, que postgres doit attendre avant de redémarrer un processus, si celui-ci a subi un arrêt brutal. Cet intervalle peut être une valeur positive ou BGW_NEVER_RESTART, indiquant de ne pas redémarrer le processus suite à un arrêt brutal.

bgw_library_name est le nom d'une bibliothèque dans laquelle le point d'entrée initial pour le processus en tâche de fond devrait être recherché. La bibliothèque nommée sera chargée dynamiquement par le processus en tâche de fond et bgw_function_name sera utiliser pour identifier la fonction à appeler. S'il appelle une fonction du code du moteur, il faudrait plutôt le configurer à "postgres".

bgw_function_name est le nom de la fonction à utiliser comme point d'entrée vers le nouveau background worker. Si cette fonction est dans une bibliothèque chargée dynamiquement, elle doit être marquée PGDLLEXPORT (et non pas static).

bgw_main_arg est l'argument Datum de la fonction principale du processus. Cette fonction principale devrait prendre un seul argument de type Datum et renvoyer void. bgw_main_arg sera passé comme argument. De plus, la variable globale MyBgworkerEntry pointe vers une copie de la structure BackgroundWorker passé au moment de l'enregistrement ; le processus pourrait trouver utile d'examiner cette structure.

Sur Windows (et partout où EXEC_BACKEND est défini) ou dans des processus en tâche de fond dynamiques, il n'est pas sûr de passer un Datum par référence, il faut le passe par valeur. Si un argument est requis, il est plus sûr de passer un int32 ou toute autre petite valeur et l'utiliser comme un index d'un tableau alloué en mémoire partagée. Si une valeur comme un cstring ou un text est passée, alors le pointeur ne sera pas valide à partir du nouveau processus en tâche de fond.

bgw_extra peut contenir des données supplémentaires à fournir au background worker. Contrairement à bgw_main_arg, cette donnée n'est pas fourni comme argument de la fonction principale du processus. Elle est accessible via la variable MyBgworkerEntry, comme discuté ci-dessus.

bgw_notify_pid est le PID d'un processus client PostgreSQL auquel le postmaster devrait envoyer un signal SIGUSR1 quand le processus est démarré ou quitte. Il devrait valoir 0 pour les processus en tâche de fond enregistrés lors du démarrage du postmaster, ou quand le processus client enregistrant le processus en tâche de fond ne souhaite pas attendre que le processus en tâche de fond ne démarre. Sinon, il devrait être initialisé à MyProcPid.

Une fois démarré, le processus peut se connecter à une base en appelant BackgroundWorkerInitializeConnection(char *dbname, char *username, uint32 flags) ou BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, uint32 flags). Cela autorise le processus à exécuter des transactions et des requêtes en utilisant l'interface SPI. Si dbname vaut NULL ou que dboid vaut InvalidOid, la session n'est pas connectée à une base en particulier, mais les catalogues partagés peuvent être accédés. Si username vaut NULL ou que useroid vaut InvalidOid, le processus sera démarré avec le superutilisateur créé durant initdb. Si BGWORKER_BYPASS_ALLOWCONN est indiqué pour le paramètre flags, il est possible de contourner la restriction de se connecter aux bases de données ne permettant pas une connexions des utilisateurs. Un background worker ne peut être appelé que par une de ces deux fonctions, et seulement une fois. Il n'est pas possible de changer de base de données.

Les signaux sont initialement bloqués jusqu'à ce que le contrôle atteigne la fonction principale du background worker, et doivent être débloqués par elle ; cela permet une personnalisation des gestionnaires de signaux du processus, si nécessaire. Les signaux peuvent être débloqués dans le nouveau processus en appellant BackgroundWorkerUnblockSignals et bloqués en appelant BackgroundWorkerBlockSignals.

Si bgw_restart_time est configuré à BGW_NEVER_RESTART pour un processus en tâche de fond ou s'il quitte avec un code de sortie 0, ou encore s'il est terminé par TerminateBackgroundWorker, il sera automatiquement désenregistré par le postmaster lors de sa sortie. Sinon, il sera redémarré après que la période de temps configurée via bgw_restart_time, ou immédiatement si le postmaster réinitialise l'instance à cause d'une défaillance d'un processus client. Les processus en tâche de fond qui nécessitent de suspendre leur exécution seulement temporairement devraient utiliser un sommeil interruptible plutôt que de quitter. Vérifiez que le drapeau WL_POSTMASTER_DEATH est positionné lors de l'appel à cette fonction, et vérifiez le code retour pour une sortie rapide dans le cas d'urgence où postgres lui-même se termine.

Quand un processus en tâche de fond est enregistré en utilisant la fonction RegisterDynamicBackgroundWorker, le processus client effectuant cet enregistrement peut obtenir des informations concernant le statut du processus en tâche de fond. Les processus clients souhaitant faire cela devraient fournir l'adresse d'un BackgroundWorkerHandle * comme second argument pour RegisterDynamicBackgroundWorker. Si l'enregistrement du processus en tâche de fond est réussi, ce pointeur sera initialisé avec un handle opaque qui peut alors être fourni à GetBackgroundWorkerPid(BackgroundWorkerHandle *, pid_t *) ou TerminateBackgroundWorker(BackgroundWorkerHandle *). GetBackgroundWorkerPid peut être utilisé pour interroger le statut du processus en tâche de fond : une valeur de retour valant BGWH_NOT_YET_STARTED indique que le processus en tâche de fond n'a pas encore été démarré par le postmaster; BGWH_STOPPED indique qu'il a été démarré mais n'est plus en fonctionnement; et BGWH_STARTED indique qu'il est actuellement en fonctionnement. Dans le dernier cas, le PID sera également renvoyé via le deuxième argument. TerminateBackgroundWorker demande postmaster d'envoyer un signal SIGTERM au processus en tâche de fond s'il est en train de fonctionner, et de le désenregistrer dès qu'il ne sera plus en fonctionnement.

Dans certains cas, un processus qui enregistre un processus en tâche de fond peut souhaiter attendre le démarrage du processus en tâche de fond. Ceci peut être fait en initialisant bgw_notify_pid à MyProcPid et en fournissant ensuite le BackgroundWorkerHandle * obtenu au moment de l'enregistrement à la fonction WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *). Cette fonctionne bloquera jusqu'à ce que le postmaster ait tenté de démarrer le processus en tâche de fond, ou jusqu'à l'arrêt du postmaster. Si le processus en tâche de fond est en fonctionnement, la valeur retournée sera BGWH_STARTED, et le PID sera écrit à l'adresse fournie. Sinon, la valeur de retour sera BGWH_STOPPED ou BGWH_POSTMASTER_DIED.

Un processus peut aussi attendre l'arrêt d'un autre processus en tâche de fond, en utilisant la fonction WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle) et en passant le BackgroundWorkerHandle * obtenu à l'enregistrement. Cette fonction bloquera l'exécution jusqu'à l'arrêt de l'autre processus ou jusqu'à la mort de postmaster. Quand le processus en tâche de fond quitte, la valeur de retour est BGWH_STOPPED. Si postmaster meurt, il renverra BGWH_POSTMASTER_DIED.

Les processus en tâche de fond peuvent envoyer des messages de notificatio asynchrones, soit en utilisant la commande NOTIFY via SPI, soit directement avec Async_Notify (). De telles notifications seront envoyées au moment du commit. Les processus en tâche de fond ne doivent pas s'enregistrer pour recevoir des notifications asynchrones avec la commande LISTEN, car il n'existe pas d'infrastructure pour qu'un tel processus puisse consommer ce type de notifications.

Le module contrib src/test/modules/worker_spi contient un exemple fonctionnel, qui démontre quelques techniques utiles.

Le nombre maximum de processus en tâche de fond enregistré est limité par max_worker_processes.