Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/lib/dshash.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * dshash.c
4
 *    Concurrent hash tables backed by dynamic shared memory areas.
5
 *
6
 * This is an open hashing hash table, with a linked list at each table
7
 * entry.  It supports dynamic resizing, as required to prevent the linked
8
 * lists from growing too long on average.  Currently, only growing is
9
 * supported: the hash table never becomes smaller.
10
 *
11
 * To deal with concurrency, it has a fixed size set of partitions, each of
12
 * which is independently locked.  Each bucket maps to a partition; so insert,
13
 * find and iterate operations normally only acquire one lock.  Therefore,
14
 * good concurrency is achieved whenever such operations don't collide at the
15
 * lock partition level.  However, when a resize operation begins, all
16
 * partition locks must be acquired simultaneously for a brief period.  This
17
 * is only expected to happen a small number of times until a stable size is
18
 * found, since growth is geometric.
19
 *
20
 * Future versions may support iterators and incremental resizing; for now
21
 * the implementation is minimalist.
22
 *
23
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
24
 * Portions Copyright (c) 1994, Regents of the University of California
25
 *
26
 * IDENTIFICATION
27
 *    src/backend/lib/dshash.c
28
 *
29
 *-------------------------------------------------------------------------
30
 */
31
32
#include "postgres.h"
33
34
#include <limits.h>
35
36
#include "common/hashfn.h"
37
#include "lib/dshash.h"
38
#include "storage/lwlock.h"
39
#include "utils/dsa.h"
40
41
/*
42
 * An item in the hash table.  This wraps the user's entry object in an
43
 * envelop that holds a pointer back to the bucket and a pointer to the next
44
 * item in the bucket.
45
 */
46
struct dshash_table_item
47
{
48
  /* The next item in the same bucket. */
49
  dsa_pointer next;
50
  /* The hashed key, to avoid having to recompute it. */
51
  dshash_hash hash;
52
  /* The user's entry object follows here.  See ENTRY_FROM_ITEM(item). */
53
};
54
55
/*
56
 * The number of partitions for locking purposes.  This is set to match
57
 * NUM_BUFFER_PARTITIONS for now, on the basis that whatever's good enough for
58
 * the buffer pool must be good enough for any other purpose.  This could
59
 * become a runtime parameter in future.
60
 */
61
0
#define DSHASH_NUM_PARTITIONS_LOG2 7
62
0
#define DSHASH_NUM_PARTITIONS (1 << DSHASH_NUM_PARTITIONS_LOG2)
63
64
/* A magic value used to identify our hash tables. */
65
0
#define DSHASH_MAGIC 0x75ff6a20
66
67
/*
68
 * Tracking information for each lock partition.  Initially, each partition
69
 * corresponds to one bucket, but each time the hash table grows, the buckets
70
 * covered by each partition split so the number of buckets covered doubles.
71
 *
72
 * We might want to add padding here so that each partition is on a different
73
 * cache line, but doing so would bloat this structure considerably.
74
 */
75
typedef struct dshash_partition
76
{
77
  LWLock    lock;     /* Protects all buckets in this partition. */
78
  size_t    count;      /* # of items in this partition's buckets */
79
} dshash_partition;
80
81
/*
82
 * The head object for a hash table.  This will be stored in dynamic shared
83
 * memory.
84
 */
85
typedef struct dshash_table_control
86
{
87
  dshash_table_handle handle;
88
  uint32    magic;
89
  dshash_partition partitions[DSHASH_NUM_PARTITIONS];
90
  int     lwlock_tranche_id;
91
92
  /*
93
   * The following members are written to only when ALL partitions locks are
94
   * held.  They can be read when any one partition lock is held.
95
   */
96
97
  /* Number of buckets expressed as power of 2 (8 = 256 buckets). */
98
  size_t    size_log2;    /* log2(number of buckets) */
99
  dsa_pointer buckets;    /* current bucket array */
100
} dshash_table_control;
101
102
/*
103
 * Per-backend state for a dynamic hash table.
104
 */
105
struct dshash_table
106
{
107
  dsa_area   *area;     /* Backing dynamic shared memory area. */
108
  dshash_parameters params; /* Parameters. */
109
  void     *arg;      /* User-supplied data pointer. */
110
  dshash_table_control *control;  /* Control object in DSM. */
111
  dsa_pointer *buckets;   /* Current bucket pointers in DSM. */
112
  size_t    size_log2;    /* log2(number of buckets) */
113
};
114
115
/* Given a pointer to an item, find the entry (user data) it holds. */
116
#define ENTRY_FROM_ITEM(item) \
117
0
  ((char *)(item) + MAXALIGN(sizeof(dshash_table_item)))
118
119
/* Given a pointer to an entry, find the item that holds it. */
120
#define ITEM_FROM_ENTRY(entry)                      \
121
0
  ((dshash_table_item *)((char *)(entry) -              \
122
0
               MAXALIGN(sizeof(dshash_table_item))))
123
124
/* How many resize operations (bucket splits) have there been? */
125
#define NUM_SPLITS(size_log2)         \
126
0
  (size_log2 - DSHASH_NUM_PARTITIONS_LOG2)
127
128
/* How many buckets are there in a given size? */
129
#define NUM_BUCKETS(size_log2)    \
130
0
  (((size_t) 1) << (size_log2))
131
132
/* How many buckets are there in each partition at a given size? */
133
#define BUCKETS_PER_PARTITION(size_log2)    \
134
0
  (((size_t) 1) << NUM_SPLITS(size_log2))
