2 * Written by Josh Dybnis and released to the public domain, as explained at
3 * http://creativecommons.org/licenses/publicdomain
5 * Harris-Michael lock-free list-based set
6 * http://www.research.ibm.com/people/m/michael/spaa-2002.pdf
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;
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;
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);
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);
51 // A tag means an item is logically removed but not physically unlinked yet.
52 while (EXPECT_FALSE(IS_TAGGED(next))) {
54 // Skip over logically removed items.
56 item = (node_t *)STRIP_TAG(item->next);
57 if (EXPECT_FALSE(item == NULL))
63 // Unlink logically removed items.
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))
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);
73 // The thread that completes the unlink should free the memory.
74 nbd_defer_free(other);
76 TRACE("l3", "find_pred: lost race to unlink from pred %p; its link changed to %p", pred, other);
78 return find_pred(pred_ptr, ll, key_data, key_len, help_remove); // retry
80 if (EXPECT_FALSE(item == NULL))
86 if (EXPECT_FALSE(item == NULL))
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);
92 TRACE("l3", "find_pred: found pred %p item %p", pred, item);
93 if (pred_ptr != NULL) {
96 return x == 0 ? item : NULL;
104 // <key> is not in <ll>.
105 if (pred_ptr != NULL) {
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);
116 // If we found an <item> matching the key return its value.
117 return item != NULL ? item->value : DOES_NOT_EXIST;
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);
126 node_t *next = find_pred(&pred, ll, key_data, key_len, TRUE);
128 // If a node matching the key already exists in <ll> return its value.
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); }
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); }
139 node_t *other = SYNC_CAS(&pred->next, next, item);
141 TRACE("l3", "ll_add: successfully inserted item %p", item, 0);
142 return DOES_NOT_EXIST; // success
144 TRACE("l3", "ll_add: failed to change pred's link: expected %p found %p", next, other);
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);
152 node_t *item = find_pred(&pred, ll, key_data, key_len, TRUE);
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;
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;
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;
170 uint64_t value = item->value;
172 // Unlink <item> from <ll>.
173 TRACE("l3", "ll_remove: link item's pred %p to it's successor %p", pred, next);
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.
182 // The thread that completes the unlink should free the memory.
183 nbd_defer_free(item);
187 void ll_print (list_t *ll) {
189 item = ll->head->next;
191 printf("%s ", (char *)ns_data(item->key));