This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
tango-maat/src/rcu_hash.c
2023-10-27 17:31:35 +08:00

409 lines
10 KiB
C

/*
**********************************************************************************************
* File: rcu_hash.c
* Description:
* Authors: Liu WenTan <liuwentan@geedgenetworks.com>
* Date: 2022-10-31
* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
***********************************************************************************************
*/
#include <assert.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/queue.h>
#include <stdio.h>
#include "rcu_hash.h"
#include "maat_utils.h"
struct rcu_hash_garbage_bag {
time_t create_time;
void *garbage;
void (* garbage_free)(void *garbage);
TAILQ_ENTRY(rcu_hash_garbage_bag) entries;
};
TAILQ_HEAD(rcu_hash_garbage_q, rcu_hash_garbage_bag);
struct rcu_hash_table {
int is_updating;
char effective_hash; // 'a' or 'b'
/* two hash map for rcu */
struct rcu_hash_node *hashmap_a;
struct rcu_hash_node *hashmap_b;
void (*data_free_fn)(void *user_ctx, void *data);
void *user_ctx;
struct rcu_hash_garbage_q garbage_q;
size_t garbage_q_len;
int gc_timeout_s;
pthread_mutex_t update_mutex;
};
struct rcu_hash_node {
char *key;
size_t key_len;
void *data;
/* htable the node belongs to */
struct rcu_hash_table *htable;
UT_hash_handle hh_a;
UT_hash_handle hh_b;
};
static void rcu_hash_garbage_collect_force(struct rcu_hash_table *htable)
{
struct rcu_hash_garbage_bag *p = NULL;
while ((p = TAILQ_FIRST(&(htable->garbage_q))) != NULL) {
p->garbage_free(p->garbage);
p->garbage = NULL;
TAILQ_REMOVE(&(htable->garbage_q), p, entries);
FREE(p);
htable->garbage_q_len--;
}
}
void rcu_hash_garbage_collect_routine(struct rcu_hash_table *htable)
{
struct rcu_hash_garbage_bag *p = NULL, *tmp = NULL;
time_t now = time(NULL);
for (p = TAILQ_FIRST(&(htable->garbage_q)); p != NULL; p = tmp) {
tmp = TAILQ_NEXT(p, entries);
if ((now - p->create_time) > htable->gc_timeout_s ||
htable->gc_timeout_s == 0) {
p->garbage_free(p->garbage);
p->garbage = NULL;
TAILQ_REMOVE(&(htable->garbage_q), p, entries);
FREE(p);
htable->garbage_q_len--;
}
}
}
static void rcu_hash_garbage_bagging(struct rcu_hash_garbage_q *garbage_q,
void *garbage, void (* func)(void *))
{
struct rcu_hash_garbage_bag *bag = ALLOC(struct rcu_hash_garbage_bag, 1);
bag->create_time = time(NULL);
bag->garbage = garbage;
bag->garbage_free = func;
TAILQ_INSERT_TAIL(garbage_q, bag, entries);
}
static void rcu_hash_node_free(struct rcu_hash_node *node)
{
if (NULL == node) {
return;
}
if (node->key != NULL) {
FREE(node->key);
}
if (node->data != NULL) {
node->htable->data_free_fn(node->htable->user_ctx, node->data);
node->data = NULL;
}
FREE(node);
}
struct rcu_hash_table *
rcu_hash_new(data_free_fn *free_fn, void *user_ctx, int gc_timeout_s)
{
if (NULL == free_fn) {
return NULL;
}
struct rcu_hash_table *htable = ALLOC(struct rcu_hash_table, 1);
htable->is_updating = 0;
htable->effective_hash = 'a';
TAILQ_INIT(&htable->garbage_q);
htable->garbage_q_len = 0;
htable->gc_timeout_s = gc_timeout_s;
htable->data_free_fn = free_fn;
htable->user_ctx = user_ctx;
pthread_mutex_init(&htable->update_mutex, NULL);
return htable;
}
void rcu_hash_free(struct rcu_hash_table *htable)
{
if (NULL == htable) {
return;
}
struct rcu_hash_node *tmp = NULL;
struct rcu_hash_node *item = NULL;
if (htable->effective_hash == 'a') {
HASH_ITER(hh_a, htable->hashmap_a, item, tmp) {
HASH_DELETE(hh_a, htable->hashmap_a, item);
rcu_hash_node_free(item);
}
} else {
HASH_ITER(hh_b, htable->hashmap_b, item, tmp) {
HASH_DELETE(hh_b, htable->hashmap_b, item);
rcu_hash_node_free(item);
}
}
rcu_hash_garbage_collect_force(htable);
pthread_mutex_destroy(&htable->update_mutex);
FREE(htable);
}
static void rcu_hash_commit_prepare(struct rcu_hash_table *htable)
{
struct rcu_hash_node *node = NULL;
struct rcu_hash_node *tmp = NULL;
if (htable->effective_hash == 'a') {
assert(htable->hashmap_b == NULL);
HASH_ITER(hh_a, htable->hashmap_a, node, tmp) {
HASH_ADD_KEYPTR(hh_b, htable->hashmap_b, node->key, node->key_len, node);
}
} else {
assert(htable->hashmap_a == NULL);
HASH_ITER(hh_b, htable->hashmap_b, node, tmp) {
HASH_ADD_KEYPTR(hh_a, htable->hashmap_a, node->key, node->key_len, node);
}
}
htable->is_updating = 1;
}
int rcu_hash_add(struct rcu_hash_table *htable, const char *key,
size_t key_len, void *data)
{
if (NULL == htable || NULL == key || 0 == key_len) {
return -1;
}
if (!htable->is_updating) {
rcu_hash_commit_prepare(htable);
}
struct rcu_hash_node *tmp = NULL;
if (htable->effective_hash == 'a') {
HASH_FIND(hh_b, htable->hashmap_b, key, key_len, tmp);
} else {
HASH_FIND(hh_a, htable->hashmap_a, key, key_len, tmp);
}
if (tmp != NULL) {
return -1;
}
struct rcu_hash_node *node = ALLOC(struct rcu_hash_node, 1);
node->key = ALLOC(char, key_len);
memcpy(node->key, key, key_len);
node->key_len = key_len;
node->data = data;
node->htable = htable;
if (htable->effective_hash == 'a') {
HASH_ADD_KEYPTR(hh_b, htable->hashmap_b, node->key, node->key_len, node);
} else {
HASH_ADD_KEYPTR(hh_a, htable->hashmap_a, node->key, node->key_len, node);
}
return 0;
}
int rcu_hash_del(struct rcu_hash_table *htable, const char *key, size_t key_len)
{
if (NULL == htable || NULL == key || 0 == key_len) {
return -1;
}
if (!htable->is_updating) {
rcu_hash_commit_prepare(htable);
}
struct rcu_hash_node *node = NULL;
if (htable->effective_hash == 'a') {
HASH_FIND(hh_b, htable->hashmap_b, key, key_len, node);
if (node != NULL) {
HASH_DELETE(hh_b, htable->hashmap_b, node);
}
} else {
HASH_FIND(hh_a, htable->hashmap_a, key, key_len, node);
if (node != NULL) {
HASH_DELETE(hh_a, htable->hashmap_a, node);
}
}
if (node != NULL) {
rcu_hash_garbage_bagging(&(htable->garbage_q), node,
(void (*)(void*))rcu_hash_node_free);
htable->garbage_q_len++;
}
return 0;
}
void *rcu_hash_find(struct rcu_hash_table *htable, const char *key, size_t key_len)
{
if (NULL == htable || NULL == key || 0 == key_len) {
return NULL;
}
struct rcu_hash_node *node = NULL;
if (htable->effective_hash == 'a') {
HASH_FIND(hh_a, htable->hashmap_a, key, key_len, node);
if (node != NULL) {
return node->data;
}
} else {
HASH_FIND(hh_b, htable->hashmap_b, key, key_len, node);
if (node != NULL) {
return node->data;
}
}
return NULL;
}
void *rcu_updating_hash_find(struct rcu_hash_table *htable, const char *key, size_t key_len)
{
if (NULL == htable || NULL == key || 0 == key_len) {
return NULL;
}
struct rcu_hash_node *node = NULL;
if (htable->effective_hash == 'a') {
HASH_FIND(hh_b, htable->hashmap_b, key, key_len, node);
if (node != NULL) {
return node->data;
}
} else {
HASH_FIND(hh_a, htable->hashmap_a, key, key_len, node);
if (node != NULL) {
return node->data;
}
}
return NULL;
}
size_t rcu_hash_count(struct rcu_hash_table *htable)
{
if (NULL == htable) {
return 0;
}
if (htable->effective_hash == 'a') {
return HASH_CNT(hh_a, htable->hashmap_a);
} else {
return HASH_CNT(hh_b, htable->hashmap_b);
}
}
int rcu_hash_is_updating(struct rcu_hash_table *htable)
{
if (NULL == htable) {
return 0;
}
return htable->is_updating;
}
void rcu_hash_commit(struct rcu_hash_table *htable)
{
if (NULL == htable) {
return;
}
struct rcu_hash_node *node = NULL;
struct rcu_hash_node *tmp = NULL;
pthread_mutex_lock(&htable->update_mutex);
if (!htable->is_updating) {
pthread_mutex_unlock(&htable->update_mutex);
return;
}
/* updating hash_map is ready, so change effective hash_map */
if (htable->effective_hash == 'a') {
htable->effective_hash = 'b';
usleep(100);
HASH_ITER(hh_a, htable->hashmap_a, node, tmp) {
HASH_DELETE(hh_a, htable->hashmap_a, node);
}
} else {
htable->effective_hash = 'a';
usleep(100);
HASH_ITER(hh_b, htable->hashmap_b, node, tmp) {
HASH_DELETE(hh_b, htable->hashmap_b, node);
}
}
htable->is_updating = 0;
rcu_hash_garbage_collect_routine(htable);
htable->garbage_q_len = 0;
pthread_mutex_unlock(&htable->update_mutex);
}
size_t rcu_hash_list(struct rcu_hash_table *htable, void ***data_array)
{
size_t i = 0;
size_t node_cnt = 0;
struct rcu_hash_node *node = NULL, *tmp = NULL;
if (htable->effective_hash == 'a') {
node_cnt = HASH_CNT(hh_a, htable->hashmap_a);
*data_array = ALLOC(void *, node_cnt);
HASH_ITER(hh_a, htable->hashmap_a, node, tmp) {
(*data_array)[i] = node->data;
i++;
}
} else {
node_cnt = HASH_CNT(hh_b, htable->hashmap_b);
*data_array = ALLOC(void *, node_cnt);
HASH_ITER(hh_b, htable->hashmap_b, node, tmp) {
(*data_array)[i] = node->data;
i++;
}
}
return node_cnt;
}
size_t rcu_updating_hash_list(struct rcu_hash_table *htable, void ***data_array)
{
if (NULL == htable || NULL == data_array) {
return 0;
}
size_t i = 0;
size_t node_cnt = 0;
struct rcu_hash_node *node = NULL, *tmp = NULL;
if (htable->effective_hash == 'a') {
node_cnt = HASH_CNT(hh_b, htable->hashmap_b);
*data_array = ALLOC(void *, node_cnt);
HASH_ITER(hh_b, htable->hashmap_b, node, tmp) {
(*data_array)[i] = node->data;
i++;
}
} else {
node_cnt = HASH_CNT(hh_a, htable->hashmap_a);
*data_array = ALLOC(void *, node_cnt);
HASH_ITER(hh_a, htable->hashmap_a, node, tmp) {
(*data_array)[i] = node->data;
i++;
}
}
return node_cnt;
}