E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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