Commit 21786db8 authored by Robert Haas's avatar Robert Haas

Fix cache flush hazard in event trigger cache.

Bug spotted by Jeff Davis using -DCLOBBER_CACHE_ALWAYS.
parent 2751740a
...@@ -29,6 +29,13 @@ ...@@ -29,6 +29,13 @@
#include "utils/snapmgr.h" #include "utils/snapmgr.h"
#include "utils/syscache.h" #include "utils/syscache.h"
typedef enum
{
ETCS_NEEDS_REBUILD,
ETCS_REBUILD_STARTED,
ETCS_VALID
} EventTriggerCacheStateType;
typedef struct typedef struct
{ {
EventTriggerEvent event; EventTriggerEvent event;
...@@ -37,6 +44,7 @@ typedef struct ...@@ -37,6 +44,7 @@ typedef struct
static HTAB *EventTriggerCache; static HTAB *EventTriggerCache;
static MemoryContext EventTriggerCacheContext; static MemoryContext EventTriggerCacheContext;
static EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD;
static void BuildEventTriggerCache(void); static void BuildEventTriggerCache(void);
static void InvalidateEventCacheCallback(Datum arg, static void InvalidateEventCacheCallback(Datum arg,
...@@ -55,7 +63,7 @@ EventCacheLookup(EventTriggerEvent event) ...@@ -55,7 +63,7 @@ EventCacheLookup(EventTriggerEvent event)
{ {
EventTriggerCacheEntry *entry; EventTriggerCacheEntry *entry;
if (EventTriggerCache == NULL) if (EventTriggerCacheState != ETCS_VALID)
BuildEventTriggerCache(); BuildEventTriggerCache();
entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL); entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
return entry != NULL ? entry->triggerlist : NULL; return entry != NULL ? entry->triggerlist : NULL;
...@@ -77,12 +85,9 @@ BuildEventTriggerCache(void) ...@@ -77,12 +85,9 @@ BuildEventTriggerCache(void)
if (EventTriggerCacheContext != NULL) if (EventTriggerCacheContext != NULL)
{ {
/* /*
* The cache has been previously built, and subsequently invalidated, * Free up any memory already allocated in EventTriggerCacheContext.
* and now we're trying to rebuild it. Normally, there won't be * This can happen either because a previous rebuild failed, or
* anything in the context at this point, because the invalidation * because an invalidation happened before the rebuild was complete.
* will have already reset it. But if the previous attempt to rebuild
* the cache failed, then there might be some junk lying around
* that we want to reclaim.
*/ */
MemoryContextResetAndDeleteChildren(EventTriggerCacheContext); MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
} }
...@@ -109,12 +114,10 @@ BuildEventTriggerCache(void) ...@@ -109,12 +114,10 @@ BuildEventTriggerCache(void)
/* Switch to correct memory context. */ /* Switch to correct memory context. */
oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext); oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
/* /* Prevent the memory context from being nuked while we're rebuilding. */
* Create a new hash table, but don't assign it to the global variable EventTriggerCacheState = ETCS_REBUILD_STARTED;
* until it accurately represents the state of the catalogs, so that
* if we fail midway through this we won't end up with incorrect cache /* Create new hash table. */
* contents.
*/
MemSet(&ctl, 0, sizeof(ctl)); MemSet(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(EventTriggerEvent); ctl.keysize = sizeof(EventTriggerEvent);
ctl.entrysize = sizeof(EventTriggerCacheEntry); ctl.entrysize = sizeof(EventTriggerCacheEntry);
...@@ -195,8 +198,17 @@ BuildEventTriggerCache(void) ...@@ -195,8 +198,17 @@ BuildEventTriggerCache(void)
/* Restore previous memory context. */ /* Restore previous memory context. */
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
/* Cache is now valid. */ /* Install new cache. */
EventTriggerCache = cache; EventTriggerCache = cache;
/*
* If the cache has been invalidated since we entered this routine, we
* still use and return the cache we just finished constructing, to avoid
* infinite loops, but we leave the cache marked stale so that we'll
* rebuild it again on next access. Otherwise, we mark the cache valid.
*/
if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
EventTriggerCacheState = ETCS_VALID;
} }
/* /*
...@@ -238,6 +250,17 @@ DecodeTextArrayToCString(Datum array, char ***cstringp) ...@@ -238,6 +250,17 @@ DecodeTextArrayToCString(Datum array, char ***cstringp)
static void static void
InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue) InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
{ {
MemoryContextResetAndDeleteChildren(EventTriggerCacheContext); /*
EventTriggerCache = NULL; * If the cache isn't valid, then there might be a rebuild in progress,
* so we can't immediately blow it away. But it's advantageous to do
* this when possible, so as to immediately free memory.
*/
if (EventTriggerCacheState == ETCS_VALID)
{
MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
EventTriggerCache = NULL;
}
/* Mark cache for rebuild. */
EventTriggerCacheState = ETCS_NEEDS_REBUILD;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment