/*------------------------------------------------------------------------- * * libpq-be-fe-helpers.h * Helper functions for using libpq in extensions * * Code built directly into the backend is not allowed to link to libpq * directly. Extension code is allowed to use libpq however. However, libpq * used in extensions has to be careful to block inside libpq, otherwise * interrupts will not be processed, leading to issues like unresolvable * deadlocks. Backend code also needs to take care to acquire/release an * external fd for the connection, otherwise fd.c's accounting of fd's is * broken. * * This file provides helper functions to make it easier to comply with these * rules. It is a header only library as it needs to be linked into each * extension using libpq, and it seems too small to be worth adding a * dedicated static library for. * * TODO: For historical reasons the connections established here are not put * into non-blocking mode. That can lead to blocking even when only the async * libpq functions are used. This should be fixed. * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/libpq/libpq-be-fe-helpers.h * *------------------------------------------------------------------------- */ #ifndef LIBPQ_BE_FE_HELPERS_H #define LIBPQ_BE_FE_HELPERS_H /* * Despite the name, BUILDING_DLL is set only when building code directly part * of the backend. Which also is where libpq isn't allowed to be * used. Obviously this doesn't protect against libpq-fe.h getting included * otherwise, but perhaps still protects against a few mistakes... */ #ifdef BUILDING_DLL #error "libpq may not be used code directly built into the backend" #endif #include "libpq-fe.h" #include "miscadmin.h" #include "storage/fd.h" #include "storage/latch.h" #include "utils/wait_event.h" static inline void libpqsrv_connect_prepare(void); static inline void libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info); /* * PQconnectdb() wrapper that reserves a file descriptor and processes * interrupts during connection establishment. * * Throws an error if AcquireExternalFD() fails, but does not throw if * connection establishment itself fails. Callers need to use PQstatus() to * check if connection establishment succeeded. */ static inline PGconn * libpqsrv_connect(const char *conninfo, uint32 wait_event_info) { PGconn *conn = NULL; libpqsrv_connect_prepare(); conn = PQconnectStart(conninfo); libpqsrv_connect_internal(conn, wait_event_info); return conn; } /* * Like libpqsrv_connect(), except that this is a wrapper for * PQconnectdbParams(). */ static inline PGconn * libpqsrv_connect_params(const char *const *keywords, const char *const *values, int expand_dbname, uint32 wait_event_info) { PGconn *conn = NULL; libpqsrv_connect_prepare(); conn = PQconnectStartParams(keywords, values, expand_dbname); libpqsrv_connect_internal(conn, wait_event_info); return conn; } /* * PQfinish() wrapper that additionally releases the reserved file descriptor. * * It is allowed to call this with a NULL pgconn iff NULL was returned by * libpqsrv_connect*. */ static inline void libpqsrv_disconnect(PGconn *conn) { /* * If no connection was established, we haven't reserved an FD for it (or * already released it). This rule makes it easier to write PG_CATCH() * handlers for this facility's users. * * See also libpqsrv_connect_internal(). */ if (conn == NULL) return; ReleaseExternalFD(); PQfinish(conn); } /* internal helper functions follow */ /* * Helper function for all connection establishment functions. */ static inline void libpqsrv_connect_prepare(void) { /* * We must obey fd.c's limit on non-virtual file descriptors. Assume that * a PGconn represents one long-lived FD. (Doing this here also ensures * that VFDs are closed if needed to make room.) */ if (!AcquireExternalFD()) { #ifndef WIN32 /* can't write #if within ereport() macro */ ereport(ERROR, (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), errmsg("could not establish connection"), errdetail("There are too many open files on the local server."), errhint("Raise the server's max_files_per_process and/or \"ulimit -n\" limits."))); #else ereport(ERROR, (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), errmsg("could not establish connection"), errdetail("There are too many open files on the local server."), errhint("Raise the server's max_files_per_process setting."))); #endif } } /* * Helper function for all connection establishment functions. */ static inline void libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info) { /* * With conn == NULL libpqsrv_disconnect() wouldn't release the FD. So do * that here. */ if (conn == NULL) { ReleaseExternalFD(); return; } /* * Can't wait without a socket. Note that we don't want to close the libpq * connection yet, so callers can emit a useful error. */ if (PQstatus(conn) == CONNECTION_BAD) return; /* * WaitLatchOrSocket() can conceivably fail, handle that case here instead * of requiring all callers to do so. */ PG_TRY(); { PostgresPollingStatusType status; /* * Poll connection until we have OK or FAILED status. * * Per spec for PQconnectPoll, first wait till socket is write-ready. */ status = PGRES_POLLING_WRITING; while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED) { int io_flag; int rc; if (status == PGRES_POLLING_READING) io_flag = WL_SOCKET_READABLE; #ifdef WIN32 /* * Windows needs a different test while waiting for * connection-made */ else if (PQstatus(conn) == CONNECTION_STARTED) io_flag = WL_SOCKET_CONNECTED; #endif else io_flag = WL_SOCKET_WRITEABLE; rc = WaitLatchOrSocket(MyLatch, WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag, PQsocket(conn), 0, wait_event_info); /* Interrupted? */ if (rc & WL_LATCH_SET) { ResetLatch(MyLatch); CHECK_FOR_INTERRUPTS(); } /* If socket is ready, advance the libpq state machine */ if (rc & io_flag) status = PQconnectPoll(conn); } } PG_CATCH(); { /* * If an error is thrown here, the callers won't call * libpqsrv_disconnect() with a conn, so release resources * immediately. */ ReleaseExternalFD(); PQfinish(conn); PG_RE_THROW(); } PG_END_TRY(); } #endif /* LIBPQ_BE_FE_HELPERS_H */