|
| 1 | +/*------------------------------------------------------------------------- |
| 2 | + * |
| 3 | + * libpq-be-fe-helpers.h |
| 4 | + * Helper functions for using libpq in extensions |
| 5 | + * |
| 6 | + * Code built directly into the backend is not allowed to link to libpq |
| 7 | + * directly. Extension code is allowed to use libpq however. However, libpq |
| 8 | + * used in extensions has to be careful to block inside libpq, otherwise |
| 9 | + * interrupts will not be processed, leading to issues like unresolvable |
| 10 | + * deadlocks. Backend code also needs to take care to acquire/release an |
| 11 | + * external fd for the connection, otherwise fd.c's accounting of fd's is |
| 12 | + * broken. |
| 13 | + * |
| 14 | + * This file provides helper functions to make it easier to comply with these |
| 15 | + * rules. It is a header only library as it needs to be linked into each |
| 16 | + * extension using libpq, and it seems too small to be worth adding a |
| 17 | + * dedicated static library for. |
| 18 | + * |
| 19 | + * TODO: For historical reasons the connections established here are not put |
| 20 | + * into non-blocking mode. That can lead to blocking even when only the async |
| 21 | + * libpq functions are used. This should be fixed. |
| 22 | + * |
| 23 | + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| 24 | + * Portions Copyright (c) 1994, Regents of the University of California |
| 25 | + * |
| 26 | + * src/include/libpq/libpq-be-fe-helpers.h |
| 27 | + * |
| 28 | + *------------------------------------------------------------------------- |
| 29 | + */ |
| 30 | +#ifndef LIBPQ_BE_FE_HELPERS_H |
| 31 | +#define LIBPQ_BE_FE_HELPERS_H |
| 32 | + |
| 33 | +/* |
| 34 | + * Despite the name, BUILDING_DLL is set only when building code directly part |
| 35 | + * of the backend. Which also is where libpq isn't allowed to be |
| 36 | + * used. Obviously this doesn't protect against libpq-fe.h getting included |
| 37 | + * otherwise, but perhaps still protects against a few mistakes... |
| 38 | + */ |
| 39 | +#ifdef BUILDING_DLL |
| 40 | +#error "libpq may not be used code directly built into the backend" |
| 41 | +#endif |
| 42 | + |
| 43 | +#include "libpq-fe.h" |
| 44 | +#include "miscadmin.h" |
| 45 | +#include "storage/fd.h" |
| 46 | +#include "storage/latch.h" |
| 47 | +#include "utils/wait_event.h" |
| 48 | + |
| 49 | + |
| 50 | +static inline void libpqsrv_connect_prepare(void); |
| 51 | +static inline void libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info); |
| 52 | + |
| 53 | + |
| 54 | +/* |
| 55 | + * PQconnectdb() wrapper that reserves a file descriptor and processes |
| 56 | + * interrupts during connection establishment. |
| 57 | + * |
| 58 | + * Throws an error if AcquireExternalFD() fails, but does not throw if |
| 59 | + * connection establishment itself fails. Callers need to use PQstatus() to |
| 60 | + * check if connection establishment succeeded. |
| 61 | + */ |
| 62 | +static inline PGconn * |
| 63 | +libpqsrv_connect(const char *conninfo, uint32 wait_event_info) |
| 64 | +{ |
| 65 | + PGconn *conn = NULL; |
| 66 | + |
| 67 | + libpqsrv_connect_prepare(); |
| 68 | + |
| 69 | + conn = PQconnectStart(conninfo); |
| 70 | + |
| 71 | + libpqsrv_connect_internal(conn, wait_event_info); |
| 72 | + |
| 73 | + return conn; |
| 74 | +} |
| 75 | + |
| 76 | +/* |
| 77 | + * Like libpqsrv_connect(), except that this is a wrapper for |
| 78 | + * PQconnectdbParams(). |
| 79 | + */ |
| 80 | +static inline PGconn * |
| 81 | +libpqsrv_connect_params(const char *const *keywords, |
| 82 | + const char *const *values, |
| 83 | + int expand_dbname, |
| 84 | + uint32 wait_event_info) |
| 85 | +{ |
| 86 | + PGconn *conn = NULL; |
| 87 | + |
| 88 | + libpqsrv_connect_prepare(); |
| 89 | + |
| 90 | + conn = PQconnectStartParams(keywords, values, expand_dbname); |
| 91 | + |
| 92 | + libpqsrv_connect_internal(conn, wait_event_info); |
| 93 | + |
| 94 | + return conn; |
| 95 | +} |
| 96 | + |
| 97 | +/* |
| 98 | + * PQfinish() wrapper that additionally releases the reserved file descriptor. |
| 99 | + * |
| 100 | + * It is allowed to call this with a NULL pgconn iff NULL was returned by |
| 101 | + * libpqsrv_connect*. |
| 102 | + */ |
| 103 | +static inline void |
| 104 | +libpqsrv_disconnect(PGconn *conn) |
| 105 | +{ |
| 106 | + /* |
| 107 | + * If no connection was established, we haven't reserved an FD for it (or |
| 108 | + * already released it). This rule makes it easier to write PG_CATCH() |
| 109 | + * handlers for this facility's users. |
| 110 | + * |
| 111 | + * See also libpqsrv_connect_internal(). |
| 112 | + */ |
| 113 | + if (conn == NULL) |
| 114 | + return; |
| 115 | + |
| 116 | + ReleaseExternalFD(); |
| 117 | + PQfinish(conn); |
| 118 | +} |
| 119 | + |
| 120 | + |
| 121 | +/* internal helper functions follow */ |
| 122 | + |
| 123 | + |
| 124 | +/* |
| 125 | + * Helper function for all connection establishment functions. |
| 126 | + */ |
| 127 | +static inline void |
| 128 | +libpqsrv_connect_prepare(void) |
| 129 | +{ |
| 130 | + /* |
| 131 | + * We must obey fd.c's limit on non-virtual file descriptors. Assume that |
| 132 | + * a PGconn represents one long-lived FD. (Doing this here also ensures |
| 133 | + * that VFDs are closed if needed to make room.) |
| 134 | + */ |
| 135 | + if (!AcquireExternalFD()) |
| 136 | + { |
| 137 | +#ifndef WIN32 /* can't write #if within ereport() macro */ |
| 138 | + ereport(ERROR, |
| 139 | + (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), |
| 140 | + errmsg("could not establish connection"), |
| 141 | + errdetail("There are too many open files on the local server."), |
| 142 | + errhint("Raise the server's max_files_per_process and/or \"ulimit -n\" limits."))); |
| 143 | +#else |
| 144 | + ereport(ERROR, |
| 145 | + (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), |
| 146 | + errmsg("could not establish connection"), |
| 147 | + errdetail("There are too many open files on the local server."), |
| 148 | + errhint("Raise the server's max_files_per_process setting."))); |
| 149 | +#endif |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +/* |
| 154 | + * Helper function for all connection establishment functions. |
| 155 | + */ |
| 156 | +static inline void |
| 157 | +libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info) |
| 158 | +{ |
| 159 | + /* |
| 160 | + * With conn == NULL libpqsrv_disconnect() wouldn't release the FD. So do |
| 161 | + * that here. |
| 162 | + */ |
| 163 | + if (conn == NULL) |
| 164 | + { |
| 165 | + ReleaseExternalFD(); |
| 166 | + return; |
| 167 | + } |
| 168 | + |
| 169 | + /* |
| 170 | + * Can't wait without a socket. Note that we don't want to close the libpq |
| 171 | + * connection yet, so callers can emit a useful error. |
| 172 | + */ |
| 173 | + if (PQstatus(conn) == CONNECTION_BAD) |
| 174 | + return; |
| 175 | + |
| 176 | + /* |
| 177 | + * WaitLatchOrSocket() can conceivably fail, handle that case here instead |
| 178 | + * of requiring all callers to do so. |
| 179 | + */ |
| 180 | + PG_TRY(); |
| 181 | + { |
| 182 | + PostgresPollingStatusType status; |
| 183 | + |
| 184 | + /* |
| 185 | + * Poll connection until we have OK or FAILED status. |
| 186 | + * |
| 187 | + * Per spec for PQconnectPoll, first wait till socket is write-ready. |
| 188 | + */ |
| 189 | + status = PGRES_POLLING_WRITING; |
| 190 | + while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED) |
| 191 | + { |
| 192 | + int io_flag; |
| 193 | + int rc; |
| 194 | + |
| 195 | + if (status == PGRES_POLLING_READING) |
| 196 | + io_flag = WL_SOCKET_READABLE; |
| 197 | +#ifdef WIN32 |
| 198 | + |
| 199 | + /* |
| 200 | + * Windows needs a different test while waiting for |
| 201 | + * connection-made |
| 202 | + */ |
| 203 | + else if (PQstatus(conn) == CONNECTION_STARTED) |
| 204 | + io_flag = WL_SOCKET_CONNECTED; |
| 205 | +#endif |
| 206 | + else |
| 207 | + io_flag = WL_SOCKET_WRITEABLE; |
| 208 | + |
| 209 | + rc = WaitLatchOrSocket(MyLatch, |
| 210 | + WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag, |
| 211 | + PQsocket(conn), |
| 212 | + 0, |
| 213 | + wait_event_info); |
| 214 | + |
| 215 | + /* Interrupted? */ |
| 216 | + if (rc & WL_LATCH_SET) |
| 217 | + { |
| 218 | + ResetLatch(MyLatch); |
| 219 | + CHECK_FOR_INTERRUPTS(); |
| 220 | + } |
| 221 | + |
| 222 | + /* If socket is ready, advance the libpq state machine */ |
| 223 | + if (rc & io_flag) |
| 224 | + status = PQconnectPoll(conn); |
| 225 | + } |
| 226 | + } |
| 227 | + PG_CATCH(); |
| 228 | + { |
| 229 | + /* |
| 230 | + * If an error is thrown here, the callers won't call |
| 231 | + * libpqsrv_disconnect() with a conn, so release resources |
| 232 | + * immediately. |
| 233 | + */ |
| 234 | + ReleaseExternalFD(); |
| 235 | + PQfinish(conn); |
| 236 | + |
| 237 | + PG_RE_THROW(); |
| 238 | + } |
| 239 | + PG_END_TRY(); |
| 240 | +} |
| 241 | + |
| 242 | +#endif /* LIBPQ_BE_FE_HELPERS_H */ |
0 commit comments