E-MailRelay
geventloop_win32.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_win32.cpp
19///
20
21#include "gdef.h"
22#include "geventloop.h"
23#include "geventloophandles.h"
24#include "gevent.h"
25#include "gpump.h"
26#include "gexception.h"
27#include "gtimer.h"
28#include "gtimerlist.h"
29#include "gscope.h"
30#include "gstr.h"
31#include "gtest.h"
32#include "gassert.h"
33#include "glog.h"
34#include <algorithm>
35#include <stdexcept>
36#include <vector>
37#include <memory>
38#include <new>
39
40namespace GNet
41{
42 class EventLoopImp ;
43}
44
45class GNet::EventLoopImp : public EventLoop
46{
47public:
48 EventLoopImp() ;
49 // Default constructor.
50
51 virtual ~EventLoopImp() ;
52 // Destructor.
53
54private: // overrides
55 std::string run() override ;
56 bool running() const override ;
57 void quit( const std::string & ) override ;
58 void quit( const G::SignalSafe & ) override ;
59 void disarm( ExceptionHandler * ) noexcept override ;
60 void addRead( Descriptor , EventHandler & , EventState ) override ;
61 void addWrite( Descriptor , EventHandler & , EventState ) override ;
62 void addOther( Descriptor , EventHandler & , EventState ) override ;
63 void dropRead( Descriptor ) noexcept override ;
64 void dropWrite( Descriptor ) noexcept override ;
65 void dropOther( Descriptor ) noexcept override ;
66 void drop( Descriptor ) noexcept override ;
67
68public:
69 EventLoopImp( const EventLoopImp & ) = delete ;
70 EventLoopImp( EventLoopImp && ) = delete ;
71 EventLoopImp & operator=( const EventLoopImp & ) = delete ;
72 EventLoopImp & operator=( EventLoopImp && ) = delete ;
73
74private:
75 void runOnce() ;
76
77private:
78 using Rc = EventLoopHandles::Rc ;
79 using RcType = EventLoopHandles::RcType ;
80 static constexpr long READ_EVENTS = (FD_READ | FD_ACCEPT | FD_OOB) ;
81 static constexpr long WRITE_EVENTS = (FD_WRITE) ;
82 static constexpr long EXCEPTION_EVENTS = (FD_CLOSE | FD_CONNECT) ;
83 using s_type = G::TimeInterval::s_type ;
84 using us_type = G::TimeInterval::us_type ;
85 struct Library
86 {
87 Library() ;
88 ~Library() ;
89 Library( const Library & ) = delete ;
90 Library( Library && ) = delete ;
91 Library & operator=( const Library & ) = delete ;
92 Library & operator=( Library && ) = delete ;
93 } ;
94
95public:
96 enum class ListItemType // A type enumeration for the list of event sources.
97 {
98 socket ,
99 simple
100 } ;
101 struct ListItem /// A structure holding an event handle and its event handlers.
102 {
103 explicit ListItem( Descriptor fdd ) :
104 m_type(fdd.fd()==INVALID_SOCKET?ListItemType::simple:ListItemType::socket) ,
105 m_socket(fdd.fd()) ,
106 m_handle(fdd.h())
107 {
108 }
109 ListItemType m_type {ListItemType::socket} ;
110 SOCKET m_socket {INVALID_SOCKET} ;
111 HANDLE m_handle {HNULL} ;
112 long m_events {0L} ; // 0 => new or garbage
113 EventState m_es {EventState::Private(),nullptr,nullptr} ;
114 EventHandler * m_handler {nullptr} ;
115 } ;
116 using List = std::vector<ListItem> ;
117
118private:
119 ListItem * find( Descriptor ) ;
120 ListItem & findOrCreate( Descriptor ) ;
121 void fdupdate( Descriptor , long ) ;
122 bool fdupdate( Descriptor , long , std::nothrow_t ) noexcept ;
123 void handleSimpleEvent( ListItem & ) ;
124 void handleSocketEvent( std::size_t ) ;
125 void checkForOverflow( const ListItem & ) ;
126 bool collectGarbage() ;
127 DWORD ms() ;
128 static DWORD ms( s_type , us_type ) noexcept ;
129 static bool isValid( const ListItem & ) ;
130 static bool isInvalid( const ListItem & ) ;
131
132private:
133 Library m_library ;
134 List m_list ;
135 std::unique_ptr<EventLoopHandles> m_handles ;
136 bool m_running ;
137 bool m_dirty ;
138 bool m_quit ;
139 std::string m_quit_reason ;
140 EventState m_es_current {EventState::Private(),nullptr,nullptr} ;
141} ;
142
143std::unique_ptr<GNet::EventLoop> GNet::EventLoop::create()
144{
145 return std::make_unique<EventLoopImp>() ;
146}
147
148// ===
149
150GNet::EventLoopImp::EventLoopImp() :
151 m_running(false) ,
152 m_dirty(false) ,
153 m_quit(false)
154{
155 m_handles = std::make_unique<EventLoopHandles>() ;
156}
157
158GNet::EventLoopImp::~EventLoopImp()
159{
160}
161
162void GNet::EventLoopImp::disarm( ExceptionHandler * eh ) noexcept
163{
164 if( m_es_current.eh() == eh )
165 m_es_current.disarm() ;
166
167 for( auto & item : m_list )
168 {
169 if( item.m_es.eh() == eh )
170 item.m_es.disarm() ;
171 }
172}
173
175{
176 return m_running ;
177}
178
179std::string GNet::EventLoopImp::run()
180{
181 collectGarbage() ; // in case re-run() after an exception
182
183 std::size_t i = 0U ;
184 List * list_p = &m_list ;
185 m_handles->update( m_list.size() , [list_p,&i](){return (*list_p)[i++].m_handle;} ) ;
186
187 G::ScopeExitSetFalse running( m_running = true ) ;
188 m_dirty = false ;
189 m_quit = false ;
190 while( !m_quit )
191 {
192 runOnce() ;
193 }
194 std::string quit_reason = m_quit_reason ;
195 m_quit_reason.clear() ;
196 m_quit = false ;
197 return quit_reason ;
198}
199
200void GNet::EventLoopImp::runOnce()
201{
202 G_ASSERT( std::find_if(m_list.begin(),m_list.end(),[](const ListItem & i){return i.m_handle==HNULL;}) == m_list.end() ) ;
203 G_ASSERT( std::find_if(m_list.begin(),m_list.end(),[](const ListItem & i){return i.m_events==0L;}) == m_list.end() ) ;
204 G_ASSERT( !m_dirty ) ;
205
206 EventLoopHandles & handles = *m_handles ;
207
208 auto rc = handles.wait( ms() ) ;
209 if( rc == RcType::overflow )
210 {
211 throw Overflow() ;
212 }
213 else if( rc == RcType::timeout )
214 {
215 TimerList::instance().doTimeouts() ;
216 }
217 else if( rc == RcType::event )
218 {
219 ListItem & list_item = m_list[rc.index()] ;
220 if( list_item.m_type == ListItemType::socket )
221 handleSocketEvent( rc.index() ) ;
222 else
223 handleSimpleEvent( list_item ) ;
224 }
225 else if( rc == RcType::message )
226 {
227 std::pair<bool,std::string> quit = GGui::Pump::runToEmpty() ;
228 if( quit.first )
229 {
230 G_DEBUG( "GNet::EventLoopImp::run: quit" ) ;
231 m_quit_reason = quit.second ;
232 m_quit = true ;
233 }
234 }
235 else if( rc == RcType::failed )
236 {
237 throw Error( "wait failed (" + std::to_string(rc.m_error) + ")" ) ;
238 }
239 else // rc == RcType::other
240 {
241 ; // no-op
242 }
243
244 // remove from m_list where logically deleted
245 bool was_dirty = collectGarbage() ;
246
247 // let the handles object see the new, garbage-collected list
248 std::size_t i = 0U ;
249 List * list_p = &m_list ;
250 handles.update( m_list.size() , [list_p,&i](){return (*list_p)[i++].m_handle;} , was_dirty ) ;
251}
252
253bool GNet::EventLoopImp::collectGarbage()
254{
255 bool was_dirty = m_dirty ; // ie. m_list updated by adds and/or drops during event handling
256 if( m_dirty )
257 {
258 m_list.erase( std::remove_if(m_list.begin(),m_list.end(),&EventLoopImp::isInvalid) , m_list.end() ) ;
259 m_dirty = false ;
260 }
261 return was_dirty ;
262}
263
264DWORD GNet::EventLoopImp::ms()
265{
266 if( TimerList::ptr() )
267 {
268 auto pair = TimerList::instance().interval() ;
269 if( pair.second )
270 return INFINITE ;
271 else if( pair.first.s() == 0 && pair.first.us() == 0U )
272 return 0 ;
273 else
274 return std::max( DWORD(1) , ms(pair.first.s(),pair.first.us()) ) ;
275 }
276 else
277 {
278 return INFINITE ;
279 }
280}
281
282DWORD GNet::EventLoopImp::ms( s_type s , us_type us ) noexcept
283{
284 constexpr DWORD dword_max = 0xffffffff ;
285 constexpr DWORD dword_max_1 = dword_max - 1 ;
286 static_assert( INFINITE == dword_max , "" ) ;
287 constexpr auto s_max = static_cast<s_type>( dword_max/1000U - 1U ) ;
288 return
289 s >= s_max ?
290 dword_max_1 :
291 static_cast<DWORD>( (s*1000U) + ((us+999U)/1000U) ) ;
292}
293
294void GNet::EventLoopImp::quit( const std::string & reason )
295{
296 GGui::Pump::quit( reason ) ;
297}
298
300{
301 // (quit without processing window messages)
302 m_quit = true ;
303}
304
305GNet::EventLoopImp::ListItem * GNet::EventLoopImp::find( Descriptor fdd )
306{
307 const HANDLE h = fdd.h() ;
308 auto p = std::find_if( m_list.begin() , m_list.end() , [h](const ListItem & i){return i.m_handle==h;} ) ;
309 return p == m_list.end() ? nullptr : &(*p) ;
310}
311
312GNet::EventLoopImp::ListItem & GNet::EventLoopImp::findOrCreate( Descriptor fdd )
313{
314 G_ASSERT( fdd.h() != HNULL ) ;
315 const HANDLE h = fdd.h() ;
316 auto p = std::find_if( m_list.begin() , m_list.end() , [h](const ListItem & i){return i.m_handle==h;} ) ;
317 if( p == m_list.end() )
318 {
319 m_list.emplace_back( fdd ) ;
320 p = m_list.begin() + m_list.size() - 1U ;
321 (*p).m_events = 0 ; // => newly created
322 }
323 return *p ;
324}
325
326void GNet::EventLoopImp::addRead( Descriptor fdd , EventHandler & handler , EventState es )
327{
328 G_ASSERT( fdd.h() != HNULL ) ;
329 handler.setDescriptor( fdd ) ; // see EventHandler::dtor
330 ListItem & item = findOrCreate( fdd ) ;
331 checkForOverflow( item ) ;
332 m_dirty |= ( item.m_events == 0L ) ; // dirty if new Item created
333 item.m_events |= READ_EVENTS ;
334 item.m_handler = &handler ;
335 item.m_es = es ;
336 fdupdate( fdd , item.m_events ) ;
337}
338
339void GNet::EventLoopImp::addWrite( Descriptor fdd , EventHandler & handler , EventState es )
340{
341 G_ASSERT( fdd.h() != HNULL ) ;
342 handler.setDescriptor( fdd ) ; // see EventHandler::dtor
343 ListItem & item = findOrCreate( fdd ) ;
344 checkForOverflow( item ) ;
345 m_dirty |= ( item.m_events == 0L ) ;
346 item.m_events |= WRITE_EVENTS ;
347 item.m_handler = &handler ;
348 item.m_es = es ;
349 fdupdate( fdd , item.m_events ) ;
350}
351
352void GNet::EventLoopImp::addOther( Descriptor fdd , EventHandler & handler , EventState es )
353{
354 G_ASSERT( fdd.h() != HNULL ) ;
355 handler.setDescriptor( fdd ) ; // see EventHandler::dtor
356 ListItem & item = findOrCreate( fdd ) ;
357 checkForOverflow( item ) ;
358 m_dirty |= ( item.m_events == 0L ) ;
359 item.m_events |= EXCEPTION_EVENTS ;
360 item.m_handler = &handler ;
361 item.m_es = es ;
362 fdupdate( fdd , item.m_events ) ;
363}
364
365void GNet::EventLoopImp::dropRead( Descriptor fdd ) noexcept
366{
367 ListItem * item = find( fdd ) ;
368 if( item )
369 {
370 item->m_events &= ~READ_EVENTS ;
371 fdupdate( fdd , item->m_events , std::nothrow ) ;
372 if( item->m_events == 0L ) // logically deleted
373 {
374 m_dirty = true ;
375 m_handles->onClose( fdd.h() ) ;
376 }
377 }
378}
379
380void GNet::EventLoopImp::dropWrite( Descriptor fdd ) noexcept
381{
382 ListItem * item = find( fdd ) ;
383 if( item )
384 {
385 item->m_events &= ~WRITE_EVENTS ;
386 fdupdate( fdd , item->m_events , std::nothrow ) ;
387 if( item->m_events == 0L )
388 {
389 m_dirty = true ;
390 m_handles->onClose( fdd.h() ) ;
391 }
392 }
393}
394
395void GNet::EventLoopImp::dropOther( Descriptor fdd ) noexcept
396{
397 ListItem * item = find( fdd ) ;
398 if( item )
399 {
400 item->m_events &= ~EXCEPTION_EVENTS ;
401 fdupdate( fdd , item->m_events , std::nothrow ) ;
402 if( item->m_events == 0L )
403 {
404 m_dirty = true ;
405 m_handles->onClose( fdd.h() ) ;
406 }
407 }
408}
409
410void GNet::EventLoopImp::drop( Descriptor fdd ) noexcept
411{
412 ListItem * item = find( fdd ) ;
413 if( item )
414 {
415 item->m_events = 0U ;
416 fdupdate( fdd , item->m_events , std::nothrow ) ;
417 item->m_handler = nullptr ;
418 m_dirty = true ;
419 m_handles->onClose( fdd.h() ) ;
420 }
421}
422
423bool GNet::EventLoopImp::isInvalid( const ListItem & item )
424{
425 return item.m_events == 0L ;
426}
427
428bool GNet::EventLoopImp::isValid( const ListItem & item )
429{
430 return item.m_events != 0L ;
431}
432
433void GNet::EventLoopImp::handleSimpleEvent( ListItem & item )
434{
435 ResetEvent( item.m_handle ) ; // manual-reset event-object -- see GNet::FutureEvent
436 m_es_current = item.m_es ; // see disarm()
437 EventEmitter::raiseReadEvent( item.m_handler , m_es_current ) ;
438}
439
440void GNet::EventLoopImp::handleSocketEvent( std::size_t index )
441{
442 ListItem * item = &m_list[index] ;
443
444 WSANETWORKEVENTS events_info ;
445 bool e_not_sock = false ;
446 int rc = WSAEnumNetworkEvents( item->m_socket , item->m_handle , &events_info ) ;
447 if( rc != 0 )
448 e_not_sock = WSAGetLastError() == WSAENOTSOCK ;
449 if( rc != 0 && !e_not_sock )
450 throw Error( "enum-network-events failed" ) ;
451 if( e_not_sock )
452 throw Error( "enum-network-events failed: not a socket" ) ;
453
454 // we might do more than one raiseEvent() here and m_list can change
455 // between each call, potentially invalidating our ListItem pointer --
456 // however we use garbage collection and no inserts on m_list so we
457 // can recover a valid pointer from the index
458
459 long events = events_info.lNetworkEvents ;
460 if( events & READ_EVENTS )
461 {
462 m_es_current = item->m_es ;
463 EventEmitter::raiseReadEvent( item->m_handler , m_es_current ) ;
464 item = nullptr ;
465 }
466 if( events & WRITE_EVENTS )
467 {
468 item = item ? item : &m_list[index] ;
469 m_es_current = item->m_es ;
470 EventEmitter::raiseWriteEvent( item->m_handler , m_es_current ) ;
471 item = nullptr ;
472 }
473 if( events & EXCEPTION_EVENTS )
474 {
475 static_assert( EXCEPTION_EVENTS == (FD_CLOSE|FD_CONNECT) , "" ) ;
476 item = item ? item : &m_list[index] ;
477 if( events_info.lNetworkEvents & FD_CONNECT )
478 {
479 int e = events_info.iErrorCode[FD_CONNECT_BIT] ;
480 if( e )
481 {
482 m_es_current = item->m_es ;
483 EventEmitter::raiseOtherEvent( item->m_handler , m_es_current , EventHandler::Reason::failed ) ;
484 }
485 }
486 else
487 {
488 int e = events_info.iErrorCode[FD_CLOSE_BIT] ;
489 EventHandler::Reason reason = EventHandler::Reason::other ;
490 if( e == 0 ) reason = EventHandler::Reason::closed ;
491 if( e == WSAENETDOWN ) reason = EventHandler::Reason::down ;
492 if( e == WSAECONNRESET ) reason = EventHandler::Reason::reset ;
493 if( e == WSAECONNABORTED ) reason = EventHandler::Reason::abort ;
494 m_es_current = item->m_es ;
495 EventEmitter::raiseOtherEvent( item->m_handler , m_es_current , reason ) ;
496 }
497 }
498}
499
500void GNet::EventLoopImp::checkForOverflow( const ListItem & item )
501{
502 G_ASSERT( m_handles != nullptr ) ;
503 const bool is_new = item.m_events == 0L ;
504
505 List * list_p = &m_list ;
506 std::function<std::size_t()> count_valid_fn = [list_p](){
507 std::size_t n = 0 ;
508 for( auto p = list_p->cbegin() ; p != list_p->cend() ; ++p )
509 {
510 if( std::next(p) == list_p->cend() || isValid(*p) ) // always count the one we've just added
511 n++ ;
512 }
513 return n ;
514 } ;
515
516 if( is_new && m_handles->overflow( m_list.size() , count_valid_fn ) )
517 {
518 m_list.pop_back() ;
519 throw Overflow() ;
520 }
521}
522
523void GNet::EventLoopImp::fdupdate( Descriptor fdd , long events )
524{
525 G_ASSERT( fdd.h() != 0 ) ;
526 if( fdd.fd() != INVALID_SOCKET ) // see GNet::FutureEvent
527 {
528 int rc = WSAEventSelect( fdd.fd() , fdd.h() , events ) ;
529 if( rc != 0 )
530 throw Error( "wsa-event-select failed" ) ;
531 }
532}
533
534bool GNet::EventLoopImp::fdupdate( Descriptor fdd , long events , std::nothrow_t ) noexcept
535{
536 int rc = 0 ;
537 if( fdd.fd() != INVALID_SOCKET )
538 rc = WSAEventSelect( fdd.fd() , fdd.h() , events ) ;
539 return rc == 0 ;
540}
541
542// ==
543
544GNet::EventLoopImp::Library::Library()
545{
546 WSADATA info ;
547 WORD version = MAKEWORD( 2 , 2 ) ;
548 int rc = WSAStartup( version , &info ) ;
549 if( rc != 0 )
550 {
551 throw EventLoopImp::Error( "winsock startup failure" ) ;
552 }
553 if( LOBYTE(info.wVersion) != 2 || HIBYTE(info.wVersion) != 2 )
554 {
555 WSACleanup() ;
556 throw EventLoopImp::Error( "incompatible winsock version" ) ;
557 }
558}
559
560GNet::EventLoopImp::Library::~Library()
561{
562 // WSACleanup() not
563}
564
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 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