LCOV - code coverage report
Current view: top level - src/include/libpq - libpq-be-fe-helpers.h (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 37 44 84.1 %
Date: 2023-12-07 06:11:03 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

          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 *
      63          42 : libpqsrv_connect(const char *conninfo, uint32 wait_event_info)
      64             : {
      65          42 :     PGconn     *conn = NULL;
      66             : 
      67          42 :     libpqsrv_connect_prepare();
      68             : 
      69          42 :     conn = PQconnectStart(conninfo);
      70             : 
      71          42 :     libpqsrv_connect_internal(conn, wait_event_info);
      72             : 
      73          42 :     return conn;
      74             : }
      75             : 
      76             : /*
      77             :  * Like libpqsrv_connect(), except that this is a wrapper for
      78             :  * PQconnectdbParams().
      79             :   */
      80             : static inline PGconn *
      81        1406 : libpqsrv_connect_params(const char *const *keywords,
      82             :                         const char *const *values,
      83             :                         int expand_dbname,
      84             :                         uint32 wait_event_info)
      85             : {
      86        1406 :     PGconn     *conn = NULL;
      87             : 
      88        1406 :     libpqsrv_connect_prepare();
      89             : 
      90        1406 :     conn = PQconnectStartParams(keywords, values, expand_dbname);
      91             : 
      92        1406 :     libpqsrv_connect_internal(conn, wait_event_info);
      93             : 
      94        1406 :     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        1444 : 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        1444 :     if (conn == NULL)
     114           4 :         return;
     115             : 
     116        1440 :     ReleaseExternalFD();
     117        1440 :     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        1448 : 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        1448 :     if (!AcquireExternalFD())
     136             :     {
     137             : #ifndef WIN32                   /* can't write #if within ereport() macro */
     138           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             :     }
     151        1448 : }
     152             : 
     153             : /*
     154             :  * Helper function for all connection establishment functions.
     155             :  */
     156             : static inline void
     157        1448 : 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        1448 :     if (conn == NULL)
     164             :     {
     165           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             :      */
     173        1448 :     if (PQstatus(conn) == CONNECTION_BAD)
     174         128 :         return;
     175             : 
     176             :     /*
     177             :      * WaitLatchOrSocket() can conceivably fail, handle that case here instead
     178             :      * of requiring all callers to do so.
     179             :      */
     180        1320 :     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        1320 :         status = PGRES_POLLING_WRITING;
     190        4838 :         while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED)
     191             :         {
     192             :             int         io_flag;
     193             :             int         rc;
     194             : 
     195        3518 :             if (status == PGRES_POLLING_READING)
     196        1326 :                 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        2192 :                 io_flag = WL_SOCKET_WRITEABLE;
     208             : 
     209        3518 :             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        3518 :             if (rc & WL_LATCH_SET)
     217             :             {
     218         878 :                 ResetLatch(MyLatch);
     219         878 :                 CHECK_FOR_INTERRUPTS();
     220             :             }
     221             : 
     222             :             /* If socket is ready, advance the libpq state machine */
     223        3518 :             if (rc & io_flag)
     224        2640 :                 status = PQconnectPoll(conn);
     225             :         }
     226             :     }
     227           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             :     }
     239        1320 :     PG_END_TRY();
     240             : }
     241             : 
     242             : #endif                          /* LIBPQ_BE_FE_HELPERS_H */

Generated by: LCOV version 1.14