Capítulo 48. Processos trabalhadores em segundo plano

O PostgreSQL pode ser estendido para executar código fornecido pelo usuário em processos separados. Esses processos são iniciados, parados e monitorados pelo postgres, o que permite que tenham uma vida útil intimamente ligada ao estado do servidor. Esses processos têm a opção de se conectar à área de memória compartilhada do PostgreSQL, e de se conectar a bancos de dados internamente; eles também podem executar múltiplas transações em série, da mesma forma como um processo servidor normal conectado ao cliente faria. Além disso, vinculando-se à libpq eles podem se conectar ao servidor e se comportar como uma aplicação cliente normal.

Atenção

Existem riscos consideráveis de robustez e segurança no uso de processos trabalhadores em segundo plano, porque sendo escritos na linguagem C, têm acesso irrestrito aos dados. Os administradores que desejem ativar módulos que incluam processos trabalhadores em segundo plano devem ter extremo cuidado. Somente módulos cuidadosamente auditados devem ter permissão para executar processos trabalhadores em segundo plano.

Os processos trabalhadores em segundo plano podem ser iniciados no momento em que o PostgreSQL é iniciado, incluindo o nome do módulo em shared_preload_libraries. Um módulo que deseja executar um processo trabalhador em segundo plano pode registrá-lo chamando RegisterBackgroundWorker(​BackgroundWorker *worker) em sua função _PG_init(). Os processos trabalhadores em segundo plano também podem ser iniciados depois que o sistema estiver carregado e funcionando, chamando RegisterDynamicBackgroundWorker(​BackgroundWorker *worker, BackgroundWorkerHandle **handle). Ao contrário de RegisterBackgroundWorker, que só pode ser chamado de dentro do processo postmaster, RegisterDynamicBackgroundWorker deve ser chamado de um processo servidor regular, ou de outro processo trabalhador em segundo plano.

A estrutura BackgroundWorker é definida assim:

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;       /* em segundos, ou BGW_NEVER_RESTART */
    char        bgw_library_name[BGW_MAXLEN];
    char        bgw_function_name[BGW_MAXLEN];
    Datum       bgw_main_arg;
    char        bgw_extra[BGW_EXTRALEN];
    int         bgw_notify_pid;
} BackgroundWorker;

Os membros bgw_name e bgw_type são cadeias de caracteres a serem usadas em mensagens de registro de eventos (log), listagens de processos e contextos semelhantes. O membro bgw_type deve ser igual para todos os processos trabalhadores em segundo plano do mesmo tipo, para ser possível agrupar esses processos trabalhadores em uma listagem de processos, por exemplo. Por outro lado, o membro bgw_name pode conter informações adicionais sobre o processo específico. (Normalmente, a cadeia de caracteres para bgw_name irá conter o tipo de alguma forma, mas isso não é estritamente necessário.)

O membro bgw_flags é uma máscara bit-a-bit que indica os recursos que o módulo deseja usar. Os valores possíveis são:

BGWORKER_SHMEM_ACCESS

Requer acesso à memória compartilhada. Os processos trabalhadores sem acesso à memória compartilhada não podem acessar nenhuma estrutura de dados compartilhada do PostgreSQL, como bloqueios pesados ou leves, buffers compartilhados, ou quaisquer estruturas de dados personalizadas que o próprio processo trabalhador queira criar e usar.

BGWORKER_BACKEND_DATABASE_CONNECTION

Requer a capacidade de estabelecer uma conexão com o banco de dados, por meio da qual poderá executar transações e consultas posteriormente. Um processo trabalhador em segundo plano usando BGWORKER_BACKEND_DATABASE_​CONNECTION para se conectar a um banco de dados também deve anexar memória compartilhada usando BGWORKER_SHMEM_ACCESS, ou a ativação do processo trabalhador irá falhar.

