3 #ifndef CDSLIB_GC_DETAILS_HP_H
4 #define CDSLIB_GC_DETAILS_HP_H
6 #include <cds/algo/atomic.h>
7 #include <cds/os/thread.h>
8 #include <cds/details/bounded_array.h>
9 #include <cds/user_setup/cache_line.h>
11 #include <cds/gc/details/hp_type.h>
12 #include <cds/gc/details/hp_alloc.h>
14 #if CDS_COMPILER == CDS_COMPILER_MSVC
15 # pragma warning(push)
16 // warning C4251: 'cds::gc::hp::GarbageCollector::m_pListHead' : class 'cds::cxx11_atomic::atomic<T>'
17 // needs to have dll-interface to be used by clients of class 'cds::gc::hp::GarbageCollector'
18 # pragma warning(disable: 4251)
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
33 /// Different safe memory reclamation schemas (garbage collectors)
34 /** @ingroup cds_garbage_collector
36 This namespace specifies different safe memory reclamation (SMR) algorithms.
37 See \ref cds_garbage_collector "Garbage collectors"
41 /// Michael's Hazard Pointers reclamation schema
44 - [2002] Maged M.Michael "Safe memory reclamation for dynamic lock-freeobjects using atomic reads and writes"
45 - [2003] Maged M.Michael "Hazard Pointers: Safe memory reclamation for lock-free objects"
46 - [2004] Andrei Alexandrescy, Maged Michael "Lock-free Data Structures with Hazard Pointers"
48 The \p cds::gc::hp namespace and its members are internal representation of Hazard Pointer GC and should not be used directly.
49 Use \p cds::gc::HP class in your code.
51 Hazard Pointer garbage collector is a singleton. The main user-level part of Hazard Pointer schema is
52 GC class and its nested classes. Before use any HP-related class you must initialize HP garbage collector
53 by contructing \p cds::gc::HP object in beginning of your \p main().
54 See \p cds::gc::HP class for explanation.
59 class GarbageCollector;
65 typedef cds::gc::details::retired_ptr retired_ptr;
67 /// Array of retired pointers
69 The vector of retired pointer ready to delete.
71 The Hazard Pointer schema is build on thread-static arrays. For each HP-enabled thread the HP manager allocates
72 array of retired pointers. The array belongs to the thread: owner thread writes to the array, other threads
75 class retired_vector {
76 /// Underlying vector implementation
77 typedef cds::details::bounded_array<retired_ptr> retired_vector_impl;
79 retired_vector_impl m_arr ; ///< the array of retired pointers
80 size_t m_nSize ; ///< Current size of \p m_arr
84 typedef retired_vector_impl::iterator iterator;
87 retired_vector( const cds::gc::hp::GarbageCollector& HzpMgr ); // inline
93 The capacity is constant for any thread. It is defined by cds::gc::hp::GarbageCollector.
95 size_t capacity() const CDS_NOEXCEPT
97 return m_arr.capacity();
100 /// Current vector size (count of retired pointers in the vector)
101 size_t size() const CDS_NOEXCEPT
106 /// Set vector size. Uses internally
107 void size( size_t nSize )
109 assert( nSize <= capacity() );
113 /// Pushes retired pointer to the vector
114 void push( retired_ptr const& p )
116 assert( m_nSize < capacity() );
117 m_arr[ m_nSize ] = p;
121 /// Checks if the vector is full (size() == capacity() )
122 bool isFull() const CDS_NOEXCEPT
124 return m_nSize >= capacity();
128 iterator begin() CDS_NOEXCEPT
130 return m_arr.begin();
134 iterator end() CDS_NOEXCEPT
136 return m_arr.begin() + m_nSize;
139 /// Clears the vector. After clearing, size() == 0
140 void clear() CDS_NOEXCEPT
146 /// Hazard pointer record of the thread
148 The structure of type "single writer - multiple reader": only the owner thread may write to this structure
149 other threads have read-only access.
152 hp_allocator<> m_hzp; ///< array of hazard pointers. Implicit \ref CDS_DEFAULT_ALLOCATOR dependency
153 retired_vector m_arrRetired ; ///< Retired pointer array
155 char padding[cds::c_nCacheLineSize];
156 atomics::atomic<unsigned int> m_nSync; ///< dummy var to introduce synchronizes-with relationship between threads
159 hp_record( const cds::gc::hp::GarbageCollector& HzpMgr ); // inline
163 /// Clears all hazard pointers
171 m_nSync.fetch_add( 1, atomics::memory_order_acq_rel );
174 } // namespace details
176 /// GarbageCollector::Scan phase strategy
178 See GarbageCollector::Scan for explanation
181 classic, ///< classic scan as described in Michael's works (see GarbageCollector::classic_scan)
182 inplace ///< inplace scan without allocation (see GarbageCollector::inplace_scan)
185 /// Hazard Pointer singleton
187 Safe memory reclamation schema by Michael "Hazard Pointers"
190 \li [2002] Maged M.Michael "Safe memory reclamation for dynamic lock-freeobjects using atomic reads and writes"
191 \li [2003] Maged M.Michael "Hazard Pointers: Safe memory reclamation for lock-free objects"
192 \li [2004] Andrei Alexandrescy, Maged Michael "Lock-free Data Structures with Hazard Pointers"
195 class CDS_EXPORT_API GarbageCollector
198 typedef cds::atomicity::event_counter event_counter ; ///< event counter type
200 /// Internal GC statistics
201 struct InternalState {
202 size_t nHPCount ; ///< HP count per thread (const)
203 size_t nMaxThreadCount ; ///< Max thread count (const)
204 size_t nMaxRetiredPtrCount ; ///< Max retired pointer count per thread (const)
205 size_t nHPRecSize ; ///< Size of HP record, bytes (const)
207 size_t nHPRecAllocated ; ///< Count of HP record allocations
208 size_t nHPRecUsed ; ///< Count of HP record used
209 size_t nTotalRetiredPtrCount ; ///< Current total count of retired pointers
210 size_t nRetiredPtrInFreeHPRecs ; ///< Count of retired pointer in free (unused) HP records
212 event_counter::value_type evcAllocHPRec ; ///< Count of \p hp_record allocations
213 event_counter::value_type evcRetireHPRec ; ///< Count of \p hp_record retire events
214 event_counter::value_type evcAllocNewHPRec; ///< Count of new \p hp_record allocations from heap
215 event_counter::value_type evcDeleteHPRec ; ///< Count of \p hp_record deletions
217 event_counter::value_type evcScanCall ; ///< Count of Scan calling
218 event_counter::value_type evcHelpScanCall ; ///< Count of HelpScan calling
219 event_counter::value_type evcScanFromHelpScan;///< Count of Scan calls from HelpScan
221 event_counter::value_type evcDeletedNode ; ///< Count of deleting of retired objects
222 event_counter::value_type evcDeferredNode ; ///< Count of objects that cannot be deleted in Scan phase because of a hazard_pointer guards it
225 /// No GarbageCollector object is created
226 class not_initialized : public std::runtime_error
231 : std::runtime_error( "Global Hazard Pointer GarbageCollector is not initialized" )
236 /// Not enough required Hazard Pointer count
237 class too_many_hazard_ptr : public std::length_error
241 too_many_hazard_ptr()
242 : std::length_error( "Not enough required Hazard Pointer count" )
248 /// Internal GC statistics
250 event_counter m_AllocHPRec ; ///< Count of \p hp_record allocations
251 event_counter m_RetireHPRec ; ///< Count of \p hp_record retire events
252 event_counter m_AllocNewHPRec ; ///< Count of new \p hp_record allocations from heap
253 event_counter m_DeleteHPRec ; ///< Count of \p hp_record deletions
255 event_counter m_ScanCallCount ; ///< Count of Scan calling
256 event_counter m_HelpScanCallCount ; ///< Count of HelpScan calling
257 event_counter m_CallScanFromHelpScan ; ///< Count of Scan calls from HelpScan
259 event_counter m_DeletedNode ; ///< Count of retired objects deleting
260 event_counter m_DeferredNode ; ///< Count of objects that cannot be deleted in Scan phase because of a hazard_pointer guards it
263 /// Internal list of cds::gc::hp::details::hp_record
264 struct hplist_node : public details::hp_record
266 hplist_node * m_pNextNode; ///< next hazard ptr record in list
267 atomics::atomic<OS::ThreadId> m_idOwner; ///< Owner thread id; 0 - the record is free (not owned)
268 atomics::atomic<bool> m_bFree; ///< true if record if free (not owned)
271 hplist_node( const GarbageCollector& HzpMgr )
272 : hp_record( HzpMgr ),
273 m_pNextNode( nullptr ),
274 m_idOwner( OS::c_NullThreadId ),
280 assert( m_idOwner.load( atomics::memory_order_relaxed ) == OS::c_NullThreadId );
281 assert( m_bFree.load(atomics::memory_order_relaxed) );
286 atomics::atomic<hplist_node *> m_pListHead ; ///< Head of GC list
288 static GarbageCollector * m_pHZPManager ; ///< GC instance pointer
290 Statistics m_Stat ; ///< Internal statistics
291 bool m_bStatEnabled ; ///< true - statistics enabled
293 const size_t m_nHazardPointerCount ; ///< max count of thread's hazard pointer
294 const size_t m_nMaxThreadCount ; ///< max count of thread
295 const size_t m_nMaxRetiredPtrCount ; ///< max count of retired ptr per thread
296 scan_type m_nScanType ; ///< scan type (see \ref scan_type enum)
302 size_t nHazardPtrCount = 0, ///< Hazard pointer count per thread
303 size_t nMaxThreadCount = 0, ///< Max count of thread
304 size_t nMaxRetiredPtrCount = 0, ///< Capacity of the array of retired objects
305 scan_type nScanType = inplace ///< Scan type (see \ref scan_type enum)
311 /// Allocate new HP record
312 hplist_node * NewHPRec();
314 /// Permanently deletes HPrecord \p pNode
316 Caveat: for performance reason this function is defined as inline and cannot be called directly
318 void DeleteHPRec( hplist_node * pNode );
320 void detachAllThread();
323 /// Creates GarbageCollector singleton
325 GC is the singleton. If GC instance is not exist then the function creates the instance.
326 Otherwise it does nothing.
328 The Michael's HP reclamation schema depends of three parameters:
330 \p nHazardPtrCount - HP pointer count per thread. Usually it is small number (2-4) depending from
331 the data structure algorithms. By default, if \p nHazardPtrCount = 0,
332 the function uses maximum of HP count for CDS library.
334 \p nMaxThreadCount - max count of thread with using HP GC in your application. Default is 100.
336 \p nMaxRetiredPtrCount - capacity of array of retired pointers for each thread. Must be greater than
337 \p nHazardPtrCount * \p nMaxThreadCount.
338 Default is 2 * \p nHazardPtrCount * \p nMaxThreadCount.
340 static void CDS_STDCALL Construct(
341 size_t nHazardPtrCount = 0, ///< Hazard pointer count per thread
342 size_t nMaxThreadCount = 0, ///< Max count of simultaneous working thread in your application
343 size_t nMaxRetiredPtrCount = 0, ///< Capacity of the array of retired objects for the thread
344 scan_type nScanType = inplace ///< Scan type (see \ref scan_type enum)
347 /// Destroys global instance of GarbageCollector
349 The parameter \p bDetachAll should be used carefully: if its value is \p true,
350 then the destroying GC automatically detaches all attached threads. This feature
351 can be useful when you have no control over the thread termination, for example,
352 when \p libcds is injected into existing external thread.
354 static void CDS_STDCALL Destruct(
355 bool bDetachAll = false ///< Detach all threads
358 /// Returns pointer to GarbageCollector instance
359 static GarbageCollector& instance()
361 if ( !m_pHZPManager )
362 throw not_initialized();
363 return *m_pHZPManager;
366 /// Checks if global GC object is constructed and may be used
367 static bool isUsed() CDS_NOEXCEPT
369 return m_pHZPManager != nullptr;
372 /// Returns max Hazard Pointer count defined in construction time
373 size_t getHazardPointerCount() const CDS_NOEXCEPT
375 return m_nHazardPointerCount;
378 /// Returns max thread count defined in construction time
379 size_t getMaxThreadCount() const CDS_NOEXCEPT
381 return m_nMaxThreadCount;
384 /// Returns max size of retired objects array. It is defined in construction time
385 size_t getMaxRetiredPtrCount() const CDS_NOEXCEPT
387 return m_nMaxRetiredPtrCount;
390 // Internal statistics
392 /// Get internal statistics
393 InternalState& getInternalState(InternalState& stat) const;
395 /// Checks if internal statistics enabled
396 bool isStatisticsEnabled() const { return m_bStatEnabled; }
398 /// Enables/disables internal statistics
399 bool enableStatistics( bool bEnable )
401 bool bEnabled = m_bStatEnabled;
402 m_bStatEnabled = bEnable;
406 /// Checks that required hazard pointer count \p nRequiredCount is less or equal then max hazard pointer count
408 If \p nRequiredCount > getHazardPointerCount() then the exception \p too_many_hazard_ptr is thrown
410 static void checkHPCount( unsigned int nRequiredCount )
412 if ( instance().getHazardPointerCount() < nRequiredCount )
413 throw too_many_hazard_ptr();
416 /// Get current scan strategy
417 scan_type getScanType() const
422 /// Set current scan strategy
423 /** @anchor hzp_gc_setScanType
424 Scan strategy changing is allowed on the fly.
427 scan_type nScanType ///< new scan strategy
430 m_nScanType = nScanType;
433 public: // Internals for threads
435 /// Allocates Hazard Pointer GC record. For internal use only
436 details::hp_record * alloc_hp_record();
438 /// Free HP record. For internal use only
439 void free_hp_record( details::hp_record * pRec );
441 /// The main garbage collecting function
443 This function is called internally by ThreadGC object when upper bound of thread's list of reclaimed pointers
446 There are the following scan algorithm:
447 - \ref hzp_gc_classic_scan "classic_scan" allocates memory for internal use
448 - \ref hzp_gc_inplace_scan "inplace_scan" does not allocate any memory
450 Use \ref hzp_gc_setScanType "setScanType" member function to setup appropriate scan algorithm.
452 void Scan( details::hp_record * pRec )
454 switch ( m_nScanType ) {
456 inplace_scan( pRec );
459 assert(false) ; // Forgotten something?..
461 classic_scan( pRec );
466 /// Helper scan routine
468 The function guarantees that every node that is eligible for reuse is eventually freed, barring
469 thread failures. To do so, after executing Scan, a thread executes a HelpScan,
470 where it checks every HP record. If an HP record is inactive, the thread moves all "lost" reclaimed pointers
471 to thread's list of reclaimed pointers.
473 The function is called internally by Scan.
475 void HelpScan( details::hp_record * pThis );
478 /// Classic scan algorithm
479 /** @anchor hzp_gc_classic_scan
480 Classical scan algorithm as described in Michael's paper.
482 A scan includes four stages. The first stage involves scanning the array HP for non-null values.
483 Whenever a non-null value is encountered, it is inserted in a local list of currently protected pointer.
484 Only stage 1 accesses shared variables. The following stages operate only on private variables.
486 The second stage of a scan involves sorting local list of protected pointers to allow
487 binary search in the third stage.
489 The third stage of a scan involves checking each reclaimed node
490 against the pointers in local list of protected pointers. If the binary search yields
491 no match, the node is freed. Otherwise, it cannot be deleted now and must kept in thread's list
492 of reclaimed pointers.
494 The forth stage prepares new thread's private list of reclaimed pointers
495 that could not be freed during the current scan, where they remain until the next scan.
497 This algorithm allocates memory for internal HP array.
499 This function is called internally by ThreadGC object when upper bound of thread's list of reclaimed pointers
502 void classic_scan( details::hp_record * pRec );
504 /// In-place scan algorithm
505 /** @anchor hzp_gc_inplace_scan
506 Unlike the \ref hzp_gc_classic_scan "classic_scan" algorithm, \p inplace_scan does not allocate any memory.
507 All operations are performed in-place.
509 void inplace_scan( details::hp_record * pRec );
512 /// Thread's hazard pointer manager
514 To use Hazard Pointer reclamation schema each thread object must be linked with the object of ThreadGC class
515 that interacts with GarbageCollector global object. The linkage is performed by calling \ref cds_threading "cds::threading::Manager::attachThread()"
516 on the start of each thread that uses HP GC. Before terminating the thread linked to HP GC it is necessary to call
517 \ref cds_threading "cds::threading::Manager::detachThread()".
521 GarbageCollector& m_HzpManager; ///< Hazard Pointer GC singleton
522 details::hp_record * m_pHzpRec; ///< Pointer to thread's HZP record
525 /// Default constructor
527 : m_HzpManager( GarbageCollector::instance() ),
531 /// The object is not copy-constructible
532 ThreadGC( ThreadGC const& ) = delete;
539 /// Checks if thread GC is initialized
540 bool isInitialized() const { return m_pHzpRec != nullptr; }
542 /// Initialization. Repeat call is available
546 m_pHzpRec = m_HzpManager.alloc_hp_record();
549 /// Finalization. Repeat call is available
553 details::hp_record * pRec = m_pHzpRec;
555 m_HzpManager.free_hp_record( pRec );
559 /// Initializes HP guard \p guard
560 details::hp_guard& allocGuard()
563 return m_pHzpRec->m_hzp.alloc();
566 /// Frees HP guard \p guard
567 void freeGuard( details::hp_guard& guard )
570 m_pHzpRec->m_hzp.free( guard );
573 /// Initializes HP guard array \p arr
574 template <size_t Count>
575 void allocGuard( details::hp_array<Count>& arr )
578 m_pHzpRec->m_hzp.alloc( arr );
581 /// Frees HP guard array \p arr
582 template <size_t Count>
583 void freeGuard( details::hp_array<Count>& arr )
586 m_pHzpRec->m_hzp.free( arr );
589 /// Places retired pointer \p and its deleter \p pFunc into thread's array of retired pointer for deferred reclamation
590 template <typename T>
591 void retirePtr( T * p, void (* pFunc)(T *) )
602 free_retired_ptr_func hpFunc;
604 cast_func.pFunc = pFunc;
606 retirePtr( details::retired_ptr( cast_ptr.hp, cast_func.hpFunc ) );
608 retirePtr( details::retired_ptr( reinterpret_cast<void *>( p ), reinterpret_cast<free_retired_ptr_func>( pFunc )));
611 /// Places retired pointer \p into thread's array of retired pointer for deferred reclamation
612 void retirePtr( details::retired_ptr const& p )
614 m_pHzpRec->m_arrRetired.push( p );
616 if ( m_pHzpRec->m_arrRetired.isFull() ) {
617 // Max of retired pointer count is reached. Do scan
622 /// Run retiring scan cycle
625 m_HzpManager.Scan( m_pHzpRec );
626 m_HzpManager.HelpScan( m_pHzpRec );
631 assert( m_pHzpRec != nullptr );
638 This class encapsulates Hazard Pointer guard to protect a pointer against deletion.
639 It allocates one HP from thread's HP array in constructor and free the hazard pointer allocated
644 details::hp_guard& m_hp ; ///< Hazard pointer guarded
647 typedef details::hp_guard::hazard_ptr hazard_ptr ; ///< Hazard pointer type
650 /// Allocates HP guard
651 guard(); // inline in hp_impl.h
653 /// Allocates HP guard from \p gc and protects the pointer \p p of type \p T
654 template <typename T>
655 explicit guard( T * p ); // inline in hp_impl.h
657 /// Frees HP guard. The pointer guarded may be deleted after this.
658 ~guard(); // inline in hp_impl.h
660 /// Protects the pointer \p p against reclamation (guards the pointer).
661 template <typename T>
662 T * operator =( T * p )
668 std::nullptr_t operator =(std::nullptr_t)
670 return m_hp = nullptr;
674 /// Get raw guarded pointer
675 hazard_ptr get() const
681 /// Auto-managed array of hazard pointers
683 This class is wrapper around cds::gc::hp::details::hp_array class.
684 \p Count is the size of HP array
686 template <size_t Count>
687 class array : public details::hp_array<Count>
690 /// Rebind array for other size \p COUNT2
691 template <size_t Count2>
693 typedef array<Count2> other; ///< rebinding result
697 /// Allocates array of HP guard
698 array(); // inline in hp_impl.h
700 /// Frees array of HP guard
701 ~array(); //inline in hp_impl.h
705 }} // namespace cds::gc
711 namespace gc { namespace hp { namespace details {
713 inline retired_vector::retired_vector( const cds::gc::hp::GarbageCollector& HzpMgr )
714 : m_arr( HzpMgr.getMaxRetiredPtrCount() ),
718 inline hp_record::hp_record( const cds::gc::hp::GarbageCollector& HzpMgr )
719 : m_hzp( HzpMgr.getHazardPointerCount() )
720 , m_arrRetired( HzpMgr )
724 }}} // namespace gc::hp::details
729 #if CDS_COMPILER == CDS_COMPILER_MSVC
730 # pragma warning(pop)
733 #endif // #ifndef CDSLIB_GC_DETAILS_HP_H