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 // If <key_len> is -1 it indicates <key_data> is an integer and not a pointer
30 item->key = (key_len == (unsigned)-1)
31 ? (void *)TAG_VALUE(key_data)
32 : ns_alloc(key_data, key_len);
37 list_t *ll_alloc (void) {
38 list_t *ll = (list_t *)nbd_malloc(sizeof(list_t));
39 ll->head = node_alloc(" ", 0, 0);
40 ll->head->next = NULL;
44 static node_t *find_pred (node_t **pred_ptr, list_t *ll, const void *key_data, uint32_t key_len, int help_remove) {
45 node_t *pred = ll->head;
46 node_t *item = pred->next;
47 TRACE("l3", "find_pred: searching for key %p in ll (head is %p)", key_data, pred);
49 while (item != NULL) {
50 node_t *next = item->next;
51 TRACE("l3", "find_pred: visiting item %p (next %p)", item, next);
52 TRACE("l3", "find_pred: key %p", STRIP_TAG(item->key), item->value);
54 // A tag means an item is logically removed but not physically unlinked yet.
55 while (EXPECT_FALSE(IS_TAGGED(next))) {
57 // Skip over logically removed items.
59 item = (node_t *)STRIP_TAG(item->next);
60 if (EXPECT_FALSE(item == NULL))
66 // Unlink logically removed items.
68 if ((other = SYNC_CAS(&pred->next, item, STRIP_TAG(next))) == item) {
69 item = (node_t *)STRIP_TAG(next);
70 if (EXPECT_FALSE(item == NULL))
73 TRACE("l3", "find_pred: unlinked item %p from pred %p", item, pred);
74 TRACE("l3", "find_pred: now item is %p next is %p", item, next);
76 // The thread that completes the unlink should free the memory.
77 nbd_defer_free(other);
79 TRACE("l3", "find_pred: lost race to unlink from pred %p; its link changed to %p", pred, other);
81 return find_pred(pred_ptr, ll, key_data, key_len, help_remove); // retry
83 if (EXPECT_FALSE(item == NULL))
89 if (EXPECT_FALSE(item == NULL))
92 // If we reached the key (or passed where it should be), we found the right predesssor
93 int x = (IS_TAGGED(item->key))
94 ? (STRIP_TAG(item->key) - (uint64_t)key_data)
95 : ns_cmp_raw(item->key, key_data, key_len);
97 TRACE("l3", "find_pred: found pred %p item %p", pred, item);
98 if (pred_ptr != NULL) {
101 return x == 0 ? item : NULL;
109 // <key> is not in <ll>.
110 if (pred_ptr != NULL) {
116 // Fast find. Do not help unlink partially removed nodes and do not return the found item's predecessor.
117 uint64_t ll_lookup (list_t *ll, const void *key_data, uint32_t key_len) {
118 TRACE("l3", "ll_lookup: searching for key %p in ll %p", key_data, ll);
119 node_t *item = find_pred(NULL, ll, key_data, key_len, FALSE);
121 // If we found an <item> matching the key return its value.
122 return item != NULL ? item->value : DOES_NOT_EXIST;
125 // Insert a new item if a matching key doesn't already exist in <ll>
126 uint64_t ll_add (list_t *ll, const void *key_data, uint32_t key_len, uint64_t value) {
127 TRACE("l3", "ll_add: inserting key %p value %p", key_data, value);
131 node_t *next = find_pred(&pred, ll, key_data, key_len, TRUE);
133 // If a node matching the key already exists in <ll> return its value.
135 TRACE("l3", "ll_add: there is already an item %p (value %p) with the same key", next, next->value);
136 if (EXPECT_FALSE(item != NULL)) { nbd_free(item); }
141 TRACE("l3", "ll_add: attempting to insert item between %p and %p", pred, next);
142 if (EXPECT_TRUE(item == NULL)) { item = node_alloc(key_data, key_len, value); }
144 node_t *other = SYNC_CAS(&pred->next, next, item);
146 TRACE("l3", "ll_add: successfully inserted item %p", item, 0);
147 return DOES_NOT_EXIST; // success
149 TRACE("l3", "ll_add: failed to change pred's link: expected %p found %p", next, other);
154 uint64_t ll_remove (list_t *ll, const void *key_data, uint32_t key_len) {
155 TRACE("l3", "ll_remove: removing item with key %p from ll %p", key_data, ll);
157 node_t *item = find_pred(&pred, ll, key_data, key_len, TRUE);
159 TRACE("l3", "ll_remove: remove failed, an item with a matching key does not exist in the ll", 0, 0);
160 return DOES_NOT_EXIST;
163 // Mark <item> removed. This must be atomic. If multiple threads try to remove the same item
164 // only one of them should succeed.
165 if (EXPECT_FALSE(IS_TAGGED(item->next))) {
166 TRACE("l3", "ll_remove: %p is already marked for removal by another thread", item, 0);
167 return DOES_NOT_EXIST;
169 node_t *next = SYNC_FETCH_AND_OR(&item->next, TAG);
170 if (EXPECT_FALSE(IS_TAGGED(next))) {
171 TRACE("l3", "ll_remove: lost race -- %p is already marked for removal by another thread", item, 0);
172 return DOES_NOT_EXIST;
175 uint64_t value = item->value;
177 // Unlink <item> from <ll>.
178 TRACE("l3", "ll_remove: link item's pred %p to it's successor %p", pred, next);
180 if ((other = SYNC_CAS(&pred->next, item, next)) != item) {
181 TRACE("l3", "ll_remove: unlink failed; pred's link changed from %p to %p", item, other);
182 // By marking the item earlier, we logically removed it. It is safe to leave the item.
183 // Another thread will finish physically removing it from the ll.
187 // The thread that completes the unlink should free the memory.
188 nbd_defer_free(item);
192 void ll_print (list_t *ll) {
194 item = ll->head->next;
196 if (IS_TAGGED(item->key)) {
197 printf("0x%llx ", STRIP_TAG(item->key));
199 printf("%s ", (char *)ns_data(item->key));