myxce/src/lib/kv.c
Rongsong Shen eaf1801d6d feat: implement kvmgr_capture_keys for lock-free checkpoint serialization (P0-14a)
Add kvtab_foreach walk primitive (P0-14) to kv.c/kv.h, then implement
kvmgr_capture_keys in kvmgr.c that deep-copies all key→index mappings
into a malloc'd slot_key_t[] array safe to read after the write lock is
released during checkpoint serialization (Part 3.8).

Key design points:
- Both kvtab_foreach and kvmgr_capture_keys take var (RTTI data pointer)
  and use RTTI_DATA_TO_OBJ internally — the RTTI framework's makeInstance
  returns the data region pointer, not the meta_obj_t header pointer
- String keys are copied via strncpy into slot_key_t.key[XCE_MAXNAME_LEN]
- Integer keys are formatted as decimal strings via snprintf
- Two-pass approach: count live entries first, allocate, then fill
- Tests cover string kvmgr (3 entries), empty kvmgr, and deep-copy safety
2026-05-07 11:07:41 +00:00

289 lines
6.1 KiB
C

#include <errno.h>
#include <rtti.h>
#include <stdint.h>
#include <stdlib.h>
#include "kv.h"
#include "utils.h"
typedef struct kvent_ kvent_t;
static uint32_t hash_int_key(void* key)
{
uint64_t intkey = (uint64_t)(long)key;
return hash64(intkey);
}
static int assign_int_key(void** dst, void* src)
{
*dst = src;
return 0;
}
static int cmp_int_key(void* key1, void* key2)
{
uint64_t intkey1 = (uint64_t)(long)key1;
uint64_t intkey2 = (uint64_t)(long)key2;
if (intkey1 > intkey2) {
return 1;
} else if (intkey2 > intkey1) {
return -1;
}
return 0;
}
kv_key_ops_t int_key_ops = { .hash = (kv_key_hash_fn_t)hash_int_key,
.assign = (kv_key_assign_fn_t)assign_int_key,
.cmp = (kv_key_cmp_fn_t)cmp_int_key };
static uint32_t hash_string_key(void* key) { return hash_string((char*)key); }
static int cmp_string_key(void* key1, void* key2)
{
return strcmp((char*)key1, (char*)key2);
}
static int assign_string_key(char** dst, char* src)
{
char* p = strdup(src);
if (p == NULL) {
return -ENOMEM;
}
*dst = p;
return 1;
}
static void destroy_string_key(char* s)
{
if (s == NULL) {
return;
}
free(s);
}
kv_key_ops_t string_key_ops
= { .hash = (kv_key_hash_fn_t)hash_string_key,
.cmp = (kv_key_cmp_fn_t)cmp_string_key,
.assign = (kv_key_assign_fn_t)assign_string_key,
.destroy = (kv_key_destroy_fn_t)destroy_string_key };
struct kvent_ {
kvent_t* next;
void* key;
void* value;
};
typedef struct {
uint32_t nslots;
kv_key_ops_t* keyops;
destroy_fn_t val_killer;
kvent_t** slots;
} kvtab_t;
static size_t kvtab_datasize(va_list ap)
{
size_t sz = sizeof(kvtab_t);
return sz;
}
static int kvtab_init(meta_obj_t* obj, va_list ap)
{
kvtab_t* kvtab = RTTI_OBJ_TO_DATA(kvtab_t, obj);
kv_params_t* params = va_arg(ap, kv_params_t*);
if ((params == NULL) || (params->nslots <= 1) || (params->keyops == NULL)
|| (params->keyops->hash == NULL) || (params->keyops->cmp == NULL)) {
return -EINVAL;
}
kvtab->slots = (kvent_t**)calloc(params->nslots, sizeof(*(kvtab->slots)));
if (kvtab->slots == NULL) {
return -ENOMEM;
}
kvtab->nslots = params->nslots;
kvtab->keyops = params->keyops;
kvtab->val_killer = params->datakiller;
return 1;
}
static void kvtab_destroy(meta_obj_t* obj)
{
kvtab_t* kvtab = RTTI_OBJ_TO_DATA(kvtab_t, obj);
int i;
for (i = 0; i < kvtab->nslots; i++) {
kvent_t* e = NULL;
while ((e = kvtab->slots[i]) != NULL) {
kvtab->slots[i] = e->next;
e->next = NULL;
if (kvtab->val_killer != NULL) {
kvtab->val_killer(e->value);
}
if (kvtab->keyops->destroy != NULL) {
kvtab->keyops->destroy(e->key);
}
FREEUP(e);
}
}
FREEUP(kvtab->slots);
}
static kvent_t* kvtab_get(kvtab_t* t, void* key)
{
uint32_t hc;
kvent_t* e = NULL;
hc = t->keyops->hash(key) % t->nslots;
e = t->slots[hc];
while (e != NULL) {
if (t->keyops->cmp(e->key, key) == 0) {
break;
}
e = e->next;
}
return e;
}
static int kvtab_add(meta_obj_t* obj, void* key, void* value)
{
kvtab_t* t = RTTI_OBJ_TO_DATA(kvtab_t, obj);
kvent_t* e = NULL;
uint32_t hc;
e = kvtab_get(t, key);
if (e != NULL) {
return -EEXIST;
}
e = (kvent_t*)calloc(1, sizeof(*e));
if (e == NULL) {
return -ENOMEM;
}
if (t->keyops->assign == NULL) {
e->key = key;
} else {
int rc;
rc = t->keyops->assign(&(e->key), key);
if (rc < 0) {
free(e);
return rc;
}
}
e->value = value;
hc = t->keyops->hash(key) % t->nslots;
e->next = t->slots[hc];
t->slots[hc] = e;
return 1;
}
static int kvtab_del(meta_obj_t* obj, void* key)
{
kvtab_t* t = RTTI_OBJ_TO_DATA(kvtab_t, obj);
kvent_t** e = NULL;
uint32_t hc;
hc = t->keyops->hash(key) % t->nslots;
e = &(t->slots[hc]);
while ((*e) != NULL) {
if (t->keyops->cmp((*e)->key, key) == 0) {
kvent_t* e1 = *e;
*e = e1->next;
if (t->val_killer != NULL) {
t->val_killer(e1->value);
}
if (t->keyops->destroy != NULL) {
t->keyops->destroy(e1->key);
}
FREEUP(e1);
return 1;
}
e = &((*e)->next);
}
return 0;
}
static int kvtab_find(meta_obj_t* obj, void* key, void** value)
{
kvtab_t* t = RTTI_OBJ_TO_DATA(kvtab_t, obj);
kvent_t* e = NULL;
if (value == NULL) {
return -EINVAL;
}
e = kvtab_get(t, key);
if (e == NULL) {
return -ENOENT;
}
*value = e->value;
return 1;
}
/*
* kvtab_foreach — walk every live entry and invoke cb(key, index, arg).
* index is recovered by casting the stored void* value back to int.
* Caller must hold any necessary external lock before calling.
*/
int kvtab_foreach(var kvtab_var, kvtab_foreach_fn_t cb, void* arg)
{
if (kvtab_var == NULL || cb == NULL) {
return -EINVAL;
}
meta_obj_t* obj = RTTI_DATA_TO_OBJ(kvtab_var);
kvtab_t* t = RTTI_OBJ_TO_DATA(kvtab_t, obj);
int count = 0;
uint32_t i;
for (i = 0; i < t->nslots; i++) {
kvent_t* e = t->slots[i];
while (e != NULL) {
int idx = (int)(long)e->value;
cb(e->key, idx, arg);
count++;
e = e->next;
}
}
return count;
}
typeinfo_t kvtab_type
= { .name = "kvtab",
.privateSize = 0,
.dataSize = (datasize_fn_t)kvtab_datasize,
.init = (obj_init_fn_t)kvtab_init,
.destroy = (obj_destroy_fn_t)kvtab_destroy,
.interfaces
= { .kv = &(intf_kv_t) { .add_key = (key_add_fn_t)kvtab_add,
.del_key = (key_del_fn_t)kvtab_del,
.find_key = (key_find_fn_t)kvtab_find } } };