From: khizmax Date: Sun, 23 Oct 2016 21:07:22 +0000 (+0300) Subject: Added intrusive SplitListSet based on IterableList X-Git-Tag: v2.2.0~92 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;ds=sidebyside;h=377b2499eb0d14cd5814df70f144aaf1e132b4bd;p=libcds.git Added intrusive SplitListSet based on IterableList --- diff --git a/cds/intrusive/details/split_list_base.h b/cds/intrusive/details/split_list_base.h index a10a50f1..a44a327d 100644 --- a/cds/intrusive/details/split_list_base.h +++ b/cds/intrusive/details/split_list_base.h @@ -45,30 +45,55 @@ namespace cds { namespace intrusive { /** @ingroup cds_intrusive_helper */ namespace split_list { + //@cond + struct hash_node + { + size_t m_nHash; ///< Hash value for node + + /// Default constructor + hash_node() + : m_nHash( 0 ) + { + assert( is_dummy() ); + } + + /// Initializes dummy node with \p nHash value + hash_node( size_t nHash ) + : m_nHash( nHash ) + { + assert( is_dummy() ); + } + + /// Checks if the node is dummy node + bool is_dummy() const + { + return (m_nHash & 1) == 0; + } + }; + //@endcond + /// Split-ordered list node /** Template parameter: - \p OrderedListNode - node type for underlying ordered list */ template - struct node: public OrderedListNode + struct node: public OrderedListNode, public hash_node { //@cond typedef OrderedListNode base_class; //@endcond - size_t m_nHash ; ///< Hash value for node - /// Default constructor node() - : m_nHash(0) + : hash_node(0) { assert( is_dummy() ); } /// Initializes dummy node with \p nHash value node( size_t nHash ) - : m_nHash( nHash ) + : hash_node( nHash ) { assert( is_dummy() ); } @@ -76,10 +101,36 @@ namespace cds { namespace intrusive { /// Checks if the node is dummy node bool is_dummy() const { - return (m_nHash & 1) == 0; + return hash_node::is_dummy(); } }; + //@cond + template <> + struct node: public hash_node + { + // Default ctor + node() + : hash_node( 0 ) + { + assert( is_dummy() ); + } + + /// Initializes dummy node with \p nHash value + node( size_t nHash ) + : hash_node( nHash ) + { + assert( is_dummy() ); + } + + /// Checks if the node is dummy node + bool is_dummy() const + { + return hash_node::is_dummy(); + } + }; + //@endcond + /// \p SplitListSet internal statistics. May be used for debugging or profiling /** Template argument \p Counter defines type of counter, default is \p cds::atomicity::event_counter. @@ -729,70 +780,6 @@ namespace cds { namespace intrusive { //@endcond }; - /// Split-list node traits - /** - This traits is intended for converting between underlying ordered list node type - and split-list node type - - Template parameter: - - \p BaseNodeTraits - node traits of base ordered list type - */ - template - struct node_traits: private BaseNodeTraits - { - typedef BaseNodeTraits base_class; ///< Base ordered list node type - typedef typename base_class::value_type value_type; ///< Value type - typedef typename base_class::node_type base_node_type; ///< Ordered list node type - typedef node node_type; ///< Spit-list node type - - /// Convert value reference to node pointer - static node_type * to_node_ptr( value_type& v ) - { - return static_cast( base_class::to_node_ptr( v ) ); - } - - /// Convert value pointer to node pointer - static node_type * to_node_ptr( value_type * v ) - { - return static_cast( base_class::to_node_ptr( v ) ); - } - - /// Convert value reference to node pointer (const version) - static node_type const * to_node_ptr( value_type const& v ) - { - return static_cast( base_class::to_node_ptr( v ) ); - } - - /// Convert value pointer to node pointer (const version) - static node_type const * to_node_ptr( value_type const * v ) - { - return static_cast( base_class::to_node_ptr( v ) ); - } - - /// Convert node reference to value pointer - static value_type * to_value_ptr( node_type& n ) - { - return base_class::to_value_ptr( static_cast( n ) ); - } - - /// Convert node pointer to value pointer - static value_type * to_value_ptr( node_type * n ) - { - return base_class::to_value_ptr( static_cast( n ) ); - } - - /// Convert node reference to value pointer (const version) - static const value_type * to_value_ptr( node_type const & n ) - { - return base_class::to_value_ptr( static_cast( n ) ); - } - - /// Convert node pointer to value pointer (const version) - static const value_type * to_value_ptr( node_type const * n ) - { - return base_class::to_value_ptr( static_cast( n ) ); - } - }; //@cond namespace details { @@ -823,8 +810,11 @@ namespace cds { namespace intrusive { {} }; + template + class ordered_list_adapter; + template - class rebind_list_traits + class ordered_list_adapter< OrderedList, Traits, false > { typedef OrderedList native_ordered_list; typedef Traits traits; @@ -833,7 +823,7 @@ namespace cds { namespace intrusive { typedef typename native_ordered_list::key_comparator native_key_comparator; typedef typename native_ordered_list::node_type node_type; typedef typename native_ordered_list::value_type value_type; - typedef typename native_ordered_list::node_traits node_traits; + typedef typename native_ordered_list::node_traits native_node_traits; typedef typename native_ordered_list::disposer native_disposer; typedef split_list::node splitlist_node_type; @@ -841,8 +831,8 @@ namespace cds { namespace intrusive { struct key_compare { int operator()( value_type const& v1, value_type const& v2 ) const { - splitlist_node_type const * n1 = static_cast( node_traits::to_node_ptr( v1 )); - splitlist_node_type const * n2 = static_cast( node_traits::to_node_ptr( v2 )); + splitlist_node_type const * n1 = static_cast(native_node_traits::to_node_ptr( v1 )); + splitlist_node_type const * n2 = static_cast(native_node_traits::to_node_ptr( v2 )); if ( n1->m_nHash != n2->m_nHash ) return n1->m_nHash < n2->m_nHash ? -1 : 1; @@ -853,18 +843,18 @@ namespace cds { namespace intrusive { assert( !n1->is_dummy() && !n2->is_dummy() ); - return native_key_comparator()( v1, v2 ); + return native_key_comparator()(v1, v2); } template int operator()( value_type const& v, search_value_type const& q ) const { - splitlist_node_type const * n = static_cast( node_traits::to_node_ptr( v )); + splitlist_node_type const * n = static_cast(native_node_traits::to_node_ptr( v )); if ( n->m_nHash != q.nHash ) return n->m_nHash < q.nHash ? -1 : 1; assert( !n->is_dummy() ); - return native_key_comparator()( v, q.val ); + return native_key_comparator()(v, q.val); } template @@ -878,13 +868,72 @@ namespace cds { namespace intrusive { { void operator()( value_type * v ) { - splitlist_node_type * p = static_cast( node_traits::to_node_ptr( v )); + splitlist_node_type * p = static_cast(native_node_traits::to_node_ptr( v )); if ( !p->is_dummy() ) - native_disposer()( v ); + native_disposer()(v); } }; public: + typedef node_type ordered_list_node_type; + typedef splitlist_node_type aux_node; + + struct node_traits: private native_node_traits + { + typedef native_node_traits base_class; ///< Base ordered list node type + typedef typename base_class::value_type value_type; ///< Value type + typedef typename base_class::node_type base_node_type; ///< Ordered list node type + typedef node node_type; ///< Split-list node type + + /// Convert value reference to node pointer + static node_type * to_node_ptr( value_type& v ) + { + return static_cast(base_class::to_node_ptr( v )); + } + + /// Convert value pointer to node pointer + static node_type * to_node_ptr( value_type * v ) + { + return static_cast(base_class::to_node_ptr( v )); + } + + /// Convert value reference to node pointer (const version) + static node_type const * to_node_ptr( value_type const& v ) + { + return static_cast(base_class::to_node_ptr( v )); + } + + /// Convert value pointer to node pointer (const version) + static node_type const * to_node_ptr( value_type const * v ) + { + return static_cast(base_class::to_node_ptr( v )); + } + + /// Convert node reference to value pointer + static value_type * to_value_ptr( node_type& n ) + { + return base_class::to_value_ptr( static_cast(n) ); + } + + /// Convert node pointer to value pointer + static value_type * to_value_ptr( node_type * n ) + { + return base_class::to_value_ptr( static_cast(n) ); + } + + /// Convert node reference to value pointer (const version) + static const value_type * to_value_ptr( node_type const & n ) + { + return base_class::to_value_ptr( static_cast(n) ); + } + + /// Convert node pointer to value pointer (const version) + static const value_type * to_value_ptr( node_type const * n ) + { + return base_class::to_value_ptr( static_cast(n) ); + } + }; + template struct make_compare_from_less: public cds::opt::details::make_comparator_from_less { @@ -893,39 +942,175 @@ namespace cds { namespace intrusive { template int operator()( value_type const& v, search_value_type const& q ) const { - splitlist_node_type const * n = static_cast( node_traits::to_node_ptr( v )); + splitlist_node_type const * n = static_cast(native_node_traits::to_node_ptr( v )); if ( n->m_nHash != q.nHash ) return n->m_nHash < q.nHash ? -1 : 1; assert( !n->is_dummy() ); - return base_class()( v, q.val ); + return base_class()(v, q.val); } template int operator()( search_value_type const& q, value_type const& v ) const { - splitlist_node_type const * n = static_cast( node_traits::to_node_ptr( v )); + splitlist_node_type const * n = static_cast(native_node_traits::to_node_ptr( v )); if ( n->m_nHash != q.nHash ) return q.nHash < n->m_nHash ? -1 : 1; assert( !n->is_dummy() ); - return base_class()( q.val, v ); + return base_class()(q.val, v); + } + + template + int operator()( Q1 const& v1, Q2 const& v2 ) const + { + return base_class()(v1, v2); + } + }; + + typedef typename native_ordered_list::template rebind_traits< + opt::compare< key_compare > + , opt::disposer< wrapped_disposer > + , opt::boundary_node_type< splitlist_node_type > + >::type result; + }; + + template + class ordered_list_adapter< OrderedList, Traits, true > + { + typedef OrderedList native_ordered_list; + typedef Traits traits; + + typedef typename native_ordered_list::gc gc; + typedef typename native_ordered_list::key_comparator native_key_comparator; + typedef typename native_ordered_list::value_type value_type; + typedef typename native_ordered_list::disposer native_disposer; + + struct key_compare { + int operator()( value_type const& v1, value_type const& v2 ) const + { + hash_node const& n1 = static_cast( v1 ); + hash_node const& n2 = static_cast( v2 ); + if ( n1.m_nHash != n2.m_nHash ) + return n1.m_nHash < n2.m_nHash ? -1 : 1; + + if ( n1.is_dummy() ) { + assert( n2.is_dummy() ); + return 0; + } + + assert( !n1.is_dummy() && !n2.is_dummy() ); + + return native_key_comparator()(v1, v2); + } + + template + int operator()( value_type const& v, search_value_type const& q ) const + { + hash_node const& n = static_cast( v ); + if ( n.m_nHash != q.nHash ) + return n.m_nHash < q.nHash ? -1 : 1; + + assert( !n.is_dummy() ); + return native_key_comparator()(v, q.val); + } + + template + int operator()( search_value_type const& q, value_type const& v ) const + { + return -operator()( v, q ); + } + }; + + struct wrapped_disposer + { + void operator()( value_type * v ) + { + hash_node* p = static_cast( v ); + if ( !p->is_dummy() ) + native_disposer()(v); + } + }; + + public: + typedef void ordered_list_node_type; + + struct aux_node: public native_ordered_list::node_type, public hash_node + { + aux_node() + { + typedef typename native_ordered_list::node_type list_node_type; + list_node_type::data.store( typename list_node_type::marked_data_ptr( static_cast( static_cast( this ))), atomics::memory_order_release ); + } + }; + + struct node_traits + { + static hash_node * to_node_ptr( value_type& v ) + { + return static_cast( &v ); + } + + static hash_node * to_node_ptr( value_type * v ) + { + return static_cast( v ); + } + + static hash_node const * to_node_ptr( value_type const& v ) + { + return static_cast( &v ); + } + + static hash_node const * to_node_ptr( value_type const * v ) + { + return static_cast( v ); + } + }; + + template + struct make_compare_from_less: public cds::opt::details::make_comparator_from_less + { + typedef cds::opt::details::make_comparator_from_less base_class; + + template + int operator()( value_type const& v, search_value_type const& q ) const + { + hash_node const& n = static_cast( v ); + if ( n.m_nHash != q.nHash ) + return n.m_nHash < q.nHash ? -1 : 1; + + assert( !n.is_dummy() ); + return base_class()(v, q.val); + } + + template + int operator()( search_value_type const& q, value_type const& v ) const + { + hash_node const& n = static_cast( v ); + if ( n.m_nHash != q.nHash ) + return q.nHash < n.m_nHash ? -1 : 1; + + assert( !n.is_dummy() ); + return base_class()(q.val, v); } template int operator()( Q1 const& v1, Q2 const& v2 ) const { - return base_class()( v1, v2 ); + return base_class()(v1, v2); } }; typedef typename native_ordered_list::template rebind_traits< opt::compare< key_compare > - ,opt::disposer< wrapped_disposer > - ,opt::boundary_node_type< splitlist_node_type > + , opt::disposer< wrapped_disposer > + , opt::boundary_node_type< aux_node > >::type result; }; + template + using rebind_list_traits = ordered_list_adapter< OrderedList, Traits, is_iterable_list::value >; + template struct select_list_iterator; diff --git a/cds/intrusive/impl/iterable_list.h b/cds/intrusive/impl/iterable_list.h index fc5fb14a..a3d6e1d0 100644 --- a/cds/intrusive/impl/iterable_list.h +++ b/cds/intrusive/impl/iterable_list.h @@ -490,7 +490,7 @@ namespace cds { namespace intrusive { */ std::pair upsert( value_type& val, bool bInsert = true ) { - return update_at( &m_Head, val, []( value_type&, value_type* ) {}, bInsert ); + return upsert_at( &m_Head, val, bInsert ); } /// Unlinks the item \p val from the list @@ -828,7 +828,7 @@ namespace cds { namespace intrusive { protected: //@cond -#if 0 + // split-list support bool insert_aux_node( node_type * pNode ) { @@ -839,13 +839,25 @@ namespace cds { namespace intrusive { bool insert_aux_node( node_type* pHead, node_type * pNode ) { assert( pNode != nullptr ); + assert( pNode->data.load( memory_model::memory_order_relaxed ) != nullptr ); + + insert_position pos; - // Hack: convert node_type to value_type. - // In principle, auxiliary node can be non-reducible to value_type - // We assume that comparator can correctly distinguish aux and regular node. - return insert_at( pHead, *node_traits::to_value_ptr( pNode ) ); + while ( true ) { + if ( inserting_search( pHead, *pNode->data.load(memory_model::memory_order_relaxed).ptr(), pos, key_comparator() ) ) { + m_Stat.onInsertFailed(); + return false; + } + + if ( link_aux_node( pNode, pos ) ) { + ++m_ItemCounter; + m_Stat.onInsertSuccess(); + return true; + } + + m_Stat.onInsertRetry(); + } } -#endif bool insert_at( node_type* pHead, value_type& val ) { @@ -936,6 +948,11 @@ namespace cds { namespace intrusive { } } + std::pair upsert_at( node_type* pHead, value_type& val, bool bInsert ) + { + return update_at( pHead, val, []( value_type&, value_type* ) {}, bInsert ); + } + bool unlink_at( node_type* pHead, value_type& val ) { position pos; @@ -1129,7 +1146,7 @@ namespace cds { namespace intrusive { { pos.pHead = pHead; node_type* pPrev = const_cast(pHead); - value_type* pPrevVal = nullptr; + value_type* pPrevVal = pPrev->data.load( memory_model::memory_order_relaxed ).ptr(); while ( true ) { node_type * pCur = pPrev->next.load( memory_model::memory_order_relaxed ); @@ -1166,6 +1183,25 @@ namespace cds { namespace intrusive { } } + // split-list support + template + void destroy( Predicate pred ) + { + node_type * pNode = m_Head.next.load( memory_model::memory_order_relaxed ); + while ( pNode != pNode->next.load( memory_model::memory_order_relaxed ) ) { + value_type * pVal = pNode->data.load( memory_model::memory_order_relaxed ).ptr(); + node_type * pNext = pNode->next.load( memory_model::memory_order_relaxed ); + bool const is_regular_node = !pVal || pred( pVal ); + if ( is_regular_node ) { + if ( pVal ) + retire_data( pVal ); + delete_node( pNode ); + } + pNode = pNext; + } + + m_Head.next.store( &m_Tail, memory_model::memory_order_relaxed ); + } //@endcond private: @@ -1279,6 +1315,51 @@ namespace cds { namespace intrusive { return false; } + // split-list support + bool link_aux_node( node_type * pNode, insert_position& pos ) + { + assert( pos.pPrev != nullptr ); + assert( pos.pCur != nullptr ); + + // We need pos.pCur data should be unchanged, otherwise ordering violation can be possible + // if current thread will be preempted and another thread will delete pos.pCur data + // and then set it to another. + // To prevent this we mark pos.pCur data as undeletable by setting LSB + marked_data_ptr valCur( pos.pFound ); + if ( !pos.pCur->data.compare_exchange_strong( valCur, valCur | 1, memory_model::memory_order_acquire, atomics::memory_order_relaxed ) ) { + // oops, pos.pCur data has been changed or another thread is setting pos.pPrev data + m_Stat.onNodeMarkFailed(); + return false; + } + + marked_data_ptr valPrev( pos.pPrevVal ); + if ( !pos.pPrev->data.compare_exchange_strong( valPrev, valPrev | 1, memory_model::memory_order_acquire, atomics::memory_order_relaxed ) ) { + pos.pCur->data.store( valCur, memory_model::memory_order_relaxed ); + m_Stat.onNodeMarkFailed(); + return false; + } + + // checks if link pPrev -> pCur is broken + if ( pos.pPrev->next.load( memory_model::memory_order_acquire ) != pos.pCur ) { + // sequence pPrev - pCur is broken + pos.pPrev->data.store( valPrev, memory_model::memory_order_relaxed ); + pos.pCur->data.store( valCur, memory_model::memory_order_relaxed ); + m_Stat.onNodeSeqBreak(); + return false; + } + + // insert new node between pos.pPrev and pos.pCur + pNode->next.store( pos.pCur, memory_model::memory_order_relaxed ); + + bool result = pos.pPrev->next.compare_exchange_strong( pos.pCur, pNode, memory_model::memory_order_release, atomics::memory_order_relaxed ); + + // Clears data marks + pos.pPrev->data.store( valPrev, memory_model::memory_order_relaxed ); + pos.pCur->data.store( valCur, memory_model::memory_order_relaxed ); + + return result; + } + static bool unlink_data( position& pos ) { assert( pos.pCur != nullptr ); diff --git a/cds/intrusive/impl/lazy_list.h b/cds/intrusive/impl/lazy_list.h index 13fa9b92..e438c10e 100644 --- a/cds/intrusive/impl/lazy_list.h +++ b/cds/intrusive/impl/lazy_list.h @@ -5,7 +5,7 @@ Source code repo: http://github.com/khizmax/libcds/ Download: http://sourceforge.net/projects/libcds/files/ - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -1208,6 +1208,13 @@ namespace cds { namespace intrusive { return guarded_ptr(); } + // split-list support + template + void destroy( Predicate /*pred*/ ) + { + clear(); + } + //@endcond protected: diff --git a/cds/intrusive/impl/michael_list.h b/cds/intrusive/impl/michael_list.h index 759b13cf..2656a4e8 100644 --- a/cds/intrusive/impl/michael_list.h +++ b/cds/intrusive/impl/michael_list.h @@ -1170,6 +1170,13 @@ namespace cds { namespace intrusive { return guarded_ptr(); } + // split-list support + template + void destroy( Predicate /*pred*/ ) + { + clear(); + } + //@endcond protected: diff --git a/cds/intrusive/michael_set.h b/cds/intrusive/michael_set.h index 532ac4de..acb9b505 100644 --- a/cds/intrusive/michael_set.h +++ b/cds/intrusive/michael_set.h @@ -318,7 +318,6 @@ namespace cds { namespace intrusive { Moreover, a crash is possible when you try to iterate the next element that has been deleted by concurrent thread. Use this iterator on the concurrent container for debugging purpose only. - for \p IterableList: iterator is thread-safe. You may use it freely in concurrent environment. - */ typedef michael_set::details::iterator< internal_bucket_type, false > iterator; diff --git a/cds/intrusive/split_list.h b/cds/intrusive/split_list.h index de983fae..86fc2fec 100644 --- a/cds/intrusive/split_list.h +++ b/cds/intrusive/split_list.h @@ -97,8 +97,6 @@ namespace cds { namespace intrusive { - \p Traits - split-list traits, default is \p split_list::traits. Instead of defining \p Traits struct you can use option-based syntax provided by \p split_list::make_traits metafunction. - @warning \p IterableList is not supported as \p OrderedList template parameter. - There are several specialization of the split-list class for different \p GC: - for \ref cds_urcu_gc "RCU type" include - see \ref cds_intrusive_SplitListSet_rcu "RCU-based split-list" @@ -135,6 +133,37 @@ namespace cds { namespace intrusive { How to use + Split-list based on \p IterableList differs from split-list based on \p MichaelList or \p LazyList + because \p %IterableList stores data "as is" - it cannot use any hook. + + Suppose, your split-list contains values of type \p Foo. + For \p %MichaelList and \p %LazyList, \p Foo declaration should be based on ordered-list node: + - \p %MichaelList: + \code + struct Foo: public cds::intrusive::split_list::node< cds::intrusive::michael_list::node< cds::gc::HP > > + { + // ... field declarations + }; + \endcode + - \p %LazyList: + \code + struct Foo: public cds::intrusive::split_list::node< cds::intrusive::lazy_list::node< cds::gc::HP > > + { + // ... field declarations + }; + \endcode + + For \p %IterableList, \p Foo should be based on \p void: + \code + struct Foo: public cds::intrusive::split_list::node + { + // ... field declarations + }; + \endcode + + Everything else is the same. + Consider split-list based on \p MichaelList. + First, you should choose ordered list type to use in your split-list set: \code // For gc::HP-based MichaelList implementation @@ -228,25 +257,25 @@ namespace cds { namespace intrusive { protected: //@cond - typedef split_list::details::rebind_list_traits wrapped_ordered_list; + typedef split_list::details::rebind_list_traits ordered_list_adapter; //@endcond public: # ifdef CDS_DOXYGEN_INVOKED typedef OrderedList ordered_list; ///< type of ordered list used as a base for split-list # else - typedef typename wrapped_ordered_list::result ordered_list; + typedef typename ordered_list_adapter::result ordered_list; # endif typedef typename ordered_list::value_type value_type; ///< type of value stored in the split-list typedef typename ordered_list::key_comparator key_comparator; ///< key comparison functor typedef typename ordered_list::disposer disposer; ///< Node disposer functor - /// Hash functor for \p %value_type and all its derivatives that you use + /// Hash functor for \p %value_type and all its derivatives you use typedef typename cds::opt::v::hash_selector< typename traits::hash >::type hash; typedef typename traits::item_counter item_counter; ///< Item counter type typedef typename traits::back_off back_off; ///< back-off strategy for spinning - typedef typename traits::memory_model memory_model; ///< Memory ordering. See cds::opt::memory_model option + typedef typename traits::memory_model memory_model; ///< Memory ordering. See \p cds::opt::memory_model option typedef typename traits::stat stat; ///< Internal statistics, see \p spit_list::stat typedef typename ordered_list::guarded_ptr guarded_ptr; ///< Guarded pointer @@ -255,21 +284,14 @@ namespace cds { namespace intrusive { protected: //@cond - typedef typename ordered_list::node_type list_node_type; ///< Node type as declared in ordered list - typedef split_list::node node_type; ///< split-list node type - - /// Split-list node traits - /** - This traits is intended for converting between underlying ordered list node type \p list_node_type - and split-list node type \p node_type - */ - typedef split_list::node_traits node_traits; + typedef split_list::node node_type; ///< split-list node type + typedef typename ordered_list_adapter::node_traits node_traits; /// Bucket table implementation typedef typename split_list::details::bucket_table_selector< traits::dynamic_bucket_table , gc - , node_type + , typename ordered_list_adapter::aux_node , opt::allocator< typename traits::allocator > , opt::memory_model< memory_model > , opt::free_list< typename traits::free_list > @@ -284,7 +306,7 @@ namespace cds { namespace intrusive { class ordered_list_wrapper: public ordered_list { typedef ordered_list base_class; - typedef typename base_class::auxiliary_head bucket_head_type; + typedef typename base_class::auxiliary_head bucket_head_type; public: bool insert_at( aux_node_type* pHead, value_type& val ) @@ -310,6 +332,18 @@ namespace cds { namespace intrusive { return base_class::update_at( h, val, func, bAllowInsert ); } + template + typename std::enable_if< + std::is_same< Q, value_type>::value && is_iterable_list< ordered_list >::value, + std::pair + >::type + upsert_at( aux_node_type * pHead, Q& val, bool bAllowInsert ) + { + assert( pHead != nullptr ); + bucket_head_type h( pHead ); + return base_class::upsert_at( h, val, bAllowInsert ); + } + bool unlink_at( aux_node_type * pHead, value_type& val ) { assert( pHead != nullptr ); @@ -357,6 +391,18 @@ namespace cds { namespace intrusive { return base_class::find_at( h, val, cmp ); } + template + typename std::enable_if< + std::is_same::value && is_iterable_list< ordered_list >::value, + typename base_class::iterator + >::type + find_iterator_at( aux_node_type * pHead, split_list::details::search_value_type const& val, Compare cmp ) + { + assert( pHead != nullptr ); + bucket_head_type h( pHead ); + return base_class::find_iterator_at( h, val, cmp ); + } + template guarded_ptr get_at( aux_node_type * pHead, split_list::details::search_value_type const& val, Compare cmp ) { @@ -374,9 +420,109 @@ namespace cds { namespace intrusive { bucket_head_type h(pHead); return base_class::insert_aux_node( h, pNode ); } + + template + void destroy( Predicate pred ) + { + base_class::destroy( pred ); + } + }; + //@endcond + + protected: + //@cond + template + class iterator_type + :public split_list::details::iterator_type + { + typedef split_list::details::iterator_type iterator_base_class; + typedef typename iterator_base_class::list_iterator list_iterator; + public: + iterator_type() + : iterator_base_class() + {} + + iterator_type( iterator_type const& src ) + : iterator_base_class( src ) + {} + + // This ctor should be protected... + iterator_type( list_iterator itCur, list_iterator itEnd ) + : iterator_base_class( itCur, itEnd ) + {} }; //@endcond + public: + ///@name Forward iterators + //@{ + /// Forward iterator + /** + The forward iterator is based on \p OrderedList forward iterator and has some features: + - it has no post-increment operator + - it iterates items in unordered fashion + - iterator cannot be moved across thread boundary because it may contain GC's guard that is thread-private GC data. + + Iterator thread safety depends on type of \p OrderedList: + - for \p MichaelList and \p LazyList: iterator guarantees safety even if you delete the item that iterator points to + because that item is guarded by hazard pointer. + However, in case of concurrent deleting operations it is no guarantee that you iterate all item in the set. + Moreover, a crash is possible when you try to iterate the next element that has been deleted by concurrent thread. + Use this iterator on the concurrent container for debugging purpose only. + - for \p IterableList: iterator is thread-safe. You may use it freely in concurrent environment. + */ + typedef iterator_type iterator; + + /// Const forward iterator + /** + For iterator's features and requirements see \ref iterator + */ + typedef iterator_type const_iterator; + + /// Returns a forward iterator addressing the first element in a split-list + /** + For empty list \code begin() == end() \endcode + */ + iterator begin() + { + return iterator( m_List.begin(), m_List.end()); + } + + /// Returns an iterator that addresses the location succeeding the last element in a split-list + /** + Do not use the value returned by end function to access any item. + + The returned value can be used only to control reaching the end of the split-list. + For empty list \code begin() == end() \endcode + */ + iterator end() + { + return iterator( m_List.end(), m_List.end()); + } + + /// Returns a forward const iterator addressing the first element in a split-list + const_iterator begin() const + { + return cbegin(); + } + /// Returns a forward const iterator addressing the first element in a split-list + const_iterator cbegin() const + { + return const_iterator( m_List.cbegin(), m_List.cend()); + } + + /// Returns an const iterator that addresses the location succeeding the last element in a split-list + const_iterator end() const + { + return cend(); + } + /// Returns an const iterator that addresses the location succeeding the last element in a split-list + const_iterator cend() const + { + return const_iterator( m_List.cend(), m_List.cend()); + } + //@} + public: /// Initialize split-ordered list of default capacity /** @@ -408,7 +554,11 @@ namespace cds { namespace intrusive { { // list contains aux node that cannot be retired // all aux nodes will be destroyed by bucket table dtor - m_List.clear(); + m_List.destroy( + []( node_type * pNode ) -> bool { + return !pNode->is_dummy(); + } + ); gc::force_dispose(); } @@ -482,26 +632,38 @@ namespace cds { namespace intrusive { If the item \p val is not found in the set, then \p val is inserted iff \p bAllowInsert is \p true. Otherwise, the functor \p func is called with item found. - The functor signature is: - \code - void func( bool bNew, value_type& item, value_type& val ); - \endcode - with arguments: - - \p bNew - \p true if the item has been inserted, \p false otherwise - - \p item - item of the set - - \p val - argument \p val passed into the \p update() function - If new item has been inserted (i.e. \p bNew is \p true) then \p item and \p val arguments - refers to the same thing. - The functor may change non-key fields of the \p item. + The functor signature depends of the type of \p OrderedList: + + for \p MichaelList, \p LazyList + \code + struct functor { + void operator()( bool bNew, value_type& item, value_type& val ); + }; + \endcode + with arguments: + - \p bNew - \p true if the item has been inserted, \p false otherwise + - \p item - item of the set + - \p val - argument \p val passed into the \p %update() function + If new item has been inserted (i.e. \p bNew is \p true) then \p item and \p val arguments + refers to the same thing. + + The functor may change non-key fields of the \p item. + @warning For \ref cds_intrusive_MichaelList_hp "MichaelList" as the bucket see \ref cds_intrusive_item_creating "insert item troubleshooting". + \ref cds_intrusive_LazyList_hp "LazyList" provides exclusive access to inserted item and does not require any node-level + synchronization. + + for \p IterableList + \code + void func( value_type& val, value_type * old ); + \endcode + where + - \p val - argument \p val passed into the \p %update() function + - \p old - old value that will be retired. If new item has been inserted then \p old is \p nullptr. Returns std::pair where \p first is \p true if operation is successful, \p second is \p true if new item has been added or \p false if the item with \p val already is in the list. - - @warning For \ref cds_intrusive_MichaelList_hp "MichaelList" as the bucket see \ref cds_intrusive_item_creating "insert item troubleshooting". - \ref cds_intrusive_LazyList_hp "LazyList" provides exclusive access to inserted item and does not require any node-level - synchronization. */ template std::pair update( value_type& val, Func func, bool bAllowInsert = true ) @@ -530,6 +692,45 @@ namespace cds { namespace intrusive { } //@endcond + /// Inserts or updates the node (only for \p IterableList) + /** + The operation performs inserting or changing data with lock-free manner. + + If the item \p val is not found in the set, then \p val is inserted iff \p bAllowInsert is \p true. + Otherwise, the current element is changed to \p val, the old element will be retired later + by call \p Traits::disposer. + + Returns std::pair where \p first is \p true if operation is successful, + \p second is \p true if \p val has been added or \p false if the item with that key + already in the set. + */ +#ifdef CDS_DOXYGEN_INVOKED + std::pair upsert( value_type& val, bool bAllowInsert = true ) +#else + template + typename std::enable_if< + std::is_same< Q, value_type>::value && is_iterable_list< ordered_list >::value, + std::pair + >::type + upsert( Q& val, bool bAllowInsert = true ) +#endif + { + size_t nHash = hash_value( val ); + aux_node_type * pHead = get_bucket( nHash ); + assert( pHead != nullptr ); + + node_traits::to_node_ptr( val )->m_nHash = split_list::regular_hash( nHash ); + + std::pair bRet = m_List.upsert_at( pHead, val, bAllowInsert ); + if ( bRet.first && bRet.second ) { + inc_item_count(); + m_Stat.onUpdateNew(); + } + else + m_Stat.onUpdateExist(); + return bRet; + } + /// Unlinks the item \p val from the set /** The function searches the item \p val in the set and unlinks it from the set @@ -588,7 +789,7 @@ namespace cds { namespace intrusive { bool erase_with( const Q& key, Less pred ) { CDS_UNUSED( pred ); - return erase_( key, typename wrapped_ordered_list::template make_compare_from_less()); + return erase_( key, typename ordered_list_adapter::template make_compare_from_less()); } /// Deletes the item from the set @@ -626,7 +827,7 @@ namespace cds { namespace intrusive { bool erase_with( Q const& key, Less pred, Func f ) { CDS_UNUSED( pred ); - return erase_( key, typename wrapped_ordered_list::template make_compare_from_less(), f ); + return erase_( key, typename ordered_list_adapter::template make_compare_from_less(), f ); } /// Extracts the item with specified \p key @@ -711,6 +912,32 @@ namespace cds { namespace intrusive { } //@endcond + /// Finds \p key and returns iterator pointed to the item found (only for \p IterableList) + /** + If \p key is not found the function returns \p end(). + + @note This function is supported only for the set based on \p IterableList + */ + template +#ifdef CDS_DOXYGEN_INVOKED + iterator +#else + typename std::enable_if< std::is_same::value && is_iterable_list< ordered_list >::value, iterator >::type +#endif + find( Q& key ) + { + return find_iterator_( key, key_comparator() ); + } + //@cond + template + typename std::enable_if< std::is_same::value && is_iterable_list< ordered_list >::value, iterator >::type + find( Q const& key ) + { + return find_iterator_( key, key_comparator() ); + } + //@endcond + + /// Finds the key \p key with \p pred predicate for comparing /** The function is an analog of \ref cds_intrusive_SplitListSet_hp_find_func "find(Q&, Func)" @@ -722,17 +949,49 @@ namespace cds { namespace intrusive { bool find_with( Q& key, Less pred, Func f ) { CDS_UNUSED( pred ); - return find_( key, typename wrapped_ordered_list::template make_compare_from_less(), f ); + return find_( key, typename ordered_list_adapter::template make_compare_from_less(), f ); } //@cond template bool find_with( Q const& key, Less pred, Func f ) { CDS_UNUSED( pred ); - return find_( key, typename wrapped_ordered_list::template make_compare_from_less(), f ); + return find_( key, typename ordered_list_adapter::template make_compare_from_less(), f ); + } + //@endcond + + /// Finds \p key using \p pred predicate and returns iterator pointed to the item found (only for \p IterableList) + /** + The function is an analog of \p find(Q&) but \p pred is used for key comparing. + \p Less functor has the interface like \p std::less. + \p pred must imply the same element order as the comparator used for building the set. + + If \p key is not found the function returns \p end(). + + @note This function is supported only for the set based on \p IterableList + */ + template +#ifdef CDS_DOXYGEN_INVOKED + iterator +#else + typename std::enable_if< std::is_same::value && is_iterable_list< ordered_list >::value, iterator >::type +#endif + find_with( Q& key, Less pred ) + { + CDS_UNUSED( pred ); + return find_iterator_( key, typename ordered_list_adapter::template make_compare_from_less() ); + } + //@cond + template + typename std::enable_if< std::is_same::value && is_iterable_list< ordered_list >::value, iterator >::type + find_with( Q const& key, Less pred ) + { + CDS_UNUSED( pred ); + return find_iterator_( key, typename ordered_list_adapter::template make_compare_from_less() ); } //@endcond + /// Checks whether the set contains \p key /** The function searches the item with key equal to \p key @@ -747,14 +1006,6 @@ namespace cds { namespace intrusive { { return find_( key, key_comparator()); } - //@cond - template - CDS_DEPRECATED("deprecated, use contains()") - bool find( Q const& key ) - { - return contains( key ); - } - //@endcond /// Checks whether the set contains \p key using \p pred predicate for searching /** @@ -766,16 +1017,8 @@ namespace cds { namespace intrusive { bool contains( Q const& key, Less pred ) { CDS_UNUSED( pred ); - return find_( key, typename wrapped_ordered_list::template make_compare_from_less()); + return find_( key, typename ordered_list_adapter::template make_compare_from_less()); } - //@cond - template - CDS_DEPRECATED("deprecated, use contains()") - bool find_with( Q const& key, Less pred ) - { - return contains( key, pred ); - } - //@endcond /// Finds the key \p key and return the item found /** \anchor cds_intrusive_SplitListSet_hp_get @@ -867,96 +1110,12 @@ namespace cds { namespace intrusive { return m_Stat; } - protected: - //@cond - template - class iterator_type - :public split_list::details::iterator_type - { - typedef split_list::details::iterator_type iterator_base_class; - typedef typename iterator_base_class::list_iterator list_iterator; - public: - iterator_type() - : iterator_base_class() - {} - - iterator_type( iterator_type const& src ) - : iterator_base_class( src ) - {} - - // This ctor should be protected... - iterator_type( list_iterator itCur, list_iterator itEnd ) - : iterator_base_class( itCur, itEnd ) - {} - }; - //@endcond - public: - ///@name Forward iterators (only for debugging purpose) - //@{ - /// Forward iterator - /** - The forward iterator for a split-list has some features: - - it has no post-increment operator - - it depends on iterator of underlying \p OrderedList - - The iterator cannot be moved across thread boundary since it may contain GC's guard that is thread-private GC data. - - Iterator ensures thread-safety even if you delete the item that iterator points to. However, in case of concurrent - deleting operations it is no guarantee that you iterate all item in the set. - Moreover, a crash is possible when you try to iterate the next element that has been deleted by concurrent thread. - - @warning Use this iterator on the concurrent container for debugging purpose only. - */ - typedef iterator_type iterator; - - /// Const forward iterator - /** - For iterator's features and requirements see \ref iterator - */ - typedef iterator_type const_iterator; - - /// Returns a forward iterator addressing the first element in a split-list - /** - For empty list \code begin() == end() \endcode - */ - iterator begin() - { - return iterator( m_List.begin(), m_List.end()); - } - - /// Returns an iterator that addresses the location succeeding the last element in a split-list - /** - Do not use the value returned by end function to access any item. - - The returned value can be used only to control reaching the end of the split-list. - For empty list \code begin() == end() \endcode - */ - iterator end() - { - return iterator( m_List.end(), m_List.end()); - } - - /// Returns a forward const iterator addressing the first element in a split-list - const_iterator begin() const - { - return cbegin(); - } - /// Returns a forward const iterator addressing the first element in a split-list - const_iterator cbegin() const + /// Returns internal statistics for \p OrderedList + typename OrderedList::stat const& list_statistics() const { - return const_iterator( m_List.cbegin(), m_List.cend()); + return m_List.statistics(); } - /// Returns an const iterator that addresses the location succeeding the last element in a split-list - const_iterator end() const - { - return cend(); - } - /// Returns an const iterator that addresses the location succeeding the last element in a split-list - const_iterator cend() const - { - return const_iterator( m_List.cend(), m_List.cend()); - } - //@} - protected: //@cond aux_node_type * alloc_aux_node( size_t nHash ) @@ -1126,6 +1285,17 @@ namespace cds { namespace intrusive { return m_Stat.onFind( m_List.find_at( pHead, sv, cmp ) ); } + template + iterator find_iterator_( Q const& val, Compare cmp ) + { + size_t nHash = hash_value( val ); + split_list::details::search_value_type sv( val, split_list::regular_hash( nHash ) ); + aux_node_type * pHead = get_bucket( nHash ); + assert( pHead != nullptr ); + + return iterator( m_List.find_iterator_at( pHead, sv, cmp ), m_List.end()); + } + template guarded_ptr get_( Q const& val, Compare cmp ) { @@ -1148,7 +1318,7 @@ namespace cds { namespace intrusive { template guarded_ptr get_with_( Q const& key, Less ) { - return get_( key, typename wrapped_ordered_list::template make_compare_from_less() ); + return get_( key, typename ordered_list_adapter::template make_compare_from_less() ); } template @@ -1212,7 +1382,7 @@ namespace cds { namespace intrusive { template guarded_ptr extract_with_( Q const& key, Less ) { - return extract_( key, typename wrapped_ordered_list::template make_compare_from_less() ); + return extract_( key, typename ordered_list_adapter::template make_compare_from_less() ); } //@endcond diff --git a/cds/intrusive/split_list_nogc.h b/cds/intrusive/split_list_nogc.h index 0576c98a..8e2c44d7 100644 --- a/cds/intrusive/split_list_nogc.h +++ b/cds/intrusive/split_list_nogc.h @@ -70,14 +70,14 @@ namespace cds { namespace intrusive { protected: //@cond - typedef split_list::details::rebind_list_traits wrapped_ordered_list; + typedef split_list::details::rebind_list_traits ordered_list_adapter; //@endcond public: # ifdef CDS_DOXYGEN_INVOKED typedef OrderedList ordered_list; ///< type of ordered list used as base for split-list # else - typedef typename wrapped_ordered_list::result ordered_list; + typedef typename ordered_list_adapter::result ordered_list; # endif typedef typename ordered_list::value_type value_type; ///< type of value stored in the split-list typedef typename ordered_list::key_comparator key_comparator; ///< key comparison functor @@ -105,13 +105,13 @@ namespace cds { namespace intrusive { This traits is intended for converting between underlying ordered list node type \ref list_node_type and split-list node type \ref node_type */ - typedef split_list::node_traits node_traits; + typedef typename ordered_list_adapter::node_traits node_traits; /// Bucket table implementation typedef typename split_list::details::bucket_table_selector< traits::dynamic_bucket_table , gc - , node_type + , typename ordered_list_adapter::aux_node , opt::allocator< typename traits::allocator > , opt::memory_model< memory_model > , opt::free_list< typename traits::free_list > @@ -361,14 +361,14 @@ namespace cds { namespace intrusive { bool find_with( Q& key, Less pred, Func f ) { CDS_UNUSED( pred ); - return find_( key, typename wrapped_ordered_list::template make_compare_from_less(), f ); + return find_( key, typename ordered_list_adapter::template make_compare_from_less(), f ); } //@cond template bool find_with( Q const& key, Less pred, Func f ) { CDS_UNUSED( pred ); - return find_( key, typename wrapped_ordered_list::template make_compare_from_less(), f ); + return find_( key, typename ordered_list_adapter::template make_compare_from_less(), f ); } //@endcond @@ -549,7 +549,7 @@ namespace cds { namespace intrusive { aux_node_type * pHead = get_bucket( nHash ); assert( pHead != nullptr ); - auto it = m_List.find_at_( pHead, sv, typename wrapped_ordered_list::template make_compare_from_less() ); + auto it = m_List.find_at_( pHead, sv, typename ordered_list_adapter::template make_compare_from_less() ); m_Stat.onFind( it != m_List.end() ); return iterator( it, m_List.end() ); } diff --git a/cds/intrusive/split_list_rcu.h b/cds/intrusive/split_list_rcu.h index d464c062..f91ffa04 100644 --- a/cds/intrusive/split_list_rcu.h +++ b/cds/intrusive/split_list_rcu.h @@ -101,14 +101,14 @@ namespace cds { namespace intrusive { protected: //@cond - typedef split_list::details::rebind_list_traits wrapped_ordered_list; + typedef split_list::details::rebind_list_traits ordered_list_adapter; //@endcond public: # ifdef CDS_DOXYGEN_INVOKED typedef OrderedList ordered_list; ///< type of ordered list used as base for split-list # else - typedef typename wrapped_ordered_list::result ordered_list; + typedef typename ordered_list_adapter::result ordered_list; # endif typedef typename ordered_list::value_type value_type; ///< type of value stored in the split-list typedef typename ordered_list::key_comparator key_comparator; ///< key compare functor @@ -141,13 +141,13 @@ namespace cds { namespace intrusive { This traits is intended for converting between underlying ordered list node type \ref list_node_type and split-list node type \ref node_type */ - typedef split_list::node_traits node_traits; + typedef typename ordered_list_adapter::node_traits node_traits; /// Bucket table implementation typedef typename split_list::details::bucket_table_selector< traits::dynamic_bucket_table , gc - , node_type + , typename ordered_list_adapter::aux_node , opt::allocator< typename traits::allocator > , opt::memory_model< memory_model > , opt::free_list< typename traits::free_list > @@ -491,7 +491,7 @@ namespace cds { namespace intrusive { bool erase_with( Q const& key, Less pred ) { CDS_UNUSED( pred ); - return erase_( key, typename wrapped_ordered_list::template make_compare_from_less() ); + return erase_( key, typename ordered_list_adapter::template make_compare_from_less() ); } /// Deletes the item from the set @@ -531,7 +531,7 @@ namespace cds { namespace intrusive { bool erase_with( Q const& key, Less pred, Func f ) { CDS_UNUSED( pred ); - return erase_( key, typename wrapped_ordered_list::template make_compare_from_less(), f ); + return erase_( key, typename ordered_list_adapter::template make_compare_from_less(), f ); } /// Extracts an item from the set @@ -639,14 +639,14 @@ namespace cds { namespace intrusive { bool find_with( Q& key, Less pred, Func f ) { CDS_UNUSED( pred ); - return find_( key, typename wrapped_ordered_list::template make_compare_from_less(), f ); + return find_( key, typename ordered_list_adapter::template make_compare_from_less(), f ); } //@cond template bool find_with( Q const& key, Less pred, Func f ) { CDS_UNUSED( pred ); - return find_( key, typename wrapped_ordered_list::template make_compare_from_less(), f ); + return find_( key, typename ordered_list_adapter::template make_compare_from_less(), f ); } //@endcond @@ -683,7 +683,7 @@ namespace cds { namespace intrusive { bool contains( Q const& key, Less pred ) { CDS_UNUSED( pred ); - return find_value( key, typename wrapped_ordered_list::template make_compare_from_less() ); + return find_value( key, typename ordered_list_adapter::template make_compare_from_less() ); } //@cond template @@ -741,7 +741,7 @@ namespace cds { namespace intrusive { raw_ptr get_with( Q const& key, Less pred ) { CDS_UNUSED( pred ); - return get_( key, typename wrapped_ordered_list::template make_compare_from_less()); + return get_( key, typename ordered_list_adapter::template make_compare_from_less()); } @@ -1061,7 +1061,7 @@ namespace cds { namespace intrusive { value_type * extract_with_( Q const& val, Less pred ) { CDS_UNUSED( pred ); - return extract_( val, typename wrapped_ordered_list::template make_compare_from_less()); + return extract_( val, typename ordered_list_adapter::template make_compare_from_less()); } template diff --git a/projects/Win/vc14/gtest-intrusive-set.vcxproj b/projects/Win/vc14/gtest-intrusive-set.vcxproj index 912c132c..3acac78e 100644 --- a/projects/Win/vc14/gtest-intrusive-set.vcxproj +++ b/projects/Win/vc14/gtest-intrusive-set.vcxproj @@ -29,6 +29,8 @@ + + @@ -210,6 +212,8 @@ + + diff --git a/projects/Win/vc14/gtest-intrusive-set.vcxproj.filters b/projects/Win/vc14/gtest-intrusive-set.vcxproj.filters index 72708967..fee9f339 100644 --- a/projects/Win/vc14/gtest-intrusive-set.vcxproj.filters +++ b/projects/Win/vc14/gtest-intrusive-set.vcxproj.filters @@ -173,6 +173,12 @@ Source Files\MichaelSet + + Source Files\SplitList + + + Source Files\SplitList + @@ -220,5 +226,11 @@ Header Files + + Header Files + + + Header Files + \ No newline at end of file diff --git a/test/unit/intrusive-set/CMakeLists.txt b/test/unit/intrusive-set/CMakeLists.txt index f8c0094c..6f038379 100644 --- a/test/unit/intrusive-set/CMakeLists.txt +++ b/test/unit/intrusive-set/CMakeLists.txt @@ -37,6 +37,8 @@ set(CDSGTEST_SET_SOURCES intrusive_skiplist_rcu_gpt.cpp intrusive_skiplist_rcu_shb.cpp intrusive_skiplist_rcu_sht.cpp + intrusive_split_iterable_hp.cpp + intrusive_split_iterable_dhp.cpp intrusive_split_lazy_hp.cpp intrusive_split_lazy_dhp.cpp intrusive_split_lazy_nogc.cpp diff --git a/test/unit/intrusive-set/intrusive_split_iterable_dhp.cpp b/test/unit/intrusive-set/intrusive_split_iterable_dhp.cpp new file mode 100644 index 00000000..fe017628 --- /dev/null +++ b/test/unit/intrusive-set/intrusive_split_iterable_dhp.cpp @@ -0,0 +1,243 @@ +/* + This file is a part of libcds - Concurrent Data Structures library + + (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2016 + + Source code repo: http://github.com/khizmax/libcds/ + Download: http://sourceforge.net/projects/libcds/files/ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "test_intrusive_split_iterable_set_hp.h" + +#include +#include +#include + +namespace { + namespace ci = cds::intrusive; + typedef cds::gc::DHP gc_type; + + class IntrusiveSplitListIterableSet_DHP : public cds_test::intrusive_split_iterable_set_hp + { + protected: + typedef cds_test::intrusive_split_iterable_set_hp base_class; + typedef base_class::item_type< ci::split_list::node> item_type; + + protected: + void SetUp() + { + struct list_traits : public ci::iterable_list::traits + {}; + typedef ci::IterableList< gc_type, item_type, list_traits > list_type; + typedef ci::SplitListSet< gc_type, list_type > set_type; + + cds::gc::dhp::GarbageCollector::Construct( 16, set_type::c_nHazardPtrCount ); + cds::threading::Manager::attachThread(); + } + + void TearDown() + { + cds::threading::Manager::detachThread(); + cds::gc::hp::GarbageCollector::Destruct(); + } + }; + + + TEST_F( IntrusiveSplitListIterableSet_DHP, cmp ) + { + typedef ci::IterableList< gc_type + , item_type + ,ci::iterable_list::make_traits< + ci::opt::compare< cmp > + ,ci::opt::disposer< mock_disposer > + >::type + > bucket_type; + + typedef ci::SplitListSet< gc_type, bucket_type, + ci::split_list::make_traits< + ci::opt::hash< hash_int > + >::type + > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_DHP, less ) + { + typedef ci::IterableList< gc_type + , item_type + ,ci::iterable_list::make_traits< + ci::opt::less< less > + ,ci::opt::disposer< mock_disposer > + >::type + > bucket_type; + + typedef ci::SplitListSet< gc_type, bucket_type, + ci::split_list::make_traits< + ci::opt::hash< hash_int > + >::type + > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_DHP, cmpmix ) + { + struct list_traits : public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits : public ci::split_list::traits + { + typedef hash_int hash; + typedef simple_item_counter item_counter; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_DHP, free_list ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + typedef ci::FreeList free_list; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_DHP, static_bucket_table ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + enum { + dynamic_bucket_table = false + }; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_DHP, static_bucket_table_free_list ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + enum { + dynamic_bucket_table = false + }; + typedef ci::FreeList free_list; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_DHP, list_stat ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + typedef ci::iterable_list::stat<> stat; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + typedef simple_item_counter item_counter; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + EXPECT_GE( s.list_statistics().m_nInsertSuccess, 0u ); + } + + TEST_F( IntrusiveSplitListIterableSet_DHP, stat ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + typedef simple_item_counter item_counter; + typedef ci::split_list::stat<> stat; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + EXPECT_GE( s.statistics().m_nInsertSuccess, 0u ); + } + + +} // namespace diff --git a/test/unit/intrusive-set/intrusive_split_iterable_hp.cpp b/test/unit/intrusive-set/intrusive_split_iterable_hp.cpp new file mode 100644 index 00000000..b99e811c --- /dev/null +++ b/test/unit/intrusive-set/intrusive_split_iterable_hp.cpp @@ -0,0 +1,244 @@ +/* + This file is a part of libcds - Concurrent Data Structures library + + (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2016 + + Source code repo: http://github.com/khizmax/libcds/ + Download: http://sourceforge.net/projects/libcds/files/ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "test_intrusive_split_iterable_set_hp.h" + +#include +#include +#include + +namespace { + namespace ci = cds::intrusive; + typedef cds::gc::HP gc_type; + + class IntrusiveSplitListIterableSet_HP : public cds_test::intrusive_split_iterable_set_hp + { + protected: + typedef cds_test::intrusive_split_iterable_set_hp base_class; + typedef base_class::item_type< ci::split_list::node> item_type; + + protected: + void SetUp() + { + struct list_traits : public ci::iterable_list::traits + {}; + typedef ci::IterableList< gc_type, item_type, list_traits > list_type; + typedef ci::SplitListSet< gc_type, list_type > set_type; + + // +3 - for iterators + cds::gc::hp::GarbageCollector::Construct( set_type::c_nHazardPtrCount + 3, 1, 16 ); + cds::threading::Manager::attachThread(); + } + + void TearDown() + { + cds::threading::Manager::detachThread(); + cds::gc::hp::GarbageCollector::Destruct( true ); + } + }; + + + TEST_F( IntrusiveSplitListIterableSet_HP, cmp ) + { + typedef ci::IterableList< gc_type + , item_type + ,ci::iterable_list::make_traits< + ci::opt::compare< cmp > + ,ci::opt::disposer< mock_disposer > + >::type + > bucket_type; + + typedef ci::SplitListSet< gc_type, bucket_type, + ci::split_list::make_traits< + ci::opt::hash< hash_int > + >::type + > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_HP, less ) + { + typedef ci::IterableList< gc_type + , item_type + ,ci::iterable_list::make_traits< + ci::opt::less< less > + ,ci::opt::disposer< mock_disposer > + >::type + > bucket_type; + + typedef ci::SplitListSet< gc_type, bucket_type, + ci::split_list::make_traits< + ci::opt::hash< hash_int > + >::type + > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_HP, cmpmix ) + { + struct list_traits : public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits : public ci::split_list::traits + { + typedef hash_int hash; + typedef simple_item_counter item_counter; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_HP, free_list ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + typedef ci::FreeList free_list; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_HP, static_bucket_table ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + enum { + dynamic_bucket_table = false + }; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_HP, static_bucket_table_free_list ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + enum { + dynamic_bucket_table = false + }; + typedef ci::FreeList free_list; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + } + + TEST_F( IntrusiveSplitListIterableSet_HP, list_stat ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + typedef ci::iterable_list::stat<> stat; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + typedef simple_item_counter item_counter; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + EXPECT_GE( s.list_statistics().m_nInsertSuccess, 0u ); + } + + TEST_F( IntrusiveSplitListIterableSet_HP, stat ) + { + struct list_traits: public ci::iterable_list::traits + { + typedef base_class::less less; + typedef cmp compare; + typedef mock_disposer disposer; + }; + typedef ci::IterableList< gc_type, item_type, list_traits > bucket_type; + + struct set_traits: public ci::split_list::traits + { + typedef hash_int hash; + typedef simple_item_counter item_counter; + typedef ci::split_list::stat<> stat; + }; + typedef ci::SplitListSet< gc_type, bucket_type, set_traits > set_type; + + set_type s( kSize, 2 ); + test( s ); + EXPECT_GE( s.statistics().m_nInsertSuccess, 0u ); + } + + +} // namespace diff --git a/test/unit/intrusive-set/test_intrusive_split_iterable_set.h b/test/unit/intrusive-set/test_intrusive_split_iterable_set.h new file mode 100644 index 00000000..28e03efc --- /dev/null +++ b/test/unit/intrusive-set/test_intrusive_split_iterable_set.h @@ -0,0 +1,418 @@ +/* + This file is a part of libcds - Concurrent Data Structures library + + (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2016 + + Source code repo: http://github.com/khizmax/libcds/ + Download: http://sourceforge.net/projects/libcds/files/ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef CDSUNIT_SET_TEST_INTRUSIVE_SPLIT_ITERABLE_SET_H +#define CDSUNIT_SET_TEST_INTRUSIVE_SPLIT_ITERABLE_SET_H + +#include +#include + +#include +#include // ref + +// forward declaration +namespace cds { namespace intrusive {}} + +namespace cds_test { + + namespace ci = cds::intrusive; + namespace co = cds::opt; + + class intrusive_split_iterable_set: public fixture + { + public: + static size_t const kSize = 100; + + struct stat + { + unsigned int nDisposeCount ; // count of disposer calling + unsigned int nFindCount ; // count of find-functor calling + unsigned int nUpdateNewCount; + unsigned int nUpdateCount; + mutable unsigned int nEraseCount; + + stat() + { + clear_stat(); + } + + void clear_stat() + { + memset( this, 0, sizeof( *this ) ); + } + }; + + template + struct item_type: public stat, public Base + { + int nKey; + int nVal; + + item_type( int k ) + : nKey(k) + , nVal(0) + {} + + int key() const + { + return nKey; + } + }; + + struct hash_int { + size_t operator()( int i ) const + { + return co::v::hash()( i ); + } + template + size_t operator()( Q const& i ) const + { + return (*this)( i.key()); + } + }; + + struct simple_item_counter { + size_t m_nCount; + + simple_item_counter() + : m_nCount(0) + {} + + size_t operator ++() + { + return ++m_nCount; + } + + size_t operator --() + { + return --m_nCount; + } + + void reset() + { + m_nCount = 0; + } + + operator size_t() const + { + return m_nCount; + } + + }; + + + template + struct less + { + bool operator ()(const T& v1, const T& v2 ) const + { + return v1.key() < v2.key(); + } + + template + bool operator ()(const T& v1, const Q& v2 ) const + { + return v1.key() < v2; + } + + template + bool operator ()(const Q& v1, const T& v2 ) const + { + return v1 < v2.key(); + } + }; + + template + struct cmp { + int operator ()(const T& v1, const T& v2 ) const + { + if ( v1.key() < v2.key() ) + return -1; + return v1.key() > v2.key() ? 1 : 0; + } + + template + int operator ()(const T& v1, const Q& v2 ) const + { + if ( v1.key() < v2 ) + return -1; + return v1.key() > v2 ? 1 : 0; + } + + template + int operator ()(const Q& v1, const T& v2 ) const + { + if ( v1 < v2.key() ) + return -1; + return v1 > v2.key() ? 1 : 0; + } + }; + + struct other_item { + int nKey; + + explicit other_item( int k ) + : nKey( k ) + {} + + int key() const + { + return nKey; + } + }; + + struct other_less { + template + bool operator()( Q const& lhs, T const& rhs ) const + { + return lhs.key() < rhs.key(); + } + }; + + struct mock_disposer + { + template + void operator ()( T * p ) + { + ++p->nDisposeCount; + } + }; + + protected: + template + void test( Set& s ) + { + // Precondition: set is empty + // Postcondition: set is empty + + ASSERT_TRUE( s.empty() ); + ASSERT_CONTAINER_SIZE( s, 0 ); + size_t const nSetSize = kSize; + + typedef typename Set::value_type value_type; + + std::vector< value_type > data; + std::vector< size_t> indices; + data.reserve( kSize ); + indices.reserve( kSize ); + for ( size_t key = 0; key < kSize; ++key ) { + data.push_back( value_type( static_cast( key ))); + indices.push_back( key ); + } + shuffle( indices.begin(), indices.end() ); + + // insert/find + for ( auto idx : indices ) { + auto& i = data[ idx ]; + + ASSERT_FALSE( s.contains( i.nKey )); + ASSERT_FALSE( s.contains( i )); + ASSERT_FALSE( s.contains( other_item( i.key()), other_less())); + ASSERT_FALSE( s.find( i.nKey, []( value_type&, int ) {} )); + ASSERT_FALSE( s.find_with( other_item( i.key()), other_less(), []( value_type&, other_item const& ) {} )); + ASSERT_TRUE( s.find( i.nKey ) == s.end()); + ASSERT_TRUE( s.find_with( other_item( i.key() ), other_less()) == s.end()); + + std::pair updResult; + + updResult = s.update( i, []( value_type&, value_type* ) + { + ASSERT_TRUE( false ); + }, false ); + EXPECT_FALSE( updResult.first ); + EXPECT_FALSE( updResult.second ); + + updResult = s.upsert( i, false ); + EXPECT_FALSE( updResult.first ); + EXPECT_FALSE( updResult.second ); + + switch ( i.key() % 4 ) { + case 0: + ASSERT_TRUE( s.insert( i )); + ASSERT_FALSE( s.insert( i )); + updResult = s.update( i, []( value_type& val, value_type* arg) + { + ASSERT_TRUE( arg != nullptr ); + EXPECT_EQ( val.key(), arg->key() ); + }, false ); + EXPECT_TRUE( updResult.first ); + EXPECT_FALSE( updResult.second ); + break; + case 1: + EXPECT_EQ( i.nUpdateNewCount, 0u ); + ASSERT_TRUE( s.insert( i, []( value_type& v ) { ++v.nUpdateNewCount;} )); + EXPECT_EQ( i.nUpdateNewCount, 1u ); + ASSERT_FALSE( s.insert( i, []( value_type& v ) { ++v.nUpdateNewCount;} ) ); + EXPECT_EQ( i.nUpdateNewCount, 1u ); + i.nUpdateNewCount = 0; + break; + case 2: + updResult = s.update( i, []( value_type& /*val*/, value_type* arg ) + { + EXPECT_TRUE( arg == nullptr ); + }); + EXPECT_TRUE( updResult.first ); + EXPECT_TRUE( updResult.second ); + break; + case 3: + updResult = s.upsert( i ); + EXPECT_TRUE( updResult.first ); + EXPECT_TRUE( updResult.second ); + break; + } + + ASSERT_TRUE( s.contains( i.nKey ) ); + ASSERT_TRUE( s.contains( i ) ); + ASSERT_TRUE( s.contains( other_item( i.key() ), other_less())); + EXPECT_EQ( i.nFindCount, 0u ); + ASSERT_TRUE( s.find( i.nKey, []( value_type& v, int ) { ++v.nFindCount; } )); + EXPECT_EQ( i.nFindCount, 1u ); + ASSERT_TRUE( s.find_with( other_item( i.key() ), other_less(), []( value_type& v, other_item const& ) { ++v.nFindCount; } )); + EXPECT_EQ( i.nFindCount, 2u ); + ASSERT_TRUE( s.find( i.nKey ) != s.end() ); + ASSERT_TRUE( s.find_with( other_item( i.key() ), other_less() ) != s.end() ); + EXPECT_EQ( s.find( i.nKey )->nKey, i.key() ); + EXPECT_EQ( s.find_with( other_item( i.key() ), other_less())->nKey, i.key() ); + } + ASSERT_FALSE( s.empty() ); + ASSERT_CONTAINER_SIZE( s, nSetSize ); + + std::for_each( data.begin(), data.end(), []( value_type& v ) { v.clear_stat(); }); + + // erase + shuffle( indices.begin(), indices.end() ); + for ( auto idx : indices ) { + auto& i = data[ idx ]; + + ASSERT_TRUE( s.contains( i.nKey ) ); + ASSERT_TRUE( s.contains( i ) ); + ASSERT_TRUE( s.contains( other_item( i.key() ), other_less() ) ); + EXPECT_EQ( i.nFindCount, 0u ); + ASSERT_TRUE( s.find( i.nKey, []( value_type& v, int ) { ++v.nFindCount; } ) ); + EXPECT_EQ( i.nFindCount, 1u ); + ASSERT_TRUE( s.find_with( other_item( i.key() ), other_less(), []( value_type& v, other_item const& ) { ++v.nFindCount; } ) ); + EXPECT_EQ( i.nFindCount, 2u ); + ASSERT_TRUE( s.find( i.nKey ) != s.end() ); + ASSERT_TRUE( s.find_with( other_item( i.key() ), other_less()) != s.end() ); + EXPECT_EQ( s.find( i.nKey )->nKey, i.key() ); + EXPECT_EQ( s.find_with( other_item( i.key() ), other_less())->nKey, i.key() ); + + + value_type v( i ); + switch ( i.key() % 6 ) { + case 0: + ASSERT_FALSE( s.unlink( v )); + ASSERT_TRUE( s.unlink( i )); + ASSERT_FALSE( s.unlink( i ) ); + break; + case 1: + ASSERT_TRUE( s.erase( i.key())); + ASSERT_FALSE( s.erase( i.key() ) ); + break; + case 2: + ASSERT_TRUE( s.erase( v )); + ASSERT_FALSE( s.erase( v ) ); + break; + case 3: + ASSERT_TRUE( s.erase_with( other_item( i.key()), other_less())); + ASSERT_FALSE( s.erase_with( other_item( i.key() ), other_less() ) ); + break; + case 4: + EXPECT_EQ( i.nEraseCount, 0u ); + ASSERT_TRUE( s.erase( v, []( value_type& val ) { ++val.nEraseCount; } )); + EXPECT_EQ( i.nEraseCount, 1u ); + ASSERT_FALSE( s.erase( v, []( value_type& val ) { ++val.nEraseCount; } )); + EXPECT_EQ( i.nEraseCount, 1u ); + break; + case 5: + EXPECT_EQ( i.nEraseCount, 0u ); + ASSERT_TRUE( s.erase_with( other_item( i.key() ), other_less(), []( value_type& val ) { ++val.nEraseCount; } )); + EXPECT_EQ( i.nEraseCount, 1u ); + ASSERT_FALSE( s.erase_with( other_item( i.key() ), other_less(), []( value_type& val ) { ++val.nEraseCount; } )); + EXPECT_EQ( i.nEraseCount, 1u ); + break; + } + + ASSERT_FALSE( s.contains( i.nKey )); + ASSERT_FALSE( s.contains( i )); + ASSERT_FALSE( s.contains( other_item( i.key()), other_less())); + ASSERT_FALSE( s.find( i.nKey, []( value_type&, int ) {} )); + ASSERT_FALSE( s.find_with( other_item( i.key()), other_less(), []( value_type&, other_item const& ) {} )); + ASSERT_TRUE( s.find( i.nKey ) == s.end() ); + ASSERT_TRUE( s.find_with( other_item( i.key() ), other_less() ) == s.end() ); + } + ASSERT_TRUE( s.empty() ); + ASSERT_CONTAINER_SIZE( s, 0u ); + + // Force retiring cycle + Set::gc::force_dispose(); + for ( auto& i : data ) { + EXPECT_EQ( i.nDisposeCount, 1u ); + } + + // clear + for ( auto& i : data ) { + i.clear_stat(); + ASSERT_TRUE( s.insert( i )); + } + ASSERT_FALSE( s.empty() ); + ASSERT_CONTAINER_SIZE( s, nSetSize ); + + // Iterator test + for ( auto it = s.begin(); it != s.end(); ++it ) { + ++it->nFindCount; + } + for ( auto it = s.cbegin(); it != s.cend(); ++it ) { + EXPECT_EQ( it->nFindCount, 1u ); + } + for ( auto& i : data ) { + EXPECT_EQ( i.nFindCount, 1u ); + } + + // clear test + s.clear(); + + ASSERT_TRUE( s.empty()); + ASSERT_CONTAINER_SIZE( s, 0u ); + ASSERT_TRUE( s.begin() == s.end() ); + ASSERT_TRUE( s.cbegin() == s.cend() ); + + // Force retiring cycle + Set::gc::force_dispose(); + for ( auto& i : data ) { + EXPECT_EQ( i.nDisposeCount, 1u ); + } + + } + }; + +} // namespace cds_test + +#endif // #ifndef CDSUNIT_SET_TEST_INTRUSIVE_SPLIT_ITERABLE_SET_H diff --git a/test/unit/intrusive-set/test_intrusive_split_iterable_set_hp.h b/test/unit/intrusive-set/test_intrusive_split_iterable_set_hp.h new file mode 100644 index 00000000..05a5b7c2 --- /dev/null +++ b/test/unit/intrusive-set/test_intrusive_split_iterable_set_hp.h @@ -0,0 +1,160 @@ +/* + This file is a part of libcds - Concurrent Data Structures library + + (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2016 + + Source code repo: http://github.com/khizmax/libcds/ + Download: http://sourceforge.net/projects/libcds/files/ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef CDSUNIT_SET_TEST_INTRUSIVE_SPLIT_ITERABLE_SET_HP_H +#define CDSUNIT_SET_TEST_INTRUSIVE_SPLIT_ITERABLE_SET_HP_H + +#include "test_intrusive_split_iterable_set.h" + +// forward declaration +namespace cds { namespace intrusive {}} + +namespace cds_test { + + namespace ci = cds::intrusive; + namespace co = cds::opt; + + class intrusive_split_iterable_set_hp: public intrusive_split_iterable_set + { + typedef intrusive_split_iterable_set base_class; + + protected: + + template + void test( Set& s ) + { + // Precondition: set is empty + // Postcondition: set is empty + + base_class::test( s ); + + ASSERT_TRUE( s.empty() ); + ASSERT_CONTAINER_SIZE( s, 0 ); + + typedef typename Set::value_type value_type; + + std::vector< value_type > data; + std::vector< size_t> indices; + data.reserve( kSize ); + indices.reserve( kSize ); + for ( size_t key = 0; key < kSize; ++key ) { + data.push_back( value_type( static_cast(key) ) ); + indices.push_back( key ); + } + shuffle( indices.begin(), indices.end() ); + + typename Set::guarded_ptr gp; + + // get/extract from empty set + for ( auto idx : indices ) { + auto& i = data[idx]; + + gp = s.get( i ); + ASSERT_TRUE( !gp ); + gp = s.get( i.key() ); + ASSERT_TRUE( !gp ); + gp = s.get_with( other_item( i.key()), other_less()); + ASSERT_TRUE( !gp ); + + gp = s.extract( i ); + ASSERT_TRUE( !gp ); + gp = s.extract( i.key()); + ASSERT_TRUE( !gp ); + gp = s.extract_with( other_item( i.key()), other_less()); + ASSERT_TRUE( !gp ); + } + + // fill set + for ( auto& i : data ) { + i.nDisposeCount = 0; + ASSERT_TRUE( s.insert( i ) ); + } + + // get/extract + for ( auto idx : indices ) { + auto& i = data[idx]; + + EXPECT_EQ( i.nFindCount, 0u ); + gp = s.get( i ); + ASSERT_FALSE( !gp ); + ++gp->nFindCount; + EXPECT_EQ( i.nFindCount, 1u ); + + gp = s.get( i.key() ); + ASSERT_FALSE( !gp ); + ++gp->nFindCount; + EXPECT_EQ( i.nFindCount, 2u ); + + gp = s.get_with( other_item( i.key()), other_less()); + ASSERT_FALSE( !gp ); + ++gp->nFindCount; + EXPECT_EQ( i.nFindCount, 3u ); + + EXPECT_EQ( i.nEraseCount, 0u ); + switch ( i.key() % 3 ) { + case 0: + gp = s.extract( i.key()); + break; + case 1: + gp = s.extract( i ); + break; + case 2: + gp = s.extract_with( other_item( i.key() ), other_less() ); + break; + } + ASSERT_FALSE( !gp ); + ++gp->nEraseCount; + EXPECT_EQ( i.nEraseCount, 1u ); + + gp = s.extract( i ); + ASSERT_TRUE( !gp ); + gp = s.extract( i.key() ); + ASSERT_TRUE( !gp ); + gp = s.extract_with( other_item( i.key() ), other_less() ); + ASSERT_TRUE( !gp ); + } + + gp.release(); + + ASSERT_TRUE( s.empty() ); + ASSERT_CONTAINER_SIZE( s, 0 ); + + // Force retiring cycle + Set::gc::force_dispose(); + for ( auto& i : data ) { + EXPECT_EQ( i.nDisposeCount, 1u ); + } + + } + }; + +} // namespace cds_test + +#endif // #ifndef CDSUNIT_SET_TEST_INTRUSIVE_SPLIT_ITERABLE_SET_HP_H diff --git a/test/unit/set/split_lazy_nogc.cpp b/test/unit/set/split_lazy_nogc.cpp index 4317e323..498da03f 100644 --- a/test/unit/set/split_lazy_nogc.cpp +++ b/test/unit/set/split_lazy_nogc.cpp @@ -5,7 +5,7 @@ Source code repo: http://github.com/khizmax/libcds/ Download: http://sourceforge.net/projects/libcds/files/ - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: