]> pd.if.org Git - nbds/blob - struct/list.c
02a7c2dbfa206b9783e41ed586e610b157550af7
[nbds] / struct / 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 #include <stdio.h>
9 #include <string.h>
10
11 #include "common.h"
12 #include "struct.h"
13 #include "nstring.h"
14 #include "mem.h"
15
16 typedef struct node {
17     nstring_t *key;
18     uint64_t value;
19     struct node *next;
20 } node_t;
21
22 struct ll {
23     node_t *head;
24 };
25
26 static node_t *node_alloc (const void *key_data, uint32_t key_len, uint64_t value) {
27     node_t *item = (node_t *)nbd_malloc(sizeof(node_t));
28     memset(item, 0, sizeof(node_t));
29     item->key   = key_data ? ns_alloc(key_data, key_len) : NULL;
30     item->value = value;
31     return item;
32 }
33
34 list_t *ll_alloc (void) {
35     list_t *ll = (list_t *)nbd_malloc(sizeof(list_t));
36     ll->head = node_alloc(" ", 0, 0);
37     ll->head->next = NULL;
38     return ll;
39 }
40
41 static node_t *find_pred (node_t **pred_ptr, list_t *ll, const void *key_data, uint32_t key_len, int help_remove) {
42     node_t *pred = ll->head;
43     node_t *item = pred->next;
44     TRACE("l3", "find_pred: searching for key %p in ll (head is %p)", key_data, pred);
45
46     while (item != NULL) {
47         node_t *next = item->next;
48         TRACE("l3", "find_pred: visiting item %p (next %p)", item, next);
49         TRACE("l3", "find_pred: key \"%s\"", ns_data(item->key), item->value);
50
51         // A tag means an item is logically removed but not physically unlinked yet.
52         while (EXPECT_FALSE(IS_TAGGED(next))) {
53
54             // Skip over logically removed items.
55             if (!help_remove) {
56                 item = (node_t *)STRIP_TAG(item->next);
57                 if (EXPECT_FALSE(item == NULL))
58                     break;
59                 next = item->next;
60                 continue;
61             }
62
63             // Unlink logically removed items.
64             node_t *other;
65             if ((other = SYNC_CAS(&pred->next, item, STRIP_TAG(next))) == item) {
66                 item = (node_t *)STRIP_TAG(next);
67                 if (EXPECT_FALSE(item == NULL))
68                     break;
69                 next = item->next;
70                 TRACE("l3", "find_pred: unlinked item %p from pred %p", item, pred);
71                 TRACE("l3", "find_pred: now item is %p next is %p", item, next);
72
73                 // The thread that completes the unlink should free the memory.
74                 nbd_defer_free(other);
75             } else {
76                 TRACE("l3", "find_pred: lost race to unlink from pred %p; its link changed to %p", pred, other);
77                 if (IS_TAGGED(other))
78                     return find_pred(pred_ptr, ll, key_data, key_len, help_remove); // retry
79                 item = other;
80                 if (EXPECT_FALSE(item == NULL))
81                     break;
82                 next = item->next;
83             }
84         }
85
86         if (EXPECT_FALSE(item == NULL))
87             break;
88
89         // If we reached the key (or passed where it should be), we found the right predesssor
90         int x = ns_cmp_raw(item->key, key_data, key_len);
91         if (x >= 0) {
92             TRACE("l3", "find_pred: found pred %p item %p", pred, item);
93             if (pred_ptr != NULL) {
94                 *pred_ptr = pred;
95             }
96             return x == 0 ? item : NULL;
97         }
98
99         pred = item;
100         item = next;
101
102     }
103
104     // <key> is not in <ll>.
105     if (pred_ptr != NULL) {
106         *pred_ptr = pred;
107     }
108     return NULL;
109 }
110
111 // Fast find. Do not help unlink partially removed nodes and do not return the found item's predecessor.
112 uint64_t ll_lookup (list_t *ll, const void *key_data, uint32_t key_len) {
113     TRACE("l3", "ll_lookup: searching for key %p in ll %p", key_data, ll);
114     node_t *item = find_pred(NULL, ll, key_data, key_len, FALSE);
115
116     // If we found an <item> matching the key return its value.
117     return item != NULL ? item->value : DOES_NOT_EXIST;
118 }
119
120 // Insert a new item if a matching key doesn't already exist in <ll>
121 uint64_t ll_add (list_t *ll, const void *key_data, uint32_t key_len, uint64_t value) {
122     TRACE("l3", "ll_add: inserting key %p value %p", key_data, value);
123     node_t *pred;
124     node_t *item = NULL;
125     do {
126         node_t *next = find_pred(&pred, ll, key_data, key_len, TRUE);
127
128         // If a node matching the key already exists in <ll> return its value.
129         if (next != NULL) {
130             TRACE("l3", "ll_add: there is already an item %p (value %p) with the same key", next, next->value);
131             if (EXPECT_FALSE(item != NULL)) { nbd_free(item); }
132             return next->value;
133         }
134
135         next = pred->next;
136         TRACE("l3", "ll_add: attempting to insert item between %p and %p", pred, next);
137         if (EXPECT_TRUE(item == NULL)) { item = node_alloc(key_data, key_len, value); }
138         item->next = next;
139         node_t *other = SYNC_CAS(&pred->next, next, item);
140         if (other == next) {
141             TRACE("l3", "ll_add: successfully inserted item %p", item, 0);
142             return DOES_NOT_EXIST; // success
143         }
144         TRACE("l3", "ll_add: failed to change pred's link: expected %p found %p", next, other);
145
146     } while (1);
147 }
148
149 uint64_t ll_remove (list_t *ll, const void *key_data, uint32_t key_len) {
150     TRACE("l3", "ll_remove: removing item with key %p from ll %p", key_data, ll);
151     node_t *pred;
152     node_t *item = find_pred(&pred, ll, key_data, key_len, TRUE);
153     if (item == NULL) {
154         TRACE("l3", "ll_remove: remove failed, an item with a matching key does not exist in the ll", 0, 0);
155         return DOES_NOT_EXIST;
156     }
157
158     // Mark <item> removed. This must be atomic. If multiple threads try to remove the same item
159     // only one of them should succeed.
160     if (EXPECT_FALSE(IS_TAGGED(item->next))) {
161         TRACE("l3", "ll_remove: %p is already marked for removal by another thread", item, 0);
162         return DOES_NOT_EXIST;
163     }
164     node_t *next = SYNC_FETCH_AND_OR(&item->next, TAG);
165     if (EXPECT_FALSE(IS_TAGGED(next))) {
166         TRACE("l3", "ll_remove: lost race -- %p is already marked for removal by another thread", item, 0);
167         return DOES_NOT_EXIST;
168     }
169
170     uint64_t value = item->value;
171
172     // Unlink <item> from <ll>.
173     TRACE("l3", "ll_remove: link item's pred %p to it's successor %p", pred, next);
174     node_t *other;
175     if ((other = SYNC_CAS(&pred->next, item, next)) != item) {
176         TRACE("l3", "ll_remove: unlink failed; pred's link changed from %p to %p", item, other);
177         // By marking the item earlier, we logically removed it. It is safe to leave the item.
178         // Another thread will finish physically removing it from the ll.
179         return value;
180     } 
181
182     // The thread that completes the unlink should free the memory.
183     nbd_defer_free(item); 
184     return value;
185 }
186
187 void ll_print (list_t *ll) {
188     node_t *item;
189     item = ll->head->next;
190     while (item) {
191         printf("%s ", (char *)ns_data(item->key));
192         fflush(stdout);
193         item = item->next;
194     }
195     printf("\n");
196 }