135
136
/* Max entries before we need to grow.  Half + quarter = 75% load factor. */
137
#define MAX_COUNT_PER_PARTITION(hash_table)       \
138
0
  (BUCKETS_PER_PARTITION(hash_table->size_log2) / 2 + \
139
0
   BUCKETS_PER_PARTITION(hash_table->size_log2) / 4)
140
141
/* Choose partition based on the highest order bits of the hash. */
142
#define PARTITION_FOR_HASH(hash)                    \
143
0
  (hash >> ((sizeof(dshash_hash) * CHAR_BIT) - DSHASH_NUM_PARTITIONS_LOG2))
144
145
/*
146
 * Find the bucket index for a given hash and table size.  Each time the table
147
 * doubles in size, the appropriate bucket for a given hash value doubles and
148
 * possibly adds one, depending on the newly revealed bit, so that all buckets
149
 * are split.
150
 */
151
#define BUCKET_INDEX_FOR_HASH_AND_SIZE(hash, size_log2)   \
152
0
  (hash >> ((sizeof(dshash_hash) * CHAR_BIT) - (size_log2)))
153
154
/* The index of the first bucket in a given partition. */
155
#define BUCKET_INDEX_FOR_PARTITION(partition, size_log2)  \
156
0
  ((partition) << NUM_SPLITS(size_log2))
157
158
/* Choose partition based on bucket index. */
159
#define PARTITION_FOR_BUCKET_INDEX(bucket_idx, size_log2)       \
160
0
  ((bucket_idx) >> NUM_SPLITS(size_log2))
161
162
/* The head of the active bucket for a given hash value (lvalue). */
163
#define BUCKET_FOR_HASH(hash_table, hash)               \
164
0
  (hash_table->buckets[                       \
165
0
    BUCKET_INDEX_FOR_HASH_AND_SIZE(hash,              \
166
0
                     hash_table->size_log2)])
167
168
static void delete_item(dshash_table *hash_table,
169
            dshash_table_item *item);
170
static bool resize(dshash_table *hash_table, size_t new_size_log2,
171
           int flags);
172
static inline void ensure_valid_bucket_pointers(dshash_table *hash_table);
173
static inline dshash_table_item *find_in_bucket(dshash_table *hash_table,
174
                        const void *key,
175
                        dsa_pointer item_pointer);
176
static void insert_item_into_bucket(dshash_table *hash_table,
177
                  dsa_pointer item_pointer,
178
                  dshash_table_item *item,
179
                  dsa_pointer *bucket);
180
static dshash_table_item *insert_into_bucket(dshash_table *hash_table,
181
                       const void *key,
182
                       dsa_pointer *bucket,
183
                       int flags);
184
static bool delete_key_from_bucket(dshash_table *hash_table,
185
                   const void *key,
186
                   dsa_pointer *bucket_head);
187
static bool delete_item_from_bucket(dshash_table *hash_table,
188
                  dshash_table_item *item,
189
                  dsa_pointer *bucket_head);
190
static inline dshash_hash hash_key(dshash_table *hash_table, const void *key);
191
static inline bool equal_keys(dshash_table *hash_table,
192
                const void *a, const void *b);
193
static inline void copy_key(dshash_table *hash_table, void *dest,
194
              const void *src);
195
196
#define PARTITION_LOCK(hash_table, i)     \
197
0
  (&(hash_table)->control->partitions[(i)].lock)
198
199
#define ASSERT_NO_PARTITION_LOCKS_HELD_BY_ME(hash_table) \
200
0
  Assert(!LWLockAnyHeldByMe(&(hash_table)->control->partitions[0].lock, \
201
0
       DSHASH_NUM_PARTITIONS, sizeof(dshash_partition)))
202
203
/*
204
 * Create a new hash table backed by the given dynamic shared area, with the
205
 * given parameters.  The returned object is allocated in backend-local memory
206
 * using the current MemoryContext.  'arg' will be passed through to the
207
 * compare, hash, and copy functions.
208
 */
209
dshash_table *
210
dshash_create(dsa_area *area, const dshash_parameters *params, void *arg)
211
0
{
212
0
  dshash_table *hash_table;
213
0
  dsa_pointer control;
214
215
  /* Allocate the backend-local object representing the hash table. */
216
0
  hash_table = palloc_object(dshash_table);
217
218
  /* Allocate the control object in shared memory. */
219
0
  control = dsa_allocate(area, sizeof(dshash_table_control));
220
221
  /* Set up the local and shared hash table structs. */
222
0
  hash_table->area = area;
223
0
  hash_table->params = *params;
224
0
  hash_table->arg = arg;
225
0
  hash_table->control = dsa_get_address(area, control);
226
0
  hash_table->control->handle = control;
227
0
  hash_table->control->magic = DSHASH_MAGIC;
228
0
  hash_table->control->lwlock_tranche_id = params->tranche_id;
229
230
  /* Set up the array of lock partitions. */
231
0
  {
232
0
    dshash_partition *partitions = hash_table->control->partitions;
233
0
    int     tranche_id = hash_table->control->lwlock_tranche_id;
234
0
    int     i;
235
236
0
    for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
237
0
    {
238
0
      LWLockInitialize(&partitions[i].lock, tranche_id);
239
0
      partitions[i].count = 0;
240
0
    }
241
0
  }
242
243
  /*
244
   * Set up the initial array of buckets.  Our initial size is the same as
245
   * the number of partitions.
246
   */
247
0
  hash_table->control->size_log2 = DSHASH_NUM_PARTITIONS_LOG2;
248
0
  hash_table->control->buckets =
249
0
    dsa_allocate_extended(area,
250
0
                sizeof(dsa_pointer) * DSHASH_NUM_PARTITIONS,
251
0
                DSA_ALLOC_NO_OOM | DSA_ALLOC_ZERO);
252
0
  if (!DsaPointerIsValid(hash_table->control->buckets))
253
0
  {
254
0
    dsa_free(area, control);
255
0
    ereport(ERROR,
256
0
        (errcode(ERRCODE_OUT_OF_MEMORY),
257
0
         errmsg("out of memory"),
258
0
         errdetail("Failed on DSA request of size %zu.",
259
0
               sizeof(dsa_pointer) * DSHASH_NUM_PARTITIONS)));
260
0
  }
261
0
  hash_table->buckets = dsa_get_address(area,
262
0
                      hash_table->control->buckets);
263
0
  hash_table->size_log2 = hash_table->control->size_log2;
264
265
0
  return hash_table;
266
0
}
267
268
/*
269
 * Attach to an existing hash table using a handle.  The returned object is
270
 * allocated in backend-local memory using the current MemoryContext.  'arg'
271
 * will be passed through to the compare and hash functions.
272
 */
