E-MailRelay
geventloop_epoll.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_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 <sys/epoll.h>
32
33namespace GNet
34{
35 class EventLoopImp ;
36}
37
38class GNet::EventLoopImp : public EventLoop
39{
40public:
41 G_EXCEPTION( Error , tx("epoll error") ) ;
42 EventLoopImp() ;
43 ~EventLoopImp() override ;
44
45private: // overrides
46 std::string run() override ;
47 bool running() const override ;
48 void quit( const std::string & ) override ;
49 void quit( const G::SignalSafe & ) override ;
50 void addRead( Descriptor fd , EventHandler & , ExceptionSink ) override ;
51 void addWrite( Descriptor fd , EventHandler & , ExceptionSink ) override ;
52 void addOther( Descriptor fd , EventHandler & , ExceptionSink ) override ;
53 void dropRead( Descriptor fd ) noexcept override ;
54 void dropWrite( Descriptor fd ) noexcept override ;
55 void dropOther( Descriptor fd ) noexcept override ;
56 void drop( Descriptor fd ) noexcept override ;
57 void disarm( ExceptionHandler * ) noexcept override ;
58
59public:
60 EventLoopImp( const EventLoopImp & ) = delete ;
61 EventLoopImp( EventLoopImp && ) = delete ;
62 EventLoopImp & operator=( const EventLoopImp & ) = delete ;
63 EventLoopImp & operator=( EventLoopImp && ) = delete ;
64
65private:
66 struct ListItem
67 {
68 unsigned int m_events {0U} ;
69 EventEmitter m_read_emitter ;
70 EventEmitter m_write_emitter ;
71 } ;
72 using List = std::vector<ListItem> ;
73
74private:
75 void runOnce() ;
76 ListItem * find( Descriptor fd ) noexcept ;
77 ListItem & findOrCreate( Descriptor fd ) ;
78 int ms() const ;
79 static int ms( unsigned int , unsigned int ) ;
80 void fdupdate( int fd , unsigned int old_events , unsigned int new_events ) ;
81 void fdupdate( int fd , unsigned int old_events , unsigned int new_events , std::nothrow_t ) noexcept ;
82 void fdadd( int fd , unsigned int events ) ;
83 void fdmodify( int fd , unsigned int events ) ;
84 int fdmodify( int fd , unsigned int events , std::nothrow_t ) noexcept ;
85 void fdremove( int fd ) noexcept ;
86
87private:
88 std::vector<struct epoll_event> m_wait_events ;
89 int m_wait_rc {0} ;
90 bool m_quit {false} ;
91 std::string m_quit_reason ;
92 bool m_running {false} ;
93 int m_fd {-1} ;
94 List m_list ;
95} ;
96
97// ===
98
99std::unique_ptr<GNet::EventLoop> GNet::EventLoop::create()
100{
101 return std::make_unique<EventLoopImp>() ;
102}
103
104// ===
105
106GNet::EventLoopImp::EventLoopImp() :
107 m_fd(epoll_create1(EPOLL_CLOEXEC))
108{
109 if( m_fd == -1 )
110 throw Error( "epoll_create" ) ;
111}
112
113GNet::EventLoopImp::~EventLoopImp()
114{
115 close( m_fd ) ;
116}
117
118void GNet::EventLoopImp::disarm( ExceptionHandler * p ) noexcept
119{
120 for( auto & item : m_list )
121 {
122 item.m_read_emitter.disarm( p ) ;
123 item.m_write_emitter.disarm( p ) ;
124 }
125}
126
128{
129 return m_running ;
130}
131
132std::string GNet::EventLoopImp::run()
133{
134 G::ScopeExitSetFalse running( m_running = true ) ;
135 m_quit = false ;
136 while( !m_quit )
137 {
138 runOnce() ;
139 }
140 std::string quit_reason = m_quit_reason ;
141 m_quit_reason.clear() ;
142 m_quit = false ;
143 return quit_reason ;
144}
145
146void GNet::EventLoopImp::runOnce()
147{
148 // make the output array big enough for the largest file descriptor --
149 // probably better than trying to count non-negative fds
150 m_wait_events.resize( std::max(std::size_t(1U),m_list.size()) ) ;
151
152 // extract the pending events
153 int timeout_ms = ms() ;
154 m_wait_rc = epoll_wait( m_fd , &m_wait_events[0] , m_wait_events.size() , timeout_ms ) ;
155 if( m_wait_rc < 0 )
156 {
157 int e = G::Process::errno_() ;
158 if( e != EINTR )
159 throw Error( "epoll_wait" , G::Process::strerror(e) ) ;
160 }
161
162 // handle timer events
163 if( m_wait_rc == 0 || timeout_ms == 0 )
164 {
166 }
167
168 // handle i/o events
169 for( int i = 0 ; m_wait_rc > 0 && i < m_wait_rc ; i++ )
170 {
171 unsigned int e = m_wait_events[i].events ;
172 Descriptor fd( m_wait_events[i].data.fd ) ;
173 if( e & EPOLLIN )
174 {
175 ListItem * item = find( fd ) ;
176 if( item )
177 item->m_read_emitter.raiseReadEvent( fd ) ;
178 }
179 if( e & EPOLLOUT )
180 {
181 ListItem * item = find( fd ) ; // again
182 if( item )
183 item->m_write_emitter.raiseWriteEvent( fd ) ;
184 }
185 }
186}
187
188int GNet::EventLoopImp::ms() const
189{
190 constexpr int infinite = -1 ;
191 if( TimerList::ptr() )
192 {
193 auto pair = TimerList::instance().interval() ;
194 if( pair.second )
195 return infinite ;
196 else if( pair.first.s() == 0 && pair.first.us() == 0U )
197 return 0 ;
198 else
199 return std::max( 1 , ms(pair.first.s(),pair.first.us()) ) ;
200 }
201 else
202 {
203 return infinite ;
204 }
205}
206
207int GNet::EventLoopImp::ms( unsigned int s , unsigned int us )
208{
209 constexpr unsigned int s_max = static_cast<unsigned int>( std::numeric_limits<int>::max()/1000 - 1 ) ;
210 static_assert( s_max > 600 , "" ) ; // sanity check that clipping at more than ten mins
211 return
212 s >= s_max ?
213 std::numeric_limits<int>::max() :
214 static_cast<int>( (s*1000U) + ((us+999U)/1000U) ) ;
215}
216
217void GNet::EventLoopImp::quit( const std::string & reason )
218{
219 m_quit_reason = reason ;
220 m_quit = true ;
221}
222
224{
225 m_quit = true ;
226}
227
228GNet::EventLoopImp::ListItem * GNet::EventLoopImp::find( Descriptor fd ) noexcept
229{
230 std::size_t ufd = static_cast<unsigned int>(fd.fd()) ;
231 return fd.valid() && ufd < m_list.size() ? &m_list[ufd] : nullptr ;
232}
233
234GNet::EventLoopImp::ListItem & GNet::EventLoopImp::findOrCreate( Descriptor fd )
235{
236 ListItem * p = find( fd ) ;
237 if( p == nullptr )
238 {
239 std::size_t ufd = static_cast<unsigned int>(fd.fd()) ;
240 m_list.resize( std::max(m_list.size(),ufd+1U) ) ; // grow, not shrink
241 p = &m_list[ufd] ;
242 *p = ListItem() ;
243 }
244 return *p ;
245}
246
247void GNet::EventLoopImp::addRead( Descriptor fd , EventHandler & handler , ExceptionSink es )
248{
249 G_ASSERT( fd.valid() ) ;
250 handler.setDescriptor( fd ) ; // see EventHandler::dtor
251 unsigned int new_events = EPOLLIN ;
252 ListItem & item = findOrCreate( Descriptor(fd) ) ;
253 fdupdate( fd.fd() , item.m_events , item.m_events | new_events ) ;
254 item.m_events |= new_events ;
255 item.m_read_emitter.update( &handler , es ) ;
256}
257
258void GNet::EventLoopImp::addWrite( Descriptor fd , EventHandler & handler , ExceptionSink es )
259{
260 G_ASSERT( fd.valid() ) ;
261 handler.setDescriptor( fd ) ; // see EventHandler::dtor
262 unsigned int new_events = EPOLLOUT ;
263 ListItem & item = findOrCreate( Descriptor(fd) ) ;
264 fdupdate( fd.fd() , item.m_events , item.m_events | new_events ) ;
265 item.m_events |= new_events ;
266 item.m_write_emitter.update( &handler , es ) ;
267}
268
269void GNet::EventLoopImp::addOther( Descriptor , EventHandler & , ExceptionSink )
270{
271 // no-op
272}
273
274void GNet::EventLoopImp::dropRead( Descriptor fd ) noexcept
275{
276 ListItem * item = find( Descriptor(fd) ) ;
277 if( item && ( item->m_events & EPOLLIN ) )
278 {
279 unsigned int new_events = item->m_events & ~EPOLLIN ;
280 fdupdate( fd.fd() , item->m_events , new_events , std::nothrow ) ;
281 item->m_events = new_events ;
282 }
283}
284
285void GNet::EventLoopImp::dropWrite( Descriptor fd ) noexcept
286{
287 ListItem * item = find( Descriptor(fd) ) ;
288 if( item && ( item->m_events & EPOLLOUT ) )
289 {
290 unsigned int new_events = item->m_events & ~EPOLLOUT ;
291 fdupdate( fd.fd() , item->m_events , new_events , std::nothrow ) ;
292 item->m_events = new_events ;
293 }
294}
295
296void GNet::EventLoopImp::dropOther( Descriptor ) noexcept
297{
298 // no-op
299}
300
301void GNet::EventLoopImp::drop( Descriptor fd ) noexcept
302{
303 ListItem * item = find( fd ) ;
304 if( item )
305 {
306 if( item->m_events )
307 fdremove( fd.fd() ) ;
308 item->m_events = 0U ;
309 item->m_read_emitter.reset() ;
310 item->m_write_emitter.reset() ;
311 }
312}
313
314void GNet::EventLoopImp::fdupdate( int fd , unsigned int old_events , unsigned int new_events )
315{
316 if( new_events == 0U )
317 fdremove( fd ) ;
318 else if( old_events == 0U )
319 fdadd( fd , new_events ) ;
320 else
321 fdmodify( fd , new_events ) ;
322}
323
324void GNet::EventLoopImp::fdupdate( int fd , unsigned int /*old_events*/ , unsigned int new_events , std::nothrow_t ) noexcept
325{
326 if( new_events == 0U )
327 fdremove( fd ) ;
328 else
329 fdmodify( fd , new_events , std::nothrow ) ;
330}
331
332void GNet::EventLoopImp::fdadd( int fd , unsigned int events )
333{
334 epoll_event event {} ;
335 event.data.fd = fd ;
336 event.events = events ;
337 int rc = epoll_ctl( m_fd , EPOLL_CTL_ADD , fd , &event ) ;
338 if( rc == -1 )
339 {
340 int e = G::Process::errno_() ;
341 throw Error( "epoll_ctl" , "add" , G::Process::strerror(e) ) ;
342 }
343}
344
345void GNet::EventLoopImp::fdmodify( int fd , unsigned int events )
346{
347 epoll_event event {} ;
348 event.data.fd = fd ;
349 event.events = events ;
350 int rc = epoll_ctl( m_fd , EPOLL_CTL_MOD , fd , &event ) ;
351 if( rc == -1 )
352 {
353 int e = G::Process::errno_() ;
354 throw Error( "epoll_ctl" , "modify" , G::Process::strerror(e) ) ;
355 }
356}
357
358int GNet::EventLoopImp::fdmodify( int fd , unsigned int events , std::nothrow_t ) noexcept
359{
360 epoll_event event {} ;
361 event.data.fd = fd ;
362 event.events = events ;
363 int rc = epoll_ctl( m_fd , EPOLL_CTL_MOD , fd , &event ) ;
364 int e = G::Process::errno_() ;
365 return rc == -1 ? e : 0 ;
366}
367
368void GNet::EventLoopImp::fdremove( int fd ) noexcept
369{
370 epoll_event event {} ;
371 epoll_ctl( m_fd , EPOLL_CTL_DEL , fd , &event ) ;
372}
373
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 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 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
Network classes.
Definition: gdef.h:1144
constexpr const char * tx(const char *p)
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84