2 This file is a part of libcds - Concurrent Data Structures library
4 (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2017
6 Source code repo: http://github.com/khizmax/libcds/
7 Download: http://sourceforge.net/projects/libcds/files/
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
12 * Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
15 * Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 #include <cds/gc/hp.h>
35 #include <cds/os/thread.h>
37 namespace cds { namespace gc { namespace hp {
40 void * default_alloc_memory( size_t size )
42 return new uintptr_t[( size + sizeof( uintptr_t ) - 1 ) / sizeof( uintptr_t) ];
45 void default_free_memory( void* p )
47 delete[] reinterpret_cast<uintptr_t*>( p );
50 void* ( *s_alloc_memory )( size_t size ) = default_alloc_memory;
51 void ( *s_free_memory )( void* p ) = default_free_memory;
60 allocator( allocator const& ) {}
62 explicit allocator( allocator<U> const& ) {}
64 static T* allocate( size_t nCount )
66 return reinterpret_cast<T*>( s_alloc_memory( sizeof( value_type ) * nCount ) );
69 static void deallocate( T* p, size_t /*nCount*/ )
71 s_free_memory( reinterpret_cast<void*>( p ) );
76 static const size_t c_nHazardPointerPerThread = 8;
77 static const size_t c_nMaxThreadCount = 100;
80 size_t calc_retired_size( size_t nSize, size_t nHPCount, size_t nThreadCount )
82 size_t const min_size = nHPCount * nThreadCount;
83 return nSize < min_size ? min_size * 2 : nSize;
86 stat s_postmortem_stat;
89 /*static*/ CDS_EXPORT_API smr* smr::instance_ = nullptr;
90 thread_local thread_data* tls_ = nullptr;
92 /*static*/ CDS_EXPORT_API thread_data* smr::tls()
94 assert( tls_ != nullptr );
98 struct smr::thread_record: thread_data
100 atomics::atomic<thread_record*> m_pNextNode; ///< next hazard ptr record in list
101 atomics::atomic<cds::OS::ThreadId> m_idOwner; ///< Owner thread id; 0 - the record is free (not owned)
102 atomics::atomic<bool> m_bFree; ///< true if record is free (not owned)
104 thread_record( guard* guards, size_t guard_count, retired_ptr* retired_arr, size_t retired_capacity )
105 : thread_data( guards, guard_count, retired_arr, retired_capacity )
110 /*static*/ CDS_EXPORT_API void smr::set_memory_allocator(
111 void* ( *alloc_func )( size_t size ),
112 void( *free_func )( void * p )
115 // The memory allocation functions may be set BEFORE initializing HP SMR!!!
116 assert( instance_ == nullptr );
118 s_alloc_memory = alloc_func;
119 s_free_memory = free_func;
123 /*static*/ CDS_EXPORT_API void smr::construct( size_t nHazardPtrCount, size_t nMaxThreadCount, size_t nMaxRetiredPtrCount, scan_type nScanType )
126 instance_ = new( s_alloc_memory(sizeof(smr))) smr( nHazardPtrCount, nMaxThreadCount, nMaxRetiredPtrCount, nScanType );
130 /*static*/ CDS_EXPORT_API void smr::destruct( bool bDetachAll )
134 instance_->detach_all_thread();
137 s_free_memory( instance_ );
142 CDS_EXPORT_API smr::smr( size_t nHazardPtrCount, size_t nMaxThreadCount, size_t nMaxRetiredPtrCount, scan_type nScanType )
143 : thread_list_( nullptr )
144 , hazard_ptr_count_( nHazardPtrCount == 0 ? defaults::c_nHazardPointerPerThread : nHazardPtrCount )
145 , max_thread_count_( nMaxThreadCount == 0 ? defaults::c_nMaxThreadCount : nMaxThreadCount )
146 , max_retired_ptr_count_( calc_retired_size( nMaxRetiredPtrCount, hazard_ptr_count_, max_thread_count_ ))
147 , scan_type_( nScanType )
148 , scan_func_( nScanType == classic ? &smr::classic_scan : &smr::inplace_scan )
151 CDS_EXPORT_API smr::~smr()
153 CDS_DEBUG_ONLY( const cds::OS::ThreadId nullThreadId = cds::OS::c_NullThreadId; )
154 CDS_DEBUG_ONLY( const cds::OS::ThreadId mainThreadId = cds::OS::get_current_thread_id();)
156 CDS_HPSTAT( statistics( s_postmortem_stat ));
158 thread_record* pHead = thread_list_.load( atomics::memory_order_relaxed );
159 thread_list_.store( nullptr, atomics::memory_order_relaxed );
161 thread_record* pNext = nullptr;
162 for ( thread_record* hprec = pHead; hprec; hprec = pNext )
164 assert( hprec->m_idOwner.load( atomics::memory_order_relaxed ) == nullThreadId
165 || hprec->m_idOwner.load( atomics::memory_order_relaxed ) == mainThreadId )
168 retired_array& arr = hprec->retired_;
169 for ( retired_ptr* cur{ arr.first() }, *last{ arr.last() }; cur != last; ++cur ) {
171 CDS_HPSTAT( ++s_postmortem_stat.free_count );
175 pNext = hprec->m_pNextNode.load( atomics::memory_order_relaxed );
176 hprec->m_bFree.store( true, atomics::memory_order_relaxed );
177 destroy_thread_data( hprec );
182 CDS_EXPORT_API smr::thread_record* smr::create_thread_data()
184 size_t const guard_array_size = thread_hp_storage::calc_array_size( get_hazard_ptr_count());
185 size_t const retired_array_size = retired_array::calc_array_size( get_max_retired_ptr_count());
186 size_t const nSize = sizeof( thread_record ) + guard_array_size + retired_array_size;
189 The memory is allocated by contnuous block
191 +--------------------------+
197 | |--------------------------| |
198 | | hazard_ptr[] |<--+
201 | |--------------------------|
202 +-->| retired_ptr[] |
205 +--------------------------+
208 char* mem = reinterpret_cast<char*>( s_alloc_memory( nSize ));
209 return new( mem ) thread_record(
210 reinterpret_cast<guard*>( mem + sizeof( thread_record )), get_hazard_ptr_count(),
211 reinterpret_cast<retired_ptr*>( mem + sizeof( thread_record ) + guard_array_size ), get_max_retired_ptr_count()
215 /*static*/ CDS_EXPORT_API void smr::destroy_thread_data( thread_record* pRec )
217 // all retired pointers must be freed
218 assert( pRec->retired_.size() == 0 );
220 pRec->~thread_record();
221 s_free_memory( pRec );
225 CDS_EXPORT_API smr::thread_record* smr::alloc_thread_data()
227 //CDS_HAZARDPTR_STATISTIC( ++m_Stat.m_AllocHPRec )
229 thread_record * hprec;
230 const cds::OS::ThreadId nullThreadId = cds::OS::c_NullThreadId;
231 const cds::OS::ThreadId curThreadId = cds::OS::get_current_thread_id();
233 // First try to reuse a free (non-active) HP record
234 for ( hprec = thread_list_.load( atomics::memory_order_acquire ); hprec; hprec = hprec->m_pNextNode.load( atomics::memory_order_relaxed ) ) {
235 cds::OS::ThreadId thId = nullThreadId;
236 if ( !hprec->m_idOwner.compare_exchange_strong( thId, curThreadId, atomics::memory_order_relaxed, atomics::memory_order_relaxed ) )
238 hprec->m_bFree.store( false, atomics::memory_order_release );
242 // No HP records available for reuse
243 // Allocate and push a new HP record
244 hprec = create_thread_data();
245 hprec->m_idOwner.store( curThreadId, atomics::memory_order_relaxed );
247 thread_record* pOldHead = thread_list_.load( atomics::memory_order_relaxed );
249 hprec->m_pNextNode.store( pOldHead, atomics::memory_order_relaxed );
250 } while ( !thread_list_.compare_exchange_weak( pOldHead, hprec, atomics::memory_order_release, atomics::memory_order_acquire ) );
255 CDS_EXPORT_API void smr::free_thread_data( smr::thread_record* pRec )
257 assert( pRec != nullptr );
259 pRec->hazards_.clear();
262 pRec->m_idOwner.store( cds::OS::c_NullThreadId, atomics::memory_order_release );
265 CDS_EXPORT_API void smr::detach_all_thread()
267 thread_record * pNext = nullptr;
268 const cds::OS::ThreadId nullThreadId = cds::OS::c_NullThreadId;
270 for ( thread_record * hprec = thread_list_.load( atomics::memory_order_relaxed ); hprec; hprec = pNext ) {
271 pNext = hprec->m_pNextNode.load( atomics::memory_order_relaxed );
272 if ( hprec->m_idOwner.load( atomics::memory_order_relaxed ) != nullThreadId ) {
273 free_thread_data( hprec );
278 /*static*/ CDS_EXPORT_API void smr::attach_thread()
281 tls_ = instance().alloc_thread_data();
284 /*static*/ CDS_EXPORT_API void smr::detach_thread()
286 thread_data* rec = tls_;
289 instance().free_thread_data( static_cast<thread_record*>( rec ));
294 CDS_EXPORT_API void smr::inplace_scan( thread_data* pThreadRec )
296 thread_record* pRec = static_cast<thread_record*>( pThreadRec );
298 //CDS_HAZARDPTR_STATISTIC( ++m_Stat.m_ScanCallCount )
300 // In-place scan algo uses LSB of retired ptr as a mark for internal purposes.
301 // It is correct if all retired pointers are ar least 2-byte aligned (LSB is zero).
302 // If it is wrong, we use classic scan algorithm
304 // Check if all retired pointers has zero LSB
305 // LSB is used for marking pointers that cannot be deleted yet
306 retired_ptr* first_retired = pRec->retired_.first();
307 retired_ptr* last_retired = pRec->retired_.last();
308 if ( first_retired == last_retired )
311 for ( auto it = first_retired; it != last_retired; ++it ) {
313 // found a pointer with LSB bit set - use classic_scan
314 classic_scan( pRec );
319 CDS_HPSTAT( ++pRec->scan_count_ );
321 // Sort retired pointer array
322 std::sort( first_retired, last_retired, retired_ptr::less );
327 auto it = first_retired;
329 while ( ++it != last_retired ) {
330 assert( itPrev->m_p < it->m_p );
336 // Search guarded pointers in retired array
337 thread_record* pNode = thread_list_.load( atomics::memory_order_acquire );
340 retired_ptr dummy_retired;
342 if ( pNode->m_idOwner.load( atomics::memory_order_relaxed ) != cds::OS::c_NullThreadId ) {
343 thread_hp_storage& hpstg = pNode->hazards_;
344 for ( size_t i = 0; i < hazard_ptr_count_; ++i ) {
346 void * hptr = hpstg[i].get();
348 dummy_retired.m_p = hptr;
349 retired_ptr* it = std::lower_bound( first_retired, last_retired, dummy_retired, retired_ptr::less );
350 if ( it != last_retired && it->m_p == hptr ) {
351 // Mark retired pointer as guarded
357 pNode = pNode->m_pNextNode.load( atomics::memory_order_relaxed );
361 // Move all marked pointers to head of array
363 retired_ptr* insert_pos = first_retired;
364 for ( retired_ptr* it = first_retired; it != last_retired; ++it ) {
366 it->m_n &= ~uintptr_t(1);
367 if ( insert_pos != it )
372 // Retired pointer may be freed
374 CDS_HPSTAT( ++pRec->free_count_ );
377 const size_t nDeferred = insert_pos - first_retired;
378 pRec->retired_.reset( nDeferred );
382 // cppcheck-suppress functionConst
383 CDS_EXPORT_API void smr::classic_scan( thread_data* pThreadRec )
385 thread_record* pRec = static_cast<thread_record*>( pThreadRec );
387 CDS_HPSTAT( ++pRec->scan_count_ );
389 std::vector< void*, allocator<void*>> plist;
390 plist.reserve( get_max_thread_count() * get_hazard_ptr_count());
391 assert( plist.size() == 0 );
393 // Stage 1: Scan HP list and insert non-null values in plist
395 thread_record* pNode = thread_list_.load( atomics::memory_order_acquire );
398 if ( pNode->m_idOwner.load( std::memory_order_relaxed ) != cds::OS::c_NullThreadId ) {
399 for ( size_t i = 0; i < get_hazard_ptr_count(); ++i ) {
401 void * hptr = pNode->hazards_[i].get();
403 plist.push_back( hptr );
406 pNode = pNode->m_pNextNode.load( atomics::memory_order_relaxed );
409 // Sort plist to simplify search in
410 std::sort( plist.begin(), plist.end() );
412 // Stage 2: Search plist
413 retired_array& retired = pRec->retired_;
415 retired_ptr* first_retired = retired.first();
416 retired_ptr* last_retired = retired.last();
419 auto itBegin = plist.begin();
420 auto itEnd = plist.end();
421 retired_ptr* insert_pos = first_retired;
422 for ( retired_ptr* it = first_retired; it != last_retired; ++it ) {
423 if ( std::binary_search( itBegin, itEnd, first_retired->m_p ) ) {
424 if ( insert_pos != it )
430 CDS_HPSTAT( ++pRec->free_count_ );
434 retired.reset( insert_pos - first_retired );
438 CDS_EXPORT_API void smr::help_scan( thread_data* pThis )
440 assert( static_cast<thread_record*>( pThis )->m_idOwner.load( atomics::memory_order_relaxed ) == cds::OS::get_current_thread_id() );
442 CDS_HPSTAT( ++pThis->help_scan_count_ );
444 const cds::OS::ThreadId nullThreadId = cds::OS::c_NullThreadId;
445 const cds::OS::ThreadId curThreadId = cds::OS::get_current_thread_id();
446 for ( thread_record* hprec = thread_list_.load( atomics::memory_order_acquire ); hprec; hprec = hprec->m_pNextNode.load( atomics::memory_order_relaxed ))
448 if ( hprec == static_cast<thread_record*>( pThis ))
451 // If m_bFree == true then hprec->retired_ is empty - we don't need to see it
452 if ( hprec->m_bFree.load( atomics::memory_order_acquire ))
455 // Owns hprec if it is empty.
456 // Several threads may work concurrently so we use atomic technique only.
458 cds::OS::ThreadId curOwner = hprec->m_idOwner.load( atomics::memory_order_relaxed );
459 if ( curOwner == nullThreadId ) {
460 if ( !hprec->m_idOwner.compare_exchange_strong( curOwner, curThreadId, atomics::memory_order_acquire, atomics::memory_order_relaxed ) )
467 // We own the thread record successfully. Now, we can see whether it has retired pointers.
468 // If it has ones then we move them to pThis that is private for current thread.
469 retired_array& src = hprec->retired_;
470 retired_array& dest = pThis->retired_;
471 assert( !dest.full() );
473 retired_ptr* src_first = src.first();
474 retired_ptr* src_last = src.last();
476 for ( ; src_first != src_last; ++src_first ) {
477 if ( !dest.push( std::move( *src_first )))
483 hprec->m_bFree.store( true, atomics::memory_order_relaxed );
484 hprec->m_idOwner.store( nullThreadId, atomics::memory_order_release );
490 CDS_EXPORT_API void smr::statistics( stat& st )
493 # ifdef CDS_ENABLE_HPSTAT
494 for ( thread_record* hprec = thread_list_.load( atomics::memory_order_acquire ); hprec; hprec = hprec->m_pNextNode.load( atomics::memory_order_relaxed ) )
496 ++st.thread_rec_count;
497 st.guard_allocated += hprec->hazards_.alloc_guard_count_;
498 st.guard_freed += hprec->hazards_.free_guard_count_;
499 st.retired_count += hprec->retired_.retire_call_count_;
500 st.free_count += hprec->free_count_;
501 st.scan_count += hprec->scan_count_;
502 st.help_scan_count += hprec->help_scan_count_;
507 }}} // namespace cds::gc::hp
509 CDS_EXPORT_API /*static*/ cds::gc::HP::stat const& cds::gc::HP::postmortem_statistics()
511 return cds::gc::hp::s_postmortem_stat;