PostgreSQL puede extenderse para ejecutar código suministrado por el usuario en procesos
independientes. Dichos procesos son iniciados, detenidos y supervisados por postgres,
lo que les permite tener un ciclo de vida estrechamente vinculado al estado del servidor.
Estos procesos están conectados al área de memoria compartida de PostgreSQL
y tienen la opción de conectarse a las bases de datos internamente; también pueden ejecutar
múltiples transacciones de forma secuencial, al igual que un proceso de servidor normal
conectado a un cliente. Además, al enlazarse con libpq, pueden conectarse al
servidor y comportarse como una aplicación cliente normal.
Existen riesgos considerables de robustez y seguridad al utilizar procesos de
soporte en segundo plano porque, al estar escritos en el lenguaje C,
tienen acceso ilimitado a los datos. Los administradores que deseen habilitar
módulos que incluyan procesos de soporte en segundo plano deben extremar las
precauciones. Solo se debe permitir la ejecución de procesos en segundo plano a
módulos cuidadosamente auditados.
Los procesos en segundo plano pueden inicializarse en el momento en que
se inicia PostgreSQL incluyendo el nombre del módulo en
shared_preload_libraries. Un módulo que desee ejecutar un proceso en segundo
plano puede registrarlo llamando a
RegisterBackgroundWorker(
desde su función BackgroundWorker
*worker)_PG_init().
Los procesos en segundo plano también se pueden iniciar
después de que el sistema esté funcionando llamando a
RegisterDynamicBackgroundWorker(. A diferencia de
BackgroundWorker
*worker, BackgroundWorkerHandle
**handle)RegisterBackgroundWorker, que solo se puede llamar desde
dentro del proceso postmaster,
RegisterDynamicBackgroundWorker debe llamarse
desde un backend normal u otro proceso en segundo plano.
La estructura BackgroundWorker se define así:
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[MAXPGPATH];
char bgw_function_name[BGW_MAXLEN];
Datum bgw_main_arg;
char bgw_extra[BGW_EXTRALEN];
pid_t bgw_notify_pid;
} BackgroundWorker;
bgw_name y bgw_type son
cadenas que se utilizarán en los mensajes de log, listados de procesos y contextos similares.
bgw_type debería ser el mismo para todos los procesos en segundo
plano del mismo tipo, de modo que sea posible agrupar dichos procesos en un
listado de procesos, por ejemplo. bgw_name, por otro
lado, puede contener información adicional sobre el proceso específico.
(Típicamente, la cadena para bgw_name contendrá
el tipo de alguna manera, pero esto no es estrictamente obligatorio).
bgw_flags es una máscara de bits (con operación OR a nivel
de bits) que indica las capacidades que requiere el módulo. Los valores posibles son:
BGWORKER_SHMEM_ACCESSSolicita acceso a la memoria compartida. Este flag es obligatorio.
BGWORKER_BACKEND_DATABASE_CONNECTION
Solicita la capacidad de establecer una conexión a la base de datos a través de la cual pueda
ejecutar posteriormente transacciones y consultas. Un proceso en segundo plano que utilice
BGWORKER_BACKEND_DATABASE_CONNECTION para conectarse a una
base de datos también debe conectar la memoria compartida mediante
BGWORKER_SHMEM_ACCESS, o de lo contrario el inicio del proceso fallará.
bgw_start_time es el estado del servidor durante el cual
postgres debe iniciar el proceso; puede ser uno de
BgWorkerStart_PostmasterStart (iniciar tan pronto como
postgres haya terminado su propia inicialización; los procesos que
solicitan esto no son elegibles para conexiones a bases de datos),
BgWorkerStart_ConsistentState (iniciar tan pronto como se haya alcanzado
un estado consistente en un hot standby, permitiendo que los procesos se conecten a las
bases de datos y ejecuten consultas de solo lectura), y
BgWorkerStart_RecoveryFinished (iniciar tan pronto como el sistema haya
entrado en el estado normal de lectura y escritura). Ten en cuenta que los dos últimos valores
son equivalentes en un servidor que no es un hot standby. Ten en cuenta que esta configuración
solo indica cuándo deben iniciarse los procesos; no se detienen cuando se alcanza un estado
diferente.
bgw_restart_time es el intervalo, en segundos, que
postgres debe esperar antes de reiniciar el proceso en
caso de que se caiga. Puede ser cualquier valor positivo,
o BGW_NEVER_RESTART, lo que indica que no se debe reiniciar el
proceso en caso de caída.
bgw_library_name es el nombre de la biblioteca en
la que se debe buscar el punto de entrada inicial para el proceso en segundo plano.
La biblioteca especificada será cargada dinámicamente por el proceso y
bgw_function_name se utilizará para identificar la
función a llamar. Si se llama a una función en el código del núcleo (core), debe
establecerse en "postgres".
bgw_function_name es el nombre de la función
que se utilizará como punto de entrada inicial para el nuevo proceso en segundo plano. Si
esta función está en una biblioteca cargada dinámicamente, debe estar marcada como
PGDLLEXPORT (y no como static).
bgw_main_arg es el argumento Datum
para la función principal del proceso en segundo plano. Esta función principal debe tomar un
único argumento de tipo Datum y devolver void.
Se pasará bgw_main_arg como argumento.
Además, la variable global MyBgworkerEntry
apunta a una copia de la estructura BackgroundWorker
pasada en el momento del registro; al proceso puede resultarle útil examinar
esta estructura.
En Windows (y en cualquier otro lugar donde esté definido EXEC_BACKEND)
o en procesos en segundo plano dinámicos, no es seguro pasar un
Datum por referencia, solo por valor. Si se requiere un argumento, lo más
seguro es pasar un int32 u otro valor pequeño y usarlo como índice en un array asignado
en la memoria compartida. Si se pasa un valor como cstring
o text, el puntero no será válido desde el nuevo proceso en segundo plano.
bgw_extra puede contener datos adicionales para pasar
al proceso en segundo plano. A diferencia de bgw_main_arg, estos datos
no se pasan como argumento a la función principal del proceso, pero se puede
acceder a ellos a través de MyBgworkerEntry, como se mencionó anteriormente.
bgw_notify_pid es el PID de un proceso backend de PostgreSQL
al que el postmaster debe enviar SIGUSR1 cuando el proceso se inicia o
finaliza. Debe ser 0 para los procesos registrados en el momento del inicio del postmaster,
o cuando el backend que registra el proceso no desea esperar a que este se inicie.
De lo contrario, debe inicializarse con MyProcPid.
Una vez en funcionamiento, el proceso puede conectarse a una base de datos llamando a
BackgroundWorkerInitializeConnection( o
char *dbname, char *username, uint32 flags)BackgroundWorkerInitializeConnectionByOid(.
Esto permite al proceso ejecutar transacciones y consultas utilizando la
interfaz Oid dboid, Oid useroid, uint32 flags)SPI. Si dbname es NULL o
dboid es InvalidOid, la sesión no se conecta
a ninguna base de datos en particular, pero se puede acceder a los catálogos compartidos.
Si username es NULL o useroid es
InvalidOid, el proceso se ejecutará como el superusuario creado
durante initdb. Si se especifica BGWORKER_BYPASS_ALLOWCONN
como flags, es posible omitir la restricción de conectarse a bases de datos
que no permiten conexiones de usuario. Si se especifica BGWORKER_BYPASS_ROLELOGINCHECK
como flags, es posible omitir la comprobación de inicio de sesión para el
rol utilizado para conectarse a las bases de datos.
Un proceso en segundo plano solo puede llamar a una de estas dos funciones, y solo
una vez. No es posible cambiar de base de datos.
Los procesos bloquean inicialmente las señales cuando el control llega a la
función principal del proceso en segundo plano, y deben ser desbloqueadas por esta; esto es para
permitir que el proceso personalice sus manejadores de señales, si es necesario.
Las señales se pueden desbloquear en el nuevo proceso llamando a
BackgroundWorkerUnblockSignals y bloquear llamando a
BackgroundWorkerBlockSignals.
Si el parámetro bgw_restart_time para un proceso en segundo plano
está configurado como BGW_NEVER_RESTART, o si finaliza con un código
de salida de 0 o es terminado por TerminateBackgroundWorker,
el postmaster lo desregistrará automáticamente al salir.
De lo contrario, se reiniciará después del período de tiempo configurado a través de
bgw_restart_time, o inmediatamente si el postmaster
reinicializa el clúster debido a un fallo del backend. Los backends que necesiten
suspender la ejecución solo temporalmente deben usar una espera interrumpible en
lugar de salir; esto se puede lograr llamando a
WaitLatch(). Asegúrate de que el flag
WL_POSTMASTER_DEATH esté establecido al llamar a esa función, y
verifica el código de retorno para una salida rápida en el caso de emergencia de que
postgres mismo haya terminado.
Cuando se registra un proceso en segundo plano utilizando la función
RegisterDynamicBackgroundWorker, es posible que el backend que
realiza el registro obtenga información sobre el estado del proceso. Los backends que
deseen hacer esto deben pasar la dirección de un BackgroundWorkerHandle * como
segundo argumento a RegisterDynamicBackgroundWorker. Si el
proceso se registra con éxito, este puntero se inicializará con un manejador opaco que
posteriormente se puede pasar a
GetBackgroundWorkerPid( o a
BackgroundWorkerHandle *, pid_t *)TerminateBackgroundWorker(.
BackgroundWorkerHandle *)GetBackgroundWorkerPid se puede utilizar para sondear el estado del
proceso: un valor de retorno de BGWH_NOT_YET_STARTED indica que
el proceso aún no ha sido iniciado por el postmaster;
BGWH_STOPPED indica que se ha iniciado pero ya no está en ejecución;
y BGWH_STARTED indica que se está ejecutando actualmente. En este
último caso, el PID también se devolverá a través del segundo argumento.
TerminateBackgroundWorker hace que el postmaster envíe
SIGTERM al proceso si se está ejecutando, y que lo desregistre
tan pronto como deje de hacerlo.
En algunos casos, un proceso que registra un proceso en segundo plano puede desear esperar
a que este se inicie. Esto se puede lograr inicializando
bgw_notify_pid con MyProcPid y
luego pasando el BackgroundWorkerHandle * obtenido en el
momento del registro a la función
WaitForBackgroundWorkerStartup(.
Esta función se bloqueará hasta que el postmaster haya intentado iniciar el
proceso en segundo plano, o hasta que el postmaster muera. Si el proceso en segundo plano
se está ejecutando, el valor de retorno será BackgroundWorkerHandle
*handle, pid_t *)BGWH_STARTED, y
el PID se escribirá en la dirección proporcionada. De lo contrario, el valor de retorno
será BGWH_STOPPED o
BGWH_POSTMASTER_DIED.
Un proceso también puede esperar a que un proceso en segundo plano se apague, utilizando la
función WaitForBackgroundWorkerShutdown( y pasando el
BackgroundWorkerHandle
*handle)BackgroundWorkerHandle * obtenido al registrarse. Esta
función se bloqueará hasta que el proceso en segundo plano salga o el postmaster muera.
Cuando el proceso en segundo plano sale, el valor de retorno es
BGWH_STOPPED, si el postmaster muere devolverá
BGWH_POSTMASTER_DIED.
Los procesos en segundo plano pueden enviar mensajes de notificación asíncronos, ya sea
utilizando el comando NOTIFY a través de SPI,
o directamente a través de Async_Notify(). Dichas notificaciones
se enviarán al confirmar la transacción.
Los procesos en segundo plano no deben registrarse para recibir notificaciones asíncronas
con el comando LISTEN, ya que no existe infraestructura para que un
proceso consuma dichas notificaciones.
El módulo src/test/modules/worker_spi
contiene un ejemplo funcional que demuestra algunas técnicas útiles.
El número máximo de procesos en segundo plano registrados está limitado por max_worker_processes.