]> pd.if.org Git - nbds/blob - map/list.c
0d9c11f24f275254c238e9a538f5e8332b3b2729
[nbds] / map / list.c
1 /* 
2  * Written by Josh Dybnis and released to the public domain, as explained at
3  * http://creativecommons.org/licenses/publicdomain
4  *
5  * Harris-Michael lock-free list-based set
6  * http://www.research.ibm.com/people/m/michael/spaa-2002.pdf
7  */
8
9 #include <stdio.h>
10 #include <string.h>
11
12 #include "common.h"
13 #include "list.h"
14 #include "mem.h"
15 #ifdef LIST_USE_HAZARD_POINTER
16 #include "hazard.h"
17 #else
18 #include "rcu.h"
19 #endif
20
21 typedef struct node {
22     map_key_t  key;
23     map_val_t  val;
24     markable_t next; // next node
25 } node_t;
26
27 struct ll_iter {
28     node_t *pred;
29 };
30
31 struct ll {
32     node_t *head;
33     const datatype_t *key_type;
34 };
35
36 // Marking the <next> field of a node logically removes it from the list
37 #define  MARK_NODE(x) TAG_VALUE((markable_t)(x), TAG1)
38 #define   HAS_MARK(x) (IS_TAGGED((x), TAG1) == TAG1)
39 #define   GET_NODE(x) ((node_t *)(x))
40 #define STRIP_MARK(x) ((node_t *)STRIP_TAG((x), TAG1))
41
42 static node_t *node_alloc (map_key_t key, map_val_t val) {
43     node_t *item = (node_t *)nbd_malloc(sizeof(node_t));
44     item->key = key;
45     item->val = val;
46     return item;
47 }
48
49 list_t *ll_alloc (const datatype_t *key_type) {
50     list_t *ll = (list_t *)nbd_malloc(sizeof(list_t));
51     ll->key_type = key_type;
52     ll->head = node_alloc(0, 0);
53     ll->head->next = DOES_NOT_EXIST;
54     return ll;
55 }
56
57 void ll_free (list_t *ll) {
58     node_t *item = STRIP_MARK(ll->head->next);
59     while (item != NULL) {
60         node_t *next = STRIP_MARK(item->next);
61         if (ll->key_type != NULL) {
62             nbd_free((void *)item->key);
63         }
64         nbd_free(item);
65         item = next;
66     }
67 }
68
69 size_t ll_count (list_t *ll) {
70     size_t count = 0;
71     node_t *item = STRIP_MARK(ll->head->next);
72     while (item) {
73         if (!HAS_MARK(item->next)) {
74             count++;
75         }
76         item = STRIP_MARK(item->next);
77     }
78     return count;
79 }
80
81 #ifdef LIST_USE_HAZARD_POINTER
82 static void nbd_free_node (node_t *x) {
83     nbd_free((void *)x->key);
84     nbd_free(x);
85 }
86 #endif
87
88 static int find_pred (node_t **pred_ptr, node_t **item_ptr, list_t *ll, map_key_t key, int help_remove) {
89     node_t *pred = ll->head;
90     node_t *item = GET_NODE(pred->next);
91     TRACE("l2", "find_pred: searching for key %p in list (head is %p)", key, pred);
92 #ifdef LIST_USE_HAZARD_POINTER
93     haz_t *temp, *hp0 = haz_get_static(0), *hp1 = haz_get_static(1);
94 #endif
95
96     while (item != NULL) {
97 #ifdef LIST_USE_HAZARD_POINTER
98         haz_set(hp0, item);
99         if (STRIP_MARK(pred->next) != item)
100             return find_pred(pred_ptr, item_ptr, ll, key, help_remove); // retry
101 #endif
102         markable_t next = item->next;
103
104         // A mark means the node is logically removed but not physically unlinked yet.
105         while (EXPECT_FALSE(HAS_MARK(next))) {
106
107             // Skip over logically removed items.
108             if (!help_remove) {
109                 item = STRIP_MARK(item->next);
110                 if (EXPECT_FALSE(item == NULL))
111                     break;
112                 TRACE("l3", "find_pred: skipping marked item %p (next is %p)", item, next);
113                 next = item->next;
114                 continue;
115             }
116
117             // Unlink logically removed items.
118             TRACE("l3", "find_pred: unlinking marked item %p next is %p", item, next);
119
120             markable_t other = SYNC_CAS(&pred->next, item, STRIP_MARK(next));
121             if (other == (markable_t)item) {
122                 TRACE("l2", "find_pred: unlinked item %p from pred %p", item, pred);
123                 item = STRIP_MARK(next);
124                 next = (item != NULL) ? item->next : DOES_NOT_EXIST;
125                 TRACE("l3", "find_pred: now current item is %p next is %p", item, next);
126
127                 // The thread that completes the unlink should free the memory.
128 #ifdef LIST_USE_HAZARD_POINTER
129                 free_t free_ = (ll->key_type != NULL ? (free_t)nbd_free_node : nbd_free);
130                 haz_defer_free(GET_NODE(other), free_);
131 #else
132                 if (ll->key_type != NULL) {
133                     rcu_defer_free((void *)GET_NODE(other)->key);
134                 }
135                 rcu_defer_free(GET_NODE(other));
136 #endif
137             } else {
138                 TRACE("l2", "find_pred: lost a race to unlink item %p from pred %p", item, pred);
139                 TRACE("l2", "find_pred: pred's link changed to %p", other, 0);
140                 if (HAS_MARK(other))
141                     return find_pred(pred_ptr, item_ptr, ll, key, help_remove); // retry
142                 item = GET_NODE(other);
143                 next = (item != NULL) ? item->next : DOES_NOT_EXIST;
144             }
145         }
146
147         if (EXPECT_FALSE(item == NULL))
148             break;
149
150         TRACE("l3", "find_pred: visiting item %p (next is %p)", item, next);
151         TRACE("l4", "find_pred: key %p val %p", item->key, item->val);
152
153         int d;
154         if (EXPECT_TRUE(ll->key_type == NULL)) {
155             d = item->key - key;
156         } else {
157             d = ll->key_type->cmp((void *)item->key, (void *)key);
158         }
159
160         // If we reached the key (or passed where it should be), we found the right predesssor
161         if (d >= 0) {
162             if (pred_ptr != NULL) {
163                 *pred_ptr = pred;
164             }
165             if (item_ptr != NULL) {
166                 *item_ptr = item;
167             }
168             if (d == 0) {
169                 TRACE("l2", "find_pred: found matching item %p in list, pred is %p", item, pred);
170                 return TRUE;
171             } 
172             TRACE("l2", "find_pred: found proper place for key %p in list, pred is %p", key, pred);
173             return FALSE;
174         }
175
176         pred = item;
177 #ifdef LIST_USE_HAZARD_POINTER
178         temp = hp0; hp0 = hp1; hp1 = temp;
179 #endif
180         item = GET_NODE(next);
181     }
182
183     // <key> is not in <ll>.
184     if (pred_ptr != NULL) {
185         *pred_ptr = pred;
186     }
187     *item_ptr = NULL;
188     TRACE("l2", "find_pred: reached end of list. last item is %p", pred, 0);
189     return FALSE;
190 }
191
192 // Fast find. Do not help unlink partially removed nodes and do not return the found item's predecessor.
193 map_val_t ll_lookup (list_t *ll, map_key_t key) {
194     TRACE("l1", "ll_lookup: searching for key %p in list %p", key, ll);
195     node_t *item;
196     int found = find_pred(NULL, &item, ll, key, FALSE);
197
198     // If we found an <item> matching the key return its value.
199     if (found) {
200         map_val_t val = item->val;
201         if (val != DOES_NOT_EXIST) {
202             TRACE("l1", "ll_lookup: found item %p. val %p. returning item", item, item->val);
203             return val;
204         }
205     }
206
207     TRACE("l1", "ll_lookup: no item in the list matched the key", 0, 0);
208     return DOES_NOT_EXIST;
209 }
210
211 map_val_t ll_cas (list_t *ll, map_key_t key, map_val_t expectation, map_val_t new_val) {
212     TRACE("l1", "ll_cas: key %p list %p", key, ll);
213     TRACE("l1", "ll_cas: expectation %p new value %p", expectation, new_val);
214     ASSERT((int64_t)new_val > 0);
215
216     do {
217         node_t *pred, *old_item;
218         int found = find_pred(&pred, &old_item, ll, key, TRUE);
219         if (!found) {
220
221             // There was not an item in the list that matches the key. 
222             if (EXPECT_FALSE(expectation != CAS_EXPECT_DOES_NOT_EXIST && expectation != CAS_EXPECT_WHATEVER)) {
223                 TRACE("l1", "ll_cas: the expectation was not met, the list was not changed", 0, 0);
224                 return DOES_NOT_EXIST; // failure
225             }
226
227             // Create a new item and insert it into the list.
228             TRACE("l2", "ll_cas: attempting to insert item between %p and %p", pred, pred->next);
229             map_key_t new_key = ll->key_type == NULL ? key : (map_key_t)ll->key_type->clone((void *)key);
230             node_t *new_item = node_alloc(new_key, new_val);
231             markable_t next = new_item->next = (markable_t)old_item;
232             markable_t other = SYNC_CAS(&pred->next, next, new_item);
233             if (other == next) {
234                 TRACE("l1", "ll_cas: successfully inserted new item %p", new_item, 0);
235                 return DOES_NOT_EXIST; // success
236             }
237
238             // Lost a race. Failed to insert the new item into the list.
239             TRACE("l1", "ll_cas: lost a race. CAS failed. expected pred's link to be %p but found %p", next, other);
240             if (ll->key_type != NULL) {
241                 nbd_free((void *)new_key);
242             }
243             nbd_free(new_item);
244             continue; // retry
245         }
246
247         // Found an item in the list that matches the key.
248         map_val_t old_item_val = old_item->val;
249         do {
250             // If the item's value is DOES_NOT_EXIST it means another thread removed the node out from under us.
251             if (EXPECT_FALSE(old_item_val == DOES_NOT_EXIST)) {
252                 TRACE("l2", "ll_cas: lost a race, found an item but another thread removed it. retry", 0, 0);
253                 break; // retry
254             }
255
256             if (EXPECT_FALSE(expectation == CAS_EXPECT_DOES_NOT_EXIST)) {
257                 TRACE("l1", "ll_cas: found an item %p in the list that matched the key. the expectation was "
258                         "not met, the list was not changed", old_item, old_item_val);
259                 return old_item_val; // failure
260             }
261
262             // Use a CAS and not a SWAP. If the node is in the process of being removed and we used a SWAP, we could
263             // replace DOES_NOT_EXIST with our value. Then another thread that is updating the value could think it
264             // succeeded and return our value even though we indicated that the node has been removed. If the CAS 
265             // fails it means another thread either removed the node or updated its value.
266             map_val_t ret_val = SYNC_CAS(&old_item->val, old_item_val, new_val);
267             if (ret_val == old_item_val) {
268                 TRACE("l1", "ll_cas: the CAS succeeded. updated the value of the item", 0, 0);
269                 return ret_val; // success
270             }
271             TRACE("l2", "ll_cas: lost a race. the CAS failed. another thread changed the item's value", 0, 0);
272
273             old_item_val = ret_val;
274         } while (1);
275     } while (1);
276 }
277
278 map_val_t ll_remove (list_t *ll, map_key_t key) {
279     TRACE("l1", "ll_remove: removing item with key %p from list %p", key, ll);
280     node_t *pred;
281     node_t *item;
282     int found = find_pred(&pred, &item, ll, key, TRUE);
283     if (!found) {
284         TRACE("l1", "ll_remove: remove failed, an item with a matching key does not exist in the list", 0, 0);
285         return DOES_NOT_EXIST;
286     }
287
288     // Mark <item> removed. If multiple threads try to remove the same item only one of them should succeed.
289     markable_t next;
290     markable_t old_next = item->next;
291     do {
292         next     = old_next;
293         old_next = SYNC_CAS(&item->next, next, MARK_NODE(STRIP_MARK(next)));
294         if (HAS_MARK(old_next)) {
295             TRACE("l1", "ll_remove: lost a race -- %p is already marked for removal by another thread", item, 0);
296             return DOES_NOT_EXIST;
297         }
298     } while (next != old_next);
299     TRACE("l2", "ll_remove: logically removed item %p", item, 0);
300     ASSERT(HAS_MARK(((volatile node_t *)item)->next));
301
302     // Atomically swap out the item's value in case another thread is updating the item while we are 
303     // removing it. This establishes which operation occurs first logically, the update or the remove. 
304     map_val_t val = SYNC_SWAP(&item->val, DOES_NOT_EXIST); 
305     TRACE("l2", "ll_remove: replaced item's val %p with DOES_NOT_EXIT", val, 0);
306
307     // Unlink <item> from <ll>. If we lose a race to another thread just back off. It is safe to leave the
308     // item logically removed for a later call (or some other thread) to physically unlink. By marking the
309     // item earlier, we logically removed it. 
310     TRACE("l2", "ll_remove: unlink the item by linking its pred %p to its successor %p", pred, next);
311     markable_t other;
312     if ((other = SYNC_CAS(&pred->next, item, next)) != (markable_t)item) {
313         TRACE("l1", "ll_remove: unlink failed; pred's link changed from %p to %p", item, other);
314         return val;
315     } 
316
317     // The thread that completes the unlink should free the memory.
318 #ifdef LIST_USE_HAZARD_POINTER
319     free_t free_ = (ll->key_type != NULL ? (free_t)nbd_free_node : nbd_free);
320     haz_defer_free(GET_NODE(item), free_);
321 #else
322     if (ll->key_type != NULL) {
323         rcu_defer_free((void *)item->key);
324     }
325     rcu_defer_free(item);
326 #endif
327     TRACE("l1", "ll_remove: successfully unlinked item %p from the list", item, 0);
328     return val;
329 }
330
331 void ll_print (list_t *ll) {
332     markable_t next = ll->head->next;
333     int i = 0;
334     while (next != DOES_NOT_EXIST) {
335         node_t *item = STRIP_MARK(next);
336         if (item == NULL)
337             break;
338         printf("%s%p:0x%llx ", HAS_MARK(item->next) ? "*" : "", item, (uint64_t)item->key);
339         fflush(stdout);
340         if (i++ > 30) {
341             printf("...");
342             break;
343         }
344         next = item->next;
345     }
346     printf("\n");
347 }
348
349 ll_iter_t *ll_iter_begin (list_t *ll, map_key_t key) {
350     ll_iter_t *iter = (ll_iter_t *)nbd_malloc(sizeof(ll_iter_t));
351     if (key != DOES_NOT_EXIST) {
352         find_pred(&iter->pred, NULL, ll, key, FALSE);
353     } else {
354         iter->pred = ll->head;
355     }
356 #ifdef LIST_USE_HAZARD_POINTER
357     haz_register_dynamic((void **)&iter->pred);
358 #endif
359     return iter;
360 }
361
362 map_val_t ll_iter_next (ll_iter_t *iter, map_key_t *key_ptr) {
363     assert(iter);
364     if (iter->pred == NULL)
365         return DOES_NOT_EXIST;
366
367     // advance iterator to next item; skip items that have been removed
368     markable_t item;
369 #ifdef LIST_USE_HAZARD_POINTER 
370     haz_t *hp0 = haz_get_static(0);
371 #endif
372     do {
373 #ifndef LIST_USE_HAZARD_POINTER 
374         item = iter->pred->next;
375 #else //LIST_USE_HAZARD_POINTER 
376         do {
377             item = iter->pred->next;
378             haz_set(hp0, STRIP_MARK(item));
379         } while (item != ((volatile node_t *)iter->pred)->next);
380 #endif//LIST_USE_HAZARD_POINTER
381         iter->pred = STRIP_MARK(item);
382         if (iter->pred == NULL)
383             return DOES_NOT_EXIST;
384     } while (HAS_MARK(item));
385
386     if (key_ptr != NULL) {
387         *key_ptr = GET_NODE(item)->key;
388     }
389     return GET_NODE(item)->val;
390 }
391
392 void ll_iter_free (ll_iter_t *iter) {
393 #ifdef LIST_USE_HAZARD_POINTER
394     haz_unregister_dynamic((void **)&iter->pred);
395 #endif
396     nbd_free(iter);
397 }