]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Correct serious bug in hashtable expansion routine: under the
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 31 May 1999 17:01:52 +0000 (17:01 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 31 May 1999 17:01:52 +0000 (17:01 +0000)
right circumstances it would leave old and new bucket headers pointing to
the same list of records.

src/backend/utils/hash/dynahash.c

index 933533b754958cf1c8d0fa9252438a102920292c..28ee6a15dee9bf66fc5acc497b2f1ba57a4715a1 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/hash/dynahash.c,v 1.22 1999/05/25 16:12:28 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/utils/hash/dynahash.c,v 1.23 1999/05/31 17:01:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include          "utils/memutils.h"
 
 /*
- * Fast arithmetic, relying on powers of 2,
- * and on pre-processor concatenation property
+ * Fast MOD arithmetic, assuming that y is a power of 2 !
  */
 
 #define MOD(x,y)                          ((x) & ((y)-1))
 
-/*
- * external routines
- */
-
 /*
  * Private function prototypes
  */
 static long *DynaHashAlloc(unsigned int size);
 static void DynaHashFree(Pointer ptr);
-static uint32 call_hash(HTAB *hashp, char *k, int len);
+static uint32 call_hash(HTAB *hashp, char *k);
 static SEG_OFFSET seg_alloc(HTAB *hashp);
 static int     bucket_alloc(HTAB *hashp);
 static int     dir_realloc(HTAB *hashp);
+static int     expand_table(HTAB *hashp);
+static int     hdefault(HTAB *hashp);
+static int     init_htab(HTAB *hashp, int nelem);
 
 typedef long *((*dhalloc_ptr) ());
 
@@ -118,15 +116,6 @@ DynaHashFree(Pointer ptr)
 
 #endif  /* FRONTEND */
 
-/* ----------------
- * Internal routines
- * ----------------
- */
-
-static int     expand_table(HTAB *hashp);
-static int     hdefault(HTAB *hashp);
-static int     init_htab(HTAB *hashp, int nelem);
-
 
 /*
  * pointer access macros.  Shared memory implementation cannot
@@ -220,6 +209,8 @@ hash_create(int nelem, HASHCTL *info, int flags)
        {
                hctl->ssize = info->ssize;
                hctl->sshift = my_log2(info->ssize);
+               /* ssize had better be a power of 2 */
+               Assert(hctl->ssize == (1L << hctl->sshift));
        }
        if (flags & HASH_FFACTOR)
                hctl->ffactor = info->ffactor;
@@ -412,6 +403,8 @@ hash_estimate_size(long num_entries, long keysize, long datasize)
  * XXX this sure looks thoroughly broken to me --- tgl 2/99.
  * It's freeing every entry individually --- but they weren't
  * allocated individually, see bucket_alloc!!  Why doesn't it crash?
+ * ANSWER: it probably does crash, but is never invoked in normal
+ * operations...
  */
 
 void
@@ -479,14 +472,13 @@ hash_stats(char *where, HTAB *hashp)
 /*******************************SEARCH ROUTINES *****************************/
 
 static uint32
