Mark default ctor as =delete for segment
[libcds.git] / cds / intrusive / segmented_queue.h
1 //$$CDS-header$$
2
3 #ifndef __CDS_INTRUSIVE_SEGMENTED_QUEUE_H
4 #define __CDS_INTRUSIVE_SEGMENTED_QUEUE_H
5
6 #include <mutex>
7 #include <cds/intrusive/details/base.h>
8 #include <cds/details/marked_ptr.h>
9 #include <cds/algo/int_algo.h>
10 #include <cds/lock/spinlock.h>
11 #include <cds/opt/permutation.h>
12
13 #include <boost/intrusive/slist.hpp>
14
15 #if CDS_COMPILER == CDS_COMPILER_MSVC
16 #   pragma warning( push )
17 #   pragma warning( disable: 4355 ) // warning C4355: 'this' : used in base member initializer list
18 #endif
19
20 namespace cds { namespace intrusive {
21
22     /// SegmentedQueue -related declarations
23     namespace segmented_queue {
24
25         /// SegmentedQueue internal statistics. May be used for debugging or profiling
26         template <typename Counter = cds::atomicity::event_counter >
27         struct stat
28         {
29             typedef Counter  counter_type;  ///< Counter type
30
31             counter_type    m_nPush;            ///< Push count
32             counter_type    m_nPushPopulated;   ///< Number of attempts to push to populated (non-empty) cell
33             counter_type    m_nPushContended;   ///< Number of failed CAS when pushing
34             counter_type    m_nPop;             ///< Pop count
35             counter_type    m_nPopEmpty;        ///< Number of dequeuing from empty queue
36             counter_type    m_nPopContended;    ///< Number of failed CAS when popping
37
38             counter_type    m_nCreateSegmentReq;    ///< Number of request to create new segment
39             counter_type    m_nDeleteSegmentReq;    ///< Number to request to delete segment
40             counter_type    m_nSegmentCreated;  ///< Number of created segments
41             counter_type    m_nSegmentDeleted;  ///< Number of deleted segments
42
43             //@cond
44             void onPush()               { ++m_nPush; }
45             void onPushPopulated()      { ++m_nPushPopulated; }
46             void onPushContended()      { ++m_nPushContended; }
47             void onPop()                { ++m_nPop;  }
48             void onPopEmpty()           { ++m_nPopEmpty; }
49             void onPopContended()       { ++m_nPopContended; }
50             void onCreateSegmentReq()   { ++m_nCreateSegmentReq; }
51             void onDeleteSegmentReq()   { ++m_nDeleteSegmentReq; }
52             void onSegmentCreated()     { ++m_nSegmentCreated; }
53             void onSegmentDeleted()     { ++m_nSegmentDeleted; }
54             //@endcond
55         };
56
57         /// Dummy SegmentedQueue statistics, no overhead
58         struct empty_stat {
59             //@cond
60             void onPush() const             {}
61             void onPushPopulated() const    {}
62             void onPushContended() const    {}
63             void onPop() const              {}
64             void onPopEmpty() const         {}
65             void onPopContended() const     {}
66             void onCreateSegmentReq() const {}
67             void onDeleteSegmentReq() const {}
68             void onSegmentCreated() const   {}
69             void onSegmentDeleted() const   {}
70             //@endcond
71         };
72
73         /// SegmentedQueue default traits
74         struct traits {
75             /// Element disposer that is called when the item to be dequeued. Default is opt::v::empty_disposer (no disposer)
76             typedef opt::v::empty_disposer disposer;
77
78             /// Item counter, default is atomicity::item_counter
79             /**
80                 The item counting is an essential part of segmented queue algorithm.
81                 The \p empty() member function is based on checking <tt>size() == 0</tt>.
82                 Therefore, dummy item counter like atomicity::empty_item_counter is not the proper counter.
83             */
84             typedef atomicity::item_counter item_counter;
85
86             /// Internal statistics, possible predefined types are \ref stat, \ref empty_stat (the default)
87             typedef segmented_queue::empty_stat        stat;
88
89             /// Memory model, default is opt::v::relaxed_ordering. See cds::opt::memory_model for the full list of possible types
90             typedef opt::v::relaxed_ordering  memory_model;
91
92             /// Alignment of critical data, default is cache line alignment. See cds::opt::alignment option specification
93             enum { alignment = opt::cache_line_alignment };
94
95             /// Segment allocator. Default is \ref CDS_DEFAULT_ALLOCATOR
96             typedef CDS_DEFAULT_ALLOCATOR allocator;
97
98             /// Lock type used to maintain an internal list of allocated segments
99             typedef cds::lock::Spin lock_type;
100
101             /// Random \ref cds::opt::permutation_generator "permutation generator" for sequence [0, quasi_factor)
102             typedef cds::opt::v::random2_permutation<int>    permutation_generator;
103         };
104
105         /// Metafunction converting option list to traits for SegmentedQueue
106         /**
107             The metafunction can be useful if a few fields in \p segmented_queue::traits should be changed.
108             For example:
109             \code
110             typedef cds::intrusive::segmented_queue::make_traits<
111                 cds::opt::item_counter< cds::atomicity::item_counter >
112             >::type my_segmented_queue_traits;
113             \endcode
114             This code creates \p %SegmentedQueue type traits with item counting feature,
115             all other \p %segmented_queue::traits members left unchanged.
116
117             \p Options are:
118             - \p opt::disposer - the functor used for dispose removed items.
119             - \p opt::stat - internal statistics, possible type: \p segmented_queue::stat, \p segmented_queue::empty_stat (the default)
120             - \p opt::item_counter - item counting feature. Note that \p atomicity::empty_item_counetr is not suitable
121                 for segmented queue.
122             - \p opt::memory_model - memory model, default is \p opt::v::relaxed_ordering.
123                 See option description for the full list of possible models
124             - \p opt::alignment - the alignment for critical data, see option description for explanation
125             - \p opt::allocator - the allocator to be used for maintaining segments.
126             - \p opt::lock_type - a mutual exclusion lock type used to maintain internal list of allocated
127                 segments. Default is \p cds::opt::Spin, \p std::mutex is also suitable.
128             - \p opt::permutation_generator - a random permutation generator for sequence [0, quasi_factor),
129                 default is \p cds::opt::v::random2_permutation<int>
130         */
131         template <typename... Options>
132         struct make_traits {
133 #   ifdef CDS_DOXYGEN_INVOKED
134             typedef implementation_defined type ;   ///< Metafunction result
135 #   else
136             typedef typename cds::opt::make_options<
137                 typename cds::opt::find_type_traits< traits, Options... >::type
138                 ,Options...
139             >::type   type;
140 #   endif
141         };
142     } // namespace segmented_queue
143
144     /// Segmented queue
145     /** @ingroup cds_intrusive_queue
146
147         The queue is based on work
148         - [2010] Afek, Korland, Yanovsky "Quasi-Linearizability: relaxed consistency for improved concurrency"
149
150         In this paper the authors offer a relaxed version of linearizability, so-called quasi-linearizability,
151         that preserves some of the intuition, provides a flexible way to control the level of relaxation
152         and supports th implementation of more concurrent and scalable data structure.
153         Intuitively, the linearizability requires each run to be equivalent in some sense to a serial run
154         of the algorithm. This equivalence to some serial run imposes strong synchronization requirements
155         that in many cases results in limited scalability and synchronization bottleneck.
156
157         The general idea is that the queue maintains a linked list of segments, each segment is an array of
158         nodes in the size of the quasi factor, and each node has a deleted boolean marker, which states
159         if it has been dequeued. Each producer iterates over last segment in the linked list in some random
160         permutation order. Whet it finds an empty cell it performs a CAS operation attempting to enqueue its
161         new element. In case the entire segment has been scanned and no available cell is found (implying
162         that the segment is full), then it attempts to add a new segment to the list.
163
164         The dequeue operation is similar: the consumer iterates over the first segment in the linked list
165         in some random permutation order. When it finds an item which has not yet been dequeued, it performs
166         CAS on its deleted marker in order to "delete" it, if succeeded this item is considered dequeued.
167         In case the entire segment was scanned and all the nodes have already been dequeued (implying that
168         the segment is empty), then it attempts to remove this segment from the linked list and starts
169         the same process on the next segment. If there is no next segment, the queue is considered empty.
170
171         Based on the fact that most of the time threads do not add or remove segments, most of the work
172         is done in parallel on different cells in the segments. This ensures a controlled contention
173         depending on the segment size, which is quasi factor.
174
175         The segmented queue is an <i>unfair</i> queue since it violates the strong FIFO order but no more than
176         quasi factor. This means that the consumer dequeues <i>any</i> item from the current first segment.
177
178         Template parameters:
179         - \p GC - a garbage collector, possible types are cds::gc::HP, cds::gc::PTB
180         - \p T - the type of values stored in the queue
181         - \p Traits - queue type traits, default is \p segmented_queue::traits.
182             \p segmented_queue::make_traits metafunction can be used to construct the
183             type traits.
184
185         The queue stores the pointers to enqueued items so no special node hooks are needed.
186     */
187     template <class GC, typename T, typename Traits = segmented_queue::traits >
188     class SegmentedQueue
189     {
190     public:
191         typedef GC  gc;         ///< Garbage collector
192         typedef T   value_type; ///< type of the value stored in the queue
193         typedef Traits traits;  ///< Queue traits
194
195         typedef typename traits::disposer      disposer    ;   ///< value disposer, called only in \p clear() when the element to be dequeued
196         typedef typename traits::allocator     allocator;   ///< Allocator maintaining the segments
197         typedef typename traits::memory_model  memory_model;   ///< Memory ordering. See cds::opt::memory_model option
198         typedef typename traits::item_counter  item_counter;   ///< Item counting policy, see cds::opt::item_counter option setter
199         typedef typename traits::stat          stat;   ///< Internal statistics policy
200         typedef typename traits::lock_type     lock_type;   ///< Type of mutex for maintaining an internal list of allocated segments.
201         typedef typename traits::permutation_generator permutation_generator; ///< Random permutation generator for sequence [0, quasi-factor)
202
203         static const size_t m_nHazardPtrCount = 2 ; ///< Count of hazard pointer required for the algorithm
204
205     protected:
206         //@cond
207         // Segment cell. LSB is used as deleted mark
208         typedef cds::details::marked_ptr< value_type, 1 >   cell;
209
210         // Segment
211         struct segment: public boost::intrusive::slist_base_hook<>
212         {
213             atomics::atomic< cell > *    cells;  // Cell array of size \ref m_nQuasiFactor
214             size_t   version;   // version tag (ABA prevention tag)
215             // cell array is placed here in one continuous memory block
216
217             // Initializes the segment
218             segment( size_t nCellCount )
219                 // MSVC warning C4355: 'this': used in base member initializer list
220                 : cells( reinterpret_cast< atomics::atomic< cell > * >( this + 1 ))
221                 , version( 0 )
222             {
223                 init( nCellCount );
224             }
225
226             segment() = delete;
227
228             void init( size_t nCellCount )
229             {
230                 atomics::atomic< cell > * pLastCell = cells + nCellCount;
231                 for ( atomics::atomic< cell > * pCell = cells; pCell < pLastCell; ++pCell )
232                     pCell->store( cell(), atomics::memory_order_relaxed );
233                 atomics::atomic_thread_fence( memory_model::memory_order_release );
234             }
235         };
236
237         typedef typename opt::details::alignment_setter< atomics::atomic<segment *>, traits::alignment >::type aligned_segment_ptr;
238         //@endcond
239
240     protected:
241         //@cond
242         class segment_list
243         {
244             typedef boost::intrusive::slist< segment, boost::intrusive::cache_last< true > > list_impl;
245             typedef std::unique_lock< lock_type > scoped_lock;
246
247             aligned_segment_ptr m_pHead;
248             aligned_segment_ptr m_pTail;
249
250             list_impl           m_List;
251             mutable lock_type   m_Lock;
252             size_t const        m_nQuasiFactor;
253             stat&               m_Stat;
254
255         private:
256             struct segment_disposer
257             {
258                 void operator()( segment * pSegment )
259                 {
260                     assert( pSegment != nullptr );
261                     free_segment( pSegment );
262                 }
263             };
264
265             struct gc_segment_disposer
266             {
267                 void operator()( segment * pSegment )
268                 {
269                     assert( pSegment != nullptr );
270                     retire_segment( pSegment );
271                 }
272             };
273
274         public:
275             segment_list( size_t nQuasiFactor, stat& st )
276                 : m_pHead( nullptr )
277                 , m_pTail( nullptr )
278                 , m_nQuasiFactor( nQuasiFactor )
279                 , m_Stat( st )
280             {
281                 assert( cds::beans::is_power2( nQuasiFactor ));
282             }
283
284             ~segment_list()
285             {
286                 m_List.clear_and_dispose( gc_segment_disposer() );
287             }
288
289             segment * head( typename gc::Guard& guard )
290             {
291                 return guard.protect( m_pHead );
292             }
293
294             segment * tail( typename gc::Guard& guard )
295             {
296                 return guard.protect( m_pTail );
297             }
298
299 #       ifdef _DEBUG
300             bool populated( segment const& s ) const
301             {
302                 // The lock should be held
303                 atomics::atomic< cell > const * pLastCell = s.cells + quasi_factor();
304                 for ( atomics::atomic< cell > const * pCell = s.cells; pCell < pLastCell; ++pCell ) {
305                     if ( !pCell->load( memory_model::memory_order_relaxed ).all() )
306                         return false;
307                 }
308                 return true;
309             }
310             bool exhausted( segment const& s ) const
311             {
312                 // The lock should be held
313                 atomics::atomic< cell > const * pLastCell = s.cells + quasi_factor();
314                 for ( atomics::atomic< cell > const * pCell = s.cells; pCell < pLastCell; ++pCell ) {
315                     if ( !pCell->load( memory_model::memory_order_relaxed ).bits() )
316                         return false;
317                 }
318                 return true;
319             }
320 #       endif
321
322             segment * create_tail( segment * pTail, typename gc::Guard& guard )
323             {
324                 // pTail is guarded by GC
325
326                 m_Stat.onCreateSegmentReq();
327
328                 scoped_lock l( m_Lock );
329
330                 if ( !m_List.empty() && ( pTail != &m_List.back() || get_version(pTail) != m_List.back().version )) {
331                     m_pTail.store( &m_List.back(), memory_model::memory_order_relaxed );
332
333                     return guard.assign( &m_List.back() );
334                 }
335
336                 assert( m_List.empty() || populated( m_List.back() ));
337
338                 segment * pNew = allocate_segment();
339                 m_Stat.onSegmentCreated();
340
341                 if ( m_List.empty() )
342                     m_pHead.store( pNew, memory_model::memory_order_relaxed );
343                 m_List.push_back( *pNew );
344                 m_pTail.store( pNew, memory_model::memory_order_release );
345                 return guard.assign( pNew );
346             }
347
348             segment * remove_head( segment * pHead, typename gc::Guard& guard )
349             {
350                 // pHead is guarded by GC
351                 m_Stat.onDeleteSegmentReq();
352
353                 segment * pRet;
354                 {
355                     scoped_lock l( m_Lock );
356
357                     if ( m_List.empty() ) {
358                         m_pTail.store( nullptr, memory_model::memory_order_relaxed );
359                         m_pHead.store( nullptr, memory_model::memory_order_relaxed );
360                         return guard.assign( nullptr );
361                     }
362
363                     if ( pHead != &m_List.front() || get_version(pHead) != m_List.front().version ) {
364                         m_pHead.store( &m_List.front(), memory_model::memory_order_relaxed );
365                         return guard.assign( &m_List.front() );
366                     }
367
368                     assert( exhausted(m_List.front()) );
369
370                     m_List.pop_front();
371                     if ( m_List.empty() ) {
372                         pRet = guard.assign( nullptr );
373                         m_pTail.store( nullptr, memory_model::memory_order_relaxed );
374                     }
375                     else
376                         pRet = guard.assign( &m_List.front() );
377                     m_pHead.store( pRet, memory_model::memory_order_release );
378                 }
379
380                 retire_segment( pHead );
381                 m_Stat.onSegmentDeleted();
382
383                 return pRet;
384             }
385
386             size_t quasi_factor() const
387             {
388                 return m_nQuasiFactor;
389             }
390
391         private:
392             typedef cds::details::Allocator< segment, allocator >   segment_allocator;
393
394             static size_t get_version( segment * pSegment )
395             {
396                 return pSegment ? pSegment->version : 0;
397             }
398
399             segment * allocate_segment()
400             {
401                 return segment_allocator().NewBlock( sizeof(segment) + sizeof(cell) * m_nQuasiFactor,
402                     quasi_factor() );
403             }
404
405             static void free_segment( segment * pSegment )
406             {
407                 segment_allocator().Delete( pSegment );
408             }
409
410             static void retire_segment( segment * pSegment )
411             {
412                 gc::template retire<segment_disposer>( pSegment );
413             }
414         };
415         //@endcond
416
417     protected:
418         segment_list              m_SegmentList;  ///< List of segments
419
420         item_counter              m_ItemCounter;  ///< Item counter
421         stat                      m_Stat;         ///< Internal statistics
422
423     public:
424         /// Initializes the empty queue
425         SegmentedQueue(
426             size_t nQuasiFactor     ///< Quasi factor. If it is not a power of 2 it is rounded up to nearest power of 2. Minimum is 2.
427             )
428             : m_SegmentList( cds::beans::ceil2(nQuasiFactor), m_Stat )
429         {
430             static_assert( (!std::is_same< item_counter, cds::atomicity::empty_item_counter >::value),
431                 "cds::atomicity::empty_item_counter is not supported for SegmentedQueue"
432                 );
433             assert( m_SegmentList.quasi_factor() > 1 );
434         }
435
436         /// Clears the queue and deletes all internal data
437         ~SegmentedQueue()
438         {
439             clear();
440         }
441
442         /// Inserts a new element at last segment of the queue
443         bool enqueue( value_type& val )
444         {
445             // LSB is used as a flag in marked pointer
446             assert( (reinterpret_cast<uintptr_t>( &val ) & 1) == 0 );
447
448             typename gc::Guard segmentGuard;
449             segment * pTailSegment = m_SegmentList.tail( segmentGuard );
450             if ( !pTailSegment ) {
451                 // no segments, create the new one
452                 pTailSegment = m_SegmentList.create_tail( pTailSegment, segmentGuard );
453                 assert( pTailSegment );
454             }
455
456             permutation_generator gen( quasi_factor() );
457
458             // First, increment item counter.
459             // We sure that the item will be enqueued
460             // but if we increment the counter after inserting we can get a negative counter value
461             // if dequeuing occurs before incrementing (enqueue/dequeue race)
462             ++m_ItemCounter;
463
464             while ( true ) {
465                 CDS_DEBUG_ONLY( size_t nLoopCount = 0);
466                 do {
467                     typename permutation_generator::integer_type i = gen;
468                     CDS_DEBUG_ONLY( ++nLoopCount );
469                     if ( pTailSegment->cells[i].load(memory_model::memory_order_relaxed).all() ) {
470                         // Cell is not empty, go next
471                         m_Stat.onPushPopulated();
472                     }
473                     else {
474                         // Empty cell found, try to enqueue here
475                         cell nullCell;
476                         if ( pTailSegment->cells[i].compare_exchange_strong( nullCell, cell( &val ),
477                             memory_model::memory_order_release, atomics::memory_order_relaxed ))
478                         {
479                             // Ok to push item
480                             m_Stat.onPush();
481                             return true;
482                         }
483                         assert( nullCell.ptr() );
484                         m_Stat.onPushContended();
485                     }
486                 } while ( gen.next() );
487
488                 assert( nLoopCount == quasi_factor());
489
490                 // No available position, create a new segment
491                 pTailSegment = m_SegmentList.create_tail( pTailSegment, segmentGuard );
492
493                 // Get new permutation
494                 gen.reset();
495             }
496         }
497
498         /// Removes an element from first segment of the queue and returns it
499         /**
500             If the queue is empty the function returns \p nullptr.
501
502             The disposer specified in \p Traits template argument is <b>not</b> called for returned item.
503             You should manually dispose the item:
504             <code>
505             struct my_disposer {
506                 void operator()( foo * p )
507                 {
508                     delete p;
509                 }
510             };
511             cds::intrusive::SegmentedQueue< cds::gc::HP, foo > theQueue;
512             // ...
513
514             // Dequeue an item
515             foo * pItem = theQueue.dequeue();
516             // deal with pItem
517             //...
518
519             // pItem is not longer needed and can be deleted
520             // Do it via gc::HP::retire
521             cds::gc::HP::template retire< my_disposer >( pItem );
522             </code>
523         */
524         value_type * dequeue()
525         {
526             typename gc::Guard itemGuard;
527             if ( do_dequeue( itemGuard )) {
528                 value_type * pVal = itemGuard.template get<value_type>();
529                 assert( pVal );
530                 return pVal;
531             }
532             return nullptr;
533
534         }
535
536         /// Synonym for \p enqueue(value_type&) member function
537         bool push( value_type& val )
538         {
539             return enqueue( val );
540         }
541
542         /// Synonym for \p dequeue() member function
543         value_type * pop()
544         {
545             return dequeue();
546         }
547
548         /// Checks if the queue is empty
549         /**
550             The original segmented queue algorithm does not allow to check emptiness accurately
551             because \p empty() is unlinearizable.
552             This function tests queue's emptiness checking <tt>size() == 0</tt>,
553             so, the item counting feature is an essential part of queue's algorithm.
554         */
555         bool empty() const
556         {
557             return size() == 0;
558         }
559
560         /// Clear the queue
561         /**
562             The function repeatedly calls \ref dequeue until it returns \p nullptr.
563             The disposer specified in \p Traits template argument is called for each removed item.
564         */
565         void clear()
566         {
567             clear_with( disposer() );
568         }
569
570         /// Clear the queue
571         /**
572             The function repeatedly calls \p dequeue() until it returns \p nullptr.
573             \p Disposer is called for each removed item.
574         */
575         template <class Disposer>
576         void clear_with( Disposer )
577         {
578             typename gc::Guard itemGuard;
579             while ( do_dequeue( itemGuard ) ) {
580                 assert( itemGuard.template get<value_type>() );
581                 gc::template retire<Disposer>( itemGuard.template get<value_type>() );
582                 itemGuard.clear();
583             }
584         }
585
586         /// Returns queue's item count
587         size_t size() const
588         {
589             return m_ItemCounter.value();
590         }
591
592         /// Returns reference to internal statistics
593         /**
594             The type of internal statistics is specified by \p Traits template argument.
595         */
596         const stat& statistics() const
597         {
598             return m_Stat;
599         }
600
601         /// Returns quasi factor, a power-of-two number
602         size_t quasi_factor() const
603         {
604             return m_SegmentList.quasi_factor();
605         }
606
607     protected:
608         //@cond
609         bool do_dequeue( typename gc::Guard& itemGuard )
610         {
611             typename gc::Guard segmentGuard;
612             segment * pHeadSegment = m_SegmentList.head( segmentGuard );
613
614             permutation_generator gen( quasi_factor() );
615             while ( true ) {
616                 if ( !pHeadSegment ) {
617                     // Queue is empty
618                     m_Stat.onPopEmpty();
619                     return false;
620                 }
621
622                 bool bHadNullValue = false;
623                 cell item;
624                 CDS_DEBUG_ONLY( size_t nLoopCount = 0 );
625                 do {
626                     typename permutation_generator::integer_type i = gen;
627                     CDS_DEBUG_ONLY( ++nLoopCount );
628
629                     // Guard the item
630                     // In segmented queue the cell cannot be reused
631                     // So no loop is needed here to protect the cell
632                     item = pHeadSegment->cells[i].load( memory_model::memory_order_relaxed );
633                     itemGuard.assign( item.ptr() );
634
635                     // Check if this cell is empty, which means an element
636                     // can be enqueued to this cell in the future
637                     if ( !item.ptr() )
638                         bHadNullValue = true;
639                     else {
640                         // If the item is not deleted yet
641                         if ( !item.bits() ) {
642                             // Try to mark the cell as deleted
643                             if ( pHeadSegment->cells[i].compare_exchange_strong( item, item | 1,
644                                 memory_model::memory_order_acquire, atomics::memory_order_relaxed ))
645                             {
646                                 --m_ItemCounter;
647                                 m_Stat.onPop();
648
649                                 return true;
650                             }
651                             assert( item.bits() );
652                             m_Stat.onPopContended();
653                         }
654                     }
655                 } while ( gen.next() );
656
657                 assert( nLoopCount == quasi_factor() );
658
659                 // scanning the entire segment without finding a candidate to dequeue
660                 // If there was an empty cell, the queue is considered empty
661                 if ( bHadNullValue ) {
662                     m_Stat.onPopEmpty();
663                     return false;
664                 }
665
666                 // All nodes have been dequeued, we can safely remove the first segment
667                 pHeadSegment = m_SegmentList.remove_head( pHeadSegment, segmentGuard );
668
669                 // Get new permutation
670                 gen.reset();
671             }
672         }
673         //@endcond
674     };
675 }} // namespace cds::intrusive
676
677 #if CDS_COMPILER == CDS_COMPILER_MSVC
678 #   pragma warning( pop )
679 #endif
680
681 #endif // #ifndef __CDS_INTRUSIVE_SEGMENTED_QUEUE_H