273
dshash_table *
274
dshash_attach(dsa_area *area, const dshash_parameters *params,
275
        dshash_table_handle handle, void *arg)
276
0
{
277
0
  dshash_table *hash_table;
278
0
  dsa_pointer control;
279
280
  /* Allocate the backend-local object representing the hash table. */
281
0
  hash_table = palloc_object(dshash_table);
282
283
  /* Find the control object in shared memory. */
284
0
  control = handle;
285
286
  /* Set up the local hash table struct. */
287
0
  hash_table->area = area;
288
0
  hash_table->params = *params;
289
0
  hash_table->arg = arg;
290
0
  hash_table->control = dsa_get_address(area, control);
291
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
292
293
  /*
294
   * These will later be set to the correct values by
295
   * ensure_valid_bucket_pointers(), at which time we'll be holding a
296
   * partition lock for interlocking against concurrent resizing.
297
   */
298
0
  hash_table->buckets = NULL;
299
0
  hash_table->size_log2 = 0;
300
301
0
  return hash_table;
302
0
}
303
304
/*
305
 * Detach from a hash table.  This frees backend-local resources associated
306
 * with the hash table, but the hash table will continue to exist until it is
307
 * either explicitly destroyed (by a backend that is still attached to it), or
308
 * the area that backs it is returned to the operating system.
309
 */
310
void
311
dshash_detach(dshash_table *hash_table)
312
0
{
313
0
  ASSERT_NO_PARTITION_LOCKS_HELD_BY_ME(hash_table);
314
315
  /* The hash table may have been destroyed.  Just free local memory. */
316
0
  pfree(hash_table);
317
0
}
318
319
/*
320
 * Destroy a hash table, returning all memory to the area.  The caller must be
321
 * certain that no other backend will attempt to access the hash table before
322
 * calling this function.  Other backend must explicitly call dshash_detach to
323
 * free up backend-local memory associated with the hash table.  The backend
324
 * that calls dshash_destroy must not call dshash_detach.
325
 */
326
void
327
dshash_destroy(dshash_table *hash_table)
328
0
{
329
0
  size_t    size;
330
0
  size_t    i;
331
332
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
333
0
  ensure_valid_bucket_pointers(hash_table);
334
335
  /* Free all the entries. */
336
0
  size = NUM_BUCKETS(hash_table->size_log2);
337
0
  for (i = 0; i < size; ++i)
338
0
  {
339
0
    dsa_pointer item_pointer = hash_table->buckets[i];
340
341
0
    while (DsaPointerIsValid(item_pointer))
342
0
    {
343
0
      dshash_table_item *item;
344
0
      dsa_pointer next_item_pointer;
345
346
0
      item = dsa_get_address(hash_table->area, item_pointer);
347
0
      next_item_pointer = item->next;
348
0
      dsa_free(hash_table->area, item_pointer);
349
0
      item_pointer = next_item_pointer;
350
0
    }
351
0
  }
352
353
  /*
354
   * Vandalize the control block to help catch programming errors where
355
   * other backends access the memory formerly occupied by this hash table.
356
   */
357
0
  hash_table->control->magic = 0;
358
359
  /* Free the active table and control object. */
360
0
  dsa_free(hash_table->area, hash_table->control->buckets);
361
0
  dsa_free(hash_table->area, hash_table->control->handle);
362
363
0
  pfree(hash_table);
364
0
}
365
366
/*
367
 * Get the DSA area used by this hash table.
368
 */
369
dsa_area *
370
dshash_get_dsa_area(dshash_table *hash_table)
371
0
{
372
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
373
374
0
  return hash_table->area;
375
0
}
376
377
/*
378
 * Get a handle that can be used by other processes to attach to this hash
379
 * table.
380
 */
381
dshash_table_handle
382
dshash_get_hash_table_handle(dshash_table *hash_table)
383
0
{
384
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
385
386
0
  return hash_table->control->handle;
387
0
}
388
389
/*
390
 * Look up an entry, given a key.  Returns a pointer to an entry if one can be
391
 * found with the given key.  Returns NULL if the key is not found.  If a
392
 * non-NULL value is returned, the entry is locked and must be released by
393
 * calling dshash_release_lock.  If an error is raised before
394
 * dshash_release_lock is called, the lock will be released automatically, but
395
 * the caller must take care to ensure that the entry is not left corrupted.
396
 * The lock mode is either shared or exclusive depending on 'exclusive'.
397
 *
398
 * The caller must not hold a lock already.
399
 *
400
 * Note that the lock held is in fact an LWLock, so interrupts will be held on
401
 * return from this function, and not resumed until dshash_release_lock is
402
 * called.  It is a very good idea for the caller to release the lock quickly.
403
 */
404
void *
405
dshash_find(dshash_table *hash_table, const void *key, bool exclusive)
406
0
{
407
0
  dshash_hash hash;
408
0
  size_t    partition;
409
0
  dshash_table_item *item;
410
411
0
  hash = hash_key(hash_table, key);
412
0
  partition = PARTITION_FOR_HASH(hash);
413
414
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
415
0
  ASSERT_NO_PARTITION_LOCKS_HELD_BY_ME(hash_table);
416
417
0
  LWLockAcquire(PARTITION_LOCK(hash_table, partition),
418
0
          exclusive ? LW_EXCLUSIVE : LW_SHARED);
419
0
  ensure_valid_bucket_pointers(hash_table);
420
421
  /* Search the active bucket. */
422
0
  item = find_in_bucket(hash_table, key, BUCKET_FOR_HASH(hash_table, hash));
423
424
0
  if (!item)
425
0
  {
426
    /* Not found. */
427
0
    LWLockRelease(PARTITION_LOCK(hash_table, partition));
428
0
    return NULL;
429
0
  }
430
0
  else
431
0
  {
432
    /* The caller will free the lock by calling dshash_release_lock. */
433
0
    return ENTRY_FROM_ITEM(item);
434
0
  }
435
0
}
436
437
/*
438
 * Find an existing entry in a dshash_table, or insert a new one.
439
 *
440
 * DSHASH_INSERT_NO_OOM causes this function to return NULL when no memory is
441
 * available for the new entry. Otherwise, such allocations will result in
442
 * an ERROR.
443
 *
444
 * Any entry returned by this function is exclusively locked, and the caller
445
 * must release that lock using dshash_release_lock. Notes above dshash_find()
446
 * regarding locking and error handling equally apply here.
447
 *
448
 * On return, *found is set to true if an existing entry was found in the
449
 * hash table, and otherwise false.
450
 *
451
 */
452
void *
453
dshash_find_or_insert_extended(dshash_table *hash_table,
454
                 const void *key,
455
                 bool *found,
456
                 int flags)
457
0
{
458
0
  dshash_hash hash;
459
0
  size_t    partition_index;
460
0
  dshash_partition *partition;
461
0
  dshash_table_item *item;
462
463
0
  hash = hash_key(hash_table, key);
464
0
  partition_index = PARTITION_FOR_HASH(hash);
465
0
  partition = &hash_table->control->partitions[partition_index];
466
467
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
468
0
  ASSERT_NO_PARTITION_LOCKS_HELD_BY_ME(hash_table);
469
470
0
restart:
471
0
  LWLockAcquire(PARTITION_LOCK(hash_table, partition_index),
472
0
          LW_EXCLUSIVE);
473
0
  ensure_valid_bucket_pointers(hash_table);
474
475
  /* Search the active bucket. */
476
0
  item = find_in_bucket(hash_table, key, BUCKET_FOR_HASH(hash_table, hash));
477
478
0
  if (item)
479
0
    *found = true;
480
0
  else
481
0
  {
482
0
    *found = false;
483
484
    /* Check if we are getting too full. */
485
0
    if (partition->count > MAX_COUNT_PER_PARTITION(hash_table))
486
0
    {
487
      /*
488
       * The load factor (= keys / buckets) for all buckets protected by
489
       * this partition is > 0.75.  Presumably the same applies
490
       * generally across the whole hash table (though we don't attempt
491
       * to track that directly to avoid contention on some kind of
492
       * central counter; we just assume that this partition is
493
       * representative).  This is a good time to resize.
494
       *
495
       * Give up our existing lock first, because resizing needs to
496
       * reacquire all the locks in the right order to avoid deadlocks.
497
       */
498
0
      LWLockRelease(PARTITION_LOCK(hash_table, partition_index));
499
0
      if (!resize(hash_table, hash_table->size_log2 + 1, flags))
500
0
      {
501
0
        Assert((flags & DSHASH_INSERT_NO_OOM) != 0);
502
0
        return NULL;
503
0
      }
504
505
0
      goto restart;
506
0
    }
507
508
    /* Finally we can try to insert the new item. */
509
0
    item = insert_into_bucket(hash_table, key,
510
0
                  &BUCKET_FOR_HASH(hash_table, hash),
511
0
                  flags);
512
0
    if (item == NULL)
513
0
    {
514
0
      Assert((flags & DSHASH_INSERT_NO_OOM) != 0);
515
0
      LWLockRelease(PARTITION_LOCK(hash_table, partition_index));
516
0
      return NULL;
517
0
    }
518
0
    item->hash = hash;
519
    /* Adjust per-lock-partition counter for load factor knowledge. */
520
0
    ++partition->count;
521
0
  }
522
523
  /* The caller must release the lock with dshash_release_lock. */
524
0
  return ENTRY_FROM_ITEM(item);
525
0
}
526
527
/*
528
 * Remove an entry by key.  Returns true if the key was found and the
529
 * corresponding entry was removed.
530
 *
531
 * To delete an entry that you already have a pointer to, see
532
 * dshash_delete_entry.
533
 */
