E-MailRelay
geventloop_select.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 "gfile.h"
29#include "gtimer.h"
30#include "gtimerlist.h"
31#include "gtest.h"
32#include "glog.h"
33#include "gassert.h"
34#include <memory>
35#include <tuple>
36#include <sstream>
37#include <sys/types.h>
38#include <sys/time.h>
39
40namespace GNet
41{
42 class EventLoopImp ;
43}
44
45class GNet::EventLoopImp : public EventLoop
46{
47public:
48 G_EXCEPTION( Error , tx("select error") ) ;
49 EventLoopImp() ;
50
51private: // overrides
52 std::string run() override ;
53 bool running() const override ;
54 void quit( const std::string & ) override ;
55 void quit( const G::SignalSafe & ) override ;
56 void addRead( Descriptor fd , EventHandler & , ExceptionSink ) override ;
57 void addWrite( Descriptor fd , EventHandler & , ExceptionSink ) override ;
58 void addOther( Descriptor fd , EventHandler & , ExceptionSink ) override ;
59 void dropRead( Descriptor fd ) noexcept override ;
60 void dropWrite( Descriptor fd ) noexcept override ;
61 void dropOther( Descriptor fd ) noexcept override ;
62 void drop( Descriptor fd ) noexcept override ;
63 void disarm( ExceptionHandler * ) noexcept override ;
64
65public:
66 ~EventLoopImp() override = default ;
67 EventLoopImp( const EventLoopImp & ) = delete ;
68 EventLoopImp( EventLoopImp && ) = delete ;
69 EventLoopImp & operator=( const EventLoopImp & ) = delete ;
70 EventLoopImp & operator=( EventLoopImp && ) = delete ;
71
72private:
73 using Emitters = std::vector<EventEmitter> ;
74 void runOnce() ;
75 void addImp( int fd , Emitters & , EventHandler & , ExceptionSink ) ;
76 void dropImp( int fd , Emitters & ) noexcept ;
77 void disarmImp( Emitters & , ExceptionHandler * ) noexcept ;
78 static int events( int nfds , fd_set * ) ;
79
80private:
81 bool m_quit {false} ;
82 std::string m_quit_reason ;
83 bool m_running {false} ;
84 int m_nfds {0} ;
85 fd_set m_read_set ; // NOLINT cppcoreguidelines-pro-type-member-init
86 fd_set m_write_set ; // NOLINT cppcoreguidelines-pro-type-member-init
87 fd_set m_other_set ; // NOLINT cppcoreguidelines-pro-type-member-init
88 Emitters m_read_emitters ;
89 Emitters m_write_emitters ;
90 Emitters m_other_emitters ;
91 fd_set m_read_set_copy ;
92 fd_set m_write_set_copy ;
93 fd_set m_other_set_copy ;
94} ;
95
96// ===
97
98std::unique_ptr<GNet::EventLoop> GNet::EventLoop::create()
99{
100 return std::make_unique<EventLoopImp>() ;
101}
102
103// ===
104
105GNet::EventLoopImp::EventLoopImp() // NOLINT cppcoreguidelines-pro-type-member-init
106{
107 FD_ZERO( &m_read_set ) ; // NOLINT
108 FD_ZERO( &m_write_set ) ; // NOLINT
109 FD_ZERO( &m_other_set ) ; // NOLINT
110 FD_ZERO( &m_read_set_copy ) ; // NOLINT
111 FD_ZERO( &m_write_set_copy ) ; // NOLINT
112 FD_ZERO( &m_other_set_copy ) ; // NOLINT
113}
114
115std::string GNet::EventLoopImp::run()
116{
117 G::ScopeExitSetFalse running( m_running = true ) ;
118 do
119 {
120 runOnce() ;
121 } while( !m_quit ) ;
122 std::string quit_reason = m_quit_reason ;
123 m_quit_reason.clear() ;
124 m_quit = false ;
125 return quit_reason ;
126}
127
129{
130 return m_running ;
131}
132
133void GNet::EventLoopImp::quit( const std::string & reason )
134{
135 m_quit = true ;
136 m_quit_reason = reason ;
137}
138
140{
141 m_quit = true ;
142}
143
144void GNet::EventLoopImp::runOnce()
145{
146 // get a timeout interval() from TimerList
147 //
148 using Timeval = struct timeval ;
149 Timeval timeout ;
150 Timeval * timeout_p = nullptr ;
151 bool immediate = false ;
152 if( TimerList::ptr() != nullptr )
153 {
155 bool infinite = false ;
156 std::tie( interval , infinite ) = TimerList::instance().interval() ;
157 timeout.tv_sec = interval.s() ;
158 timeout.tv_usec = interval.us() ;
159 timeout_p = infinite ? nullptr : &timeout ;
160 immediate = !infinite && interval.s() == 0 && interval.us() == 0U ;
161 }
162
163 if( G::Test::enabled("event-loop-quitfile") ) // esp. for profiling
164 {
165 if( G::File::remove(".quit",std::nothrow) )
166 m_quit = true ;
167 if( timeout_p == nullptr || timeout.tv_sec > 0 )
168 {
169 timeout.tv_sec = 0 ;
170 timeout.tv_usec = 999999U ;
171 }
172 timeout_p = &timeout ;
173 }
174
175 // do the select()
176 //
177 int nfds = m_nfds ; // make copies since modified via event handling
178 m_read_set_copy = m_read_set ;
179 m_write_set_copy = m_write_set ;
180 m_other_set_copy = m_other_set ;
181 int rc = ::select( nfds , &m_read_set_copy , &m_write_set_copy , &m_other_set_copy , timeout_p ) ;
182 if( rc < 0 )
183 {
184 int e = G::Process::errno_() ;
185 if( e != EINTR ) // eg. when profiling
186 throw Error( G::Str::fromInt(e) ) ;
187 }
188 G_ASSERT( rc < 0 ||
189 rc == (events(nfds,&m_read_set_copy)+events(nfds,&m_write_set_copy)+events(nfds,&m_other_set_copy)) ) ;
190
191 // call the timeout handlers
192 //
193 if( rc == 0 || immediate )
194 {
196 }
197
198 // call the fd event handlers -- note that event handlers can
199 // remove fds from the 'copy' sets but not add them -- that means
200 // that ecount might be smaller that expected, but it still
201 // serves as a valid optimisation
202 //
203 int ecount = 0 ; // optimisation to stop when all events accounted for
204 for( int fd = 0 ; ecount < rc && fd < nfds ; fd++ )
205 {
206 if( FD_ISSET(fd,&m_read_set_copy) )
207 {
208 ecount++ ;
209 G_ASSERT( static_cast<unsigned int>(fd) < m_read_emitters.size() ) ;
210 m_read_emitters[fd].raiseReadEvent( Descriptor(fd) ) ;
211 }
212 if( FD_ISSET(fd,&m_write_set_copy) )
213 {
214 ecount++ ;
215 G_ASSERT( static_cast<unsigned int>(fd) < m_write_emitters.size() ) ;
216 m_write_emitters[fd].raiseWriteEvent( Descriptor(fd) ) ;
217 }
218 if( FD_ISSET(fd,&m_other_set_copy) )
219 {
220 ecount++ ;
221 G_ASSERT( static_cast<unsigned int>(fd) < m_other_emitters.size() ) ;
222 m_other_emitters[fd].raiseOtherEvent( Descriptor(fd) , EventHandler::Reason::other ) ;
223 }
224 }
225
226 // collect garbage
227 int fd_max = 0 ;
228 for( int fd = 0 ; fd < m_nfds ; fd++ )
229 {
230 if( FD_ISSET(fd,&m_read_set) ||
231 FD_ISSET(fd,&m_write_set) ||
232 FD_ISSET(fd,&m_other_set) )
233 {
234 fd_max = fd ;
235 }
236 }
237 m_nfds = fd_max + 1 ;
238 m_read_emitters.resize( m_nfds ) ;
239 m_write_emitters.resize( m_nfds ) ;
240 m_other_emitters.resize( m_nfds ) ;
241
242 if( G::Test::enabled("event-loop-slow") )
243 {
244 Timeval timeout_slow ;
245 timeout_slow.tv_sec = 0 ;
246 timeout_slow.tv_usec = 100000 ;
247 ::select( 0 , nullptr , nullptr , nullptr , &timeout_slow ) ;
248 }
249}
250
251int GNet::EventLoopImp::events( int nfds , fd_set * sp )
252{
253 int n = 0 ;
254 for( int fd = 0 ; fd < nfds ; fd++ )
255 {
256 if( FD_ISSET(fd,sp) )
257 n++ ;
258 }
259 return n ;
260}
261
262void GNet::EventLoopImp::addRead( Descriptor fd , EventHandler & handler , ExceptionSink es )
263{
264 if( fd.valid() )
265 {
266 handler.setDescriptor( fd ) ; // see EventHandler::dtor
267 addImp( fd.fd() , m_read_emitters , handler , es ) ;
268 FD_SET( fd.fd() , &m_read_set ) ;
269 }
270}
271
272void GNet::EventLoopImp::addWrite( Descriptor fd , EventHandler & handler , ExceptionSink es )
273{
274 if( fd.valid() )
275 {
276 handler.setDescriptor( fd ) ; // see EventHandler::dtor
277 addImp( fd.fd() , m_write_emitters , handler , es ) ;
278 FD_SET( fd.fd() , &m_write_set ) ;
279 }
280}
281
282void GNet::EventLoopImp::addOther( Descriptor fd , EventHandler & handler , ExceptionSink es )
283{
284 if( fd.valid() )
285 {
286 handler.setDescriptor( fd ) ; // see EventHandler::dtor
287 addImp( fd.fd() , m_other_emitters , handler , es ) ;
288 FD_SET( fd.fd() , &m_other_set ) ;
289 }
290}
291
292void GNet::EventLoopImp::addImp( int fd , Emitters & emitters , EventHandler & handler , ExceptionSink es )
293{
294 if( fd >= FD_SETSIZE )
295 throw EventLoop::Overflow( "too many open file descriptors for select()" ) ;
296
297 m_nfds = std::max( m_nfds , fd+1 ) ;
298 emitters.resize( m_nfds ) ;
299 emitters[fd].update( &handler , es ) ;
300}
301
302void GNet::EventLoopImp::dropRead( Descriptor fd ) noexcept
303{
304 if( fd.valid() )
305 {
306 FD_CLR( fd.fd() , &m_read_set ) ;
307 FD_CLR( fd.fd() , &m_read_set_copy ) ;
308 }
309}
310
311void GNet::EventLoopImp::dropWrite( Descriptor fd ) noexcept
312{
313 if( fd.valid() )
314 {
315 FD_CLR( fd.fd() , &m_write_set ) ;
316 FD_CLR( fd.fd() , &m_write_set_copy ) ;
317 }
318}
319
320void GNet::EventLoopImp::dropOther( Descriptor fd ) noexcept
321{
322 if( fd.valid() )
323 {
324 FD_CLR( fd.fd() , &m_other_set ) ;
325 FD_CLR( fd.fd() , &m_other_set_copy ) ;
326 }
327}
328
329void GNet::EventLoopImp::drop( Descriptor fd ) noexcept
330{
331 dropRead( fd ) ;
332 dropWrite( fd ) ;
333 dropOther( fd ) ;
334 std::size_t ufd = static_cast<unsigned int>(fd.fd()) ;
335 if( ufd < m_read_emitters.size() )
336 m_read_emitters[ufd].reset() ;
337 if( ufd < m_write_emitters.size() )
338 m_write_emitters[ufd].reset() ;
339 if( ufd < m_other_emitters.size() )
340 m_other_emitters[ufd].reset() ;
341}
342
343void GNet::EventLoopImp::disarm( ExceptionHandler * p ) noexcept
344{
345 disarmImp( m_read_emitters , p ) ;
346 disarmImp( m_write_emitters , p ) ;
347 disarmImp( m_other_emitters , p ) ;
348}
349
350void GNet::EventLoopImp::disarmImp( Emitters & emitters , ExceptionHandler * p ) noexcept
351{
352 for( auto & emitter : emitters )
353 {
354 emitter.disarm( p ) ;
355 }
356}
357
virtual void dropWrite(Descriptor fd) noexcept=0
Removes the given event descriptor from the list of write sources.
virtual bool running() const =0
Returns true if called from within run().
virtual void drop(Descriptor fd) noexcept=0
Removes the given event descriptor from the event loop as the EventHandler is being destructed.
static std::unique_ptr< EventLoop > create()
A factory method which creates an instance of a derived class on the heap.
virtual void dropRead(Descriptor fd) noexcept=0
Removes the given event descriptor from the list of read sources.
virtual void addOther(Descriptor fd, EventHandler &, ExceptionSink)=0
Adds the given event source descriptor and associated handler to the exception list.
virtual void disarm(ExceptionHandler *) noexcept=0
Used to prevent the given interface from being used, typically called from the ExceptionHandler destr...
virtual void quit(const std::string &reason)=0
Causes run() to return (once the call stack has unwound).
virtual void addWrite(Descriptor fd, EventHandler &, ExceptionSink)=0
Adds the given event source descriptor and associated handler to the write list.
virtual std::string run()=0
Runs the main event loop.
virtual void addRead(Descriptor fd, EventHandler &, ExceptionSink)=0
Adds the given event source descriptor and associated handler to the read list.
virtual void dropOther(Descriptor fd) noexcept=0
Removes the given event descriptor from the list of other-event sources.
std::pair< G::TimeInterval, bool > interval() const
Returns the interval to the first timer expiry.
Definition: gtimerlist.cpp:152
void doTimeouts()
Triggers the timeout callbacks of any expired timers.
Definition: gtimerlist.cpp:226
static TimerList & instance()
Singleton access. Throws an exception if none.
Definition: gtimerlist.cpp:185
static TimerList * ptr() noexcept
Singleton access. Returns nullptr if none.
Definition: gtimerlist.cpp:173
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:598
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:299
static TimeInterval zero()
Factory function for the zero interval.
Definition: gdatetime.cpp:644
unsigned int s() const
Returns the number of seconds.
Definition: gdatetime.cpp:649
unsigned int us() const
Returns the fractional microseconds part.
Definition: gdatetime.cpp:654
Network classes.
Definition: gdef.h:1144
constexpr const char * tx(const char *p)
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84