59f50d9f2e12ab756ccf2c9e5a1381e5bcc8e96f
[libcds.git] / cds / gc / hzp / hzp.h
1 //$$CDS-header$$
2
3 #ifndef __CDS_GC_HZP_HZP_H
4 #define __CDS_GC_HZP_HZP_H
5
6 #include <vector>
7 #include <cds/cxx11_atomic.h>
8 #include <cds/os/thread.h>
9 #include <cds/gc/exception.h>
10 #include <cds/gc/hzp/details/hp_fwd.h>
11 #include <cds/gc/hzp/details/hp_alloc.h>
12 #include <cds/gc/hzp/details/hp_retired.h>
13
14 #if CDS_COMPILER == CDS_COMPILER_MSVC
15 #   pragma warning(push)
16     // warning C4251: 'cds::gc::hzp::GarbageCollector::m_pListHead' : class 'cds::cxx11_atomic::atomic<T>'
17     // needs to have dll-interface to be used by clients of class 'cds::gc::hzp::GarbageCollector'
18 #   pragma warning(disable: 4251)
19 #endif
20
21 /*
22     Editions:
23         2007.12.24  khizmax Add statistics and CDS_GATHER_HAZARDPTR_STAT macro
24         2008.03.06  khizmax Refactoring: implementation of HazardPtrMgr is moved to hazardptr.cpp
25         2008.03.08  khizmax Remove HazardPtrMgr singleton. Now you must initialize/destroy HazardPtrMgr calling
26                             HazardPtrMgr::Construct / HazardPtrMgr::Destruct before use (usually in main() function).
27         2008.12.06  khizmax Refactoring. Changes class name, namespace hierarchy, all helper defs have been moved to details namespace
28         2010.01.27  khizmax Introducing memory order constraint
29 */
30
31 namespace cds {
32     /**
33         @page cds_garbage_collectors_comparison GC comparison
34         @ingroup cds_garbage_collector
35
36         <table>
37             <tr>
38                 <th>Feature</th>
39                 <th>%cds::gc::HP</th>
40                 <th>%cds::gc::HRC</th>
41                 <th>%cds::gc::PTB</th>
42             </tr>
43             <tr>
44                 <td>Implementation quality</td>
45                 <td>stable</td>
46                 <td>unstable</td>
47                 <td>stable</td>
48             </tr>
49             <tr>
50                 <td>Performance rank (1 - slowest, 5 - fastest)</td>
51                 <td>5</td>
52                 <td>1</td>
53                 <td>4</td>
54             </tr>
55             <tr>
56                 <td>Max number of guarded (hazard) pointers per thread</td>
57                 <td>limited (specifies in GC object ctor)</td>
58                 <td>limited (specifies in GC object ctor)</td>
59                 <td>unlimited (dynamically allocated when needed)</td>
60             </tr>
61             <tr>
62                 <td>Max number of retired pointers<sup>1</sup></td>
63                 <td>bounded</td>
64                 <td>bounded</td>
65                 <td>bounded</td>
66            </tr>
67             <tr>
68                 <td>Array of retired pointers</td>
69                 <td>preallocated for each thread, limited in size</td>
70                 <td>preallocated for each thread, limited in size</td>
71                 <td>global for the entire process, unlimited (dynamically allocated when needed)</td>
72             </tr>
73             <tr>
74                 <td>Support direct pointer to item of lock-free container (useful for iterators)</td>
75                 <td>not supported</td>
76                 <td>potentially supported (not implemented)</td>
77                 <td>not supported</td>
78             </tr>
79         </table>
80
81         <sup>1</sup>Unbounded count of retired pointer means a possibility of memory exhaustion.
82     */
83
84     /// Different safe memory reclamation schemas (garbage collectors)
85     /** @ingroup cds_garbage_collector
86
87         This namespace specifies different safe memory reclamation (SMR) algorithms.
88         See \ref cds_garbage_collector "Garbage collectors"
89     */
90     namespace gc {
91
92     /// Michael's Hazard Pointers reclamation schema
93     /**
94     \par Sources:
95         - [2002] Maged M.Michael "Safe memory reclamation for dynamic lock-freeobjects using atomic reads and writes"
96         - [2003] Maged M.Michael "Hazard Pointers: Safe memory reclamation for lock-free objects"
97         - [2004] Andrei Alexandrescy, Maged Michael "Lock-free Data Structures with Hazard Pointers"
98
99
100         The cds::gc::hzp namespace and its members are internal representation of Hazard Pointer GC and should not be used directly.
101         Use cds::gc::HP class in your code.
102
103         Hazard Pointer garbage collector is a singleton. The main user-level part of Hazard Pointer schema is
104         GC class and its nested classes. Before use any HP-related class you must initialize HP garbage collector
105         by contructing cds::gc::HP object in beginning of your main().
106         See cds::gc::HP class for explanation.
107     */
108     namespace hzp {
109
110         namespace details {
111             /// Hazard pointer record of the thread
112             /**
113                 The structure of type "single writer - multiple reader": only the owner thread may write to this structure
114                 other threads have read-only access.
115             */
116             struct HPRec {
117                 HPAllocator<hazard_pointer>    m_hzp        ; ///< array of hazard pointers. Implicit \ref CDS_DEFAULT_ALLOCATOR dependency
118                 retired_vector            m_arrRetired ; ///< Retired pointer array
119
120                 /// Ctor
121                 HPRec( const cds::gc::hzp::GarbageCollector& HzpMgr ) ;    // inline
122                 ~HPRec()
123                 {}
124
125                 /// Clears all hazard pointers
126                 void clear()
127                 {
128                     m_hzp.clear();
129                 }
130             };
131         }    // namespace details
132
133         /// GarbageCollector::Scan phase strategy
134         /**
135             See GarbageCollector::Scan for explanation
136         */
137         enum scan_type {
138             classic,    ///< classic scan as described in Michael's works (see GarbageCollector::classic_scan)
139             inplace     ///< inplace scan without allocation (see GarbageCollector::inplace_scan)
140         };
141
142         /// Hazard Pointer singleton
143         /**
144             Safe memory reclamation schema by Michael "Hazard Pointers"
145
146         \par Sources:
147             \li [2002] Maged M.Michael "Safe memory reclamation for dynamic lock-freeobjects using atomic reads and writes"
148             \li [2003] Maged M.Michael "Hazard Pointers: Safe memory reclamation for lock-free objects"
149             \li [2004] Andrei Alexandrescy, Maged Michael "Lock-free Data Structures with Hazard Pointers"
150
151         */
152         class CDS_EXPORT_API GarbageCollector
153         {
154         public:
155             typedef cds::atomicity::event_counter  event_counter   ;   ///< event counter type
156
157             /// Internal GC statistics
158             struct InternalState {
159                 size_t              nHPCount                ;   ///< HP count per thread (const)
160                 size_t              nMaxThreadCount         ;   ///< Max thread count (const)
161                 size_t              nMaxRetiredPtrCount     ;   ///< Max retired pointer count per thread (const)
162                 size_t              nHPRecSize              ;   ///< Size of HP record, bytes (const)
163
164                 size_t              nHPRecAllocated         ;   ///< Count of HP record allocations
165                 size_t              nHPRecUsed              ;   ///< Count of HP record used
166                 size_t              nTotalRetiredPtrCount   ;   ///< Current total count of retired pointers
167                 size_t              nRetiredPtrInFreeHPRecs ;   ///< Count of retired pointer in free (unused) HP records
168
169                 event_counter::value_type   evcAllocHPRec   ;   ///< Count of HPRec allocations
170                 event_counter::value_type   evcRetireHPRec  ;   ///< Count of HPRec retire events
171                 event_counter::value_type   evcAllocNewHPRec;   ///< Count of new HPRec allocations from heap
172                 event_counter::value_type   evcDeleteHPRec  ;   ///< Count of HPRec deletions
173
174                 event_counter::value_type   evcScanCall     ;   ///< Count of Scan calling
175                 event_counter::value_type   evcHelpScanCall ;   ///< Count of HelpScan calling
176                 event_counter::value_type   evcScanFromHelpScan;///< Count of Scan calls from HelpScan
177
178                 event_counter::value_type   evcDeletedNode  ;   ///< Count of deleting of retired objects
179                 event_counter::value_type   evcDeferredNode ;   ///< Count of objects that cannot be deleted in Scan phase because of a hazard_pointer guards it
180             };
181
182             /// No GarbageCollector object is created
183             CDS_DECLARE_EXCEPTION( HZPManagerEmpty, "Global Hazard Pointer GarbageCollector is NULL" );
184
185             /// Not enough required Hazard Pointer count
186             CDS_DECLARE_EXCEPTION( HZPTooMany, "Not enough required Hazard Pointer count" );
187
188         private:
189             /// Internal GC statistics
190             struct Statistics {
191                 event_counter  m_AllocHPRec            ;    ///< Count of HPRec allocations
192                 event_counter  m_RetireHPRec            ;    ///< Count of HPRec retire events
193                 event_counter  m_AllocNewHPRec            ;    ///< Count of new HPRec allocations from heap
194                 event_counter  m_DeleteHPRec            ;    ///< Count of HPRec deletions
195
196                 event_counter  m_ScanCallCount            ;    ///< Count of Scan calling
197                 event_counter  m_HelpScanCallCount        ;    ///< Count of HelpScan calling
198                 event_counter  m_CallScanFromHelpScan    ;    ///< Count of Scan calls from HelpScan
199
200                 event_counter  m_DeletedNode            ;    ///< Count of retired objects deleting
201                 event_counter  m_DeferredNode            ;    ///< Count of objects that cannot be deleted in Scan phase because of a hazard_pointer guards it
202             };
203
204             /// Internal list of cds::gc::hzp::details::HPRec
205             struct hplist_node: public details::HPRec
206             {
207                 hplist_node *                       m_pNextNode ; ///< next hazard ptr record in list
208                 atomics::atomic<OS::ThreadId>    m_idOwner   ; ///< Owner thread id; 0 - the record is free (not owned)
209                 atomics::atomic<bool>            m_bFree     ; ///< true if record if free (not owned)
210
211                 //@cond
212                 hplist_node( const GarbageCollector& HzpMgr )
213                     : HPRec( HzpMgr ),
214                     m_pNextNode( nullptr ),
215                     m_idOwner( OS::c_NullThreadId ),
216                     m_bFree( true )
217                 {}
218
219                 ~hplist_node()
220                 {
221                     assert( m_idOwner.load( atomics::memory_order_relaxed ) == OS::c_NullThreadId );
222                     assert( m_bFree.load(atomics::memory_order_relaxed) );
223                 }
224                 //@endcond
225             };
226
227             atomics::atomic<hplist_node *>   m_pListHead  ;  ///< Head of GC list
228
229             static GarbageCollector *    m_pHZPManager  ;   ///< GC instance pointer
230
231             Statistics              m_Stat              ;   ///< Internal statistics
232             bool                    m_bStatEnabled      ;   ///< true - statistics enabled
233
234             const size_t            m_nHazardPointerCount   ;   ///< max count of thread's hazard pointer
235             const size_t            m_nMaxThreadCount       ;   ///< max count of thread
236             const size_t            m_nMaxRetiredPtrCount   ;   ///< max count of retired ptr per thread
237             scan_type               m_nScanType             ;   ///< scan type (see \ref scan_type enum)
238
239
240         private:
241             /// Ctor
242             GarbageCollector(
243                 size_t nHazardPtrCount = 0,         ///< Hazard pointer count per thread
244                 size_t nMaxThreadCount = 0,         ///< Max count of thread
245                 size_t nMaxRetiredPtrCount = 0,     ///< Capacity of the array of retired objects
246                 scan_type nScanType = inplace       ///< Scan type (see \ref scan_type enum)
247             );
248
249             /// Dtor
250             ~GarbageCollector();
251
252             /// Allocate new HP record
253             hplist_node * NewHPRec();
254
255             /// Permanently deletes HPrecord \p pNode
256             /**
257                 Caveat: for performance reason this function is defined as inline and cannot be called directly
258             */
259             void                DeleteHPRec( hplist_node * pNode );
260
261             /// Permanently deletes retired pointer \p p
262             /**
263                 Caveat: for performance reason this function is defined as inline and cannot be called directly
264             */
265             void                DeletePtr( details::retired_ptr& p );
266
267             //@cond
268             void detachAllThread();
269             //@endcond
270
271         public:
272             /// Creates GarbageCollector singleton
273             /**
274                 GC is the singleton. If GC instance is not exist then the function creates the instance.
275                 Otherwise it does nothing.
276
277                 The Michael's HP reclamation schema depends of three parameters:
278
279                 \p nHazardPtrCount - HP pointer count per thread. Usually it is small number (2-4) depending from
280                                      the data structure algorithms. By default, if \p nHazardPtrCount = 0,
281                                      the function uses maximum of HP count for CDS library.
282
283                 \p nMaxThreadCount - max count of thread with using HP GC in your application. Default is 100.
284
285                 \p nMaxRetiredPtrCount - capacity of array of retired pointers for each thread. Must be greater than
286                                     \p nHazardPtrCount * \p nMaxThreadCount.
287                                     Default is 2 * \p nHazardPtrCount * \p nMaxThreadCount.
288             */
289             static void    CDS_STDCALL Construct(
290                 size_t nHazardPtrCount = 0,     ///< Hazard pointer count per thread
291                 size_t nMaxThreadCount = 0,     ///< Max count of simultaneous working thread in your application
292                 size_t nMaxRetiredPtrCount = 0, ///< Capacity of the array of retired objects for the thread
293                 scan_type nScanType = inplace   ///< Scan type (see \ref scan_type enum)
294             );
295
296             /// Destroys global instance of GarbageCollector
297             /**
298                 The parameter \p bDetachAll should be used carefully: if its value is \p true,
299                 then the destroying GC automatically detaches all attached threads. This feature
300                 can be useful when you have no control over the thread termination, for example,
301                 when \p libcds is injected into existing external thread.
302             */
303             static void CDS_STDCALL Destruct(
304                 bool bDetachAll = false     ///< Detach all threads
305             );
306
307             /// Returns pointer to GarbageCollector instance
308             static GarbageCollector&   instance()
309             {
310                 if ( !m_pHZPManager )
311                     throw HZPManagerEmpty();
312                 return *m_pHZPManager;
313             }
314
315             /// Checks if global GC object is constructed and may be used
316             static bool isUsed()
317             {
318                 return m_pHZPManager != nullptr;
319             }
320
321             /// Returns max Hazard Pointer count defined in construction time
322             size_t            getHazardPointerCount() const        { return m_nHazardPointerCount; }
323
324             /// Returns max thread count defined in construction time
325             size_t            getMaxThreadCount() const             { return m_nMaxThreadCount; }
326
327             /// Returns max size of retired objects array. It is defined in construction time
328             size_t            getMaxRetiredPtrCount() const        { return m_nMaxRetiredPtrCount; }
329
330             // Internal statistics
331
332             /// Get internal statistics
333             InternalState& getInternalState(InternalState& stat) const;
334
335             /// Checks if internal statistics enabled
336             bool              isStatisticsEnabled() const { return m_bStatEnabled; }
337
338             /// Enables/disables internal statistics
339             bool              enableStatistics( bool bEnable )
340             {
341                 bool bEnabled = m_bStatEnabled;
342                 m_bStatEnabled = bEnable;
343                 return bEnabled;
344             }
345
346             /// Checks that required hazard pointer count \p nRequiredCount is less or equal then max hazard pointer count
347             /**
348                 If \p nRequiredCount > getHazardPointerCount() then the exception HZPTooMany is thrown
349             */
350             static void checkHPCount( unsigned int nRequiredCount )
351             {
352                 if ( instance().getHazardPointerCount() < nRequiredCount )
353                     throw HZPTooMany();
354             }
355
356             /// Get current scan strategy
357             scan_type getScanType() const
358             {
359                 return m_nScanType;
360             }
361
362             /// Set current scan strategy
363             /** @anchor hzp_gc_setScanType
364                 Scan strategy changing is allowed on the fly.
365             */
366             void setScanType(
367                 scan_type nScanType     ///< new scan strategy
368             )
369             {
370                 m_nScanType = nScanType;
371             }
372
373         public:    // Internals for threads
374
375             /// Allocates Hazard Pointer GC record. For internal use only
376             details::HPRec * AllocateHPRec();
377
378             /// Free HP record. For internal use only
379             void RetireHPRec( details::HPRec * pRec );
380
381             /// The main garbage collecting function
382             /**
383                 This function is called internally by ThreadGC object when upper bound of thread's list of reclaimed pointers
384                 is reached.
385
386                 There are the following scan algorithm:
387                 - \ref hzp_gc_classic_scan "classic_scan" allocates memory for internal use
388                 - \ref hzp_gc_inplace_scan "inplace_scan" does not allocate any memory
389
390                 Use \ref hzp_gc_setScanType "setScanType" member function to setup appropriate scan algorithm.
391             */
392             void Scan( details::HPRec * pRec )
393             {
394                 switch ( m_nScanType ) {
395                     case inplace:
396                         inplace_scan( pRec );
397                         break;
398                     default:
399                         assert(false)   ;   // Forgotten something?..
400                     case classic:
401                         classic_scan( pRec );
402                         break;
403                 }
404             }
405
406             /// Helper scan routine
407             /**
408                 The function guarantees that every node that is eligible for reuse is eventually freed, barring
409                 thread failures. To do so, after executing Scan, a thread executes a HelpScan,
410                 where it checks every HP record. If an HP record is inactive, the thread moves all "lost" reclaimed pointers
411                 to thread's list of reclaimed pointers.
412
413                 The function is called internally by Scan.
414             */
415             void HelpScan( details::HPRec * pThis );
416
417         protected:
418             /// Classic scan algorithm
419             /** @anchor hzp_gc_classic_scan
420                 Classical scan algorithm as described in Michael's paper.
421
422                 A scan includes four stages. The first stage involves scanning the array HP for non-null values.
423                 Whenever a non-null value is encountered, it is inserted in a local list of currently protected pointer.
424                 Only stage 1 accesses shared variables. The following stages operate only on private variables.
425
426                 The second stage of a scan involves sorting local list of protected pointers to allow
427                 binary search in the third stage.
428
429                 The third stage of a scan involves checking each reclaimed node
430                 against the pointers in local list of protected pointers. If the binary search yields
431                 no match, the node is freed. Otherwise, it cannot be deleted now and must kept in thread's list
432                 of reclaimed pointers.
433
434                 The forth stage prepares new thread's private list of reclaimed pointers
435                 that could not be freed during the current scan, where they remain until the next scan.
436
437                 This algorithm allocates memory for internal HP array.
438
439                 This function is called internally by ThreadGC object when upper bound of thread's list of reclaimed pointers
440                 is reached.
441             */
442             void classic_scan( details::HPRec * pRec );
443
444             /// In-place scan algorithm
445             /** @anchor hzp_gc_inplace_scan
446                 Unlike the \ref hzp_gc_classic_scan "classic_scan" algorithm, \p inplace_scan does not allocate any memory.
447                 All operations are performed in-place.
448             */
449             void inplace_scan( details::HPRec * pRec );
450         };
451
452         /// Thread's hazard pointer manager
453         /**
454             To use Hazard Pointer reclamation schema each thread object must be linked with the object of ThreadGC class
455             that interacts with GarbageCollector global object. The linkage is performed by calling \ref cds_threading "cds::threading::Manager::attachThread()"
456             on the start of each thread that uses HP GC. Before terminating the thread linked to HP GC it is necessary to call
457             \ref cds_threading "cds::threading::Manager::detachThread()".
458         */
459         class ThreadGC
460         {
461             GarbageCollector&   m_HzpManager    ; ///< Hazard Pointer GC singleton
462             details::HPRec *    m_pHzpRec       ; ///< Pointer to thread's HZP record
463
464         public:
465             /// Default constructor
466             ThreadGC()
467                 : m_HzpManager( GarbageCollector::instance() ),
468                 m_pHzpRec( nullptr )
469             {}
470
471             /// The object is not copy-constructible
472             ThreadGC( ThreadGC const& ) = delete;
473
474             ~ThreadGC()
475             {
476                 fini();
477             }
478
479             /// Checks if thread GC is initialized
480             bool    isInitialized() const   { return m_pHzpRec != nullptr; }
481
482             /// Initialization. Repeat call is available
483             void init()
484             {
485                 if ( !m_pHzpRec )
486                     m_pHzpRec = m_HzpManager.AllocateHPRec();
487             }
488
489             /// Finalization. Repeat call is available
490             void fini()
491             {
492                 if ( m_pHzpRec ) {
493                     details::HPRec * pRec = m_pHzpRec;
494                     m_pHzpRec = nullptr;
495                     m_HzpManager.RetireHPRec( pRec );
496                 }
497             }
498
499             /// Initializes HP guard \p guard
500             details::HPGuard& allocGuard()
501             {
502                 assert( m_pHzpRec );
503                 return m_pHzpRec->m_hzp.alloc();
504             }
505
506             /// Frees HP guard \p guard
507             void freeGuard( details::HPGuard& guard )
508             {
509                 assert( m_pHzpRec );
510                 m_pHzpRec->m_hzp.free( guard );
511             }
512
513             /// Initializes HP guard array \p arr
514             template <size_t Count>
515             void allocGuard( details::HPArray<Count>& arr )
516             {
517                 assert( m_pHzpRec );
518                 m_pHzpRec->m_hzp.alloc( arr );
519             }
520
521             /// Frees HP guard array \p arr
522             template <size_t Count>
523             void freeGuard( details::HPArray<Count>& arr )
524             {
525                 assert( m_pHzpRec );
526                 m_pHzpRec->m_hzp.free( arr );
527             }
528
529             /// Places retired pointer \p and its deleter \p pFunc into thread's array of retired pointer for deferred reclamation
530             template <typename T>
531             void retirePtr( T * p, void (* pFunc)(T *) )
532             {
533                 retirePtr( details::retired_ptr( reinterpret_cast<void *>( p ), reinterpret_cast<free_retired_ptr_func>( pFunc ) ) );
534             }
535
536             /// Places retired pointer \p into thread's array of retired pointer for deferred reclamation
537             void retirePtr( const details::retired_ptr& p )
538             {
539                 m_pHzpRec->m_arrRetired.push( p );
540
541                 if ( m_pHzpRec->m_arrRetired.isFull() ) {
542                     // Max of retired pointer count is reached. Do scan
543                     scan();
544                 }
545             }
546
547             //@cond
548             void scan()
549             {
550                 m_HzpManager.Scan( m_pHzpRec );
551                 m_HzpManager.HelpScan( m_pHzpRec );
552             }
553             //@endcond
554         };
555
556         /// Auto HPGuard.
557         /**
558             This class encapsulates Hazard Pointer guard to protect a pointer against deletion .
559             It allocates one HP from thread's HP array in constructor and free the HP allocated in destruction time.
560         */
561         class AutoHPGuard
562         {
563             //@cond
564             details::HPGuard&   m_hp    ; ///< Hazard pointer guarded
565             ThreadGC&           m_gc    ; ///< Thread GC
566             //@endcond
567
568         public:
569             typedef details::HPGuard::hazard_ptr hazard_ptr ;  ///< Hazard pointer type
570         public:
571             /// Allocates HP guard from \p gc
572             AutoHPGuard( ThreadGC& gc )
573                 : m_hp( gc.allocGuard() )
574                 , m_gc( gc )
575             {}
576
577             /// Allocates HP guard from \p gc and protects the pointer \p p of type \p T
578             template <typename T>
579             AutoHPGuard( ThreadGC& gc, T * p  )
580                 : m_hp( gc.allocGuard() )
581                 , m_gc( gc )
582             {
583                 m_hp = p;
584             }
585
586             /// Frees HP guard. The pointer guarded may be deleted after this.
587             ~AutoHPGuard()
588             {
589                 m_gc.freeGuard( m_hp );
590             }
591
592             /// Returns thread GC
593             ThreadGC&    getGC() const
594             {
595                 return m_gc;
596             }
597
598             /// Protects the pointer \p p against reclamation (guards the pointer).
599             template <typename T>
600             T * operator =( T * p )
601             {
602                 return m_hp = p;
603             }
604
605             //@cond
606             std::nullptr_t operator =(std::nullptr_t)
607             {
608                 return m_hp = nullptr;
609             }
610
611             hazard_ptr get() const
612             {
613                 return m_hp;
614             }
615             //@endcond
616         };
617
618         /// Auto-managed array of hazard pointers
619         /**
620             This class is wrapper around cds::gc::hzp::details::HPArray class.
621             \p Count is the size of HP array
622         */
623         template <size_t Count>
624         class AutoHPArray: public details::HPArray<Count>
625         {
626             ThreadGC&    m_mgr    ;    ///< Thread GC
627
628         public:
629             /// Rebind array for other size \p COUNT2
630             template <size_t Count2>
631             struct rebind {
632                 typedef AutoHPArray<Count2>  other   ;   ///< rebinding result
633             };
634
635         public:
636             /// Allocates array of HP guard from \p mgr
637             AutoHPArray( ThreadGC& mgr )
638                 : m_mgr( mgr )
639             {
640                 mgr.allocGuard( *this );
641             }
642
643             /// Frees array of HP guard
644             ~AutoHPArray()
645             {
646                 m_mgr.freeGuard( *this );
647             }
648
649             /// Returns thread GC
650             ThreadGC&    getGC() const { return m_mgr; }
651         };
652
653     }   // namespace hzp
654 }}  // namespace cds::gc
655
656 // Inlines
657 #include <cds/gc/hzp/details/hp_inline.h>
658
659 #if CDS_COMPILER == CDS_COMPILER_MSVC
660 #   pragma warning(pop)
661 #endif
662
663 #endif  // #ifndef __CDS_GC_HZP_HZP_H