534
bool
535
dshash_delete_key(dshash_table *hash_table, const void *key)
536
0
{
537
0
  dshash_hash hash;
538
0
  size_t    partition;
539
0
  bool    found;
540
541
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
542
0
  ASSERT_NO_PARTITION_LOCKS_HELD_BY_ME(hash_table);
543
544
0
  hash = hash_key(hash_table, key);
545
0
  partition = PARTITION_FOR_HASH(hash);
546
547
0
  LWLockAcquire(PARTITION_LOCK(hash_table, partition), LW_EXCLUSIVE);
548
0
  ensure_valid_bucket_pointers(hash_table);
549
550
0
  if (delete_key_from_bucket(hash_table, key,
551
0
                 &BUCKET_FOR_HASH(hash_table, hash)))
552
0
  {
553
0
    Assert(hash_table->control->partitions[partition].count > 0);
554
0
    found = true;
555
0
    --hash_table->control->partitions[partition].count;
556
0
  }
557
0
  else
558
0
    found = false;
559
560
0
  LWLockRelease(PARTITION_LOCK(hash_table, partition));
561
562
0
  return found;
563
0
}
564
565
/*
566
 * Remove an entry.  The entry must already be exclusively locked, and must
567
 * have been obtained by dshash_find or dshash_find_or_insert.  Note that this
568
 * function releases the lock just like dshash_release_lock.
569
 *
570
 * To delete an entry by key, see dshash_delete_key.
571
 */
572
void
573
dshash_delete_entry(dshash_table *hash_table, void *entry)
574
0
{
575
0
  dshash_table_item *item = ITEM_FROM_ENTRY(entry);
576
0
  size_t    partition = PARTITION_FOR_HASH(item->hash);
577
578
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
579
0
  Assert(LWLockHeldByMeInMode(PARTITION_LOCK(hash_table, partition),
580
0
                LW_EXCLUSIVE));
581
582
0
  delete_item(hash_table, item);
583
0
  LWLockRelease(PARTITION_LOCK(hash_table, partition));
584
0
}
585
586
/*
587
 * Unlock an entry which was locked by dshash_find or dshash_find_or_insert.
588
 */
589
void
590
dshash_release_lock(dshash_table *hash_table, void *entry)
591
0
{
592
0
  dshash_table_item *item = ITEM_FROM_ENTRY(entry);
593
0
  size_t    partition_index = PARTITION_FOR_HASH(item->hash);
594
595
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
596
597
0
  LWLockRelease(PARTITION_LOCK(hash_table, partition_index));
598
0
}
599
600
/*
601
 * A compare function that forwards to memcmp.
602
 */
603
int
604
dshash_memcmp(const void *a, const void *b, size_t size, void *arg)
605
0
{
606
0
  return memcmp(a, b, size);
607
0
}
608
609
/*
610
 * A hash function that forwards to tag_hash.
611
 */
612
dshash_hash
613
dshash_memhash(const void *v, size_t size, void *arg)
614
0
{
615
0
  return tag_hash(v, size);
616
0
}
617
618
/*
619
 * A copy function that forwards to memcpy.
620
 */
621
void
622
dshash_memcpy(void *dest, const void *src, size_t size, void *arg)
623
0
{
624
0
  (void) memcpy(dest, src, size);
625
0
}
626
627
/*
628
 * A compare function that forwards to strcmp.
629
 */
630
int
631
dshash_strcmp(const void *a, const void *b, size_t size, void *arg)
632
0
{
633
0
  Assert(strlen((const char *) a) < size);
634
0
  Assert(strlen((const char *) b) < size);
635
636
0
  return strcmp((const char *) a, (const char *) b);
637
0
}
638
639
/*
640
 * A hash function that forwards to string_hash.
641
 */
642
dshash_hash
643
dshash_strhash(const void *v, size_t size, void *arg)
644
0
{
645
0
  Assert(strlen((const char *) v) < size);
646
647
0
  return string_hash((const char *) v, size);
648
0
}
649
650
/*
651
 * A copy function that forwards to strcpy.
652
 */
653
void
654
dshash_strcpy(void *dest, const void *src, size_t size, void *arg)
655
0
{
656
0
  Assert(strlen((const char *) src) < size);
657
658
0
  (void) strcpy((char *) dest, (const char *) src);
659
0
}
660
661
/*
662
 * Sequentially scan through dshash table and return all the elements one by
663
 * one, return NULL when all elements have been returned.
664
 *
665
 * dshash_seq_term needs to be called when a scan finished.  The caller may
666
 * delete returned elements midst of a scan by using dshash_delete_current()
667
 * if exclusive = true.
668
 */
669
void
670
dshash_seq_init(dshash_seq_status *status, dshash_table *hash_table,
671
        bool exclusive)
672
0
{
673
0
  status->hash_table = hash_table;
674
0
  status->curbucket = 0;
675
0
  status->nbuckets = 0;
676
0
  status->curitem = NULL;
677
0
  status->pnextitem = InvalidDsaPointer;
678
0
  status->curpartition = -1;
679
0
  status->exclusive = exclusive;
680
0
}
681
682
/*
683
 * Returns the next element.
684
 *
685
 * Returned elements are locked and the caller may not release the lock. It is
686
 * released by future calls to dshash_seq_next() or dshash_seq_term().
687
 */
