-
Notifications
You must be signed in to change notification settings - Fork 44
/
doubly_linked_list.h
299 lines (243 loc) · 8.45 KB
/
doubly_linked_list.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#pragma once
#include "common/allocator_internal.h"
#include "mwcas/mwcas.h"
#include "util/atomics.h"
#include "util/macros.h"
#include "util/random_number_generator.h"
namespace pmwcas {
struct DListNode {
const static int kCacheLineSize = 64;
// Put prev and next in the same cacheline so that a single NVRAM::Flush is
// enough.
DListNode* prev; // 8-byte
DListNode* next; // 8-byte
uint32_t payload_size; // 4-byte
char padding[kCacheLineSize - sizeof(DListNode*) * 2 - sizeof(uint32_t)];
DListNode(DListNode* p, DListNode* n, uint32_t s)
: prev(p),
next(n),
payload_size(s) {
}
DListNode()
: DListNode(nullptr, nullptr, 0) {
}
inline char* GetPayload() {
return (char *)this + sizeof(*this);
}
};
class IDList {
public:
static DListNode* NewNode(DListNode* prev, DListNode* next,
uint32_t payload_size) {
DListNode *node = nullptr;
Allocator::Get()->Allocate((void **) &node,
sizeof(DListNode) + payload_size);
new (node) DListNode(prev, next, payload_size);
return node;
}
static const int kSyncUnknown = 0;
static const int kSyncMwCAS = 1;
static const int kSyncCAS = 2;
IDList(int sync)
: head_(nullptr, &tail_, 0),
tail_(&head_, nullptr, 0),
sync_method_(sync) {
}
~IDList() {}
/// Insert [node] right before [next]
virtual Status InsertBefore(DListNode* next, DListNode* node,
bool already_protected) = 0;
/// Insert [node] right after [prev]
virtual Status InsertAfter(DListNode* prev, DListNode* node,
bool already_protected) = 0;
/// Delete [node]
virtual Status Delete(DListNode* node, bool already_protected) = 0;
/// Figure out the node lined up after [node]
virtual DListNode* GetNext(DListNode* node,
bool already_protected = false) = 0;
virtual DListNode* GetPrev(DListNode* node,
bool already_protected = false) = 0;
inline DListNode* GetHead() { return &head_; }
inline DListNode* GetTail() { return &tail_; }
inline int GetSyncMethod() { return sync_method_; }
/// Verify the links between each pair of nodes (including head and tail).
/// For single-threaded cases only, no CC whatsoever.
void SingleThreadSanityCheck();
protected:
DListNode head_;
DListNode tail_;
int sync_method_;
};
/// A lock-free doubly linked list using single-word CAS,
/// based off of the following paper:
/// Håkan Sundell and Philippas Tsigas. 2008.
/// Lock-free deques and doubly linked lists.
/// J. Parallel Distrib. Comput. 68, 7 (July 2008), 1008-1020.
class CASDList : public IDList {
private:
/// MSB of the next pointer to indicate the underlying node is logically
/// deleted.
static const uint64_t kNodeDeleted = ((uint64_t)1 << 60);
/// An RNG for back off
RandomNumberGenerator rng{};
inline void backoff() {
uint64_t loops = rng.Generate(500);
while(loops--) {}
};
public:
#ifdef PMEM
/// The same dirty bit as in persistent MwCAS.
static const uint64_t kDirtyFlag = Descriptor::kDirtyFlag;
#endif
CASDList() : IDList(kSyncCAS), rng(__rdtsc(), 0, 500) {}
/// Insert [node] in front of [next] - [node] might end up before another node
/// in case [prev] is being deleted or due to concurrent insertions at the
/// same spot.
virtual Status InsertBefore(DListNode* next, DListNode* node,
bool already_protected = false) override;
/// Similar to InsertBefore, but try to insert [node] after [prev].
virtual Status InsertAfter(DListNode* prev, DListNode* node,
bool already_protected = false) override;
/// Delete [node]
virtual Status Delete(DListNode* node,
bool already_protected = false) override;
virtual DListNode* GetNext(DListNode* node,
bool already_protected = false) override;
virtual DListNode* GetPrev(DListNode* node, bool) override;
/// Set the deleted bit on the given node
inline void MarkNodePointer(DListNode** node) {
#ifdef PMEM
uint64_t flags = kNodeDeleted | kDirtyFlag;
#else
uint64_t flags = kNodeDeleted;
#endif
while (true) {
DListNode* node_ptr = *node;
RAW_CHECK(node != &head_.next, "cannot mark head node's next pointer");
if (((uint64_t)node_ptr & kNodeDeleted) ||
node_ptr == CompareExchange64Ptr(node,
(DListNode*)((uint64_t)node_ptr | flags), node_ptr)) {
break;
}
}
}
inline DListNode* ReadPersist(DListNode** node) {
auto* node_ptr = *node;
#ifdef PMEM
if(((uint64_t)node_ptr & kDirtyFlag)) {
NVRAM::Flush(sizeof(uint64_t), node);
CompareExchange64Ptr(node,
(DListNode*)((uint64_t)node_ptr & ~kDirtyFlag), node_ptr);
}
return (DListNode*)((uint64_t)node_ptr & ~kDirtyFlag);
#else
return node_ptr;
#endif
}
/// Extract the real underlying node (masking out the MSB and flush if needed)
inline DListNode* DereferenceNodePointer(DListNode** node) {
DListNode* ret = nullptr;
#ifdef PMEM
ret = ReadPersist(node);
#else
ret = *node;
#endif
return (DListNode*)((uint64_t)ret & ~kNodeDeleted);
}
inline static bool MarkedNext(DListNode* node) {
return (uint64_t)node->next & kNodeDeleted;
}
inline static bool MarkedPrev(DListNode* node) {
return (uint64_t)node->prev & kNodeDeleted;
}
private:
DListNode* CorrectPrev(DListNode* prev, DListNode* node);
};
/// A lock-free doubly-linked list using multi-word CAS
class MwCASDList : public IDList {
private:
DescriptorPool* descriptor_pool_;
/// MSB of the next pointer to indicate the underlying node is logically
/// deleted.
static const uint64_t kNodeDeleted = ((uint64_t)1 << 60);
public:
MwCASDList(DescriptorPool* pool) :
IDList(kSyncMwCAS), descriptor_pool_(pool) {}
/// Insert [node] in front of [next] - [node] might end up before another node
/// in case [prev] is being deleted or due to concurrent insertions at the
/// same spot.
virtual Status InsertBefore(DListNode* next, DListNode* node,
bool already_protected) override;
/// Similar to InsertBefore, but try to insert [node] after [prev].
virtual Status InsertAfter(DListNode* prev, DListNode* node,
bool already_protected) override;
/// Delete [node]
virtual Status Delete(DListNode* node, bool already_protected) override;
inline virtual DListNode* GetNext(DListNode* node,
bool already_protected = false) override {
node = (DListNode*)((uint64_t)node & ~kNodeDeleted);
DListNode* next = ResolveNodePointer(&node->next, already_protected);
return (DListNode*)((uint64_t)next & ~kNodeDeleted);
}
inline virtual DListNode* GetPrev(DListNode* node,
bool already_protected = false) override {
node = (DListNode*)((uint64_t)node & ~kNodeDeleted);
DListNode* prev = ResolveNodePointer(&node->prev, already_protected);
return (DListNode*)((uint64_t)prev & ~kNodeDeleted);
}
inline EpochManager* GetEpoch() {
return descriptor_pool_->GetEpoch();
}
private:
/// Return a stable value using MwCAS's GetValue().
/// Note: [node] must be a pointer to a pointer to a node,
/// where it denotes the node pointer's real location, as
/// we're casting it to an MwCAS field. E.g., it cannot be
/// the address of a pointer parameter passed in to the caller.
inline DListNode* ResolveNodePointer(DListNode** node, bool already_protected) {
int64_t stable_ptr = 0;
if(already_protected) {
stable_ptr = ((MwcTargetField<uint64_t> *)node)->GetValueProtected();
} else {
stable_ptr = ((MwcTargetField<uint64_t> *)node)->GetValue(
descriptor_pool_->GetEpoch());
}
return (DListNode*)stable_ptr;
}
};
class DListCursor {
private:
IDList* list_;
DListNode* current_node_;
bool unprot;
public:
DListCursor(IDList* list) :
list_(list), current_node_(list->GetHead()) {
if(list->GetSyncMethod() == IDList::kSyncMwCAS) {
auto* epoch = ((MwCASDList*)list_)->GetEpoch();
unprot = !epoch->IsProtected();
if(unprot) {
epoch->Protect();
}
}
}
~DListCursor() {
if(unprot && list_->GetSyncMethod() == IDList::kSyncMwCAS) {
((MwCASDList*)list_)->GetEpoch()->Unprotect();
}
}
inline void Reset() {
current_node_ = list_->GetHead();
}
inline DListNode* Next() {
current_node_ = list_->GetNext(current_node_, true);
return current_node_;
}
inline DListNode* Prev() {
current_node_ = list_->GetPrev(current_node_, true);
return current_node_;
}
};
} // namespace pmwcas