Be more paranoid about OOM in search_path cache.

Recent commit f26c2368dc introduced a search_path cache, but left some
potential out-of-memory hazards. Simplify the code and make it safer
against OOM.

This change reintroduces one list_copy(), losing a small amount of the
performance gained in f26c2368dc. A future change may optimize away
the list_copy() again if it can be done in a safer way.

Discussion: https://postgr.es/m/e6fded24cb8a2c53d4ef069d9f69cc7baaafe9ef.camel@j-davis.com
This commit is contained in:
Jeff Davis 2023-11-20 15:21:30 -08:00
parent 3650e7a393
commit 8efa301532

View File

@ -279,42 +279,23 @@ spcachekey_equal(SearchPathCacheKey a, SearchPathCacheKey b)
static nsphash_hash * SearchPathCache = NULL; static nsphash_hash * SearchPathCache = NULL;
/* /*
* Create search path cache. * Create or reset search_path cache as necessary.
*/ */
static void static void
spcache_init(void) spcache_init(void)
{ {
Assert(SearchPathCacheContext); Assert(SearchPathCacheContext);
if (SearchPathCache) if (SearchPathCache && searchPathCacheValid &&
SearchPathCache->members < SPCACHE_RESET_THRESHOLD)
return; return;
MemoryContextReset(SearchPathCacheContext);
/* arbitrary initial starting size of 16 elements */ /* arbitrary initial starting size of 16 elements */
SearchPathCache = nsphash_create(SearchPathCacheContext, 16, NULL); SearchPathCache = nsphash_create(SearchPathCacheContext, 16, NULL);
searchPathCacheValid = true; searchPathCacheValid = true;
} }
/*
* Reset and reinitialize search path cache.
*/
static void
spcache_reset(void)
{
Assert(SearchPathCacheContext);
Assert(SearchPathCache);
MemoryContextReset(SearchPathCacheContext);
SearchPathCache = NULL;
spcache_init();
}
static uint32
spcache_members(void)
{
return SearchPathCache->members;
}
/* /*
* Look up or insert entry in search path cache. * Look up or insert entry in search path cache.
* *
@ -325,27 +306,25 @@ static SearchPathCacheEntry *
spcache_insert(const char *searchPath, Oid roleid) spcache_insert(const char *searchPath, Oid roleid)
{ {
SearchPathCacheEntry *entry; SearchPathCacheEntry *entry;
bool found;
SearchPathCacheKey cachekey = { SearchPathCacheKey cachekey = {
.searchPath = searchPath, .searchPath = searchPath,
.roleid = roleid .roleid = roleid
}; };
/* /*
* If a new entry is created, we must ensure that it's properly * searchPath is not saved in SearchPathCacheContext. First perform a
* initialized. Set the cache invalid temporarily, so that if the * lookup, and copy searchPath only if we need to create a new entry.
* MemoryContextStrdup() below raises an OOM, the cache will be reset on
* the next use, clearing the uninitialized entry.
*/ */
searchPathCacheValid = false; entry = nsphash_lookup(SearchPathCache, cachekey);
entry = nsphash_insert(SearchPathCache, cachekey, &found); if (!entry)
/* ensure that key is initialized and the rest is zeroed */
if (!found)
{ {
entry->key.searchPath = MemoryContextStrdup(SearchPathCacheContext, searchPath); bool found;
entry->key.roleid = roleid;
cachekey.searchPath = MemoryContextStrdup(SearchPathCacheContext, searchPath);
entry = nsphash_insert(SearchPathCache, cachekey, &found);
Assert(!found);
entry->oidlist = NIL; entry->oidlist = NIL;
entry->finalPath = NIL; entry->finalPath = NIL;
entry->firstNS = InvalidOid; entry->firstNS = InvalidOid;
@ -354,7 +333,6 @@ spcache_insert(const char *searchPath, Oid roleid)
/* do not touch entry->status, used by simplehash */ /* do not touch entry->status, used by simplehash */
} }
searchPathCacheValid = true;
return entry; return entry;
} }
@ -4183,31 +4161,15 @@ finalNamespacePath(List *oidlist, Oid *firstNS)
/* /*
* Retrieve search path information from the cache; or if not there, fill * Retrieve search path information from the cache; or if not there, fill
* it. The returned entry is valid only until the next call to this function. * it. The returned entry is valid only until the next call to this function.
*
* We also determine if the newly-computed finalPath is the same as the
* prevPath passed by the caller (i.e. a no-op or a real change?). It's more
* efficient to check for a change in this function than the caller, because
* we can avoid unnecessary temporary copies of the previous path.
*/ */
static const SearchPathCacheEntry * static const SearchPathCacheEntry *
cachedNamespacePath(const char *searchPath, Oid roleid, List *prevPath, cachedNamespacePath(const char *searchPath, Oid roleid)
bool *same)
{ {
MemoryContext oldcxt; MemoryContext oldcxt;
SearchPathCacheEntry *entry; SearchPathCacheEntry *entry;
List *prevPathCopy = NIL;
spcache_init(); spcache_init();
/* invalidate cache if necessary */
if (!searchPathCacheValid || spcache_members() >= SPCACHE_RESET_THRESHOLD)
{
/* prevPath will be destroyed; make temp copy for later comparison */
prevPathCopy = list_copy(prevPath);
prevPath = prevPathCopy;
spcache_reset();
}
entry = spcache_insert(searchPath, roleid); entry = spcache_insert(searchPath, roleid);
/* /*
@ -4232,38 +4194,22 @@ cachedNamespacePath(const char *searchPath, Oid roleid, List *prevPath,
if (entry->finalPath == NIL || object_access_hook || if (entry->finalPath == NIL || object_access_hook ||
entry->forceRecompute) entry->forceRecompute)
{ {
/* list_free(entry->finalPath);
* Do not free the stale value of entry->finalPath until we've entry->finalPath = NIL;
* performed the comparison, in case it's aliased by prevPath (which
* can only happen when recomputing due to an object_access_hook).
*/
List *finalPath;
oldcxt = MemoryContextSwitchTo(SearchPathCacheContext); oldcxt = MemoryContextSwitchTo(SearchPathCacheContext);
finalPath = finalNamespacePath(entry->oidlist, entry->finalPath = finalNamespacePath(entry->oidlist,
&entry->firstNS); &entry->firstNS);
MemoryContextSwitchTo(oldcxt); MemoryContextSwitchTo(oldcxt);
*same = equal(prevPath, finalPath);
list_free(entry->finalPath);
entry->finalPath = finalPath;
/* /*
* If an object_access_hook set when finalPath is calculated, the * If an object_access_hook is set when finalPath is calculated, the
* result may be affected by the hook. Force recomputation of * result may be affected by the hook. Force recomputation of
* finalPath the next time this cache entry is used, even if the * finalPath the next time this cache entry is used, even if the
* object_access_hook is not set at that time. * object_access_hook is not set at that time.
*/ */
entry->forceRecompute = object_access_hook ? true : false; entry->forceRecompute = object_access_hook ? true : false;
} }
else
{
/* use cached version of finalPath */
*same = equal(prevPath, entry->finalPath);
}
list_free(prevPathCopy);
return entry; return entry;
} }
@ -4275,7 +4221,6 @@ static void
recomputeNamespacePath(void) recomputeNamespacePath(void)
{ {
Oid roleid = GetUserId(); Oid roleid = GetUserId();
bool newPathEqual;
bool pathChanged; bool pathChanged;
const SearchPathCacheEntry *entry; const SearchPathCacheEntry *entry;
@ -4283,24 +4228,32 @@ recomputeNamespacePath(void)
if (baseSearchPathValid && namespaceUser == roleid) if (baseSearchPathValid && namespaceUser == roleid)
return; return;
entry = cachedNamespacePath(namespace_search_path, roleid, baseSearchPath, entry = cachedNamespacePath(namespace_search_path, roleid);
&newPathEqual);
if (baseCreationNamespace == entry->firstNS && if (baseCreationNamespace == entry->firstNS &&
baseTempCreationPending == entry->temp_missing && baseTempCreationPending == entry->temp_missing &&
newPathEqual) equal(entry->finalPath, baseSearchPath))
{ {
pathChanged = false; pathChanged = false;
} }
else else
{ {
pathChanged = true; MemoryContext oldcxt;
} List *newpath;
/* Now safe to assign to state variables. */ pathChanged = true;
baseSearchPath = entry->finalPath;
baseCreationNamespace = entry->firstNS; /* Must save OID list in permanent storage. */
baseTempCreationPending = entry->temp_missing; oldcxt = MemoryContextSwitchTo(TopMemoryContext);
newpath = list_copy(entry->finalPath);
MemoryContextSwitchTo(oldcxt);
/* Now safe to assign to state variables. */
list_free(baseSearchPath);
baseSearchPath = newpath;
baseCreationNamespace = entry->firstNS;
baseTempCreationPending = entry->temp_missing;
}
/* Mark the path valid. */ /* Mark the path valid. */
baseSearchPathValid = true; baseSearchPathValid = true;