Фоновые рабочие процессы

QHB можно расширить для запуска пользовательского кода в отдельных процессах. Такие процессы запускаются, останавливаются и контролируются QHB, что позволяет им тесно связать время существования со статусом сервера. Эти процессы имеют возможность присоединяться к области разделяемой памяти QHB и подключаться к базам данных; кроме того, они могут запускать последовательно несколько транзакций так же, как обычный серверный процесс, подключенный к клиенту. Также, связываясь с libpq, они могут подключаться к серверу и вести себя как обычное клиентское приложение.

ПРЕДУПРЕЖДЕНИЕ!
С использованием фоновых рабочих процессов сопряжены значительные угрозы стабильности и безопасности, поскольку, будучи написанными на языке C/RUST, они имеют неограниченный доступ к данным. Администраторам, желающим применять модули, включающие в себя фоновые рабочие процессы, нужно проявлять крайнюю осторожность. Запускать фоновые рабочие процессы следует разрешать только тщательно проверенным модулям.

Фоновые процессы могут быть инициализированы во время запуска QHB путем включения имени модуля в shared_preload_libraries. Модуль, желающий запустить фоновый рабочий, может зарегистрировать его, вызвав функцию RegisterBackgroundWorker(BackgroundWorker *worker) из своей функции _PG_init(). Также фоновые рабочие процессы можно запустить после запуска системы, вызвав функцию RegisterDynamicBackgroundWorker(BackgroundWorker *worker, BackgroundWorkerHandle **handle). В отличие от RegisterBackgroundWorker, которую можно вызывать только из процесса qhbmaster, RegisterDynamicBackgroundWorker нужно вызывать из обычного обслуживающего процесса или другого фонового процесса.

