E-MailRelay
geventloop_epoll.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_epoll.cpp
19///
20
21#include "gdef.h"
22#include "gevent.h"
23#include "gscope.h"
24#include "gexception.h"
25#include "gtimerlist.h"
26#include "gprocess.h"
27#include "glog.h"
28#include "gassert.h"
29#include <algorithm>
30#include <vector>
31#include <limits>
32#include <sys/epoll.h>
33
34namespace GNet
35{
36 class EventLoopImp ;
37}
38
39class GNet::EventLoopImp : public EventLoop
40{
41public:
42 G_EXCEPTION( Error , tx("epoll error") )
43 EventLoopImp() ;
44 ~EventLoopImp() override ;
45
46private: // overrides
47 std::string run() override ;
48 bool running() const override ;
49 void quit( const std::string & ) override ;
50 void quit( const G::SignalSafe & ) override ;
51 void addRead( Descriptor , EventHandler & , EventState ) override ;
52 void addWrite( Descriptor , EventHandler & , EventState ) override ;
53 void addOther( Descriptor , EventHandler & , EventState ) override ;
54 void dropRead( Descriptor ) noexcept override ;
55 void dropWrite( Descriptor ) noexcept override ;
56 void dropOther( Descriptor ) noexcept override ;
57 void drop( Descriptor ) noexcept override ;
58 void disarm( ExceptionHandler * ) noexcept override ;
59
60public:
61 EventLoopImp( const EventLoopImp & ) = delete ;
62 EventLoopImp( EventLoopImp && ) = delete ;
63 EventLoopImp & operator=( const EventLoopImp & ) = delete ;
64 EventLoopImp & operator=( EventLoopImp && ) = delete ;
65
66private:
67 struct ListItem
68 {
69 unsigned int m_events {0U} ;
70 EventHandler * m_handler {nullptr} ;
71 EventState m_es {EventState::Private(),nullptr,nullptr} ;
72 int m_suppress_read {-1} ;
73 int m_suppress_write {-1} ;
74 void update( EventHandler * handler , EventState es ) noexcept { m_handler = handler ; m_es = es ; }
75 void disarm( ExceptionHandler * eh ) noexcept { if( m_es.eh() == eh ) m_es.disarm() ; }
76 void reset() noexcept { m_handler = nullptr ; }
77 } ;
78 using List = std::vector<ListItem> ;
79
80private:
81 void runOnce() ;
82 ListItem * find( Descriptor ) noexcept ;
83 ListItem & findOrCreate( Descriptor ) ;
84 int ms() const ;
85 static int ms( unsigned int , unsigned int ) noexcept ;
86 static void fdupdate( int , int fd , unsigned int old_events , unsigned int new_events ) ;
87 static void fdupdate( int , int fd , unsigned int old_events , unsigned int new_events , std::nothrow_t ) noexcept ;
88 static void fdadd( int , int fd , unsigned int events ) ;
89 static void fdmodify( int , int fd , unsigned int events ) ;
90 static int fdmodify( int , int fd , unsigned int events , std::nothrow_t ) noexcept ;
91 static void fdremove( int , int fd ) noexcept ;
92
93private:
94 std::vector<struct epoll_event> m_wait_events ;
95 int m_epoll_fd {-1} ;
96 bool m_running {false} ;
97 bool m_quit {false} ;
98 std::string m_quit_reason ;
99 List m_list ;
100 int m_wait_rc {0} ;
101 int m_index {-1} ;
102 int m_suppress_seq {0} ;
103 EventState m_es_current ;
104} ;
105
106// ===
107
108std::unique_ptr<GNet::EventLoop> GNet::EventLoop::create()
109{
110 return std::make_unique<EventLoopImp>() ;
111}
112
113// ===
114
115GNet::EventLoopImp::EventLoopImp() :
116 m_epoll_fd(epoll_create1(EPOLL_CLOEXEC)) ,
117 m_es_current(EventState::Private(),nullptr,nullptr)
118{
119 if( m_epoll_fd == -1 )
120 throw Error( "epoll_create" ) ;
121 m_list.reserve( 1024U ) ;
122}
123
124GNet::EventLoopImp::~EventLoopImp()
125{
126 close( m_epoll_fd ) ;
127}
128
130{
131 return m_running ;
132}
133
134std::string GNet::EventLoopImp::run()
135{
136 G::ScopeExitSetFalse running( m_running = true ) ;
137 m_quit = false ;
138 while( !m_quit )
139 {
140 runOnce() ;
141 }
142 std::string quit_reason = m_quit_reason ;
143 m_quit_reason.clear() ;
144 m_quit = false ;
145 return quit_reason ;
146}
147
148void GNet::EventLoopImp::runOnce()
149{
150 // make the output array big enough for the largest file descriptor --
151 // probably better than trying to count non-negative fds
152 m_wait_events.resize( std::max(std::size_t(1U),m_list.size()) ) ;
153
154 // extract the pending events
155 int timeout_ms = ms() ;
156 m_wait_rc = epoll_wait( m_epoll_fd , m_wait_events.data() , m_wait_events.size() , timeout_ms ) ; // NOLINT narrowing
157 if( m_wait_rc < 0 )
158 {
159 int e = G::Process::errno_() ;
160 if( e != EINTR )
161 throw Error( "epoll_wait" , G::Process::strerror(e) ) ;
162 }
163
164 // handle timer events
165 if( m_wait_rc == 0 || timeout_ms == 0 )
166 {
168 }
169
170 // shenanigans for O(1) callback suppression after drop()
171 m_suppress_seq++ ;
172 if( m_suppress_seq == std::numeric_limits<int>::max() )
173 {
174 m_suppress_seq = 0 ;
175 std::for_each( m_list.begin() , m_list.end() ,
176 [](ListItem & i_){ i_.m_suppress_read = i_.m_suppress_write = -1 ; } ) ;
177 }
178
179 // handle i/o events
180 G_ASSERT( m_index == -1 ) ;
181 G::ScopeExitSet<int,-1> resetter( m_index ) ;
182 auto wait_event = m_wait_events.begin() ;
183 for( m_index = 0 ; m_wait_rc > 0 && m_index < m_wait_rc ; m_index++ , ++wait_event )
184 {
185 Descriptor fdd( wait_event->data.fd ) ;
186 if( wait_event->events & EPOLLIN )
187 {
188 ListItem * item = find( fdd ) ;
189 if( item && item->m_suppress_read != m_suppress_seq && item->m_handler != nullptr )
190 {
191 m_es_current = item->m_es ; // see disarm()
192 EventEmitter::raiseReadEvent( item->m_handler , m_es_current ) ;
193 }
194 }
195 if( wait_event->events & EPOLLOUT )
196 {
197 ListItem * item = find( fdd ) ; // again
198 if( item && item->m_suppress_write != m_suppress_seq && item->m_handler != nullptr )
199 {
200 m_es_current = item->m_es ; // see disarm()
201 EventEmitter::raiseWriteEvent( item->m_handler , m_es_current ) ;
202 }
203 }
204 }
205}
206
207int GNet::EventLoopImp::ms() const
208{
209 constexpr int infinite = -1 ;
210 if( TimerList::ptr() )
211 {
212 auto pair = TimerList::instance().interval() ;
213 if( pair.second )
214 return infinite ;
215 else if( pair.first.s() == 0 && pair.first.us() == 0U )
216 return 0 ;
217 else
218 return std::max( 1 , ms(pair.first.s(),pair.first.us()) ) ;
219 }
220 else
221 {
222 return infinite ;
223 }
224}
225
226int GNet::EventLoopImp::ms( unsigned int s , unsigned int us ) noexcept
227{
228 constexpr unsigned int s_max = static_cast<unsigned int>( std::numeric_limits<int>::max()/1000 - 1 ) ;
229 static_assert( s_max > 600 , "" ) ; // sanity check that clipping at more than ten mins
230 return
231 s >= s_max ?
232 std::numeric_limits<int>::max() :
233 static_cast<int>( (s*1000U) + ((us+999U)/1000U) ) ;
234}
235
236void GNet::EventLoopImp::quit( const std::string & reason )
237{
238 m_quit_reason = reason ;
239 m_quit = true ;
240}
241
243{
244 m_quit = true ;
245}
246
247GNet::EventLoopImp::ListItem * GNet::EventLoopImp::find( Descriptor fdd ) noexcept
248{
249 std::size_t ufd = static_cast<unsigned int>(fdd.fd()) ;
250 return fdd.fd() >= 0 && ufd < m_list.size() ? &m_list[ufd] : nullptr ;
251}
252
253GNet::EventLoopImp::ListItem & GNet::EventLoopImp::findOrCreate( Descriptor fdd )
254{
255 ListItem * p = find( fdd ) ;
256 if( p == nullptr )
257 {
258 std::size_t ufd = static_cast<unsigned int>(fdd.fd()) ;
259 m_list.resize( std::max(m_list.size(),ufd+1U) ) ; // grow, not shrink
260 p = &m_list[ufd] ;
261 *p = ListItem() ;
262 }
263 return *p ;
264}
265
266void GNet::EventLoopImp::addRead( Descriptor fdd , EventHandler & handler , EventState es )
267{
268 G_ASSERT( fdd.fd() >= 0 ) ;
269 handler.setDescriptor( fdd ) ; // see EventHandler::dtor
270 unsigned int new_events = EPOLLIN ;
271 ListItem & item = findOrCreate( fdd ) ;
272 fdupdate( m_epoll_fd , fdd.fd() , item.m_events , item.m_events | new_events ) ;
273 item.m_events |= new_events ;
274 item.update( &handler , es ) ;
275}
276
277void GNet::EventLoopImp::addWrite( Descriptor fdd , EventHandler & handler , EventState es )
278{
279 G_ASSERT( fdd.fd() >= 0 ) ;
280 handler.setDescriptor( fdd ) ; // see EventHandler::dtor
281 unsigned int new_events = EPOLLOUT ;
282 ListItem & item = findOrCreate( fdd ) ;
283 fdupdate( m_epoll_fd , fdd.fd() , item.m_events , item.m_events | new_events ) ;
284 item.m_events |= new_events ;
285 item.update( &handler , es ) ;
286}
287
288void GNet::EventLoopImp::addOther( Descriptor , EventHandler & , EventState )
289{
290 // no-op
291}
292
293void GNet::EventLoopImp::dropRead( Descriptor fdd ) noexcept
294{
295 ListItem * item = find( fdd ) ;
296 if( item && ( item->m_events & EPOLLIN ) )
297 {
298 unsigned int new_events = item->m_events & ~EPOLLIN ;
299 fdupdate( m_epoll_fd , fdd.fd() , item->m_events , new_events , std::nothrow ) ;
300 item->m_events = new_events ;
301 item->m_suppress_read = m_suppress_seq ;
302 }
303}
304
305void GNet::EventLoopImp::dropWrite( Descriptor fdd ) noexcept
306{
307 ListItem * item = find( fdd ) ;
308 if( item && ( item->m_events & EPOLLOUT ) )
309 {
310 unsigned int new_events = item->m_events & ~EPOLLOUT ;
311 fdupdate( m_epoll_fd , fdd.fd() , item->m_events , new_events , std::nothrow ) ;
312 item->m_events = new_events ;
313 item->m_suppress_write = m_suppress_seq ;
314 }
315}
316
317void GNet::EventLoopImp::dropOther( Descriptor ) noexcept
318{
319 // no-op
320}
321
322void GNet::EventLoopImp::drop( Descriptor fdd ) noexcept
323{
324 ListItem * item = find( fdd ) ;
325 if( item )
326 {
327 if( item->m_events )
328 fdremove( m_epoll_fd , fdd.fd() ) ;
329 item->m_events = 0U ;
330 item->reset() ;
331 item->m_suppress_read = m_suppress_seq ;
332 item->m_suppress_write = m_suppress_seq ;
333 }
334}
335
336void GNet::EventLoopImp::disarm( ExceptionHandler * eh ) noexcept
337{
338 if( m_es_current.eh() == eh )
339 m_es_current.disarm() ;
340
341 for( auto & list_item : m_list )
342 list_item.disarm( eh ) ;
343}
344
345// --
346
347void GNet::EventLoopImp::fdupdate( int epoll_fd , int fd , unsigned int old_events , unsigned int new_events )
348{
349 if( new_events == 0U )
350 fdremove( epoll_fd , fd ) ;
351 else if( old_events == 0U )
352 fdadd( epoll_fd , fd , new_events ) ;
353 else
354 fdmodify( epoll_fd , fd , new_events ) ;
355}
356
357void GNet::EventLoopImp::fdupdate( int epoll_fd , int fd , unsigned int , unsigned int new_events , std::nothrow_t ) noexcept
358{
359 if( new_events == 0U )
360 fdremove( epoll_fd , fd ) ;
361 else
362 fdmodify( epoll_fd , fd , new_events , std::nothrow ) ;
363}
364
365void GNet::EventLoopImp::fdadd( int epoll_fd , int fd , unsigned int events )
366{
367 epoll_event event {} ;
368 event.data.fd = fd ;
369 event.events = events ;
370 int rc = epoll_ctl( epoll_fd , EPOLL_CTL_ADD , fd , &event ) ;
371 if( rc == -1 )
372 {
373 int e = G::Process::errno_() ;
374 throw Error( "epoll_ctl" , "add" , G::Process::strerror(e) ) ;
375 }
376}
377
378void GNet::EventLoopImp::fdmodify( int epoll_fd , int fd , unsigned int events )
379{
380 epoll_event event {} ;
381 event.data.fd = fd ;
382 event.events = events ;
383 int rc = epoll_ctl( epoll_fd , EPOLL_CTL_MOD , fd , &event ) ;
384 if( rc == -1 )
385 {
386 int e = G::Process::errno_() ;
387 throw Error( "epoll_ctl" , "modify" , G::Process::strerror(e) ) ;
388 }
389}
390
391int GNet::EventLoopImp::fdmodify( int epoll_fd , int fd , unsigned int events , std::nothrow_t ) noexcept
392{
393 epoll_event event {} ;
394 event.data.fd = fd ;
395 event.events = events ;
396 int rc = epoll_ctl( epoll_fd , EPOLL_CTL_MOD , fd , &event ) ;
397 int e = G::Process::errno_() ;
398 return rc == -1 ? e : 0 ;
399}
400
401void GNet::EventLoopImp::fdremove( int epoll_fd , int fd ) noexcept
402{
403 epoll_event event {} ;
404 epoll_ctl( epoll_fd , EPOLL_CTL_DEL , fd , &event ) ;
405}
406
static void raiseReadEvent(EventHandler *, EventState &)
Calls readEvent() on the event handler and catches any exceptions and delivers them to the EventState...
static void raiseWriteEvent(EventHandler *, EventState &)
Calls writeEvent() on the event handler and catches any exceptions and delivers them to the EventStat...
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 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 std::string run()=0
Runs the main event loop.
virtual void addWrite(Descriptor fd, EventHandler &, EventState)=0
Adds the given event source descriptor and associated handler to the write list.
virtual void dropOther(Descriptor fd) noexcept=0
Removes the given event descriptor from the list of other-event sources.
virtual void addRead(Descriptor fd, EventHandler &, EventState)=0
Adds the given event source descriptor and associated handler to the read list.
virtual void addOther(Descriptor fd, EventHandler &, EventState)=0
Adds the given event source descriptor and associated handler to the exception list.
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
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:227
static TimerList & instance()
Singleton access. Throws an exception if none.
Definition: gtimerlist.cpp:182
static TimerList * ptr() noexcept
Singleton access. Returns nullptr if none.
Definition: gtimerlist.cpp:170
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
A class that sets a simple variable to a particular value at the end of its scope.
Definition: gscope.h:82
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:37
Network classes.
Definition: gdef.h:1243
Low-level classes.
Definition: garg.h:36
constexpr const char * tx(const char *p) noexcept
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
STL namespace.