E-MailRelay
geventloop_select.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 "geventhandlerlist.h"
25#include "gprocess.h"
26#include "gexception.h"
27#include "gstr.h"
28#include "gfile.h"
29#include "gtimer.h"
30#include "gtimerlist.h"
31#include "gtest.h"
32#include "glog.h"
33#include "gassert.h"
34#include <sstream>
35#include <sys/types.h>
36#include <sys/time.h>
37
38namespace GNet
39{
40 class EventLoopImp ;
41 class FdSet ;
42}
43
44//| \class GNet::FdSet
45/// An "fd_set" wrapper class used by GNet::EventLoopImp.
46///
48{
49public:
50 FdSet() ;
51 void init( const EventHandlerList & ) ;
52 void raiseEvents( EventHandlerList & , void (EventHandler::*method)() ) ;
53 void raiseEvents( EventHandlerList & , void (EventHandler::*method)(EventHandler::Reason) ,
54 EventHandler::Reason ) ;
55 void invalidate() noexcept ;
56 int fdmax( int = 0 ) const ;
57 fd_set * operator()() ;
58
59private:
60 bool m_valid{false} ;
61 int m_fdmax{0} ;
62 fd_set m_set_internal ; // set from EventHandlerList
63 fd_set m_set_external ; // passed to select() and modified by it
64} ;
65
66//| \class GNet::EventLoopImp
67/// A concrete implementation of GNet::EventLoop using select() in its
68/// implementation.
69///
71{
72public:
73 G_EXCEPTION( Error , "select error" ) ;
74 EventLoopImp() ;
75 ~EventLoopImp() override ;
76
77private: // overrides
78 std::string run() override ;
79 bool running() const override ;
80 void quit( const std::string & ) override ;
81 void quit( const G::SignalSafe & ) override ;
82 void addRead( Descriptor fd , EventHandler & , ExceptionSink ) override ;
83 void addWrite( Descriptor fd , EventHandler & , ExceptionSink ) override ;
84 void addOther( Descriptor fd , EventHandler & , ExceptionSink ) override ;
85 void dropRead( Descriptor fd ) noexcept override ;
86 void dropWrite( Descriptor fd ) noexcept override ;
87 void dropOther( Descriptor fd ) noexcept override ;
88 void disarm( ExceptionHandler * ) noexcept override ;
89
90public:
91 EventLoopImp( const EventLoopImp & ) = delete ;
92 EventLoopImp( EventLoopImp && ) = delete ;
93 void operator=( const EventLoopImp & ) = delete ;
94 void operator=( EventLoopImp && ) = delete ;
95
96private:
97 void runOnce() ;
98 static void check( int ) ;
99
100private:
101 bool m_quit{false} ;
102 std::string m_quit_reason ;
103 bool m_running{false} ;
104 EventHandlerList m_read_list ;
105 FdSet m_read_set ;
106 EventHandlerList m_write_list ;
107 FdSet m_write_set ;
108 EventHandlerList m_other_list ;
109 FdSet m_other_set ;
110} ;
111
112// ===
113
114GNet::FdSet::FdSet() // NOLINT cppcoreguidelines-pro-type-member-init
115= default;
116
117fd_set * GNet::FdSet::operator()()
118{
119 return &m_set_external ;
120}
121
122void GNet::FdSet::invalidate() noexcept
123{
124 m_valid = false ;
125}
126
127void GNet::FdSet::init( const EventHandlerList & list )
128{
129 // if the internal set has been inivalidate()d then re-initialise
130 // it from the event-handler-list -- then copy the internal list
131 // to the external list -- the external list is passed to select()
132 // and modified by it -- this might look klunky but it is well
133 // optimised on the high frequency code paths and it keeps the
134 // choice of select()/fd_set hidden from client code
135 //
136 if( !m_valid )
137 {
138 // copy the event-handler-list into the internal fd-set
139 m_fdmax = 0 ;
140 FD_ZERO( &m_set_internal ) ; // NOLINT readability-isolate-declaration
141 const EventHandlerList::Iterator end = list.end() ;
142 for( EventHandlerList::Iterator p = list.begin() ; p != end ; ++p )
143 {
144 G_ASSERT( p.fd().valid() && p.fd().fd() >= 0 ) ;
145 Descriptor fd = p.fd() ;
146 if( fd.fd() < 0 ) continue ;
147 FD_SET( fd.fd() , &m_set_internal ) ;
148 if( (fd.fd()+1) > m_fdmax )
149 m_fdmax = (fd.fd()+1) ;
150 }
151 m_valid = true ;
152 }
153 m_set_external = m_set_internal ; // fast structure copy
154}
155
156int GNet::FdSet::fdmax( int n ) const
157{
158 return n > m_fdmax ? n : m_fdmax ;
159}
160
161void GNet::FdSet::raiseEvents( EventHandlerList & list , void (EventHandler::*method)() )
162{
163 EventHandlerList::Lock lock( list ) ; // since event handlers may change the list while we iterate
164 const EventHandlerList::Iterator end = list.end() ;
165 for( EventHandlerList::Iterator p = list.begin() ; p != end ; ++p )
166 {
167 Descriptor fd = p.fd() ;
168 if( fd.fd() >= 0 && FD_ISSET( fd.fd() , &m_set_external ) )
169 {
170 p.raiseEvent( method ) ;
171 }
172 }
173}
174
175void GNet::FdSet::raiseEvents( EventHandlerList & list , void (EventHandler::*method)(EventHandler::Reason) ,
176 EventHandler::Reason reason )
177{
178 EventHandlerList::Lock lock( list ) ; // since event handlers may change the list while we iterate
179 const EventHandlerList::Iterator end = list.end() ;
180 for( EventHandlerList::Iterator p = list.begin() ; p != end ; ++p )
181 {
182 Descriptor fd = p.fd() ;
183 if( fd.fd() >= 0 && FD_ISSET( fd.fd() , &m_set_external ) )
184 {
185 p.raiseEvent( method , reason ) ;
186 }
187 }
188}
189
190// ===
191
192std::unique_ptr<GNet::EventLoop> GNet::EventLoop::create()
193{
194 return std::make_unique<EventLoopImp>() ;
195}
196
197// ===
198
199GNet::EventLoopImp::EventLoopImp() :
200 m_read_list("read") ,
201 m_write_list("write") ,
202 m_other_list("other")
203{
204}
205
206GNet::EventLoopImp::~EventLoopImp()
207= default;
208
209std::string GNet::EventLoopImp::run()
210{
211 G::ScopeExitSetFalse running( m_running = true ) ;
212 do
213 {
214 runOnce() ;
215 } while( !m_quit ) ;
216 std::string quit_reason = m_quit_reason ;
217 m_quit_reason.clear() ;
218 m_quit = false ;
219 return quit_reason ;
220}
221
222bool GNet::EventLoopImp::running() const
223{
224 return m_running ;
225}
226
227void GNet::EventLoopImp::quit( const std::string & reason )
228{
229 m_quit = true ;
230 m_quit_reason = reason ;
231}
232
233void GNet::EventLoopImp::quit( const G::SignalSafe & )
234{
235 m_quit = true ;
236}
237
238void GNet::EventLoopImp::runOnce()
239{
240 // build fd-sets from handler lists
241 //
242 m_read_set.init( m_read_list ) ;
243 m_write_set.init( m_write_list ) ;
244 m_other_set.init( m_other_list ) ;
245 int n = m_read_set.fdmax( m_write_set.fdmax(m_other_set.fdmax()) ) ;
246
247 // get a timeout interval() from TimerList
248 //
249 using Timeval = struct timeval ;
250 Timeval timeout ;
251 Timeval * timeout_p = nullptr ;
252 bool timeout_immediate = false ;
253 if( TimerList::ptr() != nullptr )
254 {
255 std::pair<G::TimeInterval,bool> interval_pair = TimerList::instance().interval() ;
256 G::TimeInterval interval = interval_pair.first ;
257 bool timeout_infinite = interval_pair.second ;
258 timeout_immediate = !timeout_infinite && interval.s() == 0 && interval.us() == 0U ;
259 timeout.tv_sec = interval.s() ;
260 timeout.tv_usec = interval.us() ;
261 timeout_p = timeout_infinite ? nullptr : &timeout ;
262 }
263
264 if( G::Test::enabled("event-loop-quitfile") ) // esp. for profiling
265 {
266 if( G::File::remove(".quit",std::nothrow) )
267 m_quit = true ;
268 if( timeout_p == nullptr || timeout.tv_sec > 0 )
269 {
270 timeout.tv_sec = 0 ;
271 timeout.tv_usec = 999999U ;
272 }
273 timeout_p = &timeout ;
274 }
275
276 // do the select()
277 //
278 int rc = ::select( n , m_read_set() , m_write_set() , m_other_set() , timeout_p ) ;
279 if( rc < 0 )
280 {
281 int e = G::Process::errno_() ;
282 if( e != EINTR ) // eg. when profiling
283 throw Error( G::Str::fromInt(e) ) ;
284 }
285
286 // call the timeout handlers
287 //
288 if( rc == 0 || timeout_immediate )
289 {
290 //G_DEBUG( "GNet::EventLoopImp::runOnce: select() timeout" ) ;
292 }
293
294 // call the fd event handlers
295 //
296 if( rc > 0 )
297 {
298 //G_DEBUG( "GNet::EventLoopImp::runOnce: detected event(s) on " << rc << " fd(s)" ) ;
299 m_read_set.raiseEvents( m_read_list , &EventHandler::readEvent ) ;
300 m_write_set.raiseEvents( m_write_list , &EventHandler::writeEvent ) ;
301 m_other_set.raiseEvents( m_other_list , &EventHandler::otherEvent , EventHandler::Reason::other ) ;
302 }
303
304 if( G::Test::enabled("event-loop-slow") )
305 {
306 Timeval timeout_slow ;
307 timeout_slow.tv_sec = 0 ;
308 timeout_slow.tv_usec = 100000 ;
309 ::select( 0 , nullptr , nullptr , nullptr , &timeout_slow ) ;
310 }
311}
312
313void GNet::EventLoopImp::addRead( Descriptor fd , EventHandler & handler , ExceptionSink es )
314{
315 check( fd.fd() ) ;
316 m_read_list.add( fd , &handler , es ) ;
317 m_read_set.invalidate() ;
318}
319
320void GNet::EventLoopImp::addWrite( Descriptor fd , EventHandler & handler , ExceptionSink es )
321{
322 check( fd.fd() ) ;
323 m_write_list.add( fd , &handler , es ) ;
324 m_write_set.invalidate() ;
325}
326
327void GNet::EventLoopImp::addOther( Descriptor fd , EventHandler & handler , ExceptionSink es )
328{
329 check( fd.fd() ) ;
330 m_other_list.add( fd , &handler , es ) ;
331 m_other_set.invalidate() ;
332}
333
334void GNet::EventLoopImp::check( int fd )
335{
336 if( fd >= FD_SETSIZE )
337 throw EventLoop::Overflow( "too many open file descriptors for select()" ) ;
338}
339
340void GNet::EventLoopImp::dropRead( Descriptor fd ) noexcept
341{
342 m_read_list.remove( fd ) ;
343 m_read_set.invalidate() ;
344}
345
346void GNet::EventLoopImp::dropWrite( Descriptor fd ) noexcept
347{
348 m_write_list.remove( fd ) ;
349 m_write_set.invalidate() ;
350}
351
352void GNet::EventLoopImp::dropOther( Descriptor fd ) noexcept
353{
354 m_other_list.remove( fd ) ;
355 m_other_set.invalidate() ;
356}
357
358void GNet::EventLoopImp::disarm( ExceptionHandler * p ) noexcept
359{
360 m_read_list.disarm( p ) ;
361 m_write_list.disarm( p ) ;
362 m_other_list.disarm( p ) ;
363}
364
A class that encapsulates a network socket file descriptor and an associated windows event handle.
Definition: gdescriptor.h:37
A class that maps from a file descriptor to an event handler and exception handler,...
A base class for classes that handle asynchronous events from the event loop.
Definition: geventhandler.h:48
virtual void readEvent()
Called for a read event.
virtual void writeEvent()
Called for a write event.
virtual void otherEvent(Reason)
Called for a socket-exception event, or a socket-close event on windows.
A concrete implementation of GNet::EventLoop using select() in its implementation.
An abstract base class for a singleton that keeps track of open sockets and their associated handlers...
Definition: geventloop.h:53
static std::unique_ptr< EventLoop > create()
A factory method which creates an instance of a derived class on the heap.
An abstract interface for handling exceptions thrown out of event-loop callbacks (socket/future event...
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
An "fd_set" wrapper class used by GNet::EventLoopImp.
std::pair< G::TimeInterval, bool > interval() const
Returns the interval to the first timer expiry.
Definition: gtimerlist.cpp:149
void doTimeouts()
Triggers the timeout callbacks of any expired timers.
Definition: gtimerlist.cpp:221
static TimerList & instance()
Singleton access. Throws an exception if none.
Definition: gtimerlist.cpp:180
static TimerList * ptr() noexcept
Singleton access. Returns nullptr if none.
Definition: gtimerlist.cpp:170
static bool remove(const Path &path, std::nothrow_t) noexcept
Deletes the file or directory. Returns false on error.
Definition: gfile.cpp:29
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
A class that sets a boolean variable to false at the end of its scope.
Definition: gscope.h:80
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:37
static std::string fromInt(int i)
Converts int 'i' to a string.
Definition: gstr.h:561
static bool enabled() noexcept
Returns true if test features are enabled.
Definition: gtest.cpp:79
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:289
unsigned int s() const
Returns the number of seconds.
Definition: gdatetime.cpp:538
unsigned int us() const
Returns the fractional microseconds part.
Definition: gdatetime.cpp:543
Network classes.
Definition: gdef.h:1115