E-MailRelay

geventloop_select.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 geventloop_select.cpp
19///
20
21#include "gdef.h"
22#include "gscope.h"
23#include "gevent.h"
24#include "geventemitter.h"
25#include "gprocess.h"
26#include "gexception.h"
27#include "gstr.h"
28#include "gtimer.h"
29#include "gtimerlist.h"
30#include "gtest.h"
31#include "glog.h"
32#include "gassert.h"
33#include <memory>
34#include <sys/types.h>
35#include <sys/time.h>
36
37 namespace GNet
38{
39 class EventLoopImp ;
40}
41
42 class GNet::EventLoopImp : public EventLoop
43{
44 public:
45 G_EXCEPTION( Error , tx("select error") )
46 EventLoopImp() ;
47
48private: // overrides
49 std::string run() override ;
50 bool running() const override ;
51 void quit( const std::string & ) override ;
52 void quit( const G::SignalSafe & ) override ;
53 void addRead( Descriptor , EventHandler & , EventState ) override ;
54 void addWrite( Descriptor , EventHandler & , EventState ) override ;
55 void addOther( Descriptor , EventHandler & , EventState ) override ;
56 void dropRead( Descriptor ) noexcept override ;
57 void dropWrite( Descriptor ) noexcept override ;
58 void dropOther( Descriptor ) noexcept override ;
59 void drop( Descriptor ) noexcept override ;
60 void disarm( ExceptionHandler * ) noexcept override ;
61
62public:
63 ~EventLoopImp() override = default ;
64 EventLoopImp( const EventLoopImp & ) = delete ;
65 EventLoopImp( EventLoopImp && ) = delete ;
66 EventLoopImp & operator=( const EventLoopImp & ) = delete ;
67 EventLoopImp & operator=( EventLoopImp && ) = delete ;
68
69private:
70 struct ListItem
71 {
72 EventHandler * m_handler {nullptr} ;
73 EventState m_es {EventState::Private(),nullptr,nullptr} ;
74 void update( EventHandler * handler , EventState es ) noexcept { m_handler = handler ; m_es = es ; }
75 } ;
76 using List = std::vector<ListItem> ; // indexed by fd
77 void runOnce() ;
78 static void addImp( int fd , EventHandler & , EventState , fd_set & , List & , int & ) ;
79 static void disarmImp( List & , ExceptionHandler * ) noexcept ;
80 static void disarmImp( EventState & , ExceptionHandler * ) noexcept ;
81 static void dropImp( int fd , fd_set & , fd_set & , int & ) noexcept ;
82 static int events( int nfds , fd_set * ) noexcept ;
83 static int fdmaxof( int nfds , fd_set * ) noexcept ;
84 static int fdmaxof( std::size_t nfds , fd_set * sp ) noexcept ;
85
86 private:
87 bool m_quit {false} ;
88 std::string m_quit_reason ;
89 bool m_running {false} ;
90 fd_set m_read_set ;
91 fd_set m_write_set ;
92 fd_set m_other_set ;
93 int m_read_fdmax {-1} ;
94 int m_write_fdmax {-1} ;
95 int m_other_fdmax {-1} ;
96 fd_set m_read_set_copy ;
97 fd_set m_write_set_copy ;
98 fd_set m_other_set_copy ;
99 List m_read_list ;
100 List m_write_list ;
101 List m_other_list ;
102 EventState m_es_current {EventState::Private(),nullptr,nullptr} ;
103} ;
104
105// ===
106
107std::unique_ptr<GNet::EventLoop> GNet::EventLoop::create()
108{
109 return std::make_unique<EventLoopImp>() ;
110}
111
112// ===
113
114GNet::EventLoopImp::EventLoopImp() // NOLINT cppcoreguidelines-pro-type-member-init
115{
116 FD_ZERO( &m_read_set ) ; // NOLINT
117 FD_ZERO( &m_write_set ) ; // NOLINT
118 FD_ZERO( &m_other_set ) ; // NOLINT
119 FD_ZERO( &m_read_set_copy ) ; // NOLINT
120 FD_ZERO( &m_write_set_copy ) ; // NOLINT
121 FD_ZERO( &m_other_set_copy ) ; // NOLINT
122 m_read_list.reserve( FD_SETSIZE ) ;
123 m_write_list.reserve( FD_SETSIZE ) ;
124 m_other_list.reserve( FD_SETSIZE ) ;
125}
126
127std::string GNet::EventLoopImp::run()
128{
129 G::ScopeExitSetFalse running( m_running = true ) ;
130 do
131 {
132 runOnce() ;
133 } while( !m_quit ) ;
134 std::string quit_reason = m_quit_reason ;
135 m_quit_reason.clear() ;
136 m_quit = false ;
137 return quit_reason ;
138}
139
140 bool GNet::EventLoopImp::running() const
141{
142 return m_running ;
143}
144
145 void GNet::EventLoopImp::quit( const std::string & reason )
146{
147 m_quit = true ;
148 m_quit_reason = reason ;
149}
150
151 void GNet::EventLoopImp::quit( const G::SignalSafe & )
152{
153 m_quit = true ;
154}
155
156 void GNet::EventLoopImp::runOnce()
157{
158 // get a timeout interval() from TimerList
159 //
160 using Timeval = struct timeval ;
161 Timeval timeout ;
162 Timeval * timeout_p = nullptr ;
163 bool immediate = false ;
164 if( TimerList::ptr() != nullptr )
165 {
166 G::TimeInterval interval = G::TimeInterval::zero() ;
167 bool infinite = false ;
168 std::tie( interval , infinite ) = TimerList::instance().interval() ;
169 timeout.tv_sec = interval.s() ;
170 timeout.tv_usec = interval.us() ;
171 timeout_p = infinite ? nullptr : &timeout ;
172 immediate = !infinite && interval.s() == 0 && interval.us() == 0U ;
173 }
174
175 // find the highest fd value to pass to select() -- probably unnecessary
176 // that this is the exact maximum rather than an upper bound, but
177 // that's what is specified -- the fdmax data members are maintained by
178 // addImp() but invalidated by dropImp() so we need to re-evaluate if
179 // invalid -- we can use the size of the list as as upper bound for
180 // the required fd_set tests
181 //
182 if( m_read_fdmax == -1 ) m_read_fdmax = fdmaxof( m_read_list.size() , &m_read_set ) ;
183 if( m_write_fdmax == -1 ) m_write_fdmax = fdmaxof( m_write_list.size() , &m_write_set ) ;
184 if( m_other_fdmax == -1 ) m_other_fdmax = fdmaxof( m_other_list.size() , &m_other_set ) ;
185 int nfds = 1 + std::max( {m_read_fdmax,m_write_fdmax,m_other_fdmax} ) ;
186
187 G_ASSERT( fdmaxof(FD_SETSIZE,&m_read_set) == m_read_fdmax ) ;
188 G_ASSERT( fdmaxof(FD_SETSIZE,&m_write_set) == m_write_fdmax ) ;
189 G_ASSERT( fdmaxof(FD_SETSIZE,&m_other_set) == m_other_fdmax ) ;
190 G_ASSERT( m_read_list.size() >= std::size_t(m_read_fdmax+1) ) ;
191 G_ASSERT( m_write_list.size() >= std::size_t(m_write_fdmax+1) ) ;
192 G_ASSERT( m_other_list.size() >= std::size_t(m_other_fdmax+1) ) ;
193
194 // do the select() -- use fd_set copies for the select() parameters because select()
195 // modifies them, and in any case our originals might be modified as we iterate over
196 // the results and call event handlers
197 //
198 m_read_set_copy = m_read_set ;
199 m_write_set_copy = m_write_set ;
200 m_other_set_copy = m_other_set ;
201 int rc = ::select( nfds , &m_read_set_copy , &m_write_set_copy , &m_other_set_copy , timeout_p ) ;
202 if( rc < 0 )
203 {
204 int e = G::Process::errno_() ;
205 if( e != EINTR ) // eg. when profiling
206 throw Error( G::Str::fromInt(e) ) ;
207 }
208 G_ASSERT( rc < 0 ||
209 rc == (events(nfds,&m_read_set_copy)+events(nfds,&m_write_set_copy)+events(nfds,&m_other_set_copy)) ) ;
210
211 // call the timeout handlers
212 //
213 if( rc == 0 || immediate )
214 {
215 TimerList::instance().doTimeouts() ;
216 }
217
218 // call the fd event handlers -- count them as we go (ecount) so that we don't
219 // have to iterate over the whole set if we have handled the expected number
220 // as returned by select() -- note that event handlers can remove fds from
221 // the 'copy' sets (see dropRead() etc) but not add them -- that means ecount
222 // might never reach the expected value and we do end up iterating over the
223 // whole set, but it still works as an optimisation in the common case
224 //
225 int ecount = 0 ;
226 for( int fd = 0 ; ecount < rc && fd < nfds ; fd++ )
227 {
228 if( FD_ISSET(fd,&m_read_set_copy) )
229 {
230 G_ASSERT( static_cast<unsigned int>(fd) < m_read_list.size() ) ;
231 ecount++ ;
232 m_es_current = m_read_list[fd].m_es ; // see disarm()
233 EventEmitter::raiseReadEvent( m_read_list[fd].m_handler , m_es_current ) ;
234 }
235 if( FD_ISSET(fd,&m_write_set_copy) )
236 {
237 G_ASSERT( static_cast<unsigned int>(fd) < m_write_list.size() ) ;
238 ecount++ ;
239 m_es_current = m_write_list[fd].m_es ; // see disarm()
240 EventEmitter::raiseWriteEvent( m_write_list[fd].m_handler , m_es_current ) ;
241 }
242 if( FD_ISSET(fd,&m_other_set_copy) )
243 {
244 G_ASSERT( static_cast<unsigned int>(fd) < m_other_list.size() ) ;
245 ecount++ ;
246 m_es_current = m_other_list[fd].m_es ; // see disarm()
247 EventEmitter::raiseOtherEvent( m_other_list[fd].m_handler , m_es_current , EventHandler::Reason::other ) ;
248 }
249 }
250}
251
252 int GNet::EventLoopImp::events( int nfds , fd_set * sp ) noexcept
253{
254 int n = 0 ;
255 for( int fd = 0 ; fd < nfds ; fd++ )
256 {
257 if( FD_ISSET(fd,sp) )
258 n++ ;
259 }
260 return n ;
261}
262
263 inline int GNet::EventLoopImp::fdmaxof( std::size_t nfds , fd_set * sp ) noexcept
264{
265 return fdmaxof( static_cast<int>(nfds) , sp ) ;
266}
267
268 int GNet::EventLoopImp::fdmaxof( int nfds , fd_set * sp ) noexcept
269{
270 int fdmax = -1 ;
271 for( int fd = 0 ; fd < nfds ; fd++ )
272 {
273 if( FD_ISSET(fd,sp) )
274 fdmax = fd ;
275 }
276 return fdmax ;
277}
278
279 void GNet::EventLoopImp::addRead( Descriptor fdd , EventHandler & handler , EventState es )
280{
281 if( fdd.fd() >= 0 )
282 addImp( fdd.fd() , handler , es , m_read_set , m_read_list , m_read_fdmax ) ;
283}
284
285 void GNet::EventLoopImp::addWrite( Descriptor fdd , EventHandler & handler , EventState es )
286{
287 if( fdd.fd() >= 0 )
288 addImp( fdd.fd() , handler , es , m_write_set , m_write_list , m_write_fdmax ) ;
289}
290
291 void GNet::EventLoopImp::addOther( Descriptor fdd , EventHandler & handler , EventState es )
292{
293 if( fdd.fd() >= 0 )
294 addImp( fdd.fd() , handler , es , m_other_set , m_other_list , m_other_fdmax ) ;
295}
296
297 void GNet::EventLoopImp::addImp( int fd , EventHandler & handler , EventState es , fd_set & set , List & list , int & fdmax )
298{
299 G_ASSERT( fd >= 0 ) ;
300 G_ASSERT( fdmax >= -1 ) ;
301 if( fd >= FD_SETSIZE )
302 throw EventLoop::Overflow( "too many open file descriptors for select()" ) ;
303
304 // make sure drop() is called if the EventHandler goes away -- see EventHandler::dtor
305 handler.setDescriptor( Descriptor(fd) ) ;
306
307 // update the list
308 if( list.size() < std::size_t(fd+1) )
309 list.resize( fd+1 ) ;
310 list[fd].update( &handler , es ) ;
311
312 // update the set
313 FD_SET( fd , &set ) ;
314 fdmax = std::max( fdmax , fd ) ;
315
316 G_ASSERT( list.size() >= static_cast<std::size_t>(fdmax+1) ) ;
317}
318
319 void GNet::EventLoopImp::dropRead( Descriptor fdd ) noexcept
320{
321 if( fdd.fd() >= 0 )
322 dropImp( fdd.fd() , m_read_set , m_read_set_copy , m_read_fdmax ) ;
323}
324
325 void GNet::EventLoopImp::dropWrite( Descriptor fdd ) noexcept
326{
327 if( fdd.fd() >= 0 )
328 dropImp( fdd.fd() , m_write_set , m_write_set_copy , m_write_fdmax ) ;
329}
330
331 void GNet::EventLoopImp::dropOther( Descriptor fdd ) noexcept
332{
333 if( fdd.fd() >= 0 )
334 dropImp( fdd.fd() , m_read_set , m_read_set_copy , m_read_fdmax ) ;
335}
336
337 void GNet::EventLoopImp::dropImp( int fd , fd_set & set , fd_set & set_copy , int & fdmax ) noexcept
338{
339 G_ASSERT( fd >= 0 ) ;
340 G_ASSERT( fdmax >= -1 ) ;
341
342 // update the set
343 FD_CLR( fd , &set ) ;
344 FD_CLR( fd , &set_copy ) ; // dont deliver from the current result set
345 if( fd == fdmax )
346 fdmax = -1 ; // invalidate, force a re-evaluation before next use
347}
348
349 void GNet::EventLoopImp::drop( Descriptor fdd ) noexcept
350{
351 if( fdd.fd() >= 0 )
352 {
353 // update the sets
354 dropImp( fdd.fd() , m_read_set , m_read_set_copy , m_read_fdmax ) ;
355 dropImp( fdd.fd() , m_write_set , m_write_set_copy , m_write_fdmax ) ;
356 dropImp( fdd.fd() , m_other_set , m_other_set_copy , m_other_fdmax ) ;
357
358 // update the lists since the handler is going away
359 std::size_t ufd = static_cast<unsigned int>(fdd.fd()) ;
360 if( ufd < m_read_list.size() )
361 m_read_list[ufd].m_handler = nullptr ;
362 if( ufd < m_write_list.size() )
363 m_write_list[ufd].m_handler = nullptr ;
364 if( ufd < m_other_list.size() )
365 m_other_list[ufd].m_handler = nullptr ;
366 }
367}
368
369 void GNet::EventLoopImp::disarm( ExceptionHandler * eh ) noexcept
370{
371 // stop EventEmitter calling the specified exception handler
372 disarmImp( m_es_current , eh ) ;
373
374 // remove any other references -- this is overkill is most cases
375 // because if the exception handler is going away then all
376 // event handlers that might refer to it will have already
377 // been dropped -- exception handlers tend to be long-lived
378 // so any performance penalty is likely insignificant
379 disarmImp( m_read_list , eh ) ;
380 disarmImp( m_write_list , eh ) ;
381 disarmImp( m_other_list , eh ) ;
382}
383
384 void GNet::EventLoopImp::disarmImp( List & list , ExceptionHandler * eh ) noexcept
385{
386 for( auto & list_item : list )
387 {
388 if( list_item.m_es.eh() == eh )
389 list_item.m_es.disarm() ;
390 }
391}
392
393 void GNet::EventLoopImp::disarmImp( EventState & es , ExceptionHandler * eh ) noexcept
394{
395 if( es.eh() == eh )
396 es.disarm() ;
397}
398
GNet::EventEmitter::raiseReadEvent
static void raiseReadEvent(EventHandler *, EventState &)
Calls readEvent() on the event handler and catches any exceptions and delivers them to the EventState...
Definition: geventemitter.cpp:84
GNet::EventEmitter::raiseOtherEvent
static void raiseOtherEvent(EventHandler *, EventState &, EventHandler::Reason)
Calls otherEvent() on the event handler and catches any exceptions and delivers them to the EventStat...
Definition: geventemitter.cpp:98
GNet::EventEmitter::raiseWriteEvent
static void raiseWriteEvent(EventHandler *, EventState &)
Calls writeEvent() on the event handler and catches any exceptions and delivers them to the EventStat...
Definition: geventemitter.cpp:91
GNet::EventLoop::dropWrite
virtual void dropWrite(Descriptor fd) noexcept=0
Removes the given event descriptor from the list of write sources.
GNet::EventLoop::running
virtual bool running() const =0
Returns true if called from within run().
GNet::EventLoop::drop
virtual void drop(Descriptor fd) noexcept=0
Removes the given event descriptor from the event loop as the EventHandler is being destructed.
GNet::EventLoop::create
static std::unique_ptr< EventLoop > create()
A factory method which creates an instance of a derived class on the heap.
Definition: geventloop_epoll.cpp:108
GNet::EventLoop::dropRead
virtual void dropRead(Descriptor fd) noexcept=0
Removes the given event descriptor from the list of read sources.
GNet::EventLoop::disarm
virtual void disarm(ExceptionHandler *) noexcept=0
Used to prevent the given interface from being used, typically called from the ExceptionHandler destr...
GNet::EventLoop::quit
virtual void quit(const std::string &reason)=0
Causes run() to return (once the call stack has unwound).
GNet::EventLoop::run
virtual std::string run()=0
Runs the main event loop.
GNet::EventLoop::addWrite
virtual void addWrite(Descriptor fd, EventHandler &, EventState)=0
Adds the given event source descriptor and associated handler to the write list.
GNet::EventLoop::dropOther
virtual void dropOther(Descriptor fd) noexcept=0
Removes the given event descriptor from the list of other-event sources.
GNet::EventLoop::addRead
virtual void addRead(Descriptor fd, EventHandler &, EventState)=0
Adds the given event source descriptor and associated handler to the read list.
GNet::EventLoop::addOther
virtual void addOther(Descriptor fd, EventHandler &, EventState)=0
Adds the given event source descriptor and associated handler to the exception list.
GNet::TimerList::interval
std::pair< G::TimeInterval, bool > interval() const
Returns the interval to the first timer expiry.
Definition: gtimerlist.cpp:149
GNet::TimerList::doTimeouts
void doTimeouts()
Triggers the timeout callbacks of any expired timers.
Definition: gtimerlist.cpp:227
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
G::Process::errno_
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
Definition: gprocess_unix.cpp:125
G::ScopeExitSet
A class that sets a simple variable to a particular value at the end of its scope.
Definition: gscope.h:82
G::SignalSafe
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:37
G::Str::fromInt
static std::string fromInt(int i)
Converts int 'i' to a string.
Definition: gstr.h:594
G::TimeInterval
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:305
G::TimeInterval::zero
static TimeInterval zero()
Factory function for the zero interval.
Definition: gdatetime.cpp:644
G::TimeInterval::s
unsigned int s() const
Returns the number of seconds.
Definition: gdatetime.cpp:649
G::TimeInterval::us
unsigned int us() const
Returns the fractional microseconds part.
Definition: gdatetime.cpp:654
gassert.h
gevent.h
geventemitter.h
gexception.h
glog.h
gprocess.h
gscope.h
gstr.h
gtest.h
gtimer.h
gtimerlist.h
GNet
Network classes.
Definition: gdef.h:1243
G
Low-level classes.
Definition: garg.h:36
G::tx
constexpr const char * tx(const char *p) noexcept
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
std
STL namespace.