O membro bgw_start_time é o estado do servidor durante o qual o postgres deve iniciar o processo; pode ser um entre BgWorkerStart_PostmasterStart (inicia assim que o próprio postgres tiver terminado sua própria ativação; processos que solicitam isso não são elegíveis para conexões com banco de dados), BgWorkerStart_ConsistentState (inicia assim que for alcançado um estado consistente em um servidor em-espera ativa (hot standby), permitindo que os processos se conectem aos bancos de dados e executem consultas de leitura-apenas), e BgWorkerStart_RecoveryFinished (inicia assim que o sistema entra no estado normal de leitura e escrita). Note que os dois últimos valores são equivalentes em um servidor que não esteja em-espera ativa. Note, também, que essa configuração indica apenas quando os processos serão iniciados; eles não param quando é alcançado um estado diferente.

O membro bgw_restart_time é o intervalo, em segundos, que o postgres deve esperar antes de reiniciar o processo caso ele trave. Pode ser qualquer valor positivo, ou BGW_NEVER_RESTART, indicando não reiniciar o processo em caso de travamento.

O membro bgw_library_name é o nome de uma biblioteca na qual o ponto de entrada inicial para o processo trabalhador em segundo plano deve ser procurado. A biblioteca indicada será carregada dinamicamente pelo processo trabalhador, e será usado bgw_function_name para identificar a função a ser chamada. Se a função estiver sendo carregada a partir do código principal, deverá ser definido como postgres.

O membro bgw_function_name é o nome de uma função em uma biblioteca carregada dinamicamente que deve ser usada como ponto de entrada inicial para um novo processo trabalhador em segundo plano.

O membro bgw_main_arg é o argumento Datum para a função principal do processo trabalhador em segundo plano. Essa função principal deve receber um único argumento do tipo Datum e retornar void. O membro bgw_main_arg será passado como argumento. Além disso, a variável global MyBgworkerEntry aponta para uma cópia da estrutura BackgroundWorker passada no momento do registro; o processo trabalhador pode achar útil examinar essa estrutura.

No Windows (e em qualquer outro lugar onde EXEC_BACKEND esteja definido), ou em processos trabalhadores dinâmicos em segundo plano, não é seguro passar Datum por referência, apenas por valor. Se for requerido um argumento, será mais seguro passar um int32, ou outro valor pequeno, e usá-lo como índice em uma matriz alocada na memória compartilhada. Se for passado um valor como cstring ou text, o ponteiro não será válido no novo processo trabalhador em segundo plano.

O membro bgw_extra pode conter dados extras a serem passados para o processo trabalhador em segundo plano. Diferentemente de bgw_main_arg, esses dados não são passados como argumento para a função principal do processo trabalhador, mas podem ser acessados via MyBgworkerEntry, conforme discutido acima.

O membro bgw_notify_pid é o PID do processo servidor do PostgreSQL para o qual o postmaster deve enviar SIGUSR1 quando o processo for iniciado ou encerrado. Deve ser 0 para processos trabalhadores registrados no momento da ativação do postmaster, ou quando o processo servidor que registra o processo trabalhador não deseje esperar a ativação do processo trabalhador. Caso contrário, deve ser iniciado como MyProcPid.

Uma vez em execução, o processo pode se conectar a um banco de dados chamando BackgroundWorkerInitializeConnection(​char *dbname, char *username, uint32 flags), ou BackgroundWorkerInitializeConnection​ByOid(​Oid dboid, Oid useroid, uint32 flags). Isso permite que o processo execute transações e consultas usando a interface SPI. Se dbname for NULL ou dboid for InvalidOid, a sessão não estará conectada a nenhum banco de dados específico, mas os catálogos compartilhados poderão ser acessados. Se username for NULL, ou useroid for InvalidOid, o processo será executado como o superusuário criado durante initdb. Se BGWORKER_BYPASS_ALLOWCONN for especificado como flags, será possível contornar a restrição de conexão com bancos de dados que não permitem conexões de usuários. Um processo trabalhador em segundo plano só pode chamar uma dessas duas funções, e apenas uma vez. Não é possível mudar de banco de dados.

Os sinais são inicialmente bloqueados quando o controle atinge a função principal do processo trabalhador em segundo plano, devendo ser desbloqueados por ele; isso permite que o processo personalize seus tratadores de sinal, se necessário. Os sinais podem ser desbloqueados no novo processo chamando BackgroundWorkerUnblockSignals, e bloqueados chamando BackgroundWorkerBlockSignals.

