2222#include "pgstat.h"
2323#include "storage/latch.h"
2424#include "utils/hsearch.h"
25+ #include "utils/inval.h"
2526#include "utils/memutils.h"
2627#include "utils/syscache.h"
2728
@@ -48,11 +49,15 @@ typedef struct ConnCacheEntry
4849{
4950 ConnCacheKey key ; /* hash key (must be first) */
5051 PGconn * conn ; /* connection to foreign server, or NULL */
52+ /* Remaining fields are invalid when conn is NULL: */
5153 int xact_depth ; /* 0 = no xact open, 1 = main xact open, 2 =
5254 * one level of subxact open, etc */
5355 bool have_prep_stmt ; /* have we prepared any stmts in this xact? */
5456 bool have_error ; /* have any subxacts aborted in this xact? */
5557 bool changing_xact_state ; /* xact state change in process */
58+ bool invalidated ; /* true if reconnect is pending */
59+ uint32 server_hashvalue ; /* hash value of foreign server OID */
60+ uint32 mapping_hashvalue ; /* hash value of user mapping OID */
5661} ConnCacheEntry ;
5762
5863/*
@@ -69,6 +74,7 @@ static bool xact_got_connection = false;
6974
7075/* prototypes of private functions */
7176static PGconn * connect_pg_server (ForeignServer * server , UserMapping * user );
77+ static void disconnect_pg_server (ConnCacheEntry * entry );
7278static void check_conn_params (const char * * keywords , const char * * values );
7379static void configure_remote_session (PGconn * conn );
7480static void do_sql_command (PGconn * conn , const char * sql );
@@ -78,6 +84,7 @@ static void pgfdw_subxact_callback(SubXactEvent event,
7884 SubTransactionId mySubid ,
7985 SubTransactionId parentSubid ,
8086 void * arg );
87+ static void pgfdw_inval_callback (Datum arg , int cacheid , uint32 hashvalue );
8188static void pgfdw_reject_incomplete_xact_state_change (ConnCacheEntry * entry );
8289static bool pgfdw_cancel_query (PGconn * conn );
8390static bool pgfdw_exec_cleanup_query (PGconn * conn , const char * query ,
@@ -95,13 +102,6 @@ static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
95102 * will_prep_stmt must be true if caller intends to create any prepared
96103 * statements. Since those don't go away automatically at transaction end
97104 * (not even on error), we need this flag to cue manual cleanup.
98- *
99- * XXX Note that caching connections theoretically requires a mechanism to
100- * detect change of FDW objects to invalidate already established connections.
101- * We could manage that by watching for invalidation events on the relevant
102- * syscaches. For the moment, though, it's not clear that this would really
103- * be useful and not mere pedantry. We could not flush any active connections
104- * mid-transaction anyway.
105105 */
106106PGconn *
107107GetConnection (UserMapping * user , bool will_prep_stmt )
@@ -130,6 +130,10 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
130130 */
131131 RegisterXactCallback (pgfdw_xact_callback , NULL );
132132 RegisterSubXactCallback (pgfdw_subxact_callback , NULL );
133+ CacheRegisterSyscacheCallback (FOREIGNSERVEROID ,
134+ pgfdw_inval_callback , (Datum ) 0 );
135+ CacheRegisterSyscacheCallback (USERMAPPINGOID ,
136+ pgfdw_inval_callback , (Datum ) 0 );
133137 }
134138
135139 /* Set flag that we did GetConnection during the current transaction */
@@ -144,17 +148,27 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
144148 entry = hash_search (ConnectionHash , & key , HASH_ENTER , & found );
145149 if (!found )
146150 {
147- /* initialize new hashtable entry (key is already filled in) */
151+ /*
152+ * We need only clear "conn" here; remaining fields will be filled
153+ * later when "conn" is set.
154+ */
148155 entry -> conn = NULL ;
149- entry -> xact_depth = 0 ;
150- entry -> have_prep_stmt = false;
151- entry -> have_error = false;
152- entry -> changing_xact_state = false;
153156 }
154157
155158 /* Reject further use of connections which failed abort cleanup. */
156159 pgfdw_reject_incomplete_xact_state_change (entry );
157160
161+ /*
162+ * If the connection needs to be remade due to invalidation, disconnect as
163+ * soon as we're out of all transactions.
164+ */
165+ if (entry -> conn != NULL && entry -> invalidated && entry -> xact_depth == 0 )
166+ {
167+ elog (DEBUG3 , "closing connection %p for option changes to take effect" ,
168+ entry -> conn );
169+ disconnect_pg_server (entry );
170+ }
171+
158172 /*
159173 * We don't check the health of cached connection here, because it would
160174 * require some overhead. Broken connection will be detected when the
@@ -164,15 +178,26 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
164178 /*
165179 * If cache entry doesn't have a connection, we have to establish a new
166180 * connection. (If connect_pg_server throws an error, the cache entry
167- * will be left in a valid empty state.)
181+ * will remain in a valid empty state, ie conn == NULL .)
168182 */
169183 if (entry -> conn == NULL )
170184 {
171185 ForeignServer * server = GetForeignServer (user -> serverid );
172186
173- entry -> xact_depth = 0 ; /* just to be sure */
187+ /* Reset all transient state fields, to be sure all are clean */
188+ entry -> xact_depth = 0 ;
174189 entry -> have_prep_stmt = false;
175190 entry -> have_error = false;
191+ entry -> changing_xact_state = false;
192+ entry -> invalidated = false;
193+ entry -> server_hashvalue =
194+ GetSysCacheHashValue1 (FOREIGNSERVEROID ,
195+ ObjectIdGetDatum (server -> serverid ));
196+ entry -> mapping_hashvalue =
197+ GetSysCacheHashValue1 (USERMAPPINGOID ,
198+ ObjectIdGetDatum (user -> umid ));
199+
200+ /* Now try to make the connection */
176201 entry -> conn = connect_pg_server (server , user );
177202
178203 elog (DEBUG3 , "new postgres_fdw connection %p for server \"%s\" (user mapping oid %u, userid %u)" ,
@@ -276,6 +301,19 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
276301 return conn ;
277302}
278303
304+ /*
305+ * Disconnect any open connection for a connection cache entry.
306+ */
307+ static void
308+ disconnect_pg_server (ConnCacheEntry * entry )
309+ {
310+ if (entry -> conn != NULL )
311+ {
312+ PQfinish (entry -> conn );
313+ entry -> conn = NULL ;
314+ }
315+ }
316+
279317/*
280318 * For non-superusers, insist that the connstr specify a password. This
281319 * prevents a password from being picked up from .pgpass, a service file,
@@ -777,9 +815,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
777815 entry -> changing_xact_state )
778816 {
779817 elog (DEBUG3 , "discarding connection %p" , entry -> conn );
780- PQfinish (entry -> conn );
781- entry -> conn = NULL ;
782- entry -> changing_xact_state = false;
818+ disconnect_pg_server (entry );
783819 }
784820 }
785821
@@ -896,6 +932,47 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
896932 }
897933}
898934
935+ /*
936+ * Connection invalidation callback function
937+ *
938+ * After a change to a pg_foreign_server or pg_user_mapping catalog entry,
939+ * mark connections depending on that entry as needing to be remade.
940+ * We can't immediately destroy them, since they might be in the midst of
941+ * a transaction, but we'll remake them at the next opportunity.
942+ *
943+ * Although most cache invalidation callbacks blow away all the related stuff
944+ * regardless of the given hashvalue, connections are expensive enough that
945+ * it's worth trying to avoid that.
946+ *
947+ * NB: We could avoid unnecessary disconnection more strictly by examining
948+ * individual option values, but it seems too much effort for the gain.
949+ */
950+ static void
951+ pgfdw_inval_callback (Datum arg , int cacheid , uint32 hashvalue )
952+ {
953+ HASH_SEQ_STATUS scan ;
954+ ConnCacheEntry * entry ;
955+
956+ Assert (cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID );
957+
958+ /* ConnectionHash must exist already, if we're registered */
959+ hash_seq_init (& scan , ConnectionHash );
960+ while ((entry = (ConnCacheEntry * ) hash_seq_search (& scan )))
961+ {
962+ /* Ignore invalid entries */
963+ if (entry -> conn == NULL )
964+ continue ;
965+
966+ /* hashvalue == 0 means a cache reset, must clear all state */
967+ if (hashvalue == 0 ||
968+ (cacheid == FOREIGNSERVEROID &&
969+ entry -> server_hashvalue == hashvalue ) ||
970+ (cacheid == USERMAPPINGOID &&
971+ entry -> mapping_hashvalue == hashvalue ))
972+ entry -> invalidated = true;
973+ }
974+ }
975+
899976/*
900977 * Raise an error if the given connection cache entry is marked as being
901978 * in the middle of an xact state change. This should be called at which no
@@ -913,9 +990,14 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
913990 Form_pg_user_mapping umform ;
914991 ForeignServer * server ;
915992
916- if (!entry -> changing_xact_state )
993+ /* nothing to do for inactive entries and entries of sane state */
994+ if (entry -> conn == NULL || !entry -> changing_xact_state )
917995 return ;
918996
997+ /* make sure this entry is inactive */
998+ disconnect_pg_server (entry );
999+
1000+ /* find server name to be shown in the message below */
9191001 tup = SearchSysCache1 (USERMAPPINGOID ,
9201002 ObjectIdGetDatum (entry -> key ));
9211003 if (!HeapTupleIsValid (tup ))
0 commit comments