31.14. Система событий#
31.14. Система событий #
Система событий libpq предназначена для уведомления зарегистрированных обработчиков событий о интересующих их событиях libpq, таких как создание или удаление объектов PGconn и PGresult. Основное применение заключается в том, что это позволяет приложениям ассоциировать свои собственные данные с объектами PGconn или PGresult и гарантировать, что эти данные будут освобождены в подходящее время.
Каждый зарегистрированный обработчик событий связан с двумя данными, известными только libpq как непрозрачные указатели void *. Существует указатель прохождения, который предоставляется приложением при регистрации обработчика событий с PGconn. Указатель прохождения никогда не меняется на протяжении жизни PGconn и всех созданных из него PGresult; поэтому, если он используется, он должен указывать на долгоживущие данные. Кроме того, есть указатель данных экземпляра, который изначально равен NULL в каждом PGconn и PGresult. Этот указатель может быть изменен с помощью функций PQinstanceData, PQsetInstanceData, PQresultInstanceData и PQresultSetInstanceData. Обратите внимание, что в отличие от указателя прохождения, данные экземпляра PGconn не наследуются автоматически PGresult, созданными из него. libpq не знает, на что указывают указатели прохождения и данных экземпляра (если на что-то указывают) и никогда не попытается их освободить - это ответственность обработчика событий.
31.14.1. Типы событий #
Все значения перечисления PGEventId определяют типы событий, обрабатываемых системой событий. Все значения имеют имена, начинающиеся с PGEVT. Для каждого типа события существует соответствующая структура информации о событии, которая содержит параметры, передаваемые обработчикам событий. Типы событий:
PGEVT_REGISTER#Событие регистрации происходит, когда вызывается
PQregisterEventProc. Это идеальное время для инициализации любыхinstanceData, которые могут понадобиться процедуре события. Только одно событие регистрации будет запущено для каждого обработчика событий на каждое соединение. Если процедура события завершается неудачно (возвращает ноль), регистрация отменяется.typedef struct { PGconn *conn; } PGEventRegister;Когда получено событие
PGEVT_REGISTER, указательevtInfoдолжен быть приведен к типуPGEventRegister *. Эта структура содержитPGconn, который должен находиться в состоянииCONNECTION_OK; это гарантируется, если вызвать функциюPQregisterEventProcсразу после получения корректногоPGconn. При возврате кода ошибки необходимо выполнить все необходимые очистки, так как событиеPGEVT_CONNDESTROYне будет отправлено.PGEVT_CONNRESET#Событие сброса соединения срабатывает по завершении функции
PQresetилиPQresetPoll. В обоих случаях событие срабатывает только в случае успешного сброса. Возвращаемое значение процедуры события игнорируется в Tantor BE версии 15 и выше. Однако, в более ранних версиях важно вернуть успешный результат (ненулевое значение), иначе соединение будет прервано.typedef struct { PGconn *conn; } PGEventConnReset;Когда получено событие
PGEVT_CONNRESET, указательevtInfoдолжен быть приведен к типуPGEventConnReset *. Несмотря на то, что содержащийся в немPGconnтолько что был сброшен, все данные события остаются неизменными. Это событие должно использоваться для сброса/перезагрузки/повторного запроса любых связанныхinstanceData. Обратите внимание, что даже если процедура обработки события не сможет обработатьPGEVT_CONNRESET, она все равно получит событиеPGEVT_CONNDESTROYпри закрытии соединения.PGEVT_CONNDESTROY#Событие разрушения соединения вызывается в ответ на
PQfinish. Обязанность процедуры события заключается в правильной очистке данных события, так как libpq не имеет возможности управлять этой памятью. Неправильная очистка приведет к утечкам памяти.typedef struct { PGconn *conn; } PGEventConnDestroy;Когда получено событие
PGEVT_CONNDESTROY, указательevtInfoдолжен быть приведен к типуPGEventConnDestroy *. Это событие вызывается перед выполнением любой другой очистки вPQfinish. Возвращаемое значение процедуры события игнорируется, так как нет способа указать сбой вPQfinish. Кроме того, сбой процедуры события не должен прерывать процесс очистки нежелательной памяти.PGEVT_RESULTCREATE#Событие создания результата вызывается в ответ на любую функцию выполнения запроса, которая генерирует результат, включая
PQgetResult. Это событие будет вызвано только после успешного создания результата.typedef struct { PGconn *conn; PGresult *result; } PGEventResultCreate;Когда получено событие
PGEVT_RESULTCREATE, указательevtInfoдолжен быть приведен к типуPGEventResultCreate *. Параметрconnпредставляет собой соединение, используемое для генерации результата. Это идеальное место для инициализации любыхinstanceData, которые должны быть связаны с результатом. Если процедура события завершается неудачно (возвращает ноль), эта процедура события будет проигнорирована на оставшуюся жизнь результата; то есть, она не будет получать событияPGEVT_RESULTCOPYилиPGEVT_RESULTDESTROYдля этого результата или результатов, скопированных из него.PGEVT_RESULTCOPY#Событие копирования результата запускается в ответ на
PQcopyResult. Это событие будет запущено только после завершения копирования. Только процедуры событий, которые успешно обработали событиеPGEVT_RESULTCREATEилиPGEVT_RESULTCOPYдля исходного результата, получат событияPGEVT_RESULTCOPY.typedef struct { const PGresult *src; PGresult *dest; } PGEventResultCopy;Когда получено событие
PGEVT_RESULTCOPY, указательevtInfoдолжен быть приведен к типуPGEventResultCopy *. Результатsrc- это то, что было скопировано, в то время как результатdest- это место назначения копии. Это событие может использоваться для создания глубокой копииinstanceData, так какPQcopyResultне может этого сделать. Если процедура события завершается неудачно (возвращает ноль), эта процедура события будет проигнорирована на протяжении оставшегося срока службы нового результата; то есть она не будет получать событияPGEVT_RESULTCOPYилиPGEVT_RESULTDESTROYдля этого результата или результатов, скопированных из него.PGEVT_RESULTDESTROY#Событие destroy вызывается в ответ на
PQclear. Обязанность процедуры события заключается в правильной очистке данных события, так как libpq не имеет возможности управлять этой памятью. Неочищенные данные могут привести к утечкам памяти.typedef struct { PGresult *result; } PGEventResultDestroy;Когда получено событие
PGEVT_RESULTDESTROY, указательevtInfoдолжен быть приведен к типуPGEventResultDestroy *. Это событие вызывается перед выполнением любой другой очистки вPQclear. Возвращаемое значение процедуры события игнорируется, так как нет способа указать сбой вPQclear. Кроме того, сбой процедуры события не должен прерывать процесс очистки нежелательной памяти.
31.14.2. Процедура обратного вызова события #
PGEventProc#PGEventProc- это typedef для указателя на процедуру события, то есть пользовательскую обратную функцию, которая получает события от libpq. Сигнатура процедуры события должна бытьint eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
Параметр
evtIdуказывает, какое событиеPGEVTпроизошло. УказательevtInfoдолжен быть приведен к соответствующему типу структуры, чтобы получить дополнительную информацию о событии. ПараметрpassThroughявляется указателем, предоставленнымPQregisterEventProc, когда процедура события была зарегистрирована. Функция должна вернуть ненулевое значение в случае успеха и ноль в случае неудачи.Определенная процедура события может быть зарегистрирована только один раз в любом
PGconn. Это происходит потому, что адрес процедуры используется в качестве ключа поиска для идентификации связанных данных экземпляра.
31.14.3. Функции поддержки событий #
PQregisterEventProc#Регистрирует процедуру обратного вызова события с libpq.
int PQregisterEventProc(PGconn *conn, PGEventProc proc, const char *name, void *passThrough);Все процедуры событий должны быть зарегистрированы один раз на каждом
PGconn, для которого нужно получать события. Нет ограничений, кроме памяти, на количество процедур событий, которые могут быть зарегистрированы с соединением. Функция возвращает ненулевое значение в случае успеха и ноль в случае неудачи.Аргумент
procбудет вызван, когда происходит событие libpq. Его адрес памяти также используется для поискаinstanceData. Аргументnameиспользуется для ссылки на процедуру события в сообщениях об ошибках. Это значение не может бытьNULLили строкой нулевой длины. Строка имени копируется вPGconn, поэтому передаваемое значение не обязательно должно быть долговечным. УказательpassThroughпередается вprocпри возникновении события. Этот аргумент может бытьNULL.PQsetInstanceData#Устанавливает значение
instanceDataдля процедурыprocв соединенииconnравнымdata. Возвращает ненулевое значение в случае успеха и ноль в случае неудачи. (Неудача возможна только если процедураprocне была правильно зарегистрирована в соединенииconn).int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
PQinstanceData#Возвращает
instanceDataсоединенияconn, связанное с процедуройproc, илиNULL, если такового нет.void *PQinstanceData(const PGconn *conn, PGEventProc proc);
PQresultSetInstanceData#Устанавливает
instanceDataрезультата дляprocвdata. Это возвращает ненулевое значение в случае успеха и ноль в случае ошибки. (Ошибка возможна только еслиprocне был правильно зарегистрирован в результате).int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
Будьте осторожны, что любое хранилище, представленное
data, не будет учтеноPQresultMemorySize, если оно не выделено с использованиемPQresultAlloc. (Рекомендуется делать это, поскольку это позволяет избежать необходимости явно освобождать такое хранилище при уничтожении результата).PQresultInstanceData#Возвращает
instanceDataрезультата, связанного сproc, илиNULL, если такового нет.void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
31.14.4. Пример события #
Вот пример структуры для управления личными данными, связанными с соединениями и результатами libpq.
/* required header for libpq events (note: includes libpq-fe.h) */
#include <libpq-events.h>
/* The instanceData */
typedef struct
{
int n;
char *str;
} mydata;
/* PGEventProc */
static int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough);
int
main(void)
{
mydata *data;
PGresult *res;
PGconn *conn =
PQconnectdb("dbname=postgres options=-csearch_path=");
if (PQstatus(conn) != CONNECTION_OK)
{
/* PQerrorMessage's result includes a trailing newline */
fprintf(stderr, "%s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
/* called once on any connection that should receive events.
* Sends a PGEVT_REGISTER to myEventProc.
*/
if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL))
{
fprintf(stderr, "Cannot register PGEventProc\n");
PQfinish(conn);
return 1;
}
/* conn instanceData is available */
data = PQinstanceData(conn, myEventProc);
/* Sends a PGEVT_RESULTCREATE to myEventProc */
res = PQexec(conn, "SELECT 1 + 1");
/* result instanceData is available */
data = PQresultInstanceData(res, myEventProc);
/* If PG_COPYRES_EVENTS is used, sends a PGEVT_RESULTCOPY to myEventProc */
res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS);
/* result instanceData is available if PG_COPYRES_EVENTS was
* used during the PQcopyResult call.
*/
data = PQresultInstanceData(res_copy, myEventProc);
/* Both clears send a PGEVT_RESULTDESTROY to myEventProc */
PQclear(res);
PQclear(res_copy);
/* Sends a PGEVT_CONNDESTROY to myEventProc */
PQfinish(conn);
return 0;
}
static int
myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
{
switch (evtId)
{
case PGEVT_REGISTER:
{
PGEventRegister *e = (PGEventRegister *)evtInfo;
mydata *data = get_mydata(e->conn);
/* associate app specific data with connection */
PQsetInstanceData(e->conn, myEventProc, data);
break;
}
case PGEVT_CONNRESET:
{
PGEventConnReset *e = (PGEventConnReset *)evtInfo;
mydata *data = PQinstanceData(e->conn, myEventProc);
if (data)
memset(data, 0, sizeof(mydata));
break;
}
case PGEVT_CONNDESTROY:
{
PGEventConnDestroy *e = (PGEventConnDestroy *)evtInfo;
mydata *data = PQinstanceData(e->conn, myEventProc);
/* free instance data because the conn is being destroyed */
if (data)
free_mydata(data);
break;
}
case PGEVT_RESULTCREATE:
{
PGEventResultCreate *e = (PGEventResultCreate *)evtInfo;
mydata *conn_data = PQinstanceData(e->conn, myEventProc);
mydata *res_data = dup_mydata(conn_data);
/* associate app specific data with result (copy it from conn) */
PQresultSetInstanceData(e->result, myEventProc, res_data);
break;
}
case PGEVT_RESULTCOPY:
{
PGEventResultCopy *e = (PGEventResultCopy *)evtInfo;
mydata *src_data = PQresultInstanceData(e->src, myEventProc);
mydata *dest_data = dup_mydata(src_data);
/* associate app specific data with result (copy it from a result) */
PQresultSetInstanceData(e->dest, myEventProc, dest_data);
break;
}
case PGEVT_RESULTDESTROY:
{
PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo;
mydata *data = PQresultInstanceData(e->result, myEventProc);
/* free instance data because the result is being destroyed */
if (data)
free_mydata(data);
break;
}
/* unknown event ID, just return true. */
default:
break;
}
return true; /* event processing succeeded */
}