mirror of
https://github.com/postgres/postgres.git
synced 2025-06-14 18:42:34 +03:00
Fix cache flush hazard in event trigger cache.
Bug spotted by Jeff Davis using -DCLOBBER_CACHE_ALWAYS.
This commit is contained in:
55
src/backend/utils/cache/evtcache.c
vendored
55
src/backend/utils/cache/evtcache.c
vendored
@ -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
|
|||||||
|
|
||||||
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)
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
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)
|
|||||||
/* 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)
|
|||||||
/* 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)
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user