ccan: update to latest htable fixes, and update gossmap to meet new assertions.

Updating ccan to stricter htable revealed we were trying to put
(void *)1 in the htable, which is forbidden:

```
topology: ccan/ccan/htable/htable.c:382: htable_add_: Assertion `entry_is_valid((uintptr_t)p)' failed.
topology: FATAL SIGNAL 6 (version 1358d7f)
0x55f30c689c34 send_backtrace
	common/daemon.c:33
0x55f30c689ce0 crashdump
	common/daemon.c:46
0x7f5d150fe51f ???
	./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
0x7f5d15152828 __pthread_kill_implementation
	./nptl/pthread_kill.c:44
0x7f5d15152828 __pthread_kill_internal
	./nptl/pthread_kill.c:80
0x7f5d15152828 __GI___pthread_kill
	./nptl/pthread_kill.c:91
0x7f5d150fe475 __GI_raise
	../sysdeps/posix/raise.c:26
0x7f5d150e47b6 __GI_abort
	./stdlib/abort.c:79
0x7f5d150e46da __assert_fail_base
	./assert/assert.c:92
0x7f5d150f5e25 __GI___assert_fail
	./assert/assert.c:101
0x55f30c6adbe4 htable_add_
	ccan/ccan/htable/htable.c:382
0x55f30c65f303 chanidx_htable_add
	common/gossmap.c:35
0x55f30c6605ed new_channel
	common/gossmap.c:337
0x55f30c6609cf add_channel
	common/gossmap.c:425
0x55f30c661101 map_catchup
	common/gossmap.c:607
0x55f30c66221e gossmap_refresh
	common/gossmap.c:927
0x55f30c66e3e9 get_gossmap
	plugins/topology.c:27
0x55f30c66f939 listpeers_done
	plugins/topology.c:369
0x55f30c671f46 handle_rpc_reply
	plugins/libplugin.c:558
0x55f30c672a19 rpc_read_response_one
	plugins/libplugin.c:726
0x55f30c672b4f rpc_conn_read_response
	plugins/libplugin.c:746
0x55f30c6ae35e next_plan
	ccan/ccan/io/io.c:59
0x55f30c6aef93 do_plan
	ccan/ccan/io/io.c:407
0x55f30c6aefd5 io_ready
	ccan/ccan/io/io.c:417
0x55f30c6b1371 io_loop
	ccan/ccan/io/poll.c:453
0x55f30c67587c plugin_main
	plugins/libplugin.c:1559
0x55f30c6708eb main
	plugins/topology.c:701
0x7f5d150e5fcf __libc_start_call_main
	../sysdeps/nptl/libc_start_call_main.h:58
0x7f5d150e607c __libc_start_main_impl
	../csu/libc-start.c:409
0x55f30c65d894 ???
	???:0
0xffffffffffffffff ???
	???:0
