Honeycomb  0.1
Component-Model Framework
HazardMem.h
Go to the documentation of this file.
1 // Honeycomb, Copyright (C) 2015 NewGamePlus Inc. Distributed under the Boost Software License v1.0.
2 #pragma once
3 
4 #include "Honey/Thread/Thread.h"
5 #include "Honey/Thread/Atomic.h"
7 
8 namespace honey { namespace lockfree
9 {
10 
13 {
14  HazardMemNode() : ref(0), trace(false), del(false) {}
15 
19 };
20 
22 template<class Node>
24 {
26  Node* ptr() const { return reinterpret_cast<Node*>(*data); }
27 
29 };
30 
33 {
38 
40  static const uint8 linkMax = 2;
42  static const uint8 linkDelMax = linkMax;
44  static const uint8 hazardMax = 6;
45 
47  void cleanUpNode(Node& node);
49  void terminateNode(Node& node, bool concurrent);
50 };
51 
53 
56 template<class Config>
58 {
59 public:
60  typedef typename Config::Node Node;
61  typedef typename Config::Link Link;
62  typedef typename Config::Alloc::template rebind<Node>::other Alloc;
63 
64 private:
66  struct ThreadData
67  {
68  ThreadData(HazardMem& mem) :
69  mem(mem),
70  delNodes(new DelNode[mem._threshClean]),
71  delHazards(mem._alloc),
72  delHead(nullptr),
73  delCount(0)
74  {
75  hazards.fill(nullptr);
76  hazardRefCounts.fill(0);
77 
78  hazardFreeList.reserve(hazards.size());
79  for (auto i : range(hazards.size())) hazardFreeList.push_back(i);
80 
81  delNodeFreeList.reserve(mem._threshClean);
82  for (auto i : range(mem._threshClean)) delNodeFreeList.push_back(&delNodes[i]);
83  }
84 
85  ~ThreadData()
86  {
87  //Free all nodes waiting to be reclaimed
88  for (DelNode* delNode = delHead; delNode; delNode = delNode->next)
89  {
90  mem._alloc.destroy(delNode->node.load());
91  mem._alloc.deallocate(delNode->node, 1);
92  }
93  }
94 
95  struct DelNode
96  {
97  DelNode() : node(nullptr), claim(0), done(false), next(nullptr) {}
98 
103  };
104  typedef set<Node*, std::less<Node*>, typename Alloc::template rebind<Node*>::other> NodeLookup;
105 
106  HazardMem& mem;
107  array<Atomic<Node*>, Config::hazardMax> hazards;
108  array<uint8, Config::hazardMax> hazardRefCounts;
109  vector<uint8> hazardFreeList;
110  UniquePtr<DelNode[]> delNodes;
111  vector<DelNode*> delNodeFreeList;
112  NodeLookup delHazards;
113  DelNode* delHead;
114  szt delCount;
115  };
116 
117 public:
124  HazardMem(Config& config, const Alloc& alloc, int threadMax) :
125  _config(config),
126  _alloc(alloc),
127  _threadMax(threadMax),
128  _threshClean(_threadMax*(Config::hazardMax + Config::linkMax + Config::linkDelMax + 1)),
129  _threshScan(Config::hazardMax*2 < _threshClean ? Config::hazardMax*2 : _threshClean),
130  _threadDataList(_threadMax),
131  _threadDataCount(0),
132  _threadData(bind(&HazardMem::initThreadData, this)) {}
133 
134  template<class... Args>
135  Node& createNode(Args&&... args)
136  {
137  Node* node = _alloc.allocate(1);
138  _alloc.construct(node, forward<Args>(args)...);
139  ref(*node);
140  return *node;
141  }
142 
143  void deleteNode(Node& node)
144  {
145  ThreadData& td = threadData();
146 
147  node.del = true;
148  node.trace = false;
149 
150  //get free del node
151  assert(td.delNodeFreeList.size() > 0, "Not enough del nodes, algorithm problem");
152  auto& delNode = *td.delNodeFreeList.back();
153  td.delNodeFreeList.pop_back();
154 
155  delNode.done = false;
156  delNode.node = &node;
157  delNode.next = td.delHead;
158  td.delHead = &delNode; ++td.delCount;
159  while(true)
160  {
161  if (td.delCount == _threshClean) cleanUpLocal();
162  if (td.delCount >= _threshScan) scan();
163  if (td.delCount == _threshClean) cleanUpAll();
164  else break;
165  }
166  }
167 
169  Node* deRefLink(Link& link)
170  {
171  ThreadData& td = threadData();
172  //get free hazard index
173  assert(td.hazardFreeList.size() > 0, "Not enough hazard pointers");
174  uint8 index = td.hazardFreeList.back();
175 
176  Node* node = nullptr;
177  while(true)
178  {
179  node = link.ptr();
180  //init new hazard
181  td.hazards[index] = node;
182  //ensure that link is protected
183  if (link.ptr() == node) break;
184  }
185 
186  //only add hazard if pointer is valid
187  if (node)
188  {
189  //check if hazard is already referenced by this thread
190  sdt found = -1;
191  for (auto i: range(td.hazards.size())) if (i != index && td.hazards[i] == node) { found = i; break; }
192  if (found >= 0)
193  {
194  //don't need a new hazard
195  td.hazards[index] = nullptr;
196  index = found;
197  }
198  else
199  //use new hazard
200  td.hazardFreeList.pop_back();
201  ++td.hazardRefCounts[index];
202  }
203  return node;
204  }
205 
207  void ref(Node& node)
208  {
209  ThreadData& td = threadData();
210  //check if hazard is already referenced by this thread
211  //we could use a map here but the hazard list is tiny for known algorithms
212  sdt index = -1;
213  for (auto i: range(td.hazards.size())) if (td.hazards[i] == &node) { index = i; break; }
214 
215  if (index < 0)
216  {
217  //get free hazard index
218  assert(td.hazardFreeList.size() > 0, "Not enough hazard pointers");
219  index = td.hazardFreeList.back();
220  td.hazardFreeList.pop_back();
221  //init new hazard
222  td.hazards[index] = &node;
223  }
224  ++td.hazardRefCounts[index];
225  }
226 
228  void releaseRef(Node& node)
229  {
230  ThreadData& td = threadData();
231  //get associated hazard pointer
232  sdt index = -1;
233  for (auto i: range(td.hazards.size())) if (td.hazards[i] == &node) { index = i; break; }
234  assert(index >= 0, "Hazard pointer not found");
235 
236  //only release if this thread has no more references
237  assert(td.hazardRefCounts[index], "Hazard pointer already released");
238  if (--td.hazardRefCounts[index]) return;
239  //release hazard index to free list
240  td.hazards[index] = nullptr;
241  td.hazardFreeList.push_back(index);
242  }
243 
245  bool casRef(Link& link, const Link& val, const Link& old)
246  {
247  if (link.data.cas(val.data, old.data))
248  {
249  if (val.ptr())
250  {
251  ++val.ptr()->ref;
252  val.ptr()->trace = false;
253  }
254  if (old.ptr()) --old.ptr()->ref;
255  return true;
256  }
257  return false;
258  }
259 
261  void storeRef(Link& link, const Link& val)
262  {
263  Link old = link;
264  link = val;
265  if (val.ptr())
266  {
267  ++val.ptr()->ref;
268  val.ptr()->trace = false;
269  }
270  if (old.ptr()) --old.ptr()->ref;
271  }
272 
273 private:
275  struct ThreadDataRef { ThreadData& obj; };
276 
277  ThreadDataRef* initThreadData()
278  {
279  SpinLock::Scoped _(_threadDataLock);
280  //increase thread count
281  assert(_threadDataCount < _threadMax, "Too many threads accessing memory manager");
282  //create new data and add to list
283  auto threadData = new ThreadData(*this);
284  _threadDataList[_threadDataCount] = UniquePtr<ThreadData>(threadData);
285  ++_threadDataCount; //must increment only after initing element, or concurrent ops will fail
286  return new ThreadDataRef{*threadData};
287  }
288 
289  ThreadData& threadData() { return _threadData->obj; }
290 
292  void cleanUpLocal()
293  {
294  ThreadData& td = threadData();
295  for (auto* delNode = td.delHead; delNode; delNode = delNode->next)
296  _config.cleanUpNode(*delNode->node);
297  }
298 
300  void cleanUpAll()
301  {
302  for (auto ti : range(_threadDataCount.load()))
303  {
304  ThreadData* td = _threadDataList[ti];
305  for (auto i : range(_threshClean))
306  {
307  auto& delNode = td->delNodes[i];
308  Node* node = delNode.node;
309  if (node && !delNode.done)
310  {
311  ++delNode.claim;
312  if (node == delNode.node)
313  _config.cleanUpNode(*node);
314  --delNode.claim;
315  }
316  }
317  }
318  }
319 
321  void scan()
322  {
323  ThreadData& td = threadData();
324 
325  //set trace to make sure ref == 0 is consistent across hazard check below
326  for (auto* delNode = td.delHead; delNode; delNode = delNode->next)
327  {
328  Node& node = *delNode->node;
329  if (node.ref == 0)
330  {
331  node.trace = true;
332  if (node.ref != 0)
333  node.trace = false;
334  }
335  }
336 
337  //flag all del nodes that have a hazard so they are not reclaimed
338  for (auto ti : range(_threadDataCount.load()))
339  {
340  ThreadData* tdata = _threadDataList[ti];
341  for (auto i : range(tdata->hazards.size()))
342  {
343  Node* node = tdata->hazards[i];
344  if (node) td.delHazards.insert(node);
345  }
346  }
347 
348  //reclaim nodes and build new list of del nodes that could not be reclaimed
349  typename ThreadData::DelNode* newDelHead = nullptr;
350  szt newDelCount = 0;
351 
352  while (td.delHead)
353  {
354  auto* delNode = td.delHead;
355  td.delHead = delNode->next;
356  Node& node = *delNode->node;
357  if (node.ref == 0 && node.trace && !td.delHazards.count(&node))
358  {
359  delNode->node = nullptr;
360  if (delNode->claim == 0)
361  {
362  _config.terminateNode(node, false);
363  //Free the node
364  td.delNodeFreeList.push_back(delNode);
365  _alloc.destroy(&node);
366  _alloc.deallocate(&node, 1);
367  continue;
368  }
369  _config.terminateNode(node, true);
370  delNode->done = true;
371  delNode->node = &node;
372  }
373  delNode->next = newDelHead;
374  newDelHead = delNode;
375  ++newDelCount;
376  }
377 
378  td.delHazards.clear();
379  td.delHead = newDelHead;
380  td.delCount = newDelCount;
381  }
382 
383  Config& _config;
384  Alloc _alloc;
385  const int _threadMax;
386  const szt _threshClean;
387  const szt _threshScan;
388  vector<UniquePtr<ThreadData>> _threadDataList;
389  Atomic<int> _threadDataCount;
390  thread::Local<ThreadDataRef> _threadData;
391  SpinLock _threadDataLock;
392 };
393 
394 
395 } }
Atomic< bool > del
Marked for deletion.
Definition: HazardMem.h:18
Base node class, inherit from this class, add link members, and use as Node type in HazardMemConfig...
Definition: HazardMem.h:12
void terminateNode(Node &node, bool concurrent)
Remove all links to other nodes. If concurrent is false then the faster storeRef can be used instead ...
void releaseRef(Node &node)
Release a reference to a node, clears hazard pointer.
Definition: HazardMem.h:228
ptrdiff_t sdt
Size difference type, shorthand for ptrdiff_t.
Definition: Core.h:92
Atomic< int > claim
Definition: HazardMem.h:100
Inherit to declare that class is not copyable.
Definition: Meta.h:286
Node & createNode(Args &&...args)
Definition: HazardMem.h:135
Config::Alloc::template rebind< Node >::other Alloc
Definition: HazardMem.h:62
std::enable_if< mt::isIterator< Iter1 >::value, Range_< Iter1, Iter2 > >::type range(Iter1 &&first, Iter2 &&last)
Range from iterators [first, last)
Definition: Range.h:116
static auto _
Definition: Module.cpp:8
static const uint8 linkDelMax
Number of links per node that may transiently point to a deleted node.
Definition: HazardMem.h:42
unsigned char uint8
Definition: Core.h:12
Atomic< bool > trace
Used in scan()
Definition: HazardMem.h:17
HazardMemNode()
Definition: HazardMem.h:14
void ref(Node &node)
Add reference to node, sets up hazard pointer.
Definition: HazardMem.h:207
#define assert(...)
Forwards to assert_#args. See assert_1(), assert_2().
Definition: Debug.h:24
void storeRef(Link &link, const Link &val)
Set link in a single-threaded environment.
Definition: HazardMem.h:261
Atomic< Node * > node
Definition: HazardMem.h:99
void deleteNode(Node &node)
Definition: HazardMem.h:143
void cleanUpNode(Node &node)
Update all links in the node to point to active (non-deleted) nodes.
Config::Node Node
Definition: HazardMem.h:60
HazardMemLink< Node > Link
Definition: HazardMem.h:35
size_t szt
Size type, shorthand for size_t.
Definition: Core.h:90
T * alloc(szt count=1)
Allocate memory for count number of T objects. Objects are not constructed.
Definition: Allocator.h:31
HazardMem(Config &config, const Alloc &alloc, int threadMax)
Definition: HazardMem.h:124
Configuration interface for memory manager. Inherit this class and override types and static members...
Definition: HazardMem.h:32
Global allocator for small memory blocks. To provide a custom pool define SmallAllocator_createSingle...
Definition: SmallAllocator.h:23
static const uint8 linkMax
Number of links per node.
Definition: HazardMem.h:40
Atomic< bool > done
Definition: HazardMem.h:101
HazardMemNode Node
Definition: HazardMem.h:34
DelNode * next
Definition: HazardMem.h:102
Node * deRefLink(Link &link)
Dereference a link, protects with hazard pointer. May return null.
Definition: HazardMem.h:169
Lock-free memory manager, provides safe memory reclamation for concurrent algorithms.
Definition: HazardMem.h:57
Pointer to a unique, non-shared, object. Finalizer is run upon destruction (deletes object by default...
Definition: SharedPtr.h:164
bool casRef(Link &link, const Link &val, const Link &old)
Compare and swap link. Set link in a concurrent environment. Returns false if the link was changed by...
Definition: HazardMem.h:245
Global Honeycomb namespace.
static const uint8 hazardMax
Number of thread-local hazard pointers.
Definition: HazardMem.h:44
SmallAllocator< Node > Alloc
Allocator for nodes, should be lock-free.
Definition: HazardMem.h:37
Atomic< int > ref
Reference count by all threads.
Definition: HazardMem.h:16
Config::Link Link
Definition: HazardMem.h:61