Age Owner TLA Line data Source code
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 *
76 andres 63 GNC 21 : libpqsrv_connect(const char *conninfo, uint32 wait_event_info)
64 : {
65 21 : PGconn *conn = NULL;
66 :
67 21 : libpqsrv_connect_prepare();
68 :
69 21 : conn = PQconnectStart(conninfo);
70 :
71 21 : libpqsrv_connect_internal(conn, wait_event_info);
72 :
73 21 : return conn;
74 : }
75 :
76 : /*
77 : * Like libpqsrv_connect(), except that this is a wrapper for
78 : * PQconnectdbParams().
79 : */
80 : static inline PGconn *
81 668 : libpqsrv_connect_params(const char *const *keywords,
82 : const char *const *values,
83 : int expand_dbname,
84 : uint32 wait_event_info)
85 : {
86 668 : PGconn *conn = NULL;
87 :
88 668 : libpqsrv_connect_prepare();
89 :
90 668 : conn = PQconnectStartParams(keywords, values, expand_dbname);
91 :
92 668 : libpqsrv_connect_internal(conn, wait_event_info);
93 :
94 667 : 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 686 : 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 686 : if (conn == NULL)
114 2 : return;
115 :
116 684 : ReleaseExternalFD();
117 684 : 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 689 : 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 689 : if (!AcquireExternalFD())
136 : {
137 : #ifndef WIN32 /* can't write #if within ereport() macro */
76 andres 138 UNC 0 : 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 : }
76 andres 151 GNC 689 : }
152 :
153 : /*
154 : * Helper function for all connection establishment functions.
155 : */
156 : static inline void
157 689 : 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 689 : if (conn == NULL)
164 : {
76 andres 165 UNC 0 : ReleaseExternalFD();
166 0 : 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 : */
76 andres 173 GNC 689 : if (PQstatus(conn) == CONNECTION_BAD)
174 57 : return;
175 :
176 : /*
177 : * WaitLatchOrSocket() can conceivably fail, handle that case here instead
178 : * of requiring all callers to do so.
179 : */
180 632 : 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 632 : status = PGRES_POLLING_WRITING;
190 2975 : while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED)
191 : {
192 : int io_flag;
193 : int rc;
194 :
195 1712 : if (status == PGRES_POLLING_READING)
196 634 : 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 1078 : io_flag = WL_SOCKET_WRITEABLE;
208 :
209 1712 : 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 1712 : if (rc & WL_LATCH_SET)
217 : {
218 449 : ResetLatch(MyLatch);
219 449 : CHECK_FOR_INTERRUPTS();
220 : }
221 :
222 : /* If socket is ready, advance the libpq state machine */
223 1711 : if (rc & io_flag)
224 1263 : status = PQconnectPoll(conn);
225 : }
226 : }
76 andres 227 UNC 0 : 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 0 : ReleaseExternalFD();
235 0 : PQfinish(conn);
236 :
237 0 : PG_RE_THROW();
238 : }
76 andres 239 GNC 631 : PG_END_TRY();
240 : }
241 :
242 : #endif /* LIBPQ_BE_FE_HELPERS_H */
|