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