6767 * \since
6868 * v.1.2.1
6969 */
70- #define TIMERTT_VERSION 1002003u
70+ #define TIMERTT_VERSION 1002004u
7171
7272/* !
7373 * \brief Top-level project's namespace.
@@ -845,10 +845,11 @@ struct timer_wheel_engine_defaults
845845 *
846846 * This class uses <a href="http://www.cs.columbia.edu/~nahum/w6998/papers/ton97-timing-wheels.pdf">timer_wheel</a>
847847 * mechanism to work with timers.
848- * This mechanism is efficient for working with big amount of timers.
849- * But it requires that timer thread is working always, even in case
850- * when there is no timers. Another price for timer_wheel is the
851- * granularity of timer steps.
848+ * This mechanism is efficient for working with big amount of timers. But this
849+ * mechanism can consume some more resources when there is a small number of
850+ * waiting timers. It's because there is a wakeup at every time step of the
851+ * wheel, even if there is no elapsed timers for that step. Another price for
852+ * timer_wheel is the granularity of timer steps.
852853 *
853854 * Timer wheel data structure consists from one fixed size vector
854855 * (the wheel) and several double-linked list (one list for every wheel
@@ -861,7 +862,7 @@ struct timer_wheel_engine_defaults
861862 *
862863 * \note At the beginnig of time step thread detects elapsed timers, then
863864 * unblocks object mutex and calls timer actors for those timers. It means
864- * that actors call call timer thread object. And there won't be frequent
865+ * that actors can call timer thread object. And there won't be frequent
865866 * mutex locking/unlocking operations for building and processing
866867 * list of elapsed timers. This allows to process millions of timer actor
867868 * per second.
@@ -1631,7 +1632,7 @@ struct timer_list_engine_defaults
16311632 * \tparam Thread_Safety Thread-safety indicator.
16321633 * Must be timertt::thread_safety::unsafe or timertt::thread_safety::safe.
16331634 *
1634- * \tparam Timer_Action type of functor to perform an user-defined
1635+ * \tparam Time_Action type of functor to perform an user-defined
16351636 * action when timer expires. This must be Moveable and MoveConstructible
16361637 * type.
16371638 *
@@ -1996,7 +1997,7 @@ private :
19961997 // ! Insert timer to the list.
19971998 /* !
19981999 * Insertion starts from the tail of the list. And if \a timer
1999- * has lower timer_type::m_whan value then the last list item
2000+ * has lower timer_type::m_when value then the last list item
20002001 * there is an loop of searching appropriate place by going to
20012002 * the head of the list.
20022003 *
@@ -2231,17 +2232,14 @@ struct timer_heap_engine_defaults
22312232 * the heap. When this timer elapsed and removed next timer with the
22322233 * eralier time point is going to the top of the heap.
22332234 *
2234- * This implementation uses array-based <a
2235- * href="http://en.wikipedia.org/wiki/Binary_heap">binary heap</a>. The array
2236- * is growing as necessary to hold all the timers. The initial size of that
2237- * array can be specified in the constructor.
2235+ * This implementation uses array-based <a href="http://en.wikipedia.org/wiki/Binary_heap">binary heap</a>.
2236+ * The array is growing as necessary to hold all the timers. The initial size
2237+ * of that array can be specified in the constructor.
22382238 *
2239- * \note Unlike timer_wheel and timer_list threads this thread unlock and
2240- * lock its mutex for processing every timers. It means that processing
2241- * speed of this thread will be slower then for timer_wheel or
2242- * timer_list threads. But this type of thread doesn't consume resources
2243- * when there is no timers (unlike timer_wheel thread). And has very
2244- * efficient activation and deactivation procedures (unlike timer_list
2239+ * \note Unlike timer_wheel and timer_list threads this thread unlock and lock
2240+ * its mutex for processing every timers. It means that processing speed of
2241+ * this thread will be slower then for timer_wheel or timer_list threads. And
2242+ * has very efficient activation and deactivation procedures (unlike timer_list
22452243 * thread).
22462244 *
22472245 * \tparam Thread_Safety Thread-safety indicator.
@@ -2739,39 +2737,56 @@ private :
27392737 heap_remove ( timer_type * timer )
27402738 {
27412739 if ( timer->m_position == m_heap.size () )
2740+ {
27422741 // A special case: timer to remove is a last added item
27432742 // in the heap. It could be simply removed from heap
27442743 // without any other actions.
27452744 m_heap.pop_back ();
2745+ }
27462746 else
27472747 {
27482748 auto last_item = m_heap.back ();
27492749 heap_swap ( timer, last_item );
27502750 m_heap.pop_back ();
27512751
2752- // last_item must be heap-down to the appropriate place.
2753- while ( true )
2752+ // If timer->m_position is not 1 then we can violate
2753+ // binary heap order. The last_item can now:
2754+ //
2755+ // - be less than its new parent;
2756+ // - be greater than any of its new children.
2757+ //
2758+ // If the last_item is less than new parent then we
2759+ // have to heap it up. If we move the last_item up then there
2760+ // is no need to check order down of it.
2761+ //
2762+ // But if the last_item wasn't moved up then we have to
2763+ // check the order down of it.
2764+ bool should_be_checked_down = true ;
2765+
2766+ // NOTE: there is no need to check upward direction
2767+ // if removed timer was the head of the heap.
2768+ while ( last_item->m_position > std::size_t { 1 } )
27542769 {
2755- auto left_index = last_item->m_position * 2 ;
2756- auto right_index = left_index + 1 ;
2757- auto min_index = last_item->m_position ;
2758-
2759- if ( left_index <= m_heap.size () &&
2760- heap_item ( left_index )->m_when <=
2761- heap_item ( min_index )->m_when )
2762- min_index = left_index;
2763-
2764- if ( right_index <= m_heap.size () &&
2765- heap_item ( right_index )->m_when <=
2766- heap_item ( min_index )->m_when )
2767- min_index = right_index;
2768-
2769- if ( min_index != last_item->m_position )
2770- heap_swap ( last_item, heap_item ( min_index ) );
2770+ auto parent = heap_item (
2771+ last_item->m_position / std::size_t { 2 } );
2772+ if ( parent->m_when > last_item->m_when )
2773+ {
2774+ // Timer must be heap-up on the place of the parent node.
2775+ heap_swap ( parent, last_item );
2776+ // There is no need to do additional check in downward
2777+ // order (because parent is guaranteed to be less then
2778+ // children noded).
2779+ should_be_checked_down = false ;
2780+ }
27712781 else
2772- // Heap structure is correct.
2782+ {
2783+ // There is no need to modify heap structure anymore.
27732784 break ;
2785+ }
27742786 }
2787+
2788+ if ( should_be_checked_down )
2789+ heap_down_item ( last_item );
27752790 }
27762791 }
27772792
@@ -2785,6 +2800,40 @@ private :
27852800 std::swap ( a->m_position , b->m_position );
27862801 }
27872802
2803+ // ! Helper method to ensure that children of the specified
2804+ // ! item are greater.
2805+ // !
2806+ // ! If this isn't true then \a subtree_head will be moved down
2807+ // ! the heap until the appropriate place will be found.
2808+ void
2809+ heap_down_item ( timer_type * subtree_head )
2810+ {
2811+ do
2812+ {
2813+ auto left_index = subtree_head->m_position * std::size_t { 2 };
2814+ auto right_index = left_index + std::size_t { 1 };
2815+ auto min_index = subtree_head->m_position ;
2816+
2817+ if ( left_index <= m_heap.size () &&
2818+ heap_item ( left_index )->m_when <=
2819+ heap_item ( min_index )->m_when )
2820+ min_index = left_index;
2821+
2822+ if ( right_index <= m_heap.size () &&
2823+ heap_item ( right_index )->m_when <=
2824+ heap_item ( min_index )->m_when )
2825+ min_index = right_index;
2826+
2827+ if ( min_index != subtree_head->m_position )
2828+ {
2829+ heap_swap ( subtree_head, heap_item ( min_index ) );
2830+ }
2831+ else
2832+ // Heap structure is correct.
2833+ break ;
2834+ } while ( true );
2835+ }
2836+
27882837 // ! Get timer by it index.
27892838 /* !
27902839 * This accessor work with respect that positions are started from 1.
0 commit comments