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