PostgreSQL поддерживает расширенную возможность запускать пользовательский код в отдельных процессах. Такие процессы запускаются, останавливаются и контролируются главным процессом postgres, который позволяет тесно связать их жизненный цикл с состоянием сервера. Эти процессы могут получать доступ к области разделяемой памяти PostgreSQL и устанавливать внутренние подключения к базам данных; они также могут последовательно запускать транзакции, как и обычные серверные процессы, обслуживающие клиентов. Кроме того, используя libpq, они могут подключаться к серверу и работать как обычные клиентские приложения.
| Внимание |
С использованием фоновых рабочих процессов сопряжены угрозы стабильности и безопасности, так как они реализуются на языке C, и значит имеют неограниченный доступ к данным. Администраторы, желающие использовать модули, в которых задействованы фоновые рабочие процессы, должны быть крайне осторожными. Запускать рабочие процессы можно разрешать только модулям, прошедшим всесторонний аудит. |
Рабочие процессы могут инициализироваться во время запуска PostgreSQL, если имя соответствующего модуля добавлено в shared_preload_libraries. Модуль, желающий запустить фоновый процесс, может зарегистрировать его, вызвав RegisterBackgroundWorker(BackgroundWorker *worker) из своей функции _PG_init(). Рабочие процессы также могут быть запущены после запуска системы с помощью функции RegisterDynamicBackgroundWorker(BackgroundWorker *worker, BackgroundWorkerHandle **handle). В отличие от функции RegisterBackgroundWorker, которую можно вызывать только из управляющего процесса, RegisterDynamicBackgroundWorker должна вызываться из обычного обслуживающего процесса.
Структура BackgroundWorker определяется так:
typedef void (*bgworker_main_type)(Datum main_arg);
typedef struct BackgroundWorker
{
char bgw_name[BGW_MAXLEN];
int bgw_flags;
BgWorkerStartTime bgw_start_time;
int bgw_restart_time; /* в секундах, либо BGW_NEVER_RESTART */
bgworker_main_type bgw_main;
char bgw_library_name[BGW_MAXLEN]; /* только если bgw_main — NULL */
char bgw_function_name[BGW_MAXLEN]; /* только если bgw_main — NULL */
Datum bgw_main_arg;
char bgw_extra[BGW_EXTRALEN];
int bgw_notify_pid;
} BackgroundWorker;Поле bgw_name содержит строку, выводимую в отладочных сообщениях, списках процессов и подобных контекстах.
В поле bgw_flags задаётся битовая маска, отмечающая возможности, которые нужны этому модулю. Допустимые флаги: BGWORKER_SHMEM_ACCESS (запрашивается доступ к разделяемой памяти) и BGWORKER_BACKEND_DATABASE_CONNECTION (запрашивается возможность устанавливать подключение к базе данных, через которое затем можно запускать транзакции и запросы). Рабочий процесс, использующий возможность BGWORKER_BACKEND_DATABASE_CONNECTION для подключения к базе данных, должен также запросить доступ к разделяемой памяти, установив BGWORKER_SHMEM_ACCESS; в противном случае процесс не запустится.
В bgw_start_time определяется состояние сервера, в котором postgres должен запустить этот процесс; возможные варианты: BgWorkerStart_PostmasterStart (выполнить запуск сразу после того, как postgres завершит инициализацию; процессы, выбирающие такой режим, не могут подключаться к базам данных), BgWorkerStart_ConsistentState (выполнить запуск, когда будет достигнуто согласованное состояние горячего резерва, и когда процессы могут подключаться к базам данных и выполнять запросы на чтение), и BgWorkerStart_RecoveryFinished (выполнить запуск, как только система перейдёт в обычный режим чтения-записи). Заметьте, что два последних варианта различаются только для серверов горячего резерва. Заметьте также, что этот параметр указывает только, когда должны запускаться процессы; при переходе в другое состояние они не будут останавливаться.
bgw_restart_time задаёт паузу (в секундах), которую должен сделать postgres, прежде чем перезапускать процесс в случае его отказа. Это может быть любое положительное значение, либо BGW_NEVER_RESTART, указывающее, что процесс не нужно перезапускать в случае сбоя.
В bgw_main задаётся указатель на функцию, вызываемую при запуске процесса. Эта функция должна принимать один аргумент типа Datum и возвращать void. В качестве единственного аргумента ей будет передано значение bgw_main_arg. Заметьте, что глобальная переменная MyBgworkerEntry указывает на копию структуры BackgroundWorker, переданной при регистрации. Поле bgw_main может содержать NULL; в этом случае точка входа будет определяться значениями bgw_library_name и bgw_function_name. Это полезно для рабочих процессов, запускаемых сразу после запуска управляющего процесса, когда он ещё не загрузил необходимую библиотеку.
bgw_library_name определяет имя библиотеки, в которой следует искать точку входа для запуска рабочего процесса. Это значение игнорируется, если bgw_main не NULL. Но если bgw_main содержит NULL, указанная библиотека будет динамически загружена рабочим процессом, а имя вызываемой функции будет определяться значением bgw_function_name.
bgw_function_name определяет имя функции в динамически загружаемой библиотеке, которая будет точкой входа в новый рабочий процесс. Если bgw_main не NULL, это значение игнорируется.
Поле bgw_extra может содержать дополнительные данные, передаваемые фоновому рабочему процессу. В отличие от bgw_main_arg, эти данные не передаются в качестве аргумента основной функции рабочего процесса, но могут быть получены через MyBgworkerEntry, как описывалось выше.
В bgw_notify_pid задаётся PID обслуживающего процесса PostgreSQL, которому главный процесс должен посылать сигнал SIGUSR1 при запуске и завершении нового рабочего процесса. Это поле должно содержать 0 для рабочих процессов, регистрируемых при запуске главного процесса, либо когда обслуживающий процесс не желает ждать окончания запуска рабочего процесса. Во всех остальных случаях в нём должно быть значение MyProcPid.
Запущенный процесс может подключиться к базе данных, вызвав BackgroundWorkerInitializeConnection(char *dbname, char *username) или BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid). Через это подключение процесс сможет выполнять транзакции и запросы, используя функции SPI. Если в dbname передаётся NULL или dboid равен InvalidOid, сеанс не подключается ни к какой конкретной базе данных, но может обращаться к общим каталогам. Если в username передаётся NULL или useroid равен InvalidOid, процесс будет действовать от имени суперпользователя, созданного во время initdb. Рабочий процесс может вызывать только одну из двух этих функций и только один раз. Переключаться между базами данных он не может.
Сигналы изначально блокируются при вызове функции bgw_main и при необходимости должны быть разблокированы ей; это позволяет процессу настроить собственные обработчики событий. Новый процесс может разблокировать сигналы, вызвав BackgroundWorkerUnblockSignals, и заблокировать их, вызвав BackgroundWorkerBlockSignals.
Если bgw_restart_time для рабочего процесса имеет значение BGW_NEVER_RESTART, либо он завершается с кодом выхода 0, либо если его работа заканчивается вызовом TerminateBackgroundWorker, он автоматически перестаёт контролироваться управляющим процессом при выходе. В противном случае он будет перезапущен через время, заданное в bgw_restart_time, либо немедленно, если управляющему серверу пришлось переинициализировать кластер из-за сбоя обслуживающего процесса. Обслуживающие процессы, которым нужно только приостановить своё выполнение на время, должны переходить в состояние прерываемого ожидания, а не завершаться; для этого используется функция WaitLatch(). При вызове этой функции обязательно установите флаг WL_POSTMASTER_DEATH и проверьте код возврата, чтобы корректно выйти в экстренном случае, когда был завершён сам postgres.
Когда рабочий процесс регистрируется функцией RegisterDynamicBackgroundWorker, обслуживающий процесс, производящий эту регистрацию, может получить информацию о состоянии порождённого процесса. Обслуживающие процессы, желающие сделать это, должны передать адрес BackgroundWorkerHandle * во втором аргументе RegisterDynamicBackgroundWorker. Если рабочий процесс успешно зарегистрирован, по этому адресу будет записан указатель на скрытую структуру, который можно затем передать функции GetBackgroundWorkerPid(BackgroundWorkerHandle *, pid_t *) или TerminateBackgroundWorker(BackgroundWorkerHandle *). Вызывая GetBackgroundWorkerPid, можно опрашивать состояние рабочего процесса: значение результата BGWH_NOT_YET_STARTED показывает, что рабочий процесс ещё не запущен управляющим; BGWH_STOPPED показывает, что он был запущен, но сейчас не работает; и BGWH_STARTED показывает, что он работает в данный момент. В последнем случае через второй аргумент также возвращается PID этого процесса. Обрабатывая вызов TerminateBackgroundWorker, управляющий процесс посылает SIGTERM рабочему процессу, если он работает, и перестаёт его контролировать сразу по его завершении.
В некоторых случаях процессу, регистрирующему рабочий процесс, может потребоваться дождаться завершения запуска этого процесса. Это можно реализовать, записав в bgw_notify_pid значение MyProcPid, а затем передав указатель BackgroundWorkerHandle *, полученный во время регистрации, функции WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *). Эта функция заблокирует выполнение, пока управляющий процесс не попытается запустить рабочий процесс, либо пока сам управляющий процесс не завершится. Если рабочий процесс запущен, возвращается значение BGWH_STARTED, и по переданному адресу записывается его PID. В противном случае возвращается значение BGWH_STOPPED или BGWH_POSTMASTER_DIED.
Рабочий пример, демонстрирующий некоторые полезные приёмы, можно найти в модуле src/test/modules/worker_spi.
Максимальное число рабочих процессов, которые можно зарегистрировать, ограничивается значением max_worker_processes.
| Пред. | Начало | След. |
| Примеры | Уровень выше | Логическое декодирование |