E-MailRelay

gtimerlist.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gtimerlist.cpp
19///
20
21#include "gdef.h"
22#include "gtimerlist.h"
23#include "gtimer.h"
24#include "gnetdone.h"
25#include "geventloop.h"
26#include "geventloggingcontext.h"
27#include "glog.h"
28#include "gassert.h"
29#include <algorithm>
30#include <functional>
31#include <sstream>
32
33GNet::TimerList::ListItem::ListItem( TimerBase * t , EventState es ) :
34 m_timer(t) ,
35 m_es(es)
36{
37}
38
39#ifndef G_LIB_SMALL
40 inline bool GNet::TimerList::ListItem::operator==( const ListItem & rhs ) const noexcept
41{
42 return m_timer == rhs.m_timer ;
43}
44#endif
45
46 inline void GNet::TimerList::ListItem::resetIf( TimerBase * p ) noexcept
47{
48 if( m_timer == p )
49 m_timer = nullptr ;
50}
51
52 void GNet::TimerList::ListItem::disarmIf( ExceptionHandler * eh ) noexcept
53{
54 if( m_es.eh() == eh )
55 m_es.disarm() ;
56}
57
58// ==
59
60GNet::TimerList::Lock::Lock( TimerList & timer_list ) :
61 m_timer_list(timer_list)
62{
63 m_timer_list.lock() ;
64}
65
66GNet::TimerList::Lock::~Lock()
67{
68 m_timer_list.unlock() ;
69}
70
71// ==
72
73 GNet::TimerList * GNet::TimerList::m_this = nullptr ;
74
75 GNet::TimerList::TimerList()
76{
77 if( m_this == nullptr )
78 m_this = this ;
79}
80
81 GNet::TimerList::~TimerList()
82{
83 if( m_this == this )
84 m_this = nullptr ;
85}
86
87 void GNet::TimerList::add( TimerBase & t , EventState es )
88{
89 (m_locked?m_list_added:m_list).emplace_back( &t , es ) ;
90}
91
92 void GNet::TimerList::remove( TimerBase & timer ) noexcept
93{
94 m_removed = true ;
95 removeFrom( m_list , &timer ) ;
96 removeFrom( m_list_added , &timer ) ;
97 if( m_soonest == &timer ) m_soonest = nullptr ;
98}
99
100 void GNet::TimerList::removeFrom( List & list , TimerBase * timer_p ) noexcept
101{
102 for( auto & list_item : list )
103 list_item.resetIf( timer_p ) ;
104}
105
106 void GNet::TimerList::disarm( ExceptionHandler * eh ) noexcept
107{
108 disarmIn( m_list , eh ) ;
109 disarmIn( m_list_added , eh ) ;
110}
111
112 void GNet::TimerList::disarmIn( List & list , ExceptionHandler * eh ) noexcept
113{
114 for( auto & list_item : list )
115 list_item.disarmIf( eh ) ;
116}
117
118 void GNet::TimerList::updateOnStart( TimerBase & timer )
119{
120 if( timer.immediate() )
121 timer.adjust( m_adjust++ ) ; // well-defined t() order for immediate timers
122
123 if( m_soonest == &timer )
124 m_soonest = nullptr ;
125
126 if( m_soonest != nullptr && timer.tref() < m_soonest->tref() )
127 m_soonest = &timer ;
128}
129
130 void GNet::TimerList::updateOnCancel( TimerBase & timer )
131{
132 G_ASSERT( !timer.active() ) ;
133 if( m_soonest == &timer )
134 m_soonest = nullptr ;
135}
136
137 const GNet::TimerBase * GNet::TimerList::findSoonest() const
138{
139 G_ASSERT( !m_locked ) ;
140 TimerBase * result = nullptr ;
141 for( const auto & t : m_list )
142 {
143 if( t.m_timer != nullptr && t.m_timer->active() && ( result == nullptr || t.m_timer->tref() < result->tref() ) )
144 result = t.m_timer ;
145 }
146 return result ;
147}
148
149std::pair<G::TimeInterval,bool> GNet::TimerList::interval() const
150{
151 if( m_soonest == nullptr )
152 m_soonest = findSoonest() ;
153
154 if( m_soonest == nullptr )
155 {
156 return std::make_pair( G::TimeInterval(0) , true ) ;
157 }
158 else if( m_soonest->immediate() )
159 {
160 return std::make_pair( G::TimeInterval(0) , false ) ;
161 }
162 else
163 {
164 G::TimerTime now = G::TimerTime::now() ;
165 G::TimerTime then = m_soonest->t() ;
166 return std::make_pair( G::TimeInterval(now,then) , false ) ;
167 }
168}
169
170 GNet::TimerList * GNet::TimerList::ptr() noexcept
171{
172 return m_this ;
173}
174
175#ifndef G_LIB_SMALL
176 bool GNet::TimerList::exists()
177{
178 return m_this != nullptr ;
179}
180#endif
181
182 GNet::TimerList & GNet::TimerList::instance()
183{
184 if( m_this == nullptr )
185 throw NoInstance() ;
186 return * m_this ;
187}
188
189 void GNet::TimerList::lock()
190{
191 m_locked = true ;
192}
193
194 void GNet::TimerList::unlock()
195{
196 if( m_locked )
197 {
198 m_locked = false ;
199 mergeAdded() ; // accept any add()ed while locked
200 purgeRemoved() ; // collect garbage created by remove()
201 }
202}
203
204 void GNet::TimerList::mergeAdded()
205{
206 if( !m_list_added.empty() )
207 {
208 if( m_soonest != nullptr && (m_list.size()+m_list_added.size()) > m_list.capacity() )
209 m_soonest = nullptr ; // about to be invalidated
210 m_list.reserve( m_list.size() + m_list_added.size() ) ;
211 m_list.insert( m_list.end() , m_list_added.begin() , m_list_added.end() ) ;
212 m_list_added.clear() ;
213 }
214}
215
216 void GNet::TimerList::purgeRemoved()
217{
218 if( m_removed )
219 {
220 m_removed = false ;
221 m_soonest = nullptr ; // about to be invalidated
222 m_list.erase( std::remove_if( m_list.begin() , m_list.end() ,
223 [](const ListItem &v_){ return v_.m_timer == nullptr ; } ) , m_list.end() ) ;
224 }
225}
226
227 void GNet::TimerList::doTimeouts()
228{
229 G_ASSERT( m_list_added.empty() ) ;
230 Lock lock( *this ) ;
231 m_adjust = 0 ;
232 G::TimerTime now = G::TimerTime::zero() ; // lazy initialisation to G::TimerTime::now() in G::Timer::expired()
233
234 // move expired timers to the front
235 auto expired_end = std::partition( m_list.begin() , m_list.end() ,
236 [&now](const ListItem &li_){ return li_.m_timer != nullptr && li_.m_timer->active() && li_.m_timer->expired(now) ; } ) ;
237
238 // sort expired timers so that they are handled in time order
239 std::sort( m_list.begin() , expired_end ,
240 [](const ListItem &a,const ListItem &b){ return a.m_timer->tref() < b.m_timer->tref() ; } ) ;
241
242 // invalidate the soonest pointer, except in the degenerate case where nothing changes
243 if( expired_end != m_list.begin() )
244 m_soonest = nullptr ;
245
246 // call each expired timer's handler
247 for( List::iterator item_p = m_list.begin() ; item_p != expired_end ; ++item_p )
248 {
249 // (make sure the timer is still valid and expired in case another timer's handler has changed it)
250 if( item_p->m_timer != nullptr && item_p->m_timer->active() && item_p->m_timer->expired(now) )
251 doTimeout( *item_p ) ;
252 }
253
254 // unlock the list explicitly to avoid the Lock dtor throwing
255 unlock() ;
256}
257
258 void GNet::TimerList::doTimeout( ListItem & item )
259{
260 // see also GNet::EventEmitter::raiseEvent()
261 EventLoggingContext set_logging_context( item.m_es ) ;
262 try
263 {
264 item.m_timer->doTimeout() ;
265 }
266 catch( GNet::Done & e ) // (caught separately to avoid requiring rtti)
267 {
268 if( item.m_es.hasExceptionHandler() )
269 item.m_es.doOnException( e , true ) ;
270 else
271 throw ;
272 }
273 catch( std::exception & e )
274 {
275 if( item.m_es.hasExceptionHandler() )
276 item.m_es.doOnException( e , false ) ;
277 else
278 throw ;
279 }
280}
281
GNet::Done
An exception class that is caught separately by GNet::EventEmitter and GNet::TimerList so that onExce...
Definition: gnetdone.h:40
GNet::EventLoggingContext
A class that sets the G::LogOuput::context() while in scope.
Definition: geventloggingcontext.h:38
GNet::EventState
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
GNet::ExceptionHandler
An abstract interface for handling exceptions thrown out of event-loop callbacks (socket/future event...
Definition: gexceptionhandler.h:44
GNet::TimerBase
An interface used by GNet::TimerList to keep track of pending timeouts and to deliver timeout events.
Definition: gtimer.h:42
GNet::TimerBase::adjust
void adjust(unsigned long)
Used by TimerList to set the order of immedate() timer expiry.
Definition: gtimer.cpp:90
GNet::TimerBase::immediate
bool immediate() const
Used by TimerList.
Definition: gtimer.cpp:85
GNet::TimerBase::tref
const G::TimerTime & tref() const noexcept
An inline noexcept alternative to t().
Definition: gtimer.h:112
GNet::TimerBase::active
bool active() const noexcept
Returns true if the timer is started and not cancelled.
Definition: gtimer.h:118
GNet::TimerList
A singleton which maintains a list of all Timer objects, and interfaces to the event loop on their be...
Definition: gtimerlist.h:71
GNet::TimerList::interval
std::pair< G::TimeInterval, bool > interval() const
Returns the interval to the first timer expiry.
Definition: gtimerlist.cpp:149
GNet::TimerList::remove
void remove(TimerBase &) noexcept
Removes a timer from the list.
Definition: gtimerlist.cpp:92
GNet::TimerList::doTimeouts
void doTimeouts()
Triggers the timeout callbacks of any expired timers.
Definition: gtimerlist.cpp:227
GNet::TimerList::exists
static bool exists()
Returns true if instance() exists.
Definition: gtimerlist.cpp:176
GNet::TimerList::disarm
void disarm(ExceptionHandler *) noexcept
Resets any matching ExceptionHandler pointers.
Definition: gtimerlist.cpp:106
GNet::TimerList::updateOnStart
void updateOnStart(TimerBase &)
Called by Timer when a timer is started.
Definition: gtimerlist.cpp:118
GNet::TimerList::add
void add(TimerBase &, EventState)
Adds a timer. Called from the Timer constructor.
Definition: gtimerlist.cpp:87
GNet::TimerList::instance
static TimerList & instance()
Singleton access. Throws an exception if none.
Definition: gtimerlist.cpp:182
GNet::TimerList::ptr
static TimerList * ptr() noexcept
Singleton access. Returns nullptr if none.
Definition: gtimerlist.cpp:170
GNet::TimerList::TimerList
TimerList()
Default constructor.
Definition: gtimerlist.cpp:75
GNet::TimerList::updateOnCancel
void updateOnCancel(TimerBase &)
Called by Timer when a timer is cancelled.
Definition: gtimerlist.cpp:130
GNet::TimerList::~TimerList
~TimerList()
Destructor.
Definition: gtimerlist.cpp:81
G::TimeInterval
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:305
G::TimerTime
A monotonically increasing subsecond-resolution timestamp, notionally unrelated to time_t.
Definition: gdatetime.h:231
G::TimerTime::now
static TimerTime now()
Factory function for the current steady-clock time.
Definition: gdatetime.cpp:479
G::TimerTime::zero
static TimerTime zero()
Factory function for the start of the epoch, guaranteed to be less than any now().
Definition: gdatetime.cpp:486
gassert.h
geventloggingcontext.h
geventloop.h
glog.h
gnetdone.h
gtimer.h
gtimerlist.h