23
23
static void relmeta_cache_callback (Datum arg , Oid relid );
24
24
25
25
/*
26
- * We need a global hash table that invalidation callbacks can
27
- * access because they survive past the logical decoding context and
28
- * therefore past our local PGLogicalOutputData's lifetime when
29
- * using the SQL interface. We cannot just pass them a pointer to a
30
- * palloc'd struct.
26
+ * We need a global that invalidation callbacks can access because they
27
+ * survive past the logical decoding context and therefore past our
28
+ * local PGLogicalOutputData's lifetime when using the SQL interface. We
29
+ * cannot just pass them a pointer to a palloc'd struct.
30
+ *
31
+ * If the hash table has been destroyed the callbacks know to do nothing.
31
32
*/
32
33
static HTAB * RelMetaCache = NULL ;
33
34
35
+ /*
36
+ * The callback persists across decoding sessions so we should only
37
+ * register it once.
38
+ */
39
+ static bool callback_registered = false;
40
+
34
41
35
42
/*
36
- * Initialize the relation metadata cache if not already initialized.
37
- *
38
- * Purge it if it already exists.
43
+ * Initialize the relation metadata cache for a decoding session.
39
44
*
40
- * The hash table its self must be in CacheMemoryContext or TopMemoryContext
41
- * since it persists outside the decoding session.
45
+ * The hash table is destoyed at the end of a decoding session. While
46
+ * relcache invalidations still exist and will still be invoked, they
47
+ * will just see the null hash table global and take no action.
42
48
*/
43
49
void
44
- pglogical_init_relmetacache (void )
50
+ pglogical_init_relmetacache (MemoryContext decoding_context )
45
51
{
46
52
HASHCTL ctl ;
47
53
48
- if (RelMetaCache == NULL )
49
- {
50
- /* first time, init the cache */
51
- int hash_flags = HASH_ELEM | HASH_CONTEXT ;
54
+ Assert (RelMetaCache == NULL );
52
55
53
- /* Make sure we've initialized CacheMemoryContext. */
54
- if (CacheMemoryContext == NULL )
55
- CreateCacheMemoryContext ();
56
+ /* Make a new hash table for the cache */
57
+ int hash_flags = HASH_ELEM | HASH_CONTEXT ;
56
58
57
- MemSet (& ctl , 0 , sizeof (ctl ));
58
- ctl .keysize = sizeof (Oid );
59
- ctl .entrysize = sizeof (struct PGLRelMetaCacheEntry );
60
- /* safe to allocate to CacheMemoryContext since it's never reset */
61
- ctl .hcxt = CacheMemoryContext ;
59
+ MemSet (& ctl , 0 , sizeof (ctl ));
60
+ ctl .keysize = sizeof (Oid );
61
+ ctl .entrysize = sizeof (struct PGLRelMetaCacheEntry );
62
+ ctl .hcxt = decoding_context ;
62
63
63
64
#if PG_VERSION_NUM >= 90500
64
- hash_flags |= HASH_BLOBS ;
65
+ hash_flags |= HASH_BLOBS ;
65
66
#else
66
- ctl .hash = tag_hash ;
67
- hash_flags |= HASH_FUNCTION ;
67
+ ctl .hash = tag_hash ;
68
+ hash_flags |= HASH_FUNCTION ;
68
69
#endif
69
70
70
- RelMetaCache = hash_create ("pglogical relation metadata cache" , 128 ,
71
- & ctl , hash_flags );
71
+ RelMetaCache = hash_create ("pglogical relation metadata cache" , 128 ,
72
+ & ctl , hash_flags );
72
73
73
- Assert (RelMetaCache != NULL );
74
+ Assert (RelMetaCache != NULL );
74
75
75
- /*
76
- * Watch for invalidation events.
77
- *
78
- * We don't pass PGLogicalOutputData here because it's scoped to the
79
- * individual decoding session, which with the SQL interface has a shorter
80
- * lifetime than the relcache invalidation callback registration. We have
81
- * no way to remove invalidation callbacks at the end of the decoding
82
- * session so we have to cope with them being called later.
83
- */
84
- CacheRegisterRelcacheCallback ( relmeta_cache_callback , ( Datum ) 0 );
85
- }
86
- else
76
+ /*
77
+ * Watch for invalidation events.
78
+ *
79
+ * We don't pass PGLogicalOutputData here because it's scoped to the
80
+ * individual decoding session, which with the SQL interface has a
81
+ * shorter lifetime than the relcache invalidation callback
82
+ * registration. We have no way to remove invalidation callbacks at
83
+ * the end of the decoding session or change them - so we have to
84
+ * cope with them being called later. If we wanted to pass the
85
+ * decoding private data we'd need to stash it in a global.
86
+ */
87
+ if (! callback_registered )
87
88
{
88
- /*
89
- * On re-init we must flush the cache since there could be
90
- * dangling pointers to api_private data in the freed
91
- * decoding context of a prior session. We could go through
92
- * and clear them and the is_cached flag but it seems best
93
- * to have a clean slate.
94
- */
95
- HASH_SEQ_STATUS status ;
96
- struct PGLRelMetaCacheEntry * hentry ;
97
- hash_seq_init (& status , RelMetaCache );
98
-
99
- while ((hentry = (struct PGLRelMetaCacheEntry * ) hash_seq_search (& status )) != NULL )
100
- {
101
- if (hash_search (RelMetaCache ,
102
- (void * ) & hentry -> relid ,
103
- HASH_REMOVE , NULL ) == NULL )
104
- elog (ERROR , "pglogical RelMetaCache hash table corrupted" );
105
- }
106
-
107
- return ;
89
+ CacheRegisterRelcacheCallback (relmeta_cache_callback , (Datum )0 );
90
+ callback_registered = true;
108
91
}
109
92
}
110
93
@@ -115,6 +98,14 @@ pglogical_init_relmetacache(void)
115
98
static void
116
99
relmeta_cache_callback (Datum arg , Oid relid )
117
100
{
101
+ /*
102
+ * We can be called after decoding session teardown becaues the
103
+ * relcache callback isn't cleared. In that case there's no action
104
+ * to take.
105
+ */
106
+ if (RelMetaCache == NULL )
107
+ return ;
108
+
118
109
/*
119
110
* Nobody keeps pointers to entries in this hash table around so
120
111
* it's safe to directly HASH_REMOVE the entries as soon as they are
@@ -176,12 +167,13 @@ pglogical_cache_relmeta(struct PGLogicalOutputData *data,
176
167
177
168
178
169
/*
179
- * Tear down the relation metadata cache.
170
+ * Tear down the relation metadata cache at the end of a decoding
171
+ * session.
180
172
*
181
- * Do *not* call this at decoding shutdown. The hash table must
182
- * continue to exist so that relcache invalidation callbacks can
183
- * continue to reference it after a SQL decoding session finishes.
184
- * It must be called at backend shutdown only .
173
+ * The api_private data need not be freed explicitly; it'll be purged
174
+ * by destruction of the memory context. The main reason we do this
175
+ * much is to make sure we nullify the global, making sure that
176
+ * callbacks will see that there's nothing to do .
185
177
*/
186
178
void
187
179
pglogical_destroy_relmetacache (void )
0 commit comments