688
void *
689
dshash_seq_next(dshash_seq_status *status)
690
0
{
691
0
  dsa_pointer next_item_pointer;
692
693
  /*
694
   * Not yet holding any partition locks. Need to determine the size of the
695
   * hash table, it could have been resized since we were looking last.
696
   * Since we iterate in partition order, we can start by unconditionally
697
   * lock partition 0.
698
   *
699
   * Once we hold the lock, no resizing can happen until the scan ends. So
700
   * we don't need to repeatedly call ensure_valid_bucket_pointers().
701
   */
702
0
  if (status->curpartition == -1)
703
0
  {
704
0
    Assert(status->curbucket == 0);
705
0
    ASSERT_NO_PARTITION_LOCKS_HELD_BY_ME(status->hash_table);
706
707
0
    status->curpartition = 0;
708
709
0
    LWLockAcquire(PARTITION_LOCK(status->hash_table,
710
0
                   status->curpartition),
711
0
            status->exclusive ? LW_EXCLUSIVE : LW_SHARED);
712
713
0
    ensure_valid_bucket_pointers(status->hash_table);
714
715
0
    status->nbuckets =
716
0
      NUM_BUCKETS(status->hash_table->control->size_log2);
717
0
    next_item_pointer = status->hash_table->buckets[status->curbucket];
718
0
  }
719
0
  else
720
0
    next_item_pointer = status->pnextitem;
721
722
0
  Assert(LWLockHeldByMeInMode(PARTITION_LOCK(status->hash_table,
723
0
                         status->curpartition),
724
0
                status->exclusive ? LW_EXCLUSIVE : LW_SHARED));
725
726
  /* Move to the next bucket if we finished the current bucket */
727
0
  while (!DsaPointerIsValid(next_item_pointer))
728
0
  {
729
0
    int     next_partition;
730
731
0
    if (++status->curbucket >= status->nbuckets)
732
0
    {
733
      /* all buckets have been scanned. finish. */
734
0
      return NULL;
735
0
    }
736
737
    /* Check if move to the next partition */
738
0
    next_partition =
739
0
      PARTITION_FOR_BUCKET_INDEX(status->curbucket,
740
0
                     status->hash_table->size_log2);
741
742
0
    if (status->curpartition != next_partition)
743
0
    {
744
      /*
745
       * Move to the next partition. Lock the next partition then
746
       * release the current, not in the reverse order to avoid
747
       * concurrent resizing.  Avoid dead lock by taking lock in the
748
       * same order with resize().
749
       */
750
0
      LWLockAcquire(PARTITION_LOCK(status->hash_table,
751
0
                     next_partition),
752
0
              status->exclusive ? LW_EXCLUSIVE : LW_SHARED);
753
0
      LWLockRelease(PARTITION_LOCK(status->hash_table,
754
0
                     status->curpartition));
755
0
      status->curpartition = next_partition;
756
0
    }
757
758
0
    next_item_pointer = status->hash_table->buckets[status->curbucket];
759
0
  }
760
761
0
  status->curitem =
762
0
    dsa_get_address(status->hash_table->area, next_item_pointer);
763
764
  /*
765
   * The caller may delete the item. Store the next item in case of
766
   * deletion.
767
   */
768
0
  status->pnextitem = status->curitem->next;
769
770
0
  return ENTRY_FROM_ITEM(status->curitem);
771
0
}
772
773
/*
774
 * Terminates the seqscan and release all locks.
775
 *
776
 * Needs to be called after finishing or when exiting a seqscan.
777
 */
778
void
779
dshash_seq_term(dshash_seq_status *status)
780
0
{
781
0
  if (status->curpartition >= 0)
782
0
    LWLockRelease(PARTITION_LOCK(status->hash_table, status->curpartition));
783
0
}
784
785
/*
786
 * Remove the current entry of the seq scan.
787
 */
788
void
789
dshash_delete_current(dshash_seq_status *status)
790
0
{
791
0
  dshash_table *hash_table = status->hash_table;
792
0
  dshash_table_item *item = status->curitem;
793
0
  size_t    partition PG_USED_FOR_ASSERTS_ONLY;
794
795
0
  partition = PARTITION_FOR_HASH(item->hash);
796
797
0
  Assert(status->exclusive);
798
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
799
0
  Assert(LWLockHeldByMeInMode(PARTITION_LOCK(hash_table, partition),
800
0
                LW_EXCLUSIVE));
801
802
0
  delete_item(hash_table, item);
803
0
}
804
805
/*
806
 * Print debugging information about the internal state of the hash table to
807
 * stderr.  The caller must hold no partition locks.
808
 */
809
void
810
dshash_dump(dshash_table *hash_table)
811
0
{
812
0
  size_t    i;
813
0
  size_t    j;
814
815
0
  Assert(hash_table->control->magic == DSHASH_MAGIC);
816
0
  ASSERT_NO_PARTITION_LOCKS_HELD_BY_ME(hash_table);
817
818
0
  for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
819
0
  {
820
0
    Assert(!LWLockHeldByMe(PARTITION_LOCK(hash_table, i)));
821
0
    LWLockAcquire(PARTITION_LOCK(hash_table, i), LW_SHARED);
822
0
  }
823
824
0
  ensure_valid_bucket_pointers(hash_table);
825
826
0
  fprintf(stderr,
827
0
      "hash table size = %zu\n", (size_t) 1 << hash_table->size_log2);
828
0
  for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
829
0
  {
830
0
    dshash_partition *partition = &hash_table->control->partitions[i];
831
0
    size_t    begin = BUCKET_INDEX_FOR_PARTITION(i, hash_table->size_log2);
832
0
    size_t    end = BUCKET_INDEX_FOR_PARTITION(i + 1, hash_table->size_log2);
833
834
0
    fprintf(stderr, "  partition %zu\n", i);
835
0
    fprintf(stderr,
836
0
        "    active buckets (key count = %zu)\n", partition->count);
837
838
0
    for (j = begin; j < end; ++j)
839
0
    {
840
0
      size_t    count = 0;
841
0
      dsa_pointer bucket = hash_table->buckets[j];
842
843
0
      while (DsaPointerIsValid(bucket))
844
0
      {
845
0
        dshash_table_item *item;
846
847
0
        item = dsa_get_address(hash_table->area, bucket);
848
849
0
        bucket = item->next;
850
0
        ++count;
851
0
      }
852
0
      fprintf(stderr, "      bucket %zu (key count = %zu)\n", j, count);
853
0
    }
854
0
  }
855
856
0
  for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
857
0
    LWLockRelease(PARTITION_LOCK(hash_table, i));
858
0
}
859
860
/*
861
 * Delete a locked item to which we have a pointer.
862
 */