```

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2022-07-04 14:14:06 +09:30
committed by neil saitug
parent c589543f1c
commit ae71a87c40
15 changed files with 496 additions and 74 deletions

View File

@@ -1,3 +1,3 @@
CCAN imported from http://ccodearchive.net.
CCAN version: init-2524-g609670cc
CCAN version: init-2540-g8448fd28

View File

@@ -86,14 +86,16 @@ void htable_init(struct htable *ht,
ht->table = &ht->common_bits;
}
/* Fill to 87.5% */
static inline size_t ht_max(const struct htable *ht)
{
return ((size_t)3 << ht->bits) / 4;
return ((size_t)7 << ht->bits) / 8;
}
static inline size_t ht_max_with_deleted(const struct htable *ht)
/* Clean deleted if we're full, and more than 12.5% deleted */
static inline size_t ht_max_deleted(const struct htable *ht)
{
return ((size_t)9 << ht->bits) / 10;
return ((size_t)1 << ht->bits) / 8;
}
bool htable_init_sized(struct htable *ht,
@@ -103,7 +105,7 @@ bool htable_init_sized(struct htable *ht,
htable_init(ht, rehash, priv);
/* Don't go insane with sizing. */
for (ht->bits = 1; ((size_t)3 << ht->bits) / 4 < expect; ht->bits++) {
for (ht->bits = 1; ht_max(ht) < expect; ht->bits++) {
if (ht->bits == 30)
break;
}
@@ -195,12 +197,83 @@ void *htable_prev_(const struct htable *ht, struct htable_iter *i)
for (;;) {
if (!i->off)
return NULL;
i->off --;
i->off--;
if (entry_is_valid(ht->table[i->off]))
return get_raw_ptr(ht, ht->table[i->off]);
}
}
/* Another bit currently in mask needs to be exposed, so that a bucket with p in
* it won't appear invalid */
static COLD void unset_another_common_bit(struct htable *ht,
uintptr_t *maskdiff,
const void *p)
{
size_t i;
for (i = sizeof(uintptr_t) * CHAR_BIT - 1; i > 0; i--) {
if (((uintptr_t)p & ((uintptr_t)1 << i))
&& ht->common_mask & ~*maskdiff & ((uintptr_t)1 << i))
break;
}
/* There must have been one, right? */
assert(i > 0);
*maskdiff |= ((uintptr_t)1 << i);
}
/* We want to change the common mask: this fixes up the table */
static COLD void fixup_table_common(struct htable *ht, uintptr_t maskdiff)
{
size_t i;
uintptr_t bitsdiff;
again:
bitsdiff = ht->common_bits & maskdiff;
for (i = 0; i < (size_t)1 << ht->bits; i++) {
uintptr_t e;
if (!entry_is_valid(e = ht->table[i]))
continue;
/* Clear the bits no longer in the mask, set them as
* expected. */
e &= ~maskdiff;
e |= bitsdiff;
/* If this made it invalid, restart with more exposed */
if (!entry_is_valid(e)) {
unset_another_common_bit(ht, &maskdiff, get_raw_ptr(ht, e));
goto again;
}
ht->table[i] = e;
}
/* Take away those bits from our mask, bits and perfect bit. */
ht->common_mask &= ~maskdiff;
ht->common_bits &= ~maskdiff;
if (ht_perfect_mask(ht) & maskdiff)
ht->perfect_bitnum = NO_PERFECT_BIT;
}
/* Limited recursion */
static void ht_add(struct htable *ht, const void *new, size_t h);
/* We tried to add this entry, but it looked invalid! We need to
* let another pointer bit through mask */
static COLD void update_common_fix_invalid(struct htable *ht, const void *p, size_t h)
{
uintptr_t maskdiff;
assert(ht->elems != 0);
maskdiff = 0;
unset_another_common_bit(ht, &maskdiff, p);
fixup_table_common(ht, maskdiff);
/* Now won't recurse */
ht_add(ht, p, h);
}
/* This does not expand the hash table, that's up to caller. */
static void ht_add(struct htable *ht, const void *new, size_t h)
{
@@ -214,6 +287,8 @@ static void ht_add(struct htable *ht, const void *new, size_t h)
i = (i + 1) & ((1 << ht->bits)-1);
}
ht->table[i] = make_hval(ht, new, get_hash_ptr_bits(ht, h)|perfect);
if (!entry_is_valid(ht->table[i]))
update_common_fix_invalid(ht, new, h);
}
static COLD bool double_table(struct htable *ht)
@@ -283,20 +358,10 @@ static COLD void rehash_table(struct htable *ht)
/* We stole some bits, now we need to put them back... */
static COLD void update_common(struct htable *ht, const void *p)
{
unsigned int i;
uintptr_t maskdiff, bitsdiff;
uintptr_t maskdiff;
if (ht->elems == 0) {
/* Always reveal one bit of the pointer in the bucket,
* so it's not zero or HTABLE_DELETED (1), even if
* hash happens to be 0. Assumes (void *)1 is not a
* valid pointer. */
for (i = sizeof(uintptr_t)*CHAR_BIT - 1; i > 0; i--) {
if ((uintptr_t)p & ((uintptr_t)1 << i))
break;
}
ht->common_mask = ~((uintptr_t)1 << i);
ht->common_mask = -1;
ht->common_bits = ((uintptr_t)p & ht->common_mask);
ht->perfect_bitnum = 0;
(void)htable_debug(ht, HTABLE_LOC);
@@ -306,33 +371,25 @@ static COLD void update_common(struct htable *ht, const void *p)
/* Find bits which are unequal to old common set. */
maskdiff = ht->common_bits ^ ((uintptr_t)p & ht->common_mask);
/* These are the bits which go there in existing entries. */
bitsdiff = ht->common_bits & maskdiff;
for (i = 0; i < (size_t)1 << ht->bits; i++) {
if (!entry_is_valid(ht->table[i]))
continue;
/* Clear the bits no longer in the mask, set them as
* expected. */
ht->table[i] &= ~maskdiff;
ht->table[i] |= bitsdiff;
}
/* Take away those bits from our mask, bits and perfect bit. */
ht->common_mask &= ~maskdiff;
ht->common_bits &= ~maskdiff;
if (ht_perfect_mask(ht) & maskdiff)
ht->perfect_bitnum = NO_PERFECT_BIT;
fixup_table_common(ht, maskdiff);
(void)htable_debug(ht, HTABLE_LOC);
}
bool htable_add_(struct htable *ht, size_t hash, const void *p)
{
if (ht->elems+1 > ht_max(ht) && !double_table(ht))
return false;
if (ht->elems+1 + ht->deleted > ht_max_with_deleted(ht))
rehash_table(ht);
/* Cannot insert NULL, or (void *)1. */
assert(p);
assert(entry_is_valid((uintptr_t)p));
/* Getting too full? */
if (ht->elems+1 + ht->deleted > ht_max(ht)) {
/* If we're more than 1/8 deleted, clean those,
* otherwise double table size. */
if (ht->deleted > ht_max_deleted(ht))
rehash_table(ht);
else if (!double_table(ht))
return false;
}
if (((uintptr_t)p & ht->common_mask) != ht->common_bits)
update_common(ht, p);
@@ -361,8 +418,12 @@ void htable_delval_(struct htable *ht, struct htable_iter *i)
assert(entry_is_valid(ht->table[i->off]));
ht->elems--;
ht->table[i->off] = HTABLE_DELETED;
ht->deleted++;
/* Cheap test: if the next bucket is empty, don't need delete marker */
if (ht->table[hash_bucket(ht, i->off+1)] != 0) {
ht->table[i->off] = HTABLE_DELETED;
ht->deleted++;
} else
ht->table[i->off] = 0;
}
void *htable_pick_(const struct htable *ht, size_t seed, struct htable_iter *i)

View File

@@ -132,7 +132,7 @@ bool htable_copy_(struct htable *dst, const struct htable *src);
* htable_add - add a pointer into a hash table.
* @ht: the htable
* @hash: the hash value of the object
* @p: the non-NULL pointer
* @p: the non-NULL pointer (also cannot be (void *)1).
*
* Also note that this can only fail due to allocation failure. Otherwise, it
* returns true.

View File

@@ -0,0 +1,31 @@
#include <ccan/htable/htable.h>
#include <ccan/htable/htable.c>
#include <ccan/tap/tap.h>
#include <stdbool.h>
#include <string.h>
/* Clashy hash */
static size_t hash(const void *elem, void *unused UNNEEDED)
{
return 0;
}
int main(void)
{
struct htable ht;
plan_tests(254 * 253);
/* We try to get two elements which clash */
for (size_t i = 2; i < 256; i++) {
for (size_t j = 2; j < 256; j++) {
if (i == j)
continue;
htable_init(&ht, hash, NULL);
htable_add(&ht, hash((void *)i, NULL), (void *)i);
htable_add(&ht, hash((void *)j, NULL), (void *)j);
ok1(htable_check(&ht, "test"));
htable_clear(&ht);
}
}
return exit_status();
}

View File

@@ -107,7 +107,7 @@ static bool check_mask(struct htable *ht, uint64_t val[], unsigned num)
int main(void)
{
unsigned int i, weight;
unsigned int i;
uintptr_t perfect_bit;
struct htable ht;
uint64_t val[NUM_VALS];
@@ -131,14 +131,7 @@ int main(void)
add_vals(&ht, val, 0, 1);
ok1(ht.bits == 1);
ok1(ht_max(&ht) == 1);
weight = 0;
for (i = 0; i < sizeof(ht.common_mask) * CHAR_BIT; i++) {
if (ht.common_mask & ((uintptr_t)1 << i)) {
weight++;
}
}
/* Only one bit should be clear. */
ok1(weight == i-1);
ok1(ht.common_mask == -1);
/* Mask should be set. */
ok1(check_mask(&ht, val, 1));

View File

@@ -97,7 +97,7 @@ static bool check_mask(struct htable *ht, uint64_t val[], unsigned num)
int main(void)
{
unsigned int i, weight;
unsigned int i;
uintptr_t perfect_bit;
struct htable ht;
uint64_t val[NUM_VALS];
@@ -122,14 +122,7 @@ int main(void)
add_vals(&ht, val, 0, 1);
ok1(ht.bits == 1);
ok1(ht_max(&ht) == 1);
weight = 0;
for (i = 0; i < sizeof(ht.common_mask) * CHAR_BIT; i++) {
if (ht.common_mask & ((uintptr_t)1 << i)) {
weight++;
}
}
/* Only one bit should be clear. */
ok1(weight == i-1);
ok1(ht.common_mask == -1);
/* Mask should be set. */
ok1(check_mask(&ht, val, 1));

View File

@@ -4,9 +4,10 @@ CFLAGS=-Wall -Werror -O3 -I$(CCANDIR)
CCAN_OBJS:=ccan-tal.o ccan-tal-str.o ccan-tal-grab_file.o ccan-take.o ccan-time.o ccan-str.o ccan-noerr.o ccan-list.o
all: speed stringspeed hsearchspeed
all: speed stringspeed hsearchspeed density
speed: speed.o hash.o $(CCAN_OBJS)
density: density.o hash.o $(CCAN_OBJS)
speed.o: speed.c ../htable.h ../htable.c

View File

@@ -0,0 +1,107 @@
/* Density measurements for hashtables. */
#include <ccan/err/err.h>
#include <ccan/htable/htable_type.h>
#include <ccan/htable/htable.c>
#include <ccan/hash/hash.h>
#include <ccan/ptrint/ptrint.h>
#include <ccan/time/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* We don't actually hash objects: we put values in as if they were ptrs */
static uintptr_t key(const ptrint_t *obj)
{
return ptr2int(obj);
}
static size_t hash_uintptr(uintptr_t key)
{
return hashl(&key, 1, 0);
}
static bool cmp(const ptrint_t *p, uintptr_t k)
{
return key(p) == k;
}
HTABLE_DEFINE_TYPE(ptrint_t, key, hash_uintptr, cmp, htable_ptrint);
/* Nanoseconds per operation */
static size_t normalize(const struct timeabs *start,
const struct timeabs *stop,
unsigned int num)
{
return time_to_nsec(time_divide(time_between(*stop, *start), num));
}
static size_t average_run(const struct htable_ptrint *ht, size_t count, size_t *longest)
{
size_t i, total = 0;
*longest = 0;
for (i = 0; i < count; i++) {
size_t h = hash_uintptr(i + 2);
size_t run = 1;
while (get_raw_ptr(&ht->raw, ht->raw.table[h % ((size_t)1 << ht->raw.bits)]) != int2ptr(i + 2)) {
h++;
run++;
}
total += run;
if (run > *longest)
*longest = run;
}
return total / count;
}
int main(int argc, char *argv[])
{
unsigned int i;
size_t num;
struct timeabs start, stop;
struct htable_ptrint ht;
if (argc != 2)
errx(1, "Usage: density <power-of-2-tablesize>");
num = atoi(argv[1]);
printf("Total buckets, buckets used, nanoseconds search time per element, avg run, longest run\n");
for (i = 1; i <= 99; i++) {
uintptr_t j;
struct htable_ptrint_iter it;
size_t count, avg_run, longest_run;
ptrint_t *p;
htable_ptrint_init_sized(&ht, num * 3 / 4);
assert((1 << ht.raw.bits) == num);
/* Can't put 0 or 1 in the hash table: multiply by a prime. */
for (j = 0; j < num * i / 100; j++) {
htable_ptrint_add(&ht, int2ptr(j + 2));
/* stop it from doubling! */
ht.raw.elems = num / 2;
}
/* Must not have changed! */
assert((1 << ht.raw.bits) == num);
/* Clean cache */
count = 0;
for (p = htable_ptrint_first(&ht, &it); p; p = htable_ptrint_next(&ht, &it))
count++;
assert(count == num * i / 100);
start = time_now();
for (j = 0; j < count; j++)
assert(htable_ptrint_get(&ht, j + 2));
stop = time_now();
avg_run = average_run(&ht, count, &longest_run);
printf("%zu,%zu,%zu,%zu,%zu\n",
num, count, normalize(&start, &stop, count), avg_run, longest_run);
fflush(stdout);
htable_ptrint_clear(&ht);
}
return 0;
}

View File

@@ -416,7 +416,6 @@ struct io_plan *io_out_always_(struct io_conn *conn,
* // Freed if conn closes normally.
* timeout = tal(conn, struct timeout_timer);
* timeout->conn = conn;
* timeout->t = conn;
* timer_addrel(&timers, &timeout->t, time_from_sec(5));
* return io_sock_shutdown(conn);
* }
@@ -815,4 +814,13 @@ struct timemono (*io_time_override(struct timemono (*now)(void)))(void);
*/
int (*io_poll_override(int (*poll)(struct pollfd *fds, nfds_t nfds, int timeout)))(struct pollfd *, nfds_t, int);
/**
* io_have_fd - do we own this file descriptor?
* @fd: the file descriptor.
* @listener: if non-NULL, set to true if it's a listening socket (io_listener).
*
* Returns NULL if we don't own it, otherwise a struct io_conn * or struct io_listener *.
*/
const void *io_have_fd(int fd, bool *listener);
#endif /* CCAN_IO_H */

View File

@@ -464,3 +464,15 @@ void *io_loop(struct timers *timers, struct timer **expired)
return ret;
}
const void *io_have_fd(int fd, bool *listener)
{
for (size_t i = 0; i < num_fds; i++) {
if (fds[i]->fd != fd)
continue;
if (listener)
*listener = fds[i]->listener;
return fds[i];
}
return NULL;
}

View File

@@ -17,9 +17,8 @@ struct node {
};
/* Closest member to this in a non-empty map. */
static struct strmap *closest(struct strmap *n, const char *member)
static struct strmap *closest(struct strmap *n, const char *member, size_t len)
{
size_t len = strlen(member);
const u8 *bytes = (const u8 *)member;
/* Anything with NULL value is a node. */
@@ -35,20 +34,26 @@ static struct strmap *closest(struct strmap *n, const char *member)
return n;
}
void *strmap_get_(const struct strmap *map, const char *member)
void *strmap_getn_(const struct strmap *map,
const char *member, size_t memberlen)
{
struct strmap *n;
/* Not empty map? */
if (map->u.n) {
n = closest((struct strmap *)map, member);
if (streq(member, n->u.s))
n = closest((struct strmap *)map, member, memberlen);
if (!strncmp(member, n->u.s, memberlen) && !n->u.s[memberlen])
return n->v;
}
errno = ENOENT;
return NULL;
}
void *strmap_get_(const struct strmap *map, const char *member)
{
return strmap_getn_(map, member, strlen(member));
}
bool strmap_add_(struct strmap *map, const char *member, const void *value)
{
size_t len = strlen(member);
@@ -68,7 +73,7 @@ bool strmap_add_(struct strmap *map, const char *member, const void *value)
}
/* Find closest existing member. */
n = closest(map, member);
n = closest(map, member, len);
/* Find where they differ. */
for (byte_num = 0; n->u.s[byte_num] == member[byte_num]; byte_num++) {

View File

@@ -72,7 +72,7 @@ static inline bool strmap_empty_(const struct strmap *map)
/**
* strmap_get - get a value from a string map
* @map: the typed strmap to search.
* @member: the string to search for.
* @member: the string to search for (nul terminated)
*
* Returns the value, or NULL if it isn't in the map (and sets errno = ENOENT).
*
@@ -85,6 +85,23 @@ static inline bool strmap_empty_(const struct strmap *map)
tcon_cast((map), canary, strmap_get_(tcon_unwrap(map), (member)))
void *strmap_get_(const struct strmap *map, const char *member);
/**
* strmap_getn - get a value from a string map
* @map: the typed strmap to search.
* @member: the string to search for.
* @memberlen: the length of @member.
*
* Returns the value, or NULL if it isn't in the map (and sets errno = ENOENT).
*
* Example:
* val = strmap_getn(&map, "hello", 5);
* if (val)
* printf("hello => %i\n", *val);
*/
#define strmap_getn(map, member, n) \
tcon_cast((map), canary, strmap_getn_(tcon_unwrap(map), (member), (n)))
void *strmap_getn_(const struct strmap *map, const char *member, size_t n);
/**
* strmap_add - place a member in the string map.
* @map: the typed strmap to add to.