From c44d343369c03cf86e8f257fb0644431d3f9f5c9 Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Mon, 27 Apr 2026 10:14:28 -0700 Subject: [PATCH] Add infallable `ck_rhs_reset_preallocated` Currently, when you want to flush a `ck_rhs_t` you must call `ck_rhs_reset[_size]` which can fail under low memory conditions (when trying to allocate the backing map with `hs->m->malloc`). If it does fail, the only choice you have is to unwind the map in-place, which while possible is less than ideal. This commit gives us an infallable pathway, allowing the user to preallocate one or more backing maps ahead of time for later use. Two new functions are introduced: ```c /* Returns the internally rounded up size required for an underlyinig ck_rhs_map for the number of elements requested */ size_t ck_rhs_map_size(ck_rhs_t *, unsigned long); /* Similar to ck_rhs_reset_size except the caller provides the underlying map allocation, making it infallible */ void ck_rhs_reset_preallocated(ck_rhs_t *, unsigned long, void *); ``` Signed-off-by: michael-grunder --- doc/Makefile.in | 2 + doc/ck_rhs_map_size | 63 +++++++++++++++++++++++ doc/ck_rhs_reset | 2 + doc/ck_rhs_reset_preallocated | 77 ++++++++++++++++++++++++++++ doc/ck_rhs_reset_size | 2 + include/ck_rhs.h | 2 + regressions/ck_rhs/validate/serial.c | 34 ++++++++++++ src/ck_rhs.c | 66 ++++++++++++++++++++---- 8 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 doc/ck_rhs_map_size create mode 100644 doc/ck_rhs_reset_preallocated diff --git a/doc/Makefile.in b/doc/Makefile.in index 0c519a2e..c89c9502 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -107,7 +107,9 @@ OBJECTS=CK_ARRAY_FOREACH \ ck_rhs_grow \ ck_rhs_rebuild \ ck_rhs_count \ + ck_rhs_map_size \ ck_rhs_reset \ + ck_rhs_reset_preallocated \ ck_rhs_reset_size \ ck_rhs_stat \ ck_rwcohort \ diff --git a/doc/ck_rhs_map_size b/doc/ck_rhs_map_size new file mode 100644 index 00000000..2cafcdc5 --- /dev/null +++ b/doc/ck_rhs_map_size @@ -0,0 +1,63 @@ +.\" +.\" Copyright 2012-2013 Samy Al Bahra. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" +.Dd April 26, 2026 +.Dt CK_RHS_MAP_SIZE 3 +.Sh NAME +.Nm ck_rhs_map_size +.Nd return the backing map allocation size for a hash set capacity +.Sh LIBRARY +Concurrency Kit (libck, \-lck) +.Sh SYNOPSIS +.In ck_rhs.h +.Ft size_t +.Fn ck_rhs_map_size "ck_rhs_t *hs" "unsigned long size" +.Sh DESCRIPTION +The +.Fn ck_rhs_map_size 3 +function returns the number of bytes required for backing memory +capable of storing a hash set with enough capacity for +.Fa size +entries using the configuration of +.Fa hs . +.Pp +This is intended to size caller-managed backing memory for +.Xr ck_rhs_reset_preallocated 3 . +.Sh RETURN VALUES +The +.Fn ck_rhs_map_size 3 +function returns the required backing allocation size in bytes. +.Sh ERRORS +Behavior is undefined if +.Fa hs +is uninitialized. +.Sh SEE ALSO +.Xr ck_rhs_init 3 , +.Xr ck_rhs_reset 3 , +.Xr ck_rhs_reset_size 3 , +.Xr ck_rhs_reset_preallocated 3 +.Pp +Additional information available at http://concurrencykit.org/ diff --git a/doc/ck_rhs_reset b/doc/ck_rhs_reset index a750d85f..28179d94 100644 --- a/doc/ck_rhs_reset +++ b/doc/ck_rhs_reset @@ -67,6 +67,8 @@ thread. .Xr ck_rhs_set 3 , .Xr ck_rhs_fas 3 , .Xr ck_rhs_remove 3 , +.Xr ck_rhs_map_size 3 , +.Xr ck_rhs_reset_preallocated 3 , .Xr ck_rhs_reset_size 3 , .Xr ck_rhs_grow 3 , .Xr ck_rhs_gc 3 , diff --git a/doc/ck_rhs_reset_preallocated b/doc/ck_rhs_reset_preallocated new file mode 100644 index 00000000..aa85dad3 --- /dev/null +++ b/doc/ck_rhs_reset_preallocated @@ -0,0 +1,77 @@ +.\" +.\" Copyright 2012-2013 Samy Al Bahra. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" +.Dd April 26, 2026 +.Dt CK_RHS_RESET_PREALLOCATED 3 +.Sh NAME +.Nm ck_rhs_reset_preallocated +.Nd remove all keys from a hash set using caller-provided backing memory +.Sh LIBRARY +Concurrency Kit (libck, \-lck) +.Sh SYNOPSIS +.In ck_rhs.h +.Ft void +.Fn ck_rhs_reset_preallocated "ck_rhs_t *hs" "unsigned long size" "void *memory" +.Sh DESCRIPTION +The +.Fn ck_rhs_reset_preallocated 3 +function removes all keys stored in the hash set pointed to by the +.Fa hs +argument and creates a new generation of the hash set that is backed by +the caller-provided +.Fa memory +buffer. +.Pp +The +.Fa memory +argument must be non-NULL and must refer to a region of at least +.Fn ck_rhs_map_size 3 +bytes for the requested +.Fa size +and hash set configuration. The allocation referenced by +.Fa memory +must be safe to reclaim through the +.Li free +callback configured for +.Fa hs . +Ownership of this backing memory is transferred to the hash set and it +will later be reclaimed through that callback. +.Sh RETURN VALUES +This function does not return a value. +.Sh ERRORS +Behavior is undefined if +.Fa hs +is uninitialized. Behavior is undefined if +.Fa memory +is NULL or too small for the requested capacity. Behavior is undefined +if this function is called by a non-writer thread. +.Sh SEE ALSO +.Xr ck_rhs_init 3 , +.Xr ck_rhs_map_size 3 , +.Xr ck_rhs_reset 3 , +.Xr ck_rhs_reset_size 3 +.Pp +Additional information available at http://concurrencykit.org/ diff --git a/doc/ck_rhs_reset_size b/doc/ck_rhs_reset_size index 6e9913e8..621495a4 100644 --- a/doc/ck_rhs_reset_size +++ b/doc/ck_rhs_reset_size @@ -70,11 +70,13 @@ thread. .Xr ck_rhs_set 3 , .Xr ck_rhs_fas 3 , .Xr ck_rhs_remove 3 , +.Xr ck_rhs_map_size 3 , .Xr ck_rhs_grow 3 , .Xr ck_rhs_gc 3 , .Xr ck_rhs_rebuild 3 , .Xr ck_rhs_count 3 , .Xr ck_rhs_reset 3 , +.Xr ck_rhs_reset_preallocated 3 , .Xr ck_rhs_stat 3 .Pp Additional information available at http://concurrencykit.org/ diff --git a/include/ck_rhs.h b/include/ck_rhs.h index 2a21a731..f47f0439 100644 --- a/include/ck_rhs.h +++ b/include/ck_rhs.h @@ -126,8 +126,10 @@ bool ck_rhs_grow(ck_rhs_t *, unsigned long); bool ck_rhs_rebuild(ck_rhs_t *); bool ck_rhs_gc(ck_rhs_t *); unsigned long ck_rhs_count(ck_rhs_t *); +size_t ck_rhs_map_size(ck_rhs_t *, unsigned long); bool ck_rhs_reset(ck_rhs_t *); bool ck_rhs_reset_size(ck_rhs_t *, unsigned long); +void ck_rhs_reset_preallocated(ck_rhs_t *, unsigned long, void *); void ck_rhs_stat(ck_rhs_t *, struct ck_rhs_stat *); bool ck_rhs_set_load_factor(ck_rhs_t *, unsigned int); diff --git a/regressions/ck_rhs/validate/serial.c b/regressions/ck_rhs/validate/serial.c index 92caf18c..140d8e7b 100644 --- a/regressions/ck_rhs/validate/serial.c +++ b/regressions/ck_rhs/validate/serial.c @@ -295,6 +295,39 @@ run_test(unsigned int is, unsigned int ad) return; } +static void +test_reset_preallocated(void) +{ + ck_rhs_t hs; + size_t size; + void *buffer; + unsigned long h; + + if (ck_rhs_init(&hs, CK_RHS_MODE_SPMC | CK_RHS_MODE_OBJECT, + hs_hash, hs_compare, &my_allocator, 16, 6602834) == false) { + ck_error("ck_rhs_init (preallocated)\n"); + } + + h = test[0][0]; + if (ck_rhs_put(&hs, h, test[0]) == false) + ck_error("ck_rhs_put (preallocated)\n"); + + size = ck_rhs_map_size(&hs, 64); + buffer = hs_malloc(size); + if (buffer == NULL) + ck_error("hs_malloc (preallocated)\n"); + + ck_rhs_reset_preallocated(&hs, 64, buffer); + if (ck_rhs_count(&hs) != 0) + ck_error("ck_rhs_reset_preallocated failed to clear entries\n"); + + if (ck_rhs_put(&hs, h, test[0]) == false) + ck_error("ck_rhs_put after ck_rhs_reset_preallocated\n"); + + ck_rhs_destroy(&hs); + return; +} + int main(void) { @@ -305,6 +338,7 @@ main(void) break; } + test_reset_preallocated(); return 0; } diff --git a/src/ck_rhs.c b/src/ck_rhs.c index 81e69217..9f281699 100644 --- a/src/ck_rhs.c +++ b/src/ck_rhs.c @@ -333,31 +333,44 @@ ck_rhs_destroy(struct ck_rhs *hs) return; } -static struct ck_rhs_map * -ck_rhs_map_create(struct ck_rhs *hs, unsigned long entries) +static unsigned long +ck_rhs_map_capacity(unsigned long entries) { - struct ck_rhs_map *map; - unsigned long size, n_entries, limit; + unsigned long n_entries; n_entries = ck_internal_power_2(entries); if (n_entries < CK_RHS_PROBE_L1) n_entries = CK_RHS_PROBE_L1; + return n_entries; +} + +size_t +ck_rhs_map_size(struct ck_rhs *hs, unsigned long entries) +{ + unsigned long n_entries; + + n_entries = ck_rhs_map_capacity(entries); + if (hs->mode & CK_RHS_MODE_READ_MOSTLY) - size = sizeof(struct ck_rhs_map) + + return sizeof(struct ck_rhs_map) + (sizeof(void *) * n_entries + sizeof(struct ck_rhs_no_entry_desc) * n_entries + 2 * CK_MD_CACHELINE - 1); else - size = sizeof(struct ck_rhs_map) + + return sizeof(struct ck_rhs_map) + (sizeof(struct ck_rhs_entry_desc) * n_entries + CK_MD_CACHELINE - 1); - map = hs->m->malloc(size); - if (map == NULL) - return NULL; - map->read_mostly = !!(hs->mode & CK_RHS_MODE_READ_MOSTLY); +} - map->size = size; +static void +ck_rhs_map_init(struct ck_rhs *hs, struct ck_rhs_map *map, unsigned long entries) +{ + unsigned long n_entries, limit; + + n_entries = ck_rhs_map_capacity(entries); + map->read_mostly = !!(hs->mode & CK_RHS_MODE_READ_MOSTLY); + map->size = ck_rhs_map_size(hs, n_entries); /* We should probably use a more intelligent heuristic for default probe length. */ limit = ck_internal_max(n_entries >> (CK_RHS_PROBE_L1_SHIFT + 2), CK_RHS_PROBE_L1_DEFAULT); if (limit > UINT_MAX) @@ -394,6 +407,21 @@ ck_rhs_map_create(struct ck_rhs *hs, unsigned long entries) /* Commit entries purge with respect to map publication. */ ck_pr_fence_store(); + return; +} + +static struct ck_rhs_map * +ck_rhs_map_create(struct ck_rhs *hs, unsigned long entries) +{ + struct ck_rhs_map *map; + size_t size; + + size = ck_rhs_map_size(hs, entries); + + map = hs->m->malloc(size); + if (map == NULL) + return NULL; + ck_rhs_map_init(hs, map, entries); return map; } @@ -421,6 +449,22 @@ ck_rhs_reset(struct ck_rhs *hs) return ck_rhs_reset_size(hs, previous->capacity); } +void +ck_rhs_reset_preallocated(struct ck_rhs *hs, + unsigned long capacity, + void *memory) +{ + struct ck_rhs_map *map, *previous; + + previous = hs->map; + map = memory; + ck_rhs_map_init(hs, map, capacity); + + ck_pr_store_ptr(&hs->map, map); + ck_rhs_map_destroy(hs->m, previous, true); + return; +} + static inline unsigned long ck_rhs_map_probe_next(struct ck_rhs_map *map, unsigned long offset,