Copilot commented on code in PR #2421: URL: https://github.com/apache/age/pull/2421#discussion_r3171526315
########## src/backend/utils/cache/agehash.c: ########## @@ -0,0 +1,687 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * agehash.c - Robin Hood open-addressing hashtable for AGE. + * + * See agehash.h for the public contract. This file implements the INLINE + * mode only. + * + * Internal slot layout (INLINE): + * + * bytes 0..1 uint16 probe_dist (AGEHASH_EMPTY = 0xFFFF marks empty) + * bytes 2..3 uint16 reserved (future tombstone / flag bits) + * bytes 4..7 uint32 pad (forces key to 8-byte alignment) + * bytes 8..K+7 key + * bytes K+8.. payload + * + * slot_size = MAXALIGN(8 + key_size + payload_size). + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/agehash.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + +/* ------------------------------------------------------------------------- */ + +struct AgeHashTable +{ + /* Slot array: capacity * slot_size bytes, palloc'd in mcxt. */ + char *slots; + uint32 capacity; /* always a power of two */ + uint32 capacity_mask; /* capacity - 1 */ + uint32 size; /* live entries */ + uint32 max_size; /* size at which we grow */ + uint32 slot_size; /* total bytes per slot */ + uint32 key_size; + uint32 payload_size; + uint32 payload_offset; /* AGEHASH_SLOT_KEY_OFFSET + key_size */ + AgeHashMode mode; + bool frozen; + agehash_hash_fn hash_fn; + agehash_keyeq_fn keyeq_fn; + MemoryContext mcxt; +}; + +/* ------------------------------------------------------------------------- */ +/* Slot accessors. */ + +static inline char * +slot_at(AgeHashTable *t, uint32 idx) +{ + return t->slots + (Size) idx * t->slot_size; +} + +static inline uint16 +slot_probe_dist(const char *slot) +{ + uint16 d; + memcpy(&d, slot, sizeof(uint16)); + return d; +} + +static inline void +slot_set_probe_dist(char *slot, uint16 d) +{ + memcpy(slot, &d, sizeof(uint16)); +} + +static inline char * +slot_key_ptr(AgeHashTable *t, char *slot) +{ + (void) t; + return slot + AGEHASH_SLOT_KEY_OFFSET; +} + +static inline char * +slot_payload_ptr(AgeHashTable *t, char *slot) +{ + return slot + t->payload_offset; +} + +/* ------------------------------------------------------------------------- */ +/* Construction. */ + +static uint32 +next_pow2(uint32 v) +{ + uint32 p = 1; + while (p < v) + p <<= 1; + return p; +} + +AgeHashTable * +agehash_create_inline(MemoryContext mcxt, + Size key_size, + Size payload_size, + uint32 capacity_hint, + agehash_hash_fn hash_fn, + agehash_keyeq_fn keyeq_fn) +{ + AgeHashTable *t; + MemoryContext oldctx; + uint32 min_cap; + uint32 cap; + + Assert(mcxt != NULL); + Assert(key_size > 0 && key_size <= 64); + Assert(payload_size > 0 && payload_size <= 4096); + Assert(hash_fn != NULL); + Assert(keyeq_fn != NULL); + + oldctx = MemoryContextSwitchTo(mcxt); + + t = palloc0(sizeof(AgeHashTable)); + t->mcxt = mcxt; + t->mode = AGEHASH_INLINE; + t->frozen = false; + t->hash_fn = hash_fn; + t->keyeq_fn = keyeq_fn; + t->key_size = (uint32) key_size; + t->payload_size = (uint32) payload_size; + t->payload_offset = AGEHASH_SLOT_KEY_OFFSET + (uint32) key_size; + t->slot_size = MAXALIGN(t->payload_offset + (uint32) payload_size); Review Comment: INLINE slot layout sets payload_offset = 8 + key_size without any additional alignment, yet callers treat the returned payload pointer as a typed struct pointer. If key_size is not a multiple of MAXIMUM_ALIGNOF, the payload pointer can become misaligned on architectures with strict alignment, causing undefined behavior. Either (a) document/enforce an alignment requirement on key_size, or (b) add padding so payload_offset is MAXALIGN'd and adjust the key-from-payload recovery API accordingly. ########## src/backend/utils/adt/age_vle.c: ########## @@ -900,23 +901,24 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } /* - * Helper function to get the specified edge's state. If it does not find it, it - * creates and initializes it. + * Helper function to get the specified edge's state, using a precomputed hash + * value. The dynahash table keyed on graphid is shared with edge_hashtable + * elsewhere, so callers can compute graphid_hash() once and reuse it for + * lookups in both tables. Review Comment: This comment still refers to sharing a dynahash table with the "edge_hashtable", but the global edge structure is now agehash-backed (edge_table). Consider updating the wording to reflect that the precomputed graphid_hash is reused across the dynahash edge_state_hashtable and the agehash edge_table lookup, to avoid confusion when maintaining this hot-path code. ########## src/backend/utils/cache/agehash.c: ########## @@ -0,0 +1,687 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * agehash.c - Robin Hood open-addressing hashtable for AGE. + * + * See agehash.h for the public contract. This file implements the INLINE + * mode only. + * + * Internal slot layout (INLINE): + * + * bytes 0..1 uint16 probe_dist (AGEHASH_EMPTY = 0xFFFF marks empty) + * bytes 2..3 uint16 reserved (future tombstone / flag bits) + * bytes 4..7 uint32 pad (forces key to 8-byte alignment) + * bytes 8..K+7 key + * bytes K+8.. payload + * + * slot_size = MAXALIGN(8 + key_size + payload_size). + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/agehash.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + +/* ------------------------------------------------------------------------- */ + +struct AgeHashTable +{ + /* Slot array: capacity * slot_size bytes, palloc'd in mcxt. */ + char *slots; + uint32 capacity; /* always a power of two */ + uint32 capacity_mask; /* capacity - 1 */ + uint32 size; /* live entries */ + uint32 max_size; /* size at which we grow */ + uint32 slot_size; /* total bytes per slot */ + uint32 key_size; + uint32 payload_size; + uint32 payload_offset; /* AGEHASH_SLOT_KEY_OFFSET + key_size */ + AgeHashMode mode; + bool frozen; + agehash_hash_fn hash_fn; + agehash_keyeq_fn keyeq_fn; + MemoryContext mcxt; +}; + +/* ------------------------------------------------------------------------- */ +/* Slot accessors. */ + +static inline char * +slot_at(AgeHashTable *t, uint32 idx) +{ + return t->slots + (Size) idx * t->slot_size; +} + +static inline uint16 +slot_probe_dist(const char *slot) +{ + uint16 d; + memcpy(&d, slot, sizeof(uint16)); + return d; +} + +static inline void +slot_set_probe_dist(char *slot, uint16 d) +{ + memcpy(slot, &d, sizeof(uint16)); +} + +static inline char * +slot_key_ptr(AgeHashTable *t, char *slot) +{ + (void) t; + return slot + AGEHASH_SLOT_KEY_OFFSET; +} + +static inline char * +slot_payload_ptr(AgeHashTable *t, char *slot) +{ + return slot + t->payload_offset; +} + +/* ------------------------------------------------------------------------- */ +/* Construction. */ + +static uint32 +next_pow2(uint32 v) +{ + uint32 p = 1; + while (p < v) + p <<= 1; + return p; +} + +AgeHashTable * +agehash_create_inline(MemoryContext mcxt, + Size key_size, + Size payload_size, + uint32 capacity_hint, + agehash_hash_fn hash_fn, + agehash_keyeq_fn keyeq_fn) +{ + AgeHashTable *t; + MemoryContext oldctx; + uint32 min_cap; + uint32 cap; + + Assert(mcxt != NULL); + Assert(key_size > 0 && key_size <= 64); + Assert(payload_size > 0 && payload_size <= 4096); + Assert(hash_fn != NULL); + Assert(keyeq_fn != NULL); + Review Comment: agehash_create_inline() only Assert()s key_size/payload_size bounds, but the implementation relies on fixed-size on-stack buffers (carry_key[64], carry_payload[4096]) during inserts. In non-assert builds, a caller can create a table with larger sizes and trigger a stack buffer overflow/memory corruption on insert. Please enforce these limits with a runtime check (elog/ereport) or remove the fixed-size buffers in favor of size-based allocation. ########## src/backend/utils/cache/agehash.c: ########## @@ -0,0 +1,687 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * agehash.c - Robin Hood open-addressing hashtable for AGE. + * + * See agehash.h for the public contract. This file implements the INLINE + * mode only. + * + * Internal slot layout (INLINE): + * + * bytes 0..1 uint16 probe_dist (AGEHASH_EMPTY = 0xFFFF marks empty) + * bytes 2..3 uint16 reserved (future tombstone / flag bits) + * bytes 4..7 uint32 pad (forces key to 8-byte alignment) + * bytes 8..K+7 key + * bytes K+8.. payload + * + * slot_size = MAXALIGN(8 + key_size + payload_size). + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/agehash.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + +/* ------------------------------------------------------------------------- */ + +struct AgeHashTable +{ + /* Slot array: capacity * slot_size bytes, palloc'd in mcxt. */ + char *slots; + uint32 capacity; /* always a power of two */ + uint32 capacity_mask; /* capacity - 1 */ + uint32 size; /* live entries */ + uint32 max_size; /* size at which we grow */ + uint32 slot_size; /* total bytes per slot */ + uint32 key_size; + uint32 payload_size; + uint32 payload_offset; /* AGEHASH_SLOT_KEY_OFFSET + key_size */ + AgeHashMode mode; + bool frozen; + agehash_hash_fn hash_fn; + agehash_keyeq_fn keyeq_fn; + MemoryContext mcxt; +}; + +/* ------------------------------------------------------------------------- */ +/* Slot accessors. */ + +static inline char * +slot_at(AgeHashTable *t, uint32 idx) +{ + return t->slots + (Size) idx * t->slot_size; +} + +static inline uint16 +slot_probe_dist(const char *slot) +{ + uint16 d; + memcpy(&d, slot, sizeof(uint16)); + return d; +} + +static inline void +slot_set_probe_dist(char *slot, uint16 d) +{ + memcpy(slot, &d, sizeof(uint16)); +} + +static inline char * +slot_key_ptr(AgeHashTable *t, char *slot) +{ + (void) t; + return slot + AGEHASH_SLOT_KEY_OFFSET; +} + +static inline char * +slot_payload_ptr(AgeHashTable *t, char *slot) +{ + return slot + t->payload_offset; +} + +/* ------------------------------------------------------------------------- */ +/* Construction. */ + +static uint32 +next_pow2(uint32 v) +{ + uint32 p = 1; + while (p < v) + p <<= 1; + return p; +} + +AgeHashTable * +agehash_create_inline(MemoryContext mcxt, + Size key_size, + Size payload_size, + uint32 capacity_hint, + agehash_hash_fn hash_fn, + agehash_keyeq_fn keyeq_fn) +{ + AgeHashTable *t; + MemoryContext oldctx; + uint32 min_cap; + uint32 cap; + + Assert(mcxt != NULL); + Assert(key_size > 0 && key_size <= 64); + Assert(payload_size > 0 && payload_size <= 4096); + Assert(hash_fn != NULL); + Assert(keyeq_fn != NULL); + + oldctx = MemoryContextSwitchTo(mcxt); + + t = palloc0(sizeof(AgeHashTable)); + t->mcxt = mcxt; + t->mode = AGEHASH_INLINE; + t->frozen = false; + t->hash_fn = hash_fn; + t->keyeq_fn = keyeq_fn; + t->key_size = (uint32) key_size; + t->payload_size = (uint32) payload_size; + t->payload_offset = AGEHASH_SLOT_KEY_OFFSET + (uint32) key_size; + t->slot_size = MAXALIGN(t->payload_offset + (uint32) payload_size); + + /* + * Capacity floor of 64 keeps tiny tables out of degenerate-load territory + * and avoids a flurry of grows on the first few inserts. + */ + if (capacity_hint == 0) + min_cap = 64; + else + { + /* size capacity_hint at MAX_LOAD so we don't immediately grow */ + min_cap = (uint32) ((double) capacity_hint / AGEHASH_MAX_LOAD) + 1; + if (min_cap < 64) + min_cap = 64; + } + cap = next_pow2(min_cap); + Assert((cap & (cap - 1)) == 0); + + t->capacity = cap; + t->capacity_mask = cap - 1; + t->size = 0; + t->max_size = (uint32) ((double) cap * AGEHASH_MAX_LOAD); + /* + * The slot array can comfortably exceed 1 GiB on production graphs + * (SF3 ldbc_snb edge_table is ~3 GiB at 0.7 load). Use the HUGE + * allocator to bypass the standard MaxAllocSize check. + */ + t->slots = (char *) MemoryContextAllocHuge(mcxt, + (Size) cap * t->slot_size); + + /* Mark every slot empty. */ + { + uint32 i; + for (i = 0; i < cap; i++) + slot_set_probe_dist(slot_at(t, i), AGEHASH_EMPTY); + } + + MemoryContextSwitchTo(oldctx); + return t; +} + +/* ------------------------------------------------------------------------- */ +/* Insert. Robin Hood with rich-poor swap. */ + +static void agehash_grow(AgeHashTable *t); + +static void * +agehash_insert_internal(AgeHashTable *t, const void *key, uint32 hashvalue, + bool *found) +{ + uint32 i; + uint16 d; + /* + * Carrier for the entry currently being placed. Starts as the caller's + * key with a fresh, zero-filled payload; gets overwritten on each + * Robin Hood swap. + */ + char carry_key[64]; + char carry_payload[4096]; + void *result_payload = NULL; + bool placed_caller = false; + + Assert(!t->frozen); + Assert(t->key_size <= sizeof(carry_key)); + Assert(t->payload_size <= sizeof(carry_payload)); + + /* Grow before insert if at threshold. */ + if (t->size >= t->max_size) + agehash_grow(t); + + /* Initialize carry buffers with the caller's key and an empty payload. */ + memcpy(carry_key, key, t->key_size); + memset(carry_payload, 0, t->payload_size); + + i = hashvalue & t->capacity_mask; + d = 0; + + for (;;) + { + char *slot = slot_at(t, i); + uint16 sd = slot_probe_dist(slot); + + if (sd == AGEHASH_EMPTY) + { + /* Place the carrier here and we're done. */ + slot_set_probe_dist(slot, d); + memcpy(slot_key_ptr(t, slot), carry_key, t->key_size); + memcpy(slot_payload_ptr(t, slot), carry_payload, t->payload_size); + t->size++; + if (!placed_caller) + { + /* The caller's slot landed here. */ + if (found != NULL) + *found = false; + return slot_payload_ptr(t, slot); + } + /* + * The caller was placed earlier via a swap; result_payload + * already points at their final slot. + */ + Assert(result_payload != NULL); + return result_payload; + } + + if (sd == d && + !placed_caller && + t->keyeq_fn(slot_key_ptr(t, slot), carry_key, t->key_size)) + { + /* + * Existing entry with the caller's key. Note: this match check + * is only relevant before we've performed a swap; once we've + * placed the caller into a slot, the key in `carry` is some + * displaced entry that, by RH invariant on insert from a fresh + * key, cannot already exist in the table. + */ + if (found != NULL) + *found = true; + return slot_payload_ptr(t, slot); + } + + if (sd < d) + { + /* + * Rich-poor swap: this slot's owner is closer to its ideal + * bucket than we are. Take its place and continue with the + * displaced entry. If we have not yet placed the caller, this + * is where they end up; remember the pointer so we can return + * it once the displaced chain finishes. + */ + char tmp_key[64]; + char tmp_payload[4096]; + uint16 tmp_d = sd; + + memcpy(tmp_key, slot_key_ptr(t, slot), t->key_size); + memcpy(tmp_payload, slot_payload_ptr(t, slot), t->payload_size); + + slot_set_probe_dist(slot, d); + memcpy(slot_key_ptr(t, slot), carry_key, t->key_size); + memcpy(slot_payload_ptr(t, slot), carry_payload, t->payload_size); + + if (!placed_caller) + { + placed_caller = true; + result_payload = slot_payload_ptr(t, slot); + /* Notify caller: this insert is a fresh entry. */ + if (found != NULL) + { + *found = false; + found = NULL; /* don't write again */ + } + } + + /* Continue with the displaced entry as the new carrier. */ + memcpy(carry_key, tmp_key, t->key_size); + memcpy(carry_payload, tmp_payload, t->payload_size); + d = tmp_d; + } + + i = (i + 1) & t->capacity_mask; + d++; + + /* + * Probe distance overflow guard. With AGEHASH_MAX_LOAD = 0.7 and a + * non-degenerate hash function, max probe is empirically <= 32. + * The 0xFE00 ceiling reserves headroom while leaving probe_dist + * well clear of the AGEHASH_EMPTY sentinel. Review Comment: The probe-distance overflow guard comment references AGEHASH_MAX_LOAD = 0.7, but AGEHASH_MAX_LOAD is defined as 0.85 in agehash.h. Please update this comment (and any other related constants/assumptions) so the documented invariants match the compiled behavior. ########## src/backend/utils/cache/agehash.c: ########## @@ -0,0 +1,687 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * agehash.c - Robin Hood open-addressing hashtable for AGE. + * + * See agehash.h for the public contract. This file implements the INLINE + * mode only. + * + * Internal slot layout (INLINE): + * + * bytes 0..1 uint16 probe_dist (AGEHASH_EMPTY = 0xFFFF marks empty) + * bytes 2..3 uint16 reserved (future tombstone / flag bits) + * bytes 4..7 uint32 pad (forces key to 8-byte alignment) + * bytes 8..K+7 key + * bytes K+8.. payload + * + * slot_size = MAXALIGN(8 + key_size + payload_size). + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/agehash.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + +/* ------------------------------------------------------------------------- */ + +struct AgeHashTable +{ + /* Slot array: capacity * slot_size bytes, palloc'd in mcxt. */ + char *slots; + uint32 capacity; /* always a power of two */ + uint32 capacity_mask; /* capacity - 1 */ + uint32 size; /* live entries */ + uint32 max_size; /* size at which we grow */ + uint32 slot_size; /* total bytes per slot */ + uint32 key_size; + uint32 payload_size; + uint32 payload_offset; /* AGEHASH_SLOT_KEY_OFFSET + key_size */ + AgeHashMode mode; + bool frozen; + agehash_hash_fn hash_fn; + agehash_keyeq_fn keyeq_fn; + MemoryContext mcxt; +}; + +/* ------------------------------------------------------------------------- */ +/* Slot accessors. */ + +static inline char * +slot_at(AgeHashTable *t, uint32 idx) +{ + return t->slots + (Size) idx * t->slot_size; +} + +static inline uint16 +slot_probe_dist(const char *slot) +{ + uint16 d; + memcpy(&d, slot, sizeof(uint16)); + return d; +} + +static inline void +slot_set_probe_dist(char *slot, uint16 d) +{ + memcpy(slot, &d, sizeof(uint16)); +} + +static inline char * +slot_key_ptr(AgeHashTable *t, char *slot) +{ + (void) t; + return slot + AGEHASH_SLOT_KEY_OFFSET; +} + +static inline char * +slot_payload_ptr(AgeHashTable *t, char *slot) +{ + return slot + t->payload_offset; +} + +/* ------------------------------------------------------------------------- */ +/* Construction. */ + +static uint32 +next_pow2(uint32 v) +{ + uint32 p = 1; + while (p < v) + p <<= 1; + return p; +} + +AgeHashTable * +agehash_create_inline(MemoryContext mcxt, + Size key_size, + Size payload_size, + uint32 capacity_hint, + agehash_hash_fn hash_fn, + agehash_keyeq_fn keyeq_fn) +{ + AgeHashTable *t; + MemoryContext oldctx; + uint32 min_cap; + uint32 cap; + + Assert(mcxt != NULL); + Assert(key_size > 0 && key_size <= 64); + Assert(payload_size > 0 && payload_size <= 4096); + Assert(hash_fn != NULL); + Assert(keyeq_fn != NULL); + + oldctx = MemoryContextSwitchTo(mcxt); + + t = palloc0(sizeof(AgeHashTable)); + t->mcxt = mcxt; + t->mode = AGEHASH_INLINE; + t->frozen = false; + t->hash_fn = hash_fn; + t->keyeq_fn = keyeq_fn; + t->key_size = (uint32) key_size; + t->payload_size = (uint32) payload_size; + t->payload_offset = AGEHASH_SLOT_KEY_OFFSET + (uint32) key_size; + t->slot_size = MAXALIGN(t->payload_offset + (uint32) payload_size); + + /* + * Capacity floor of 64 keeps tiny tables out of degenerate-load territory + * and avoids a flurry of grows on the first few inserts. + */ + if (capacity_hint == 0) + min_cap = 64; + else + { + /* size capacity_hint at MAX_LOAD so we don't immediately grow */ + min_cap = (uint32) ((double) capacity_hint / AGEHASH_MAX_LOAD) + 1; + if (min_cap < 64) + min_cap = 64; + } + cap = next_pow2(min_cap); + Assert((cap & (cap - 1)) == 0); + + t->capacity = cap; + t->capacity_mask = cap - 1; + t->size = 0; + t->max_size = (uint32) ((double) cap * AGEHASH_MAX_LOAD); + /* + * The slot array can comfortably exceed 1 GiB on production graphs + * (SF3 ldbc_snb edge_table is ~3 GiB at 0.7 load). Use the HUGE + * allocator to bypass the standard MaxAllocSize check. + */ + t->slots = (char *) MemoryContextAllocHuge(mcxt, + (Size) cap * t->slot_size); + + /* Mark every slot empty. */ + { + uint32 i; + for (i = 0; i < cap; i++) + slot_set_probe_dist(slot_at(t, i), AGEHASH_EMPTY); + } + + MemoryContextSwitchTo(oldctx); + return t; +} + +/* ------------------------------------------------------------------------- */ +/* Insert. Robin Hood with rich-poor swap. */ + +static void agehash_grow(AgeHashTable *t); + +static void * +agehash_insert_internal(AgeHashTable *t, const void *key, uint32 hashvalue, + bool *found) +{ + uint32 i; + uint16 d; + /* + * Carrier for the entry currently being placed. Starts as the caller's + * key with a fresh, zero-filled payload; gets overwritten on each + * Robin Hood swap. + */ + char carry_key[64]; + char carry_payload[4096]; + void *result_payload = NULL; + bool placed_caller = false; + Review Comment: agehash_freeze() is documented to make subsequent inserts/grows fail with elog(ERROR) in production, but agehash_insert_internal() only has Assert(!t->frozen). In non-assert builds this allows inserts into a frozen table, which can silently reorder slots and invalidate any payload pointers callers consider stable after freeze. Add a non-assert runtime check (and similarly in grow paths) that errors out when t->frozen is true to match the API contract and prevent hard-to-debug corruption. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