Структура BackgroundWorker определяется таким образом:

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;       /* время в секундах или 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;

Поля bgw_name и bgw_type — это строки, которые будут использоваться в сообщениях журнала, списках процессов и тому подобных контекстах. Поле bgw_type должно быть одинаковым для всех фоновых процессов одного типа, чтобы, например, можно было сгруппировать такие рабочие процессы в список процессов. С другой стороны, поле bgw_name может содержать дополнительную информацию о конкретном процессе. (Как правило, строка для bgw_name некоторым образом содержит тип, но строго это не требуется.)

Поле bgw_flags — это побитовая битовая маска, указывающая возможности, запрашиваемые модулем. Возможные значения:

BGWORKER_SHMEM_ACCESS
Запрашивает доступ к разделяемой памяти. Рабочие процессы без доступа к разделяемой памяти, не могут обращаться к разделяемым структурам данных QHB, таким как тяжелые или легкие блокировки, разделяемые буферы, или к любым пользовательским структурам данных, которые сам рабочий процесс может пожелать создать и использовать.

BGWORKER_BACKEND_DATABASE_CONNECTION
Запрашивает возможность установить соединение с базой данных, с помощью которого она потом может выполнять транзакции и запросы. Фоновый рабочий процесс, использующий BGWORKER_BACKEND_DATABASE_CONNECTION для подключения к базе данных, должен также подключить разделяемую память с помощью BGWORKER_SHMEM_ACCESS, иначе рабочий процесс не запустится.

Поле bgw_start_time — это состояние сервера, в течение которого qhb должен запустить процесс; это может быть BgWorkerStart_QhbmasterStart (запускаться сразу после того, как сама QHB завершила собственную инициализацию; процессы, требующие этого, не подходят для подключений к базе данных), BgWorkerStart_ConsistentState (запускаться сразу после того, как было достигнуто согласованное состояние горячего резерва, позволяя процессам подключаться к базам данных и выполнять запросы только на чтение) и BgWorkerStart_RecoveryFinished (запускаться сразу после того, как система вошла в нормальное состояние чтения-записи). Обратите внимание, что последние два значения равнозначны для сервера, который не находится в режиме горячего резерва. Также обратите внимание, что этот параметр указывает только, когда эти процессы должны запускаться; они не будут останавливаться при переходе в другое состояние.

Поле bgw_restart_time — это интервал в секундах, который qhb должен выждать перед перезапуском процесса в случае его сбоя. Это может быть любое положительное значение или BGW_NEVER_RESTART, указывающее не перезапускать процесс в случае сбоя.

Поле bgw_library_name — это имя библиотеки, в которой следует искать начальную точку входа для фонового процесса. Именованная библиотека будет динамически загружена рабочим процессом, а поле bgw_function_name будет использовано для идентификации вызываемой функции. При загрузке функции из основного кода в этом поле должно быть установлено значение «qhb».

Поле bgw_function_name — это имя той функции в динамически загружаемой библиотеке, которая должна использоваться в качестве начальной точки входа для нового фонового процесса.

Поле bgw_main_arg — это аргумент типа Datum для основной функции фонового рабочего процесса. Основная функция должна принимать один аргумент типа Datum и возвращать void. Поле bgw_main_arg будет передано в качестве этого аргумента. Кроме того, глобальная переменная MyBgworkerEntry указывает на копию структуры BackgroundWorker, переданную во время регистрации; рабочему процессу может быть полезно проверить эту структуру.

Везде, где определяется EXEC_BACKEND, или в динамических рабочих процессах, небезопасно передавать Datum по ссылке, нужно передавать его по значению. Если требуется аргумент, безопаснее всего будет передать int32 или другое маленькое значение и использовать его как индекс в массиве, размещенном в разделяемой памяти. Если же передается значение типа cstring или text, то этот указатель не будет работать в новом фоновом рабочем процессе.

Поле bgw_extra может содержать дополнительные данные для передачи фоновому рабочему процессу. В отличие от bgw_main_arg, эти данные не передаются в качестве аргумента основной функции рабочего процесса, но к ним можно получить доступ через MyBgworkerEntry, как было сказано выше.

Поле bgw_notify_pid — это PID обслуживающего процесса QHB, которому qhbmaster должен отправить SIGUSR1 при запуске или окончании этого рабочего процесса. Оно должно содержать 0 для рабочих процессов, зарегистрированных во время запуска qhbmaster, или когда обслуживающий процесс, регистрирующий рабочий процесс, не хочет ждать его запуска. В противном случае его следует инициализировать как MyProcPid.

После запуска процесс может подключиться к базе данных, вызвав функцию BackgroundWorkerInitializeConnection(char *dbname, char *username, uint32 flags) или BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, uint32 flags). Это позволяет процессу выполнять транзакции и запросы с использованием интерфейса SPI. Если dbname равен NULL или dboid имеет значение InvalidOid, сеанс не подключен к какой-либо конкретной базе данных, но может обращаться к разделяемым каталогам. Если username равен NULL или useroid имеет значение InvalidOid, процесс будет работать от имени суперпользователя, созданного во время qhb_bootstrap (или initdb). Если в качестве flags указано BGWORKER_BYPASS_ALLOWCONN, можно обойти ограничение на подключение к базам данных, не допускающим подключения от пользователей. Фоновый рабочий процесс может вызвать только одну из этих двух функций и только один раз. Переключаться между базами данных невозможно.

Сигналы изначально блокируются, когда управление достигает основной функции фонового рабочего процесса, и должны быть разблокированы ей; это позволяет процессу при необходимости настраивать свои обработчики сигналов. В новом процессе сигналы могут быть разблокированы путем вызова BackgroundWorkerUnblockSignals и заблокированы путем вызова BackgroundWorkerBlockSignals.

Если поле bgw_restart_time для фонового рабочего процесса сконфигурировано как BGW_NEVER_RESTART, или если он завершается с кодом выхода 0 или уничтожается TerminateBackgroundWorker, при выходе он автоматически перестанет регистрироваться главным процессом. В противном случае он будет перезапущен по истечении периода времени, сконфигурированного посредством bgw_restart_time, или немедленно, если qhbmaster повторно инициализирует кластер из-за сбоя обслуживающего процесса. Для обслуживающих процессов, выполнение которых должно приостановиться лишь на время, вместо выхода следует использовать паузы; это можно сделать путем вызова WaitLatch(). Убедитесь, что при вызове этой функции установлен флаг WL_POSTMASTER_DEATH, и проверьте код завершения для быстрого завершения в аварийном случае, когда был уничтожен сам qhb.

Когда фоновый рабочий процесс регистрируется с помощью функции RegisterDynamicBackgroundWorker, обслуживающий процесс, выполняющий регистрацию, может получить информацию о его статусе. Обслуживающие процессы, желающие сделать это, должны передать в качестве второго аргумента RegisterDynamicBackgroundWorker адрес BackgroundWorkerHandle *. Если процесс успешно зарегистрирован, этот указатель будет инициализирован со скрытым обработчиком, который впоследствии можно будет передать в GetBackgroundWorkerPid(BackgroundWorkerHandle *, pid_t *) или в TerminateBackgroundWorker(BackgroundWorkerHandle *). GetBackgroundWorkerPid можно использовать для опроса статуса рабочего процесса: возвращаемое значение BGWH_NOT_YET_STARTED указывает, что процесс еще не был запущен qhbmaster, BGWH_STOPPED — что он был запущен, но больше не работает, а BGWH_STARTED — что он в данный момент работает. В последнем случае PID тоже будет возвращен через второй аргумент. TerminateBackgroundWorker заставляет qhbmaster отправлять рабочему процессу SIGTERM, если он работает, и отменять его регистрацию, как только он завершится.

В некоторых случаях процесс, регистрирующий фоновый рабочий процесс, может пожелать дождаться его запуска. Это можно сделать путем инициализации bgw_notify_pid для MyProcPid и последующей передачи BackgroundWorkerHandle *, полученного во время регистрации, в WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t*). Эта функция будет блокировать выполнение до тех пор, пока qhbmaster не попытается запустить фоновый рабочий процесс или не прекратит работу. Если фоновый процесс работает, будет возвращено значение BGWH_STARTED, а PID будет записан по указанному адресу. В противном случае будет возвращено значение BGWH_STOPPED или BGWH_POSTMASTER_DIED.

Процесс также может ожидать выключения фонового рабочего процесса, используя функцию WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle) и передавая BackgroundWorkerHandle *, полученный при регистрации. Эта функция будет блокировать выполнение до тех пор, пока не завершится либо фоновый рабочий процесс, либо qhbmaster. Когда завершает работу фоновый процесс, будет возвращено значение BGWH_STOPPED, если завершается qhbmaster, она вернет BGWH_POSTMASTER_DIED.

Фоновый рабочий процесс может отправлять асинхронные уведомления либо с помощью команды NOTIFY через SPI, либо напрямую вызвав функцию Async_Notify(). Такие уведомления будут передаваться при фиксации транзакции. Фоновые рабочие процессы не должны регистрироваться командой LISTEN для получения асинхронных уведомлений, поскольку для них не предусмотрена инфраструктура, позволяющая получать подобные уведомления.

Максимальное количество регистрируемых фоновых рабочих процессов ограничивается параметром max_worker_processes.