rcuja: range implementation file
[userspace-rcu.git] / rcuja / rcuja-range.c
CommitLineData
b45a45b9
MD
1/*
2 * rcuja/rcuja-range.c
3 *
4 * Userspace RCU library - RCU Judy Array Range Support
5 *
6 * Copyright 2012-2013 - Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23#define _LGPL_SOURCE
24#include <stdint.h>
25#include <errno.h>
26#include <limits.h>
27#include <string.h>
28#include <assert.h>
29#include <pthread.h>
30#include <urcu/rcuja.h>
31#include <urcu/compiler.h>
32#include <urcu/arch.h>
33#include <urcu-pointer.h>
34#include <urcu/uatomic.h>
35#include <urcu/rcuja-range.h>
36#include <urcu-flavor.h>
37
38#include "rcuja-internal.h"
39
40/*
41 * Discussion about order of lookup/lock vs allocated node deletion.
42 *
43 * - If node deletion returns before call to
44 * cds_ja_range_lookup(), the node will not be found by lookup.
45 * - If node deletion is called after cds_ja_range_lock() returns a
46 * non-NULL range, the deletion will wait until the lock is released
47 * before it takes place.
48 * - If node deletion call/return overlaps with the call to
49 * cds_ja_range_lookup() and return from cds_ja_range_lock(), the node
50 * may or may not be found by each of cds_ja_range_lookup() and
51 * cds_ja_range_lock().
52 */
53
54/*
55 * Discussion about order of lookup/lock vs allocated node add. Assuming
56 * no concurrent delete.
57 *
58 * - If node add returns before call to
59 * cds_ja_range_lookup(), the node will be found by lookup.
60 * - If node add is called after cds_ja_range_lookup returns, the node
61 * will not be found by lookup.
62 * - If node add call/return overlaps with the call to and return from
63 * cds_ja_range_lookup(), the node may or may not be found.
64 * - If node add call/return overlaps with call to cds_ja_range_lookup()
65 * and return from cds_ja_range_lock(), in the specific case where
66 * cds_ja_range_lookup() _does_ succeed, then cds_ja_range_lock() will
67 * succeed (still assuming no concurrent deletion).
68 */
69
70/*
71 * Discussion of the type state transitions.
72 *
73 * State transitions of "type" always go from either:
74 *
75 * CDS_JA_RANGE_FREE -> CDS_JA_RANGE_REMOVED
76 * or
77 * CDS_JA_RANGE_ALLOCATED -> CDS_JA_RANGE_REMOVED
78 *
79 * A range type never changes otherwise.
80 */
81
82
83struct cds_ja_range *cds_ja_range_lookup(struct cds_ja *ja, uint64_t key)
84{
85 struct cds_ja_node *node, *last_node;
86 struct cds_ja_range *range;
87
88 node = cds_ja_lookup_below_equal(ja, key, NULL);
89 assert(node);
90 /*
91 * Get the last of duplicate chain. Adding a node to Judy array
92 * duplicates inserts them at the end of the chain.
93 */
94 cds_ja_for_each_duplicate_rcu(node)
95 last_node = node;
96 range = caa_container_of(last_node, struct cds_ja_range, ja_node);
97 /*
98 * If last node in the duplicates is removed or free, we can
99 * consider that either a removal or add operation is in
100 * progress, or removal is the last completed operation to
101 * update this range. We can therefore consider that this area
102 * is not allocated.
103 */
104 if (range->type != CDS_JA_RANGE_ALLOCATED)
105 return NULL;
106 /*
107 * We found an allocated range. We can return it for use with
108 * RCU read-side prototection for existence. However, we have no
109 * mutual exclusion against removal at this point.
110 */
111 return range;
112}
113
114/*
115 * Provide mutual exclusion against removal.
116 */
117struct cds_ja_range *cds_ja_range_lock(struct cds_ja_range *range)
118{
119 pthread_mutex_lock(&range->lock);
120
121 if (range->type == CDS_JA_RANGE_REMOVED)
122 goto removed;
123 return range;
124
125removed:
126 pthread_mutex_unlock(&range->lock);
127 return NULL;
128}
129
130void cds_ja_range_unlock(struct cds_ja_range *range)
131{
132 pthread_mutex_unlock(&range->lock);
133}
134
135static
136struct cds_ja_range *range_create(
137 uint64_t start, /* inclusive */
138 uint64_t end, /* inclusive */
139 enum cds_ja_range_type type)
140{
141 struct cds_ja_range *range;
142
143 range = calloc(sizeof(*range), 1);
144 if (!range)
145 return NULL;
146 range->start = start;
147 range->end = end;
148 range->type = type;
149 pthread_mutex_init(&range->lock, NULL);
150 return range;
151}
152
153static
154void free_range_cb(struct rcu_head *head)
155{
156 struct cds_ja_range *range =
157 caa_container_of(head, struct cds_ja_range, head);
158 free(range);
159}
160
161static
162void free_range(struct cds_ja_range *range)
163{
164 free(range);
165}
166
167static
168void rcu_free_range(struct cds_ja *ja, struct cds_ja_range *range)
169{
170 cds_lfht_rcu_flavor(ja->ht)->update_call_rcu(&range->head,
171 free_range_cb);
172}
173
174struct cds_ja_range *cds_ja_range_add(struct cds_ja *ja,
175 uint64_t start, /* inclusive */
176 uint64_t end) /* inclusive */
177{
178 struct cds_ja_node *old_node, *old_node_end;
179 struct cds_ja_range *old_range, *old_range_end, *new_range, *ranges[3];
180 unsigned int nr_ranges, i;
181 int ret;
182
183retry:
184 /*
185 * Find if requested range is entirely contained within a single
186 * free range.
187 */
188 old_node = cds_ja_lookup_below_equal(ja, start, NULL);
189 assert(old_node);
190
191 old_range = caa_container_of(old_node, struct cds_ja_range, ja_node);
192 switch (CMM_LOAD_SHARED(old_range->type)) {
193 case CDS_JA_RANGE_ALLOCATED:
194 return NULL;
195 case CDS_JA_RANGE_FREE:
196 break;
197 case CDS_JA_RANGE_REMOVED:
198 goto retry;
199 }
200
201 old_node_end = cds_ja_lookup_below_equal(ja, end, NULL);
202 assert(old_node_end);
203 old_range_end = caa_container_of(old_node_end,
204 struct cds_ja_range, ja_node);
205 if (old_range_end != old_range) {
206 switch (CMM_LOAD_SHARED(old_range->type)) {
207 case CDS_JA_RANGE_ALLOCATED:
208 case CDS_JA_RANGE_FREE: /* fall-through */
209 /*
210 * TODO: think about free range merge.
211 */
212 return NULL;
213 case CDS_JA_RANGE_REMOVED:
214 goto retry;
215 }
216 }
217
218 pthread_mutex_lock(&old_range->lock);
219
220 if (old_range->type == CDS_JA_RANGE_REMOVED) {
221 pthread_mutex_unlock(&old_range->lock);
222 goto retry;
223 }
224
225 /* Create replacement ranges: at most 2 free and 1 allocated */
226 if (start == old_range->start) {
227 if (end == old_range->end) {
228 /* 1 range */
229 ranges[0] = new_range = range_create(start, end,
230 CDS_JA_RANGE_ALLOCATED);
231 nr_ranges = 1;
232 } else {
233 /* 2 ranges */
234 ranges[0] = new_range = range_create(start, end,
235 CDS_JA_RANGE_ALLOCATED);
236 ranges[1] = range_create(end + 1, old_range->end,
237 CDS_JA_RANGE_FREE);
238 nr_ranges = 2;
239 }
240 } else {
241 if (end == old_range->end) {
242 /* 2 ranges */
243 ranges[0] = range_create(old_range->start, start - 1,
244 CDS_JA_RANGE_FREE);
245 ranges[1] = new_range = range_create(start, end,
246 CDS_JA_RANGE_ALLOCATED);
247 nr_ranges = 2;
248 } else {
249 /* 3 ranges */
250 ranges[0] = range_create(old_range->start, start - 1,
251 CDS_JA_RANGE_FREE);
252 ranges[1] = new_range = range_create(start, end,
253 CDS_JA_RANGE_ALLOCATED);
254 ranges[2] = range_create(end + 1, old_range->end,
255 CDS_JA_RANGE_FREE);
256 nr_ranges = 3;
257 }
258 }
259
260 /* Add replacement ranges to Judy array */
261 for (i = 0; i < nr_ranges; i++) {
262 ret = cds_ja_add(ja, ranges[i]->start, &ranges[i]->ja_node);
263 assert(!ret);
264 }
265
266 /*
267 * We add replacement ranges _before_ removing old ranges, so
268 * concurrent traversals will always see one or the other. This
269 * is OK because we temporarily have a duplicate key, and Judy
270 * arrays provide key existence guarantee for lookups performed
271 * concurrently with add followed by del of duplicate keys.
272 */
273
274 /* Remove old free range */
275 ret = cds_ja_del(ja, old_range->start, &old_range->ja_node);
276 assert(!ret);
277 old_range->type = CDS_JA_RANGE_REMOVED;
278 pthread_mutex_unlock(&old_range->lock);
279
280 rcu_free_range(ja, old_range);
281
282 return new_range;
283}
284
285int cds_ja_range_del(struct cds_ja *ja, struct cds_ja_range *range)
286{
287 struct cds_ja_node *prev_node, *next_node;
288 struct cds_ja_range *prev_range, *next_range, *new_range;
289 struct cds_ja_range *merge_ranges[3];
290 unsigned int nr_merge, i;
291 uint64_t start, end;
292 int ret;
293
294retry:
295 nr_merge = 0;
296 prev_node = cds_ja_lookup_below_equal(ja, range->start - 1, NULL);
297 prev_range = caa_container_of(prev_node, struct cds_ja_range, ja_node);
298 if (prev_node && prev_range->type != CDS_JA_RANGE_ALLOCATED)
299 merge_ranges[nr_merge++] = prev_range;
300
301 merge_ranges[nr_merge++] = range;
302
303 next_node = cds_ja_lookup_above_equal(ja, range->end + 1, NULL);
304 next_range = caa_container_of(next_node, struct cds_ja_range, ja_node);
305 if (next_node && next_range->type != CDS_JA_RANGE_ALLOCATED)
306 merge_ranges[nr_merge++] = next_range;
307
308 /* Acquire locks in increasing key order for range merge */
309 for (i = 0; i < nr_merge; i++)
310 pthread_mutex_lock(&merge_ranges[i]->lock);
311 /* Ensure they are valid */
312 for (i = 0; i < nr_merge; i++) {
313 if (merge_ranges[i]->type == CDS_JA_RANGE_REMOVED)
314 goto unlock_retry;
315 }
316
317 /* Create new free range */
318 start = merge_ranges[0]->start;
319 end = merge_ranges[nr_merge - 1]->end;
320 new_range = range_create(start, end, CDS_JA_RANGE_FREE);
321 ret = cds_ja_add(ja, start, &new_range->ja_node);
322 assert(!ret);
323
324 /* Remove old ranges */
325 for (i = 0; i < nr_merge; i++) {
326 ret = cds_ja_del(ja, merge_ranges[i]->start,
327 &merge_ranges[i]->ja_node);
328 assert(!ret);
329 merge_ranges[i]->type = CDS_JA_RANGE_REMOVED;
330 pthread_mutex_unlock(&merge_ranges[i]->lock);
331 rcu_free_range(ja, merge_ranges[i]);
332 }
333
334 return 0;
335
336 /* retry paths */
337unlock_retry:
338 for (i = 0; i < nr_merge; i++)
339 pthread_mutex_unlock(&merge_ranges[i]->lock);
340 goto retry;
341}
342
343int cds_ja_range_init(struct cds_ja *ja)
344{
345 struct cds_ja_node *node;
346 struct cds_ja_range *range;
347 int ret;
348
349 /*
350 * Sanity check: should be empty.
351 */
352 node = cds_ja_lookup_above_equal(ja, 0, NULL);
353 if (node)
354 return -EINVAL;
355 range = range_create(0, UINT64_MAX, CDS_JA_RANGE_FREE);
356 if (!range)
357 return -EINVAL;
358 ret = cds_ja_add(ja, 0, &range->ja_node);
359 return ret;
360}
361
362int cds_ja_range_fini(struct cds_ja *ja)
363{
364 uint64_t key;
365 struct cds_ja_node *ja_node;
366 int ret = 0;
367
368 cds_ja_for_each_key_rcu(ja, key, ja_node) {
369 struct cds_ja_node *tmp_node;
370
371 cds_ja_for_each_duplicate_safe_rcu(ja_node, tmp_node) {
372 struct cds_ja_range *range;
373
374 range = caa_container_of(ja_node,
375 struct cds_ja_range, ja_node);
376 ret = cds_ja_del(ja, key, &range->ja_node);
377 if (ret) {
378 goto end;
379 }
380 /* Alone using Judy array, OK to free now */
381 free_range(range);
382 }
383 }
384end:
385 return ret;
386}
This page took 0.038609 seconds and 4 git commands to generate.