4ba20e744f41822cc9e88c36e8ded510c5ad28c2
[libcds.git] / cds / gc / hrc / hrc.h
1 //$$CDS-header$$
2
3 #ifndef __CDS_GC_HRC_HRC_H
4 #define __CDS_GC_HRC_HRC_H
5
6 /*
7     Editions:
8         2008.03.08    Maxim.Khiszinsky    Created
9 */
10
11 #include <cds/refcounter.h>
12 #include <cds/lock/spinlock.h>
13 #include <cds/gc/exception.h>
14
15 #include <cds/gc/hrc/details/hrc_fwd.h>
16 #include <cds/gc/hrc/details/hrc_retired.h>
17
18 #include <cds/gc/hzp/details/hp_alloc.h>
19
20 #if CDS_COMPILER == CDS_COMPILER_MSVC
21 #   pragma warning(push)
22 // warning C4251: 'cds::gc::hzp::GarbageCollector::m_pListHead' : class 'cds::cxx11_atomic::atomic<T>'
23 // needs to have dll-interface to be used by clients of class 'cds::gc::hzp::GarbageCollector'
24 #   pragma warning(disable: 4251)
25 #endif
26
27
28 namespace cds { namespace gc {
29
30     // forwards
31     class HRC;
32
33     /// Gidenstam's memory reclamation schema (HRC)
34     /**
35
36     \par Sources:
37         - [2006] A.Gidenstam "Algorithms for synchronization and consistency
38                 in concurrent system services", Chapter 5 "Lock-Free Memory Reclamation"
39                 Thesis for the degree of Doctor    of Philosophy
40         - [2005] Anders Gidenstam, Marina Papatriantafilou and Philippas Tsigas "Allocating
41                 memory in a lock-free manner", Proceedings of the 13th Annual European
42                 Symposium on Algorithms (ESA 2005), Lecture Notes in Computer
43                 Science Vol. 3669, pages 229 \96 242, Springer-Verlag, 2005
44
45
46         The \p %cds::gc::hrc namespace and its members are internal representation of the GC and should not be used directly.
47         Use \p cds::gc::HRC class in your code.
48
49         This reclamation schema combines Michael's Hazard Pointer schema (see \p cds::gc::hzp)
50         for deferred safe reclamation of unused objects and the reference counting
51         for controlling lifetime of the objects.
52
53         HRC garbage collector is a singleton. The main user-level part of HRC schema is
54         GC class and its nested classes. Before use any HRC-related class you must initialize HRC garbage collector
55         by contructing \p %cds::gc::HRC object in beginning of your <tt>main()</tt>.
56         See \p cds::gc::HRC class for explanation.
57     */
58     namespace hrc {
59
60         /// Base class for all HRC-based container's node
61         /**
62             This interface is placed to the separate class since in the presence of a garbage collector
63             the lifetime of the node is greater than lifetime of its container.
64             Reclaimed node may be physically deleted later than its container.
65             So, the ContainerNode must be smarter than the usual.
66         */
67         class ContainerNode
68         {
69         protected:
70
71             friend class GarbageCollector;
72             friend class ThreadGC;
73             friend class gc::HRC;
74
75             unsigned_ref_counter        m_RC        ;    ///< reference counter
76             atomics::atomic<bool>    m_bTrace    ;    ///< \p true - node is tracing by Scan
77             atomics::atomic<bool>    m_bDeleted  ;    ///< \p true - node is deleted
78
79         protected:
80             //@cond
81             ContainerNode() ;               // inline, see hrc_inline.h
82             virtual ~ContainerNode()    ;   // inline, see hrc_inline.h
83             //@endcond
84
85         public:
86             /// Returns count of reference for the node
87             unsigned_ref_counter::ref_counter_type  getRefCount() const CDS_NOEXCEPT
88             {
89                 return m_RC.value();
90             }
91
92             /// Increments the reference counter of the node
93             void            incRefCount() CDS_NOEXCEPT
94             {
95                 m_RC.inc();
96             }
97
98             /// Decrements the reference counter of the node. Returns \p true if ref counter is 0.
99             bool            decRefCount() CDS_NOEXCEPT
100             {
101                 return m_RC.dec();
102             }
103
104             /// Returns the mark whether the node is deleted
105             bool            isDeleted() const CDS_NOEXCEPT
106             {
107                 return m_bDeleted.load( atomics::memory_order_acquire );
108             }
109
110         protected:
111             //@cond
112             void clean( atomics::memory_order order ) CDS_NOEXCEPT
113             {
114                 m_bDeleted.store( false, order );
115                 m_bTrace.store( false, order );
116             }
117             //@endcond
118
119         protected:    // GC interface
120             /**
121                 [Gidenstam 2006]: "The procedure \p CleanUpNode will make sure that all claimed references from
122                 the links of the given node will only point to active nodes, thus removing redundant
123                 passages through an arbitrary number of deleted nodes"
124
125                 The pseudocode of this method must be like following:
126                 \code
127                 void cleanUp( ThreadGC * pGC )
128                     for all x where link[x] of node is reference-counted do
129                 retry:
130                         node1 := link[x];
131                         if node1 != nullptr and node1.m_bDeleted then
132                             node2 := node1->link[x];
133                             pGC->CASRef( this->link[x], node1, node2 );
134                             pGC->releaseRef( node2 );
135                             pGC->releaseRef( node1 );
136                             goto retry;
137                         pGC->releaseRef(node1);
138                 \endcode
139
140                 Be aware to use hazard pointers inside implementation of this method. cleanUp is called from
141                 the container's method when deleting the nodes. However, some hazard pointers may be occupied
142                 by container's method. You should allocate new hazard pointers inside \p cleanUp method, for example:
143                 \code
144                     gc::hrc::AutoHPArray<2> hpArr( *pGC );
145                 \endcode
146             */
147             virtual void    cleanUp( ThreadGC * pGC ) = 0;
148
149             /**
150                 [Gidenstam 2006]: "The procedure \p TerminateNode will make sure that none of the links in the
151                 given node will have any claim on any other node. TerminateNode is called on
152                 a deleted node when there are no claims from any other node or thread to the
153                 node"
154
155                 The pseudocode of this method must be like following:
156                 \code
157                 void terminate( ThreadGC * pGC, bool bConcurrent)
158                     if !bConcurrent
159                         for all this->link where link is reference-counted do
160                             link := nullptr;
161                     else
162                         for all this->link where link is reference-counted do
163                             repeat node1 := link;
164                             until pGC->CASRef(link,node1,nullptr);
165                 \endcode
166             */
167             virtual void    terminate( ThreadGC * pGC, bool bConcurrent ) = 0;
168
169         public:
170             /// Method to destroy (deallocate) node. Depends on node's allocator
171             //virtual void    destroy() = 0;
172         };
173
174         //@cond
175         /// HRC GC implementation details
176         namespace details {
177
178             /// Hazard pointer guard
179             typedef gc::hzp::details::HPGuardT<ContainerNode *>    HPGuard;
180
181             /// Array of hazard pointers.
182             /**
183                 This is wrapper for cds::gc::hzp::details::HPArray class
184             */
185             template <size_t Count> using HPArray = gc::hzp::details::HPArrayT<ContainerNode *, Count >;
186
187             /// HP record of the thread
188             /**
189                 This structure is single writer - multiple reader type. The writer is the thread owned the record
190             */
191             struct thread_descriptor {
192                 typedef ContainerNode * hazard_ptr       ; ///< base type of hazard pointer
193
194                 hzp::details::HPAllocator<hazard_ptr>   m_hzp           ;   ///< array of hazard pointers. Implicit \ref CDS_DEFAULT_ALLOCATOR dependence
195                 details::retired_vector                 m_arrRetired    ;   ///< array of retired pointers
196
197                 //@cond
198                 thread_descriptor( const GarbageCollector& HzpMgr ) ;    // inline
199                 ~thread_descriptor()
200                 {}
201                 //@endcond
202
203                 /// clear all hazard pointers
204                 void clear()
205                 {
206                     m_hzp.clear();
207                 }
208             };
209         }    // namespace details
210         //@endcond
211
212         /// Gidenstam's Garbage Collector
213         /**
214             This GC combines Hazard Pointers (HP) reclamation method by Michael's and the well-known reference counting
215             reclamation schema. The HP method is light-weight algorithm guarding local references only. Reference counting
216             schema is harder than HP with relation to the performance but can guard global references too.
217             Using Gidenstam's GC it can be possible to safely introduce to the lock-free data structures
218             very useful concepts like iterators.
219
220             GarbageCollector is the singleton.
221         */
222         class CDS_EXPORT_API GarbageCollector
223         {
224         public:
225             typedef cds::atomicity::event_counter  event_counter   ;   ///< event counter type
226
227             /// GC internal statistics
228             struct internal_state {
229                 size_t              nHPCount                ;   ///< HP count per thread (const)
230                 size_t              nMaxThreadCount         ;   ///< Max thread count (const)
231                 size_t              nMaxRetiredPtrCount     ;   ///< Max retired pointer count per thread (const)
232                 size_t              nHRCRecSize             ;   ///< Size of HRC record, bytes (const)
233
234                 size_t              nHRCRecAllocated        ;   ///< Count of HRC record allocations
235                 size_t              nHRCRecUsed             ;   ///< Count of HRC record used
236                 size_t              nTotalRetiredPtrCount   ;   ///< Current total count of retired pointers
237                 size_t              nRetiredPtrInFreeHRCRecs;   ///< Count of retired pointer in free (unused) HP records
238
239
240                 event_counter::value_type   evcAllocHRCRec        ; ///< Event count of thread descriptor allocation
241                 event_counter::value_type   evcRetireHRCRec        ; ///< Event count of thread descriptor reclamation
242                 event_counter::value_type   evcAllocNewHRCRec    ; ///< Event count of new thread descriptor allocation
243                 event_counter::value_type   evcDeleteHRCRec        ; ///< Event count of thread descriptor deletion
244                 event_counter::value_type   evcScanCall            ; ///< Number of calls Scan
245                 event_counter::value_type   evcHelpScanCalls    ; ///< Number of calls HelpScan
246                 event_counter::value_type   evcCleanUpAllCalls  ; ///< Number of calls CleanUpAll
247                 event_counter::value_type   evcDeletedNode        ; ///< Node deletion event counter
248                 event_counter::value_type   evcScanGuarded      ; ///< Count of retired nodes that could not be deleted on Scan phase
249                 event_counter::value_type   evcScanClaimGuarded ; ///< Count of retired node that could not be deleted on Scan phase because of m_nClaim != 0
250
251 #ifdef CDS_DEBUG
252                 event_counter::value_type   evcNodeConstruct    ; ///< Count of constructed ContainerNode
253                 event_counter::value_type   evcNodeDestruct     ; ///< Count of destructed ContainerNode
254 #endif
255             };
256
257             /// "Global GC object is nullptr" exception
258             CDS_DECLARE_EXCEPTION( HRCGarbageCollectorEmpty, "Global cds::gc::hrc::GarbageCollector is NULL" );
259
260             /// Not enough required Hazard Pointer count
261             CDS_DECLARE_EXCEPTION( HRCTooMany, "Not enough required Hazard Pointer count" );
262
263         private:
264             /// Internal statistics by events
265             struct statistics {
266                 event_counter  m_AllocHRCThreadDesc        ; ///< Event count of thread descriptor allocation
267                 event_counter  m_RetireHRCThreadDesc        ; ///< Event count of thread descriptor reclamation
268                 event_counter  m_AllocNewHRCThreadDesc        ; ///< Event count of new thread descriptor allocation
269                 event_counter  m_DeleteHRCThreadDesc        ; ///< Event count of deletion of thread descriptor
270                 event_counter  m_ScanCalls                    ; ///< Number of calls Scan
271                 event_counter  m_HelpScanCalls             ; ///< Number of calls HelpScan
272                 event_counter  m_CleanUpAllCalls           ; ///< Number of calls CleanUpAll
273
274                 event_counter  m_DeletedNode                ; ///< Node deletion event counter
275                 event_counter  m_ScanGuarded               ; ///< Count of retired nodes that could not be deleted on Scan phase
276                 event_counter  m_ScanClaimGuarded          ; ///< Count of retired node that could not be deleted on Scan phase because of m_nClaim != 0
277
278 #           ifdef CDS_DEBUG
279                 event_counter  m_NodeConstructed           ; ///< Count of ContainerNode constructed
280                 event_counter  m_NodeDestructed            ; ///< Count of ContainerNode destructed
281 #           endif
282             };
283
284             /// HRC control structure of global thread list
285             struct thread_list_node: public details::thread_descriptor
286             {
287                 thread_list_node *  m_pNext     ; ///< next list record
288                 ThreadGC *          m_pOwner    ; ///< Owner of record
289                 atomics::atomic<cds::OS::ThreadId>   m_idOwner   ; ///< Id of thread owned; 0 - record is free
290                 bool                m_bFree        ; ///< Node is help-scanned
291
292                 //@cond
293                 thread_list_node( const GarbageCollector& HzpMgr )
294                     : thread_descriptor( HzpMgr ),
295                     m_pNext( nullptr ),
296                     m_pOwner( nullptr ),
297                     m_idOwner(cds::OS::c_NullThreadId),
298                     m_bFree( false )
299                 {}
300
301                 ~thread_list_node()
302                 {
303                     assert( m_pOwner == nullptr );
304                     assert( m_idOwner.load( atomics::memory_order_relaxed ) == cds::OS::c_NullThreadId );
305                 }
306                 //@endcond
307             };
308
309         private:
310             atomics::atomic<thread_list_node *> m_pListHead  ;  ///< Head of thread list
311
312             static GarbageCollector *    m_pGC    ;    ///< HRC garbage collector instance
313
314             statistics              m_Stat                  ;    ///< Internal statistics
315             bool                    m_bStatEnabled          ;    ///< @a true - accumulate internal statistics
316
317             const size_t            m_nHazardPointerCount   ;    ///< max count of thread's hazard pointer
318             const size_t            m_nMaxThreadCount       ;    ///< max count of thread
319             const size_t            m_nMaxRetiredPtrCount   ;    ///< max count of retired ptr per thread
320
321         private:
322             //@cond
323             GarbageCollector(
324                 size_t nHazardPtrCount,            ///< number of hazard pointers
325                 size_t nMaxThreadCount,            ///< max number of threads
326                 size_t nRetiredNodeArraySize    ///< size of array of retired node
327             );
328             ~GarbageCollector();
329             //@endcond
330
331             /// Allocates new HRC control structure from the heap (using operator new)
332             thread_list_node *    newHRCThreadDesc();
333
334             /// Deletes \p pNode control structure
335             void                deleteHRCThreadDesc( thread_list_node * pNode );
336
337             /// Clears retired nodes of \p pNode control structure
338             void                clearHRCThreadDesc( thread_list_node * pNode );
339
340             /// Finds HRC control structure for current thread
341             thread_list_node *    getHRCThreadDescForCurrentThread() const;
342
343         public:
344             /// Create global instance of GarbageCollector
345             static void    CDS_STDCALL    Construct(
346                 size_t nHazardPtrCount = 0,        ///< number of hazard pointers
347                 size_t nMaxThreadCount = 0,        ///< max threads count
348                 size_t nMaxNodeLinkCount = 0,    ///< max number of links a @ref ContainerNode can contain
349                 size_t nMaxTransientLinks = 0    ///< max number of links in live nodes that may transiently point to a deleted node
350                 );
351
352             /// Destroy global instance of GarbageCollector
353             static void    CDS_STDCALL        Destruct();
354
355             /// Get global instance of GarbageCollector
356             static GarbageCollector&   instance()
357             {
358                 if ( !m_pGC )
359                     throw HRCGarbageCollectorEmpty();
360                 return *m_pGC;
361             }
362
363             /// Checks if global GC object is constructed and may be used
364             static bool isUsed()
365             {
366                 return m_pGC != nullptr;
367             }
368
369             /// Get max count of hazard pointers as defined in @ref Construct call
370             size_t            getHazardPointerCount() const
371             {
372                 return m_nHazardPointerCount;
373             }
374
375             /// Get max thread count as defined in @ref Construct call
376             size_t            getMaxThreadCount() const
377             {
378                 return m_nMaxThreadCount;
379             }
380
381             /// Get max retired pointers count. It is calculated by the parameters of @ref Construct call
382             size_t            getMaxRetiredPtrCount() const
383             {
384                 return m_nMaxRetiredPtrCount;
385             }
386
387             /// Get internal statistics
388             internal_state& getInternalState( internal_state& stat) const;
389
390             /// Check if statistics enabled
391             bool              isStatisticsEnabled() const
392             {
393                 return m_bStatEnabled;
394             }
395
396             /// Enable internal statistics
397             bool              enableStatistics( bool bEnable )
398             {
399                 bool bCurEnabled = m_bStatEnabled;
400                 m_bStatEnabled = bEnable;
401                 return bCurEnabled;
402             }
403
404             /// Checks that required hazard pointer count \p nRequiredCount is less or equal then max hazard pointer count
405             /**
406                 If \p nRequiredCount > getHazardPointerCount() then the exception HZPTooMany is thrown
407             */
408             static void checkHPCount( unsigned int nRequiredCount )
409             {
410                 if ( instance().getHazardPointerCount() < nRequiredCount )
411                     throw HRCTooMany();
412             }
413
414         public:    // Internals for threads
415
416             /// Allocates HRC thread descriptor (thread interface)
417             details::thread_descriptor * allocateHRCThreadDesc( ThreadGC * pThreadGC );
418
419             /// Retires HRC thread descriptor (thread interface)
420             void retireHRCThreadDesc( details::thread_descriptor * pRec );
421
422             /// The main method of GC
423             /**
424                 The procedure searches through all not yet reclaimed nodes deleted by this thread
425                 and reclaim only those that does not have any matching hazard pointers and do not have any
426                 counted references from any links inside of nodes.
427                 @a Scan is called in context of thread owned \p pRec.
428             */
429             void Scan( ThreadGC * pThreadGC );
430
431             /// Manage free thread_descriptor records and move all retired pointers to \p pThreadGC
432             void HelpScan( ThreadGC * pThreadGC );
433
434             /// Global clean up
435             /**
436                 The procedure try to remove redundant claimed references from links in deleted nodes
437                 that has been deleted by any thread. \p pThreadGC - ThreadGC of calling thread
438             */
439             void CleanUpAll( ThreadGC * pThreadGC );
440
441             //@cond
442             void try_retire( ThreadGC * pThreadGC ) ;   // inline in hrc_inline.h
443             //@endcond
444
445 #   ifdef CDS_DEBUG
446         public:
447             //@cond
448             void dbgNodeConstructed() { ++m_Stat.m_NodeConstructed; }
449             void dbgNodeDestructed()  { ++m_Stat.m_NodeDestructed;  }
450             //@endcond
451 #   endif
452
453         };
454
455         class AutoHPGuard;
456
457         /// Thread's Garbage collector
458         /**
459             To use HRC reclamation schema each thread object must be linked with the object of ThreadGC class
460             that interacts with GarbageCollector global object. The linkage is performed by calling cds::threading \p Manager::attachThread()
461             on the start of each thread that uses HRC GC. Before terminating the thread linked to HRC GC it is necessary to call
462             cds::threading \p Manager::detachThread().
463         */
464         class ThreadGC
465         {
466             GarbageCollector&               m_gc    ; ///< master garbage collector
467             details::thread_descriptor *    m_pDesc ; ///< descriptor of GC data for the thread
468
469             friend class GarbageCollector;
470
471         public:
472             //@cond
473             ThreadGC()
474                 : m_gc( GarbageCollector::instance() )
475                 , m_pDesc( nullptr )
476             {}
477
478             ThreadGC( ThreadGC const& ) = delete;
479
480             ~ThreadGC()
481             {
482                 fini();
483             }
484             //@endcond
485
486             /// Checks if thread GC is initialized
487             bool    isInitialized() const { return m_pDesc != nullptr; }
488
489             /// Initialization. Multiple calls is allowed
490             void init()
491             {
492                 if ( !m_pDesc )
493                     m_pDesc = m_gc.allocateHRCThreadDesc( this );
494             }
495
496             /// Finalization. Multiple calls is allowed
497             void fini()
498             {
499                 if ( m_pDesc ) {
500                     cleanUpLocal();
501                     m_gc.Scan( this );
502                     details::thread_descriptor * pRec = m_pDesc;
503                     m_pDesc = nullptr;
504                     if  ( pRec )
505                         m_gc.retireHRCThreadDesc( pRec );
506                 }
507             }
508         public:    // HRC garbage collector methods
509
510             /// Initializes HP guard \p guard
511             details::HPGuard& allocGuard()
512             {
513                 assert( m_pDesc != nullptr );
514                 return m_pDesc->m_hzp.alloc();
515             }
516
517             /// Frees HP guard \p guard
518             void freeGuard( details::HPGuard& guard )
519             {
520                 assert( m_pDesc != nullptr );
521                 m_pDesc->m_hzp.free( guard );
522             }
523
524             /// Initializes HP guard array \p arr
525             template <size_t Count>
526             void allocGuard( details::HPArray<Count>& arr )
527             {
528                 assert( m_pDesc != nullptr );
529                 m_pDesc->m_hzp.alloc( arr );
530             }
531
532             /// Frees HP guard array \p arr
533             template <size_t Count>
534             void freeGuard( details::HPArray<Count>& arr )
535             {
536                 assert( m_pDesc != nullptr );
537                 m_pDesc->m_hzp.free( arr );
538             }
539
540             /// Retire (deferred delete) node \p pNode guarded by \p hp hazard pointer
541             void retireNode( ContainerNode * pNode, details::HPGuard& hp, details::free_retired_ptr_func pFunc )
542             {
543                 assert( !pNode->m_bDeleted.load( atomics::memory_order_relaxed ) );
544                 assert( pNode == hp );
545
546                 retireNode( pNode, pFunc );
547                 hp.clear();
548             }
549
550             /// Retire (deferred delete) node \p pNode. Do not use this function directly!
551             void retireNode( ContainerNode * pNode, details::free_retired_ptr_func pFunc )
552             {
553                 assert( !pNode->m_bDeleted.load( atomics::memory_order_relaxed ) );
554
555                 pNode->m_bDeleted.store( true, atomics::memory_order_release );
556                 pNode->m_bTrace.store( false, atomics::memory_order_release );
557
558                 m_pDesc->m_arrRetired.push( pNode, pFunc );
559
560                 if ( m_pDesc->m_arrRetired.isFull() )
561                     m_gc.try_retire( this );
562             }
563
564             //@cond
565             void scan()
566             {
567                 m_gc.try_retire( this );
568             }
569             //@endcond
570
571         protected:
572             /// The procedure will try to remove redundant claimed references from link in deleted nodes that has been deleted by this thread
573             void cleanUpLocal()
574             {
575                 details::retired_vector::iterator itEnd = m_pDesc->m_arrRetired.end();
576                 for ( details::retired_vector::iterator it = m_pDesc->m_arrRetired.begin(); it != itEnd; ++it ) {
577                     details::retired_node& node = *it;
578                     ContainerNode * pNode = node.m_pNode.load(atomics::memory_order_acquire);
579                     if ( pNode && !node.m_bDone.load(atomics::memory_order_acquire) )
580                         pNode->cleanUp( this );
581                 }
582             }
583         };
584
585         /// Auto HPGuard.
586         class AutoHPGuard
587         {
588             //@cond
589             details::HPGuard&   m_hp  ; ///< hazard pointer
590             ThreadGC&           m_mgr ; ///< Thread GC.
591             //@endcond
592         public:
593             typedef details::HPGuard::hazard_ptr hazard_ptr ;  ///< Hazard pointer type
594
595         public:
596             /// Allocates HP guard from \p mgr
597             AutoHPGuard( ThreadGC& mgr )
598                 : m_hp( mgr.allocGuard() )
599                 , m_mgr( mgr )
600             {}
601
602             /// Allocates HP guard from \p mgr and protects the pointer \p p of type \p T
603             template <typename T>
604             AutoHPGuard( ThreadGC& mgr, T * p  )
605                 : m_hp( mgr.allocGuard() )
606                 , m_mgr( mgr )
607             {
608                 m_hp = p;
609             }
610
611             /// Frees HP guard
612             ~AutoHPGuard()
613             {
614                 m_mgr.freeGuard( m_hp );
615             }
616
617             /// Returns thread GC
618             ThreadGC&    getGC() const CDS_NOEXCEPT
619             {
620                 return m_mgr;
621             }
622
623             //@cond
624             template <typename T>
625             T * operator =( T * p ) CDS_NOEXCEPT
626             {
627                 return m_hp = p;
628             }
629             //@endcond
630
631             //@cond
632             hazard_ptr get() const CDS_NOEXCEPT
633             {
634                 return m_hp;
635             }
636             //@endcond
637
638             /// Clears the hazard pointer
639             void clear() CDS_NOEXCEPT
640             {
641                 m_hp.clear();
642             }
643         };
644
645         /// Auto-managed array of hazard pointers
646         /**
647             This class is wrapper around gc::hzp::details::HPArray class.
648         */
649         template <size_t Count>
650         class AutoHPArray: public details::HPArray<Count>
651         {
652             ThreadGC&    m_mgr    ;    ///< Thread GC
653
654         public:
655             /// Allocates array of HP guard from \p mgr
656             AutoHPArray( ThreadGC& mgr )
657                 : m_mgr( mgr )
658             {
659                 mgr.allocGuard( *this );
660             }
661
662             /// Frees array of HP guard
663             ~AutoHPArray()
664             {
665                 m_mgr.freeGuard( *this );
666             }
667
668             /// Returns thread GC
669             ThreadGC&    getGC() const
670             {
671                 return m_mgr;
672             }
673         };
674
675
676     }    // namespace hrc
677 }} // namespace cds::gc
678
679 #include <cds/gc/hrc/details/hrc_inline.h>
680
681 #if CDS_COMPILER == CDS_COMPILER_MSVC
682 #   pragma warning(pop)
683 #endif
684
685 #endif // #ifndef __CDS_GC_HRC_HRC_H