/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 | } |