863
static void
864
delete_item(dshash_table *hash_table, dshash_table_item *item)
865
0
{
866
0
  size_t    hash = item->hash;
867
0
  size_t    partition = PARTITION_FOR_HASH(hash);
868
869
0
  Assert(LWLockHeldByMe(PARTITION_LOCK(hash_table, partition)));
870
871
0
  if (delete_item_from_bucket(hash_table, item,
872
0
                &BUCKET_FOR_HASH(hash_table, hash)))
873
0
  {
874
0
    Assert(hash_table->control->partitions[partition].count > 0);
875
0
    --hash_table->control->partitions[partition].count;
876
0
  }
877
0
  else
878
0
  {
879
0
    Assert(false);
880
0
  }
881
0
}
882
883
/*
884
 * Grow the hash table if necessary to the requested number of buckets.  The
885
 * requested size must be double some previously observed size.
886
 *
887
 * If an out-of-memory condition is observed, this function returns false if
888
 * flags includes DSHASH_INSERT_NO_OOM, and otherwise throws an ERROR. In all
889
 * other cases, it returns true.
890
 *
891
 * Must be called without any partition lock held.
892
 */
893
static bool
894
resize(dshash_table *hash_table, size_t new_size_log2, int flags)
895
0
{
896
0
  dsa_pointer old_buckets;
897
0
  dsa_pointer new_buckets_shared;
898
0
  dsa_pointer *new_buckets;
899
0
  size_t    size;
900
0
  size_t    new_size = ((size_t) 1) << new_size_log2;
901
0
  size_t    i;
902
0
  int     dsa_flags = DSA_ALLOC_HUGE | DSA_ALLOC_ZERO;
903
904
  /*
905
   * Acquire the locks for all lock partitions.  This is expensive, but we
906
   * shouldn't have to do it many times.
907
   */
908
0
  for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
909
0
  {
910
0
    Assert(!LWLockHeldByMe(PARTITION_LOCK(hash_table, i)));
911
912
0
    LWLockAcquire(PARTITION_LOCK(hash_table, i), LW_EXCLUSIVE);
913
0
    if (i == 0 && hash_table->control->size_log2 >= new_size_log2)
914
0
    {
915
      /*
916
       * Another backend has already increased the size; we can avoid
917
       * obtaining all the locks and return early.
918
       */
919
0
      LWLockRelease(PARTITION_LOCK(hash_table, 0));
920
0
      return true;
921
0
    }
922
0
  }
923
924
0
  Assert(new_size_log2 == hash_table->control->size_log2 + 1);
925
926
  /* Allocate the space for the new table. */
927
0
  if (flags & DSHASH_INSERT_NO_OOM)
928
0
    dsa_flags |= DSA_ALLOC_NO_OOM;
929
0
  new_buckets_shared =
930
0
    dsa_allocate_extended(hash_table->area,
931
0
                sizeof(dsa_pointer) * new_size,
932
0
                dsa_flags);
933
934
  /* If DSHASH_INSERT_NO_OOM was specified, allocation may have failed. */
935
0
  if (!DsaPointerIsValid(new_buckets_shared))
936
0
  {
937
    /* Release all the locks and return without resizing. */
938
0
    for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
939
0
      LWLockRelease(PARTITION_LOCK(hash_table, i));
940
0
    return false;
941
0
  }
942
943
  /*
944
   * We've allocated the new bucket array; all that remains to do now is to
945
   * reinsert all items, which amounts to adjusting all the pointers.
946
   */
947
0
  new_buckets = dsa_get_address(hash_table->area, new_buckets_shared);
948
0
  size = ((size_t) 1) << hash_table->control->size_log2;
949
0
  for (i = 0; i < size; ++i)
950
0
  {
951
0
    dsa_pointer item_pointer = hash_table->buckets[i];
952
953
0
    while (DsaPointerIsValid(item_pointer))
954
0
    {
955
0
      dshash_table_item *item;
956
0
      dsa_pointer next_item_pointer;
957
958
0
      item = dsa_get_address(hash_table->area, item_pointer);
959
0
      next_item_pointer = item->next;
960
0
      insert_item_into_bucket(hash_table, item_pointer, item,
961
0
                  &new_buckets[BUCKET_INDEX_FOR_HASH_AND_SIZE(item->hash,
962
0
                                        new_size_log2)]);
963
0
      item_pointer = next_item_pointer;
964
0
    }
965
0
  }
966
967
  /* Swap the hash table into place and free the old one. */
968
0
  old_buckets = hash_table->control->buckets;
969
0
  hash_table->control->buckets = new_buckets_shared;
970
0
  hash_table->control->size_log2 = new_size_log2;
971
0
  hash_table->buckets = new_buckets;
972
0
  dsa_free(hash_table->area, old_buckets);
973
974
  /* Release all the locks. */
975
0
  for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
976
0
    LWLockRelease(PARTITION_LOCK(hash_table, i));
977
978
0
  return true;
979
0
}
980
981
/*
982
 * Make sure that our backend-local bucket pointers are up to date.  The
983
 * caller must have locked one lock partition, which prevents resize() from
984
 * running concurrently.
985
 */
986
static inline void
987
ensure_valid_bucket_pointers(dshash_table *hash_table)
988
0
{
989
0
  if (hash_table->size_log2 != hash_table->control->size_log2)
990
0
  {
991
0
    hash_table->buckets = dsa_get_address(hash_table->area,
992
0
                        hash_table->control->buckets);
993
0
    hash_table->size_log2 = hash_table->control->size_log2;
994
0
  }
995
0
}
996
997
/*
998
 * Scan a locked bucket for a match, using the provided compare function.
999
 */
1000
static inline dshash_table_item *
1001
find_in_bucket(dshash_table *hash_table, const void *key,
1002
         dsa_pointer item_pointer)
1003
0
{
1004
0
  while (DsaPointerIsValid(item_pointer))
1005
0
  {
1006
0
    dshash_table_item *item;
1007
1008
0
    item = dsa_get_address(hash_table->area, item_pointer);
1009
0
    if (equal_keys(hash_table, key, ENTRY_FROM_ITEM(item)))
1010
0
      return item;
1011
0
    item_pointer = item->next;
1012
0
  }
1013
0
  return NULL;
1014
0
}
1015
1016
/*
1017
 * Insert an already-allocated item into a bucket.
1018
 */
1019
static void
1020
insert_item_into_bucket(dshash_table *hash_table,
1021
            dsa_pointer item_pointer,
1022
            dshash_table_item *item,
1023
            dsa_pointer *bucket)
1024
0
{
1025
0
  Assert(item == dsa_get_address(hash_table->area, item_pointer));
1026
1027
0
  item->next = *bucket;
1028
0
  *bucket = item_pointer;
1029
0
}
1030
1031
/*
1032
 * Allocate space for an entry with the given key and insert it into the
1033
 * provided bucket.  Returns NULL if out of memory and DSHASH_INSERT_NO_OOM
1034
 * was specified in flags.
1035
 */
1036
static dshash_table_item *
1037
insert_into_bucket(dshash_table *hash_table,
1038
           const void *key,
1039
           dsa_pointer *bucket,
1040
           int flags)
1041
0
{
1042
0
  dsa_pointer item_pointer;
1043
0
  dshash_table_item *item;
1044
0
  int     dsa_flags;
1045
1046
0
  dsa_flags = (flags & DSHASH_INSERT_NO_OOM) ? DSA_ALLOC_NO_OOM : 0;
1047
0
  item_pointer = dsa_allocate_extended(hash_table->area,
1048
0
                     hash_table->params.entry_size +
1049
0
                     MAXALIGN(sizeof(dshash_table_item)),
1050
0
                     dsa_flags);
1051
0
  if (!DsaPointerIsValid(item_pointer))
1052
0
    return NULL;
1053
0
  item = dsa_get_address(hash_table->area, item_pointer);
1054
0
  copy_key(hash_table, ENTRY_FROM_ITEM(item), key);
1055
0
  insert_item_into_bucket(hash_table, item_pointer, item, bucket);
1056
0
  return item;
1057
0
}
1058
1059
/*
1060
 * Search a bucket for a matching key and delete it.
1061
 */
1062
static bool
1063
delete_key_from_bucket(dshash_table *hash_table,
1064
             const void *key,
1065
             dsa_pointer *bucket_head)
1066
0
{
1067
0
  while (DsaPointerIsValid(*bucket_head))
1068
0
  {
1069
0
    dshash_table_item *item;
1070
1071
0
    item = dsa_get_address(hash_table->area, *bucket_head);
1072
1073
0
    if (equal_keys(hash_table, key, ENTRY_FROM_ITEM(item)))
1074
0
    {
1075
0
      dsa_pointer next;
1076
1077
0
      next = item->next;
1078
0
      dsa_free(hash_table->area, *bucket_head);
1079
0
      *bucket_head = next;
1080
1081
0
      return true;
1082
0
    }
1083
0
    bucket_head = &item->next;
1084
0
  }
1085
0
  return false;
1086
0
}
1087
1088
/*
1089
 * Delete the specified item from the bucket.
1090
 */
1091
static bool
1092
delete_item_from_bucket(dshash_table *hash_table,
1093
            dshash_table_item *item,
1094
            dsa_pointer *bucket_head)
1095
0
{
1096
0
  while (DsaPointerIsValid(*bucket_head))
1097
0
  {
1098
0
    dshash_table_item *bucket_item;
1099
1100
0
    bucket_item = dsa_get_address(hash_table->area, *bucket_head);
1101
1102
0
    if (bucket_item == item)
1103
0
    {
1104
0
      dsa_pointer next;
1105
1106
0
      next = item->next;
1107
0
      dsa_free(hash_table->area, *bucket_head);
1108
0
      *bucket_head = next;
1109
0
      return true;
1110
0
    }
1111
0
    bucket_head = &bucket_item->next;
1112
0
  }
1113
0
  return false;
1114
0
}
1115
1116
/*
1117
 * Compute the hash value for a key.
1118
 */
1119
static inline dshash_hash
1120
hash_key(dshash_table *hash_table, const void *key)
1121
0
{
1122
0
  return hash_table->params.hash_function(key,
1123
0
                      hash_table->params.key_size,
1124
0
                      hash_table->arg);
1125
0
}
1126
1127
/*
1128
 * Check whether two keys compare equal.
1129
 */
1130
static inline bool
1131
equal_keys(dshash_table *hash_table, const void *a, const void *b)
1132
0
{
1133
0
  return hash_table->params.compare_function(a, b,
1134
0
                         hash_table->params.key_size,
1135
0
                         hash_table->arg) == 0;
1136
0
}
1137
1138
/*
1139
 * Copy a key.
1140
 */
1141
static inline void
1142
copy_key(dshash_table *hash_table, void *dest, const void *src)
1143
0
{
1144
0
  hash_table->params.copy_function(dest, src,
1145
0
                   hash_table->params.key_size,
1146
0
                   hash_table->arg);
1147
0
}