mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Be more paranoid about OOM in search_path cache.
Recent commitf26c2368dcintroduced 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 inf26c2368dc. 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:
		@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user