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