Se bgw_restart_time para um processo trabalhador em segundo plano estiver configurado como BGW_NEVER_RESTART, ou se sair com um código de saída 0, ou for encerrado por TerminateBackgroundWorker, seu registro será automaticamente cancelado pelo postmaster ao sair. Caso contrário, será reiniciado após o período configurado via bgw_restart_time, ou imediatamente se o postmaster reiniciar a instância devido a uma falha de processo servidor. Os processos servidores que precisem suspender a execução apenas temporariamente devem usar uma dormida interrompível em vez de sair; isso pode ser conseguido chamando WaitLatch(). Certifique-se de que o sinalizador WL_POSTMASTER_DEATH esteja definido ao chamar essa função, e verifique o código de retorno para uma saída imediata no caso de emergência em que o próprio postgres tenha encerrado.

Quando um processo trabalhador em segundo plano é registrado usando a função RegisterDynamicBackgroundWorker, é possível que o processo servidor que realiza o registro obtenha informações sobre o status do processo trabalhador. Os processos servidores que desejam fazer isso devem passar o endereço de BackgroundWorkerHandle * como o segundo argumento para RegisterDynamicBackgroundWorker. Se o processo trabalhador for registrado com sucesso, esse ponteiro será inicializado com um identificador opaco que poderá posteriormente ser passado para GetBackgroundWorkerPid(​BackgroundWorkerHandle *, pid_t *) ou TerminateBackgroundWorker(​BackgroundWorkerHandle *). A função GetBackgroundWorkerPid pode ser usada para pesquisar o status do processo trabalhador: o valor retornado BGWH_NOT_YET_STARTED indica que o processo trabalhador ainda não foi iniciado pelo postmaster; BGWH_STOPPED indica que foi iniciado, mas não está mais em execução; e BGWH_STARTED indica que está em execução. Nesse último caso, também será retornado o PID através do segundo argumento. A função TerminateBackgroundWorker faz com que o postmaster envie SIGTERM para o processo trabalhador se ele estiver em execução, e cancele o registro assim que não estiver mais.

Em alguns casos, um processo que registra um processo trabalhador em segundo plano pode desejar aguardar a ativação do processo trabalhador. Isso pode ser feito inicializando bgw_notify_pid como MyProcPid, e depois passar o BackgroundWorkerHandle * obtido no momento do registro para a função WaitForBackgroundWorkerStartup(​BackgroundWorkerHandle *handle, pid_t *). Essa função ficará bloqueada até que o postmaster tente iniciar o processo trabalhador em segundo plano, ou até que o postmaster deixe de existir. Se o processo trabalhador em segundo plano estiver em execução, o valor retornado será BGWH_STARTED e o PID será escrito no endereço fornecido. Caso contrário, o valor retornado será BGWH_STOPPED ou BGWH_POSTMASTER_DIED.

Um processo também pode aguardar o encerramento de um processo trabalhador em segundo plano, usando a função WaitForBackgroundWorkerShutdown(​BackgroundWorkerHandle *handle) e passando o BackgroundWorkerHandle * obtido no registro. Essa função ficará será bloqueada até que o processo trabalhador em segundo plano termine, ou o postmaster não exista mais. Quando o processo trabalhador em segundo plano encerra, o valor retornado é BGWH_STOPPED, mas se o postmaster encerrar será retornado BGWH_POSTMASTER_DIED.

Os processos trabalhadores em segundo plano podem enviar mensagens de notificação assíncronas, usando o comando NOTIFY via SPI, ou diretamente via Async_Notify(). Essas notificações serão enviadas na efetivação da transação. Os processos trabalhadores em segundo plano não devem se registrar para receber notificações assíncronas com o comando LISTEN, porque não há infraestrutura para um processo trabalhador consumir essas notificações.

O módulo src/test/modules/worker_spi contém um exemplo funcional, que demonstra algumas técnicas úteis.

O número máximo de processos trabalhadores em segundo plano registrados é limitado por max_worker_processes.

Contato

CSS válido!