On 2013-11-07 10:10:34 -0500, Tom Lane wrote:
> Andres Freund <and...@2ndquadrant.com> writes:
> > On 2013-11-07 06:49:58 -0800, Kevin Grittner wrote:
> >> It's up to the committer whether to indent
> >> after review and make both substantive and whitespace changes
> >> together, but I have definitely seen those done separately, or even
> >> left for the next global pgindent run to fix.
> 
> > Hm. I don't remember many such cases and I've just looked across the git
> > history and i didn't really find anything after
> > a04161f2eab55f72b3c3dba9baed0ec09e7f633f, but that was back when
> > individuals couldn't run pgindent because of the typedefs file.
> 
> FWIW, I tend to pgindent before committing, now that it's easy to do so.
> But in cases like this, I'd much rather review the patch *before* that
> happens.  Basically the point of the review is to follow and confirm
> the patch author's thought process, and I'll bet you put the braces in
> before reindenting too.

Well, I did both at the same time, I have an emacs command for that
;). But independent from that technicality, reindenting is part of
changing the flow of logic for me - I've spent a couple of years
primarily writing python (where indentation is significant), maybe it's
that.

So, here's the patch (slightly editorialized) with reverted indenting of
that block.

Greetings,

Andres Freund

-- 
 Andres Freund                     http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services
>From 96a637319cbe4bfa58dbd7677a74c31b23e8e248 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Thu, 7 Nov 2013 10:17:28 +0100
Subject: [PATCH] Fix fundamental issues with relfilenodemap's cache
 invalidation logic.

relfilenodemap.c's invalidation callback used to simply delete its
entire hash when a cache reset message arrived and it used to enter a
provisional cache entry into the hash before doing the lookups in
pg_class.
Both is a bad idea because the heap_open() in RelidByRelfilenode() can
cause cache invalidations to be received which could cause us to
access and return free'd memory.

Also fix the possible, but unlikely, scenario where a non-FATAL ERROR
occurs after the hash_create() in InitializeRelfilenodeMap() leaving
RelfilenodeMap in a partially initialized state.

Found by Kevin Grittner.

Needs a pgindent run, but that'd make the diff harder to read.
---
 src/backend/utils/cache/relfilenodemap.c | 111 +++++++++++++++++--------------
 1 file changed, 61 insertions(+), 50 deletions(-)

diff --git a/src/backend/utils/cache/relfilenodemap.c b/src/backend/utils/cache/relfilenodemap.c
index f3f9a09..6b06afb 100644
--- a/src/backend/utils/cache/relfilenodemap.c
+++ b/src/backend/utils/cache/relfilenodemap.c
@@ -57,23 +57,20 @@ RelfilenodeMapInvalidateCallback(Datum arg, Oid relid)
 	HASH_SEQ_STATUS status;
 	RelfilenodeMapEntry *entry;
 
-	/* nothing to do if not active or deleted */
-	if (RelfilenodeMapHash == NULL)
-		return;
-
-	/* if relid is InvalidOid, we must invalidate the entire cache */
-	if (relid == InvalidOid)
-	{
-		hash_destroy(RelfilenodeMapHash);
-		RelfilenodeMapHash = NULL;
-		return;
-	}
+	/* callback only gets registered after creating the hash */
+	Assert(RelfilenodeMapHash != NULL);
 
 	hash_seq_init(&status, RelfilenodeMapHash);
 	while ((entry = (RelfilenodeMapEntry *) hash_seq_search(&status)) != NULL)
 	{
-		/* Same OID may occur in more than one tablespace. */
-		if (entry->relid == relid)
+		/*
+		 * If relid is InvalidOid, signalling a complete reset, we must remove
+		 * all entries, otherwise just remove the specific relation's entry.
+		 * Always remove negative cache entries.
+		 */
+		if (relid == InvalidOid ||		/* complete reset */
+			entry->relid == InvalidOid ||		/* negative cache entry */
+			entry->relid == relid)		/* individual flushed relation */
 		{
 			if (hash_search(RelfilenodeMapHash,
 							(void *) &entry->key,
@@ -92,32 +89,12 @@ static void
 InitializeRelfilenodeMap(void)
 {
 	HASHCTL		ctl;
-	static bool	initial_init_done = false;
-	int i;
+	int			i;
 
 	/* Make sure we've initialized CacheMemoryContext. */
 	if (CacheMemoryContext == NULL)
 		CreateCacheMemoryContext();
 
-	/* Initialize the hash table. */
-	MemSet(&ctl, 0, sizeof(ctl));
-	ctl.keysize = sizeof(RelfilenodeMapKey);
-	ctl.entrysize = sizeof(RelfilenodeMapEntry);
-	ctl.hash = tag_hash;
-	ctl.hcxt = CacheMemoryContext;
-
-	RelfilenodeMapHash =
-		hash_create("RelfilenodeMap cache", 1024, &ctl,
-					HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
-
-	/*
-	 * For complete resets we simply delete the entire hash, but there's no
-	 * need to do the other stuff multiple times. Especially the initialization
-	 * of the relcche invalidation should only be done once.
-	 */
-	if (initial_init_done)
-		return;
-
 	/* build skey */
 	MemSet(&relfilenode_skey, 0, sizeof(relfilenode_skey));
 
@@ -134,10 +111,25 @@ InitializeRelfilenodeMap(void)
 	relfilenode_skey[0].sk_attno = Anum_pg_class_reltablespace;
 	relfilenode_skey[1].sk_attno = Anum_pg_class_relfilenode;
 
+	/* Initialize the hash table. */
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(RelfilenodeMapKey);
+	ctl.entrysize = sizeof(RelfilenodeMapEntry);
+	ctl.hash = tag_hash;
+	ctl.hcxt = CacheMemoryContext;
+
+	/*
+	 * Only create the RelfilenodeMapHash now, so we don't end up partially
+	 * initialized when fmgr_info_cxt() above ERRORs out with an out of memory
+	 * error.
+	 */
+	RelfilenodeMapHash =
+		hash_create("RelfilenodeMap cache", 1024, &ctl,
+					HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
 	/* Watch for invalidation events. */
 	CacheRegisterRelcacheCallback(RelfilenodeMapInvalidateCallback,
 								  (Datum) 0);
-	initial_init_done = true;
 }
 
 /*
@@ -156,6 +148,7 @@ RelidByRelfilenode(Oid reltablespace, Oid relfilenode)
 	Relation relation;
 	HeapTuple ntp;
 	ScanKeyData skey[2];
+	Oid			relid;
 
 	if (RelfilenodeMapHash == NULL)
 		InitializeRelfilenodeMap();
@@ -169,30 +162,37 @@ RelidByRelfilenode(Oid reltablespace, Oid relfilenode)
 	key.relfilenode = relfilenode;
 
 	/*
-	 * Check cache and enter entry if nothing could be found. Even if no target
+	 * Check cache and return entry if one is found. Even if no target
 	 * relation can be found later on we store the negative match and return a
-	 * InvalidOid from cache. That's not really necessary for performance since
-	 * querying invalid values isn't supposed to be a frequent thing, but the
-	 * implementation is simpler this way.
+	 * InvalidOid from cache. That's not really necessary for performance
+	 * since querying invalid values isn't supposed to be a frequent thing,
+	 * but it's basically free.
 	 */
-	entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_ENTER, &found);
+	entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_FIND, &found);
 
 	if (found)
 		return entry->relid;
 
-	/* initialize empty/negative cache entry before doing the actual lookup */
-	entry->relid = InvalidOid;
-
 	/* ok, no previous cache entry, do it the hard way */
 
-	/* check shared tables */
+	/* initialize empty/negative cache entry before doing the actual lookups */
+	relid = InvalidOid;
+
 	if (reltablespace == GLOBALTABLESPACE_OID)
 	{
-		entry->relid = RelationMapFilenodeToOid(relfilenode, true);
-		return entry->relid;
+		/*
+		 * Ok, shared table, check relmapper.
+		 */
+		relid = RelationMapFilenodeToOid(relfilenode, true);
 	}
+	else
+	{
+	/*
+	 * Not a shared table, could either be a plain relation or a
+	 * non-shared, nailed one, like e.g. pg_class.
+	 */
 
-	/* check plain relations by looking in pg_class */
+	/* check for plain relations by looking in pg_class */
 	relation = heap_open(RelationRelationId, AccessShareLock);
 
 	/* copy scankey to local copy, it will be modified during the scan */
@@ -236,7 +236,7 @@ RelidByRelfilenode(Oid reltablespace, Oid relfilenode)
 			Assert(!isnull && check == relfilenode);
 		}
 #endif
-		entry->relid = HeapTupleGetOid(ntp);
+		relid = HeapTupleGetOid(ntp);
 	}
 
 	systable_endscan(scandesc);
@@ -244,7 +244,18 @@ RelidByRelfilenode(Oid reltablespace, Oid relfilenode)
 
 	/* check for tables that are mapped but not shared */
 	if (!found)
-		entry->relid = RelationMapFilenodeToOid(relfilenode, false);
+		relid = RelationMapFilenodeToOid(relfilenode, false);
+	}
+
+	/*
+	 * Only enter entry into cache now, our opening of pg_class could have
+	 * caused cache invalidations to be executed which would have deleted a
+	 * new entry if we had HASH_ENTERed it above.
+	 */
+	entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_ENTER, &found);
+	if (found)
+		elog(ERROR, "corrupted hashtable");
+	entry->relid = relid;
 
-	return entry->relid;
+	return relid;
 }
-- 
1.8.3.251.g1462b67

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to