22
22
#include "pgstat.h"
23
23
#include "storage/latch.h"
24
24
#include "utils/hsearch.h"
25
+ #include "utils/inval.h"
25
26
#include "utils/memutils.h"
26
27
#include "utils/syscache.h"
27
28
@@ -48,11 +49,15 @@ typedef struct ConnCacheEntry
48
49
{
49
50
ConnCacheKey key ; /* hash key (must be first) */
50
51
PGconn * conn ; /* connection to foreign server, or NULL */
52
+ /* Remaining fields are invalid when conn is NULL: */
51
53
int xact_depth ; /* 0 = no xact open, 1 = main xact open, 2 =
52
54
* one level of subxact open, etc */
53
55
bool have_prep_stmt ; /* have we prepared any stmts in this xact? */
54
56
bool have_error ; /* have any subxacts aborted in this xact? */
55
57
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 */
56
61
} ConnCacheEntry ;
57
62
58
63
/*
@@ -69,6 +74,7 @@ static bool xact_got_connection = false;
69
74
70
75
/* prototypes of private functions */
71
76
static PGconn * connect_pg_server (ForeignServer * server , UserMapping * user );
77
+ static void disconnect_pg_server (ConnCacheEntry * entry );
72
78
static void check_conn_params (const char * * keywords , const char * * values );
73
79
static void configure_remote_session (PGconn * conn );
74
80
static void do_sql_command (PGconn * conn , const char * sql );
@@ -78,6 +84,7 @@ static void pgfdw_subxact_callback(SubXactEvent event,
78
84
SubTransactionId mySubid ,
79
85
SubTransactionId parentSubid ,
80
86
void * arg );
87
+ static void pgfdw_inval_callback (Datum arg , int cacheid , uint32 hashvalue );
81
88
static void pgfdw_reject_incomplete_xact_state_change (ConnCacheEntry * entry );
82
89
static bool pgfdw_cancel_query (PGconn * conn );
83
90
static bool pgfdw_exec_cleanup_query (PGconn * conn , const char * query ,
@@ -95,13 +102,6 @@ static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
95
102
* will_prep_stmt must be true if caller intends to create any prepared
96
103
* statements. Since those don't go away automatically at transaction end
97
104
* (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.
105
105
*/
106
106
PGconn *
107
107
GetConnection (UserMapping * user , bool will_prep_stmt )
@@ -130,6 +130,10 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
130
130
*/
131
131
RegisterXactCallback (pgfdw_xact_callback , NULL );
132
132
RegisterSubXactCallback (pgfdw_subxact_callback , NULL );
133
+ CacheRegisterSyscacheCallback (FOREIGNSERVEROID ,
134
+ pgfdw_inval_callback , (Datum ) 0 );
135
+ CacheRegisterSyscacheCallback (USERMAPPINGOID ,
136
+ pgfdw_inval_callback , (Datum ) 0 );
133
137
}
134
138
135
139
/* Set flag that we did GetConnection during the current transaction */
@@ -144,17 +148,27 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
144
148
entry = hash_search (ConnectionHash , & key , HASH_ENTER , & found );
145
149
if (!found )
146
150
{
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
+ */
148
155
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;
153
156
}
154
157
155
158
/* Reject further use of connections which failed abort cleanup. */
156
159
pgfdw_reject_incomplete_xact_state_change (entry );
157
160
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
+
158
172
/*
159
173
* We don't check the health of cached connection here, because it would
160
174
* require some overhead. Broken connection will be detected when the
@@ -164,15 +178,26 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
164
178
/*
165
179
* If cache entry doesn't have a connection, we have to establish a new
166
180
* 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 .)
168
182
*/
169
183
if (entry -> conn == NULL )
170
184
{
171
185
ForeignServer * server = GetForeignServer (user -> serverid );
172
186
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 ;
174
189
entry -> have_prep_stmt = false;
175
190
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 */
176
201
entry -> conn = connect_pg_server (server , user );
177
202
178
203
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)
276
301
return conn ;
277
302
}
278
303
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
+
279
317
/*
280
318
* For non-superusers, insist that the connstr specify a password. This
281
319
* prevents a password from being picked up from .pgpass, a service file,
@@ -777,9 +815,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
777
815
entry -> changing_xact_state )
778
816
{
779
817
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 );
783
819
}
784
820
}
785
821
@@ -896,6 +932,47 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
896
932
}
897
933
}
898
934
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
+
899
976
/*
900
977
* Raise an error if the given connection cache entry is marked as being
901
978
* 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)
913
990
Form_pg_user_mapping umform ;
914
991
ForeignServer * server ;
915
992
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 )
917
995
return ;
918
996
997
+ /* make sure this entry is inactive */
998
+ disconnect_pg_server (entry );
999
+
1000
+ /* find server name to be shown in the message below */
919
1001
tup = SearchSysCache1 (USERMAPPINGOID ,
920
1002
ObjectIdGetDatum (entry -> key ));
921
1003
if (!HeapTupleIsValid (tup ))
0 commit comments