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