-call_hash(HTAB *hashp, char *k, int len)
+call_hash(HTAB *hashp, char *k)
 {
+       HHDR       *hctl = hashp->hctl;
        long            hash_val,
                                bucket;
-       HHDR       *hctl;
 
-       hctl = hashp->hctl;
-       hash_val = hashp->hash(k, len);
+       hash_val = hashp->hash(k, (int) hctl->keysize);
 
        bucket = hash_val & hctl->high_mask;
        if (bucket > hctl->max_bucket)
@@ -550,9 +542,9 @@ hash_search(HTAB *hashp,
        }
        else
        {
-               bucket = call_hash(hashp, keyPtr, hctl->keysize);
+               bucket = call_hash(hashp, keyPtr);
                segment_num = bucket >> hctl->sshift;
-               segment_ndx = bucket & (hctl->ssize - 1);
+               segment_ndx = MOD(bucket, hctl->ssize);
 
                segp = GET_SEG(hashp, segment_num);
 
@@ -598,8 +590,10 @@ hash_search(HTAB *hashp,
                                Assert(hctl->nkeys > 0);
                                hctl->nkeys--;
 
-                               /* add the bucket to the freelist for this table.  */
+                               /* remove record from hash bucket's chain. */
                                *prevIndexPtr = curr->next;
+
+                               /* add the record to the freelist for this table.  */
                                curr->next = hctl->freeBucketIndex;
                                hctl->freeBucketIndex = currIndex;
 
@@ -639,7 +633,6 @@ hash_search(HTAB *hashp,
        currIndex = hctl->freeBucketIndex;
        if (currIndex == INVALID_INDEX)
        {
-
                /* no free elements.  allocate another chunk of buckets */
                if (!bucket_alloc(hashp))
                        return NULL;
@@ -722,7 +715,7 @@ hash_seq(HTAB *hashp)
                 * initialize the search within this bucket.
                 */
                segment_num = curBucket >> hctl->sshift;
-               segment_ndx = curBucket & (hctl->ssize - 1);
+               segment_ndx = MOD(curBucket, hctl->ssize);
 
                /*
                 * first find the right segment in the table directory.
@@ -751,6 +744,10 @@ hash_seq(HTAB *hashp)
 
 
 /********************************* UTILITIES ************************/
+
+/*
+ * Expand the table by adding one more hash bucket.
+ */
 static int
 expand_table(HTAB *hashp)
 {
@@ -790,19 +787,31 @@ expand_table(HTAB *hashp)
                hctl->nsegs++;
        }
 
-       /* OK, we got a new bucket */
+       /* OK, we created a new bucket */
        hctl->max_bucket++;
-       old_bucket = (hctl->max_bucket & hctl->low_mask);
 
+       /*
+        * *Before* changing masks, find old bucket corresponding to same hash
+        * values; values in that bucket may need to be relocated to new bucket.
+        * Note that new_bucket is certainly larger than low_mask at this point,
+        * so we can skip the first step of the regular hash mask calc.
+        */
+       old_bucket = (new_bucket & hctl->low_mask);
+
+       /*
+        * If we crossed a power of 2, readjust masks.
+        */
        if (new_bucket > hctl->high_mask)
        {
-               /* Starting a new doubling */
                hctl->low_mask = hctl->high_mask;
                hctl->high_mask = new_bucket | hctl->low_mask;
        }
 
        /*
-        * Relocate records to the new bucket
+        * Relocate records to the new bucket.  NOTE: because of the way the
+        * hash masking is done in call_hash, only one old bucket can need to
+        * be split at this point.  With a different way of reducing the hash
+        * value, that might not be true!
         */
        old_segnum = old_bucket >> hctl->sshift;
        old_segndx = MOD(old_bucket, hctl->ssize);
@@ -816,12 +825,9 @@ expand_table(HTAB *hashp)
                 chainIndex != INVALID_INDEX;
                 chainIndex = nextIndex)
        {
-
                chain = GET_BUCKET(hashp, chainIndex);
                nextIndex = chain->next;
-               if (call_hash(hashp,
-                                         (char *) &(chain->key),
-                                         hctl->keysize) == old_bucket)
+               if (call_hash(hashp, (char *) &(chain->key)) == old_bucket)
                {
                        *old = chainIndex;
                        old = &chain->next;
@@ -831,8 +837,10 @@ expand_table(HTAB *hashp)
                        *newbi = chainIndex;
                        newbi = &chain->next;
                }
-               chain->next = INVALID_INDEX;
        }
+       /* don't forget to terminate the rebuilt hash chains... */
+       *old = INVALID_INDEX;
+       *newbi = INVALID_INDEX;
        return 1;
 }
 
@@ -907,15 +915,13 @@ bucket_alloc(HTAB *hashp)
        /* make sure its aligned correctly */
        bucketSize = MAXALIGN(bucketSize);
 
-       /*
-        * tmpIndex is the shmem offset into the first bucket of the array.
-        */
        tmpBucket = (ELEMENT *)
                hashp->alloc((unsigned long) BUCKET_ALLOC_INCR * bucketSize);
 
        if (!tmpBucket)
                return 0;
 
+       /* tmpIndex is the shmem offset into the first bucket of the array */
        tmpIndex = MAKE_HASHOFFSET(hashp, tmpBucket);
 
        /* set the freebucket list to point to the first bucket */