urcu-ht: work in progress RCU hash table
[urcu.git] / urcu-ht.c
CommitLineData
2ed95849
MD
1
2#define _LGPL_SOURCE
3#include <stdlib.h>
4#include <urcu.h>
5#include <arch.h>
6#include <arch_atomic.h>
7#include <assert.h>
8#include <compiler.h>
9#include <urcu-defer.h>
10#include <errno.h>
ab7d5fc6 11#include <urcu-ht.h>
2ed95849
MD
12
13struct rcu_ht_node;
14
15struct rcu_ht_node {
16 struct rcu_ht_node *next;
17 void *key;
18 void *data;
19};
20
21struct rcu_ht {
22 struct rcu_ht_node *tbl[HASH_SIZE];
23 ht_hash_fct hash_fct;
24 void (*free_fct)(void *data); /* fct to free data */
25};
26
2ed95849
MD
27struct rcu_ht *ht_new(ht_hash_fct hash_fct, void (*free_fct)(void *data))
28{
29 struct rcu_ht *ht;
30
31 ht = calloc(1, sizeof(struct rcu_ht));
32 ht->hash_fct = hash_fct;
33 ht->free_fct = free_fct;
ab7d5fc6 34 return ht;
2ed95849
MD
35}
36
37/* delete all elements */
38void ht_delete_all(struct rcu_ht *ht)
39{
40 unsigned long i;
41 struct rcu_ht_node **prev, *node;
42
43 for (i = 0; i < HASH_SIZE; i++) {
44 rcu_read_lock();
45
46 prev = &ht->tbl[i];
47 node = rcu_dereference(*prev);
48 /*
49 * Cut the head, therefore whole bucket will be unused
50 * after GP. (except for concurrent additions)
51 */
52 rcu_assign_pointer(prev, NULL);
53 for (;;) {
54 if (likely(!node)) {
55 break;
56 }
57 prev = &node->next;
58 if (node->data)
59 call_rcu(ht->free_fct, node->data);
60 call_rcu(free, node);
61 node = rcu_dereference(*prev);
62 }
63 rcu_read_unlock();
64 }
65}
66
67/*
68 * Should only be called when no more concurrent readers nor writers can
69 * possibly access the table.
70 */
ab7d5fc6 71void ht_destroy(struct rcu_ht *ht)
2ed95849
MD
72{
73 ht_delete_all(ht);
74 free(ht);
75}
76
77void *ht_lookup(struct rcu_ht *ht, void *key)
78{
79 unsigned long hash;
80 struct rcu_ht_node *node;
81 void *ret;
82
83 hash = ht->hash_fct(key);
84
85 rcu_read_lock();
86 node = rcu_dereference(ht->tbl[hash]);
87 for (;;) {
88 if (likely(!node)) {
89 ret = NULL;
90 break;
91 }
92 if (node->key == key) {
93 ret = node->data;
94 break;
95 }
96 node = rcu_dereference(node->next);
97 }
98 rcu_read_unlock();
ab7d5fc6
MD
99
100 return ret;
2ed95849
MD
101}
102
103/*
104 * Will re-try until either:
105 * - The key is already there (-EEXIST)
106 * - We successfully add the key at the head of a table bucket.
107 */
108int ht_add(struct rcu_ht *ht, void *key, void *data)
109{
110 struct rcu_ht_node *node, *old_head, *new_head;
111 unsigned long hash;
112 int ret = 0;
113
114 new_head = calloc(1, sizeof(struct rcu_ht_node));
115 new_head->key = key;
116 new_head->data = data;
117 hash = ht->hash_fct(key);
118
119 /* here comes the fun and tricky part.
120 * Add at the beginning with a cmpxchg.
121 * Hold a read lock between the moment the first element is read
122 * and the nodes traversal (to find duplicates). This ensures
123 * the head pointer has not been reclaimed when cmpxchg is done.
124 * Always adding at the head ensures that we would have to
125 * re-try if a new item has been added concurrently. So we ensure that
126 * we never add duplicates. */
127retry:
128 rcu_read_lock();
129
130 old_head = node = rcu_dereference(ht->tbl[hash]);
131 for (;;) {
132 if (likely(!node)) {
133 break;
134 }
135 if (node->key == key) {
136 ret = -EEXIST;
137 goto end;
138 }
139 node = rcu_dereference(node->next);
140 }
141 if (rcu_cmpxchg_pointer(&ht->tbl[hash], old_head, new_head) != old_head)
142 goto restart;
143end:
144 rcu_read_unlock();
145
146 return ret;
147
148 /* restart loop, release and re-take the read lock to be kind to GP */
149restart:
150 rcu_read_unlock();
151 goto retry;
152}
153
154/*
155 * Restart until we successfully remove the entry, or no entry is left
156 * ((void *)(unsigned long)-ENOENT).
157 */
ab7d5fc6 158void *ht_steal(struct rcu_ht *ht, void *key)
2ed95849
MD
159{
160 struct rcu_ht_node **prev, *node;
161 unsigned long hash;
ab7d5fc6 162 void *data;
2ed95849
MD
163
164 hash = ht->hash_fct(key);
165
166retry:
167 rcu_read_lock();
168
169 prev = &ht->tbl[hash];
170 node = rcu_dereference(*prev);
171 for (;;) {
172 if (likely(!node)) {
ab7d5fc6 173 data = (void *)(unsigned long)-ENOENT;
2ed95849
MD
174 goto end;
175 }
176 if (node->key == key) {
177 break;
178 }
179 prev = &node->next;
180 node = rcu_dereference(*prev);
181 }
182 /* Found it ! pointer to object is in "prev" */
183 if (rcu_cmpxchg_pointer(prev, node, node->next) != node)
184 goto restart;
185end:
186 rcu_read_unlock();
187
ab7d5fc6
MD
188 data = node->data;
189 call_rcu(free, node);
190
191 return data;
2ed95849
MD
192
193 /* restart loop, release and re-take the read lock to be kind to GP */
194restart:
195 rcu_read_unlock();
196 goto retry;
197}
198
199int ht_delete(struct rcu_ht *ht, void *key)
200{
ab7d5fc6 201 void *data;
2ed95849 202
ab7d5fc6
MD
203 data = ht_steal(ht, key);
204 if (data) {
205 if (ht->free_fct && data)
206 call_rcu(ht->free_fct, data);
2ed95849
MD
207 return 0;
208 } else {
209 return -ENOENT;
210 }
211}
ab7d5fc6
MD
212
213unsigned long stupid_hash(void *key)
214{
215 return (unsigned long)key % HASH_SIZE;
216}
This page took 0.029656 seconds and 4 git commands to generate.