// // Copyright (C) 2001-2021 Graeme Walker // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // === /// /// \file geventloop_select.cpp /// #include "gdef.h" #include "gscope.h" #include "gevent.h" #include "geventhandlerlist.h" #include "gprocess.h" #include "gexception.h" #include "gstr.h" #include "gfile.h" #include "gtimer.h" #include "gtimerlist.h" #include "gtest.h" #include "glog.h" #include "gassert.h" #include #include #include namespace GNet { class EventLoopImp ; class FdSet ; } //| \class GNet::FdSet /// An "fd_set" wrapper class used by GNet::EventLoopImp. /// class GNet::FdSet { public: FdSet() ; void init( const EventHandlerList & ) ; void raiseEvents( EventHandlerList & , void (EventHandler::*method)() ) ; void raiseEvents( EventHandlerList & , void (EventHandler::*method)(EventHandler::Reason) , EventHandler::Reason ) ; void invalidate() noexcept ; int fdmax( int = 0 ) const ; fd_set * operator()() ; private: bool m_valid{false} ; int m_fdmax{0} ; fd_set m_set_internal ; // set from EventHandlerList fd_set m_set_external ; // passed to select() and modified by it } ; //| \class GNet::EventLoopImp /// A concrete implementation of GNet::EventLoop using select() in its /// implementation. /// class GNet::EventLoopImp : public EventLoop { public: G_EXCEPTION( Error , "select error" ) ; EventLoopImp() ; ~EventLoopImp() override ; private: // overrides std::string run() override ; bool running() const override ; void quit( const std::string & ) override ; void quit( const G::SignalSafe & ) override ; void addRead( Descriptor fd , EventHandler & , ExceptionSink ) override ; void addWrite( Descriptor fd , EventHandler & , ExceptionSink ) override ; void addOther( Descriptor fd , EventHandler & , ExceptionSink ) override ; void dropRead( Descriptor fd ) noexcept override ; void dropWrite( Descriptor fd ) noexcept override ; void dropOther( Descriptor fd ) noexcept override ; void disarm( ExceptionHandler * ) noexcept override ; public: EventLoopImp( const EventLoopImp & ) = delete ; EventLoopImp( EventLoopImp && ) = delete ; void operator=( const EventLoopImp & ) = delete ; void operator=( EventLoopImp && ) = delete ; private: void runOnce() ; static void check( int ) ; private: bool m_quit{false} ; std::string m_quit_reason ; bool m_running{false} ; EventHandlerList m_read_list ; FdSet m_read_set ; EventHandlerList m_write_list ; FdSet m_write_set ; EventHandlerList m_other_list ; FdSet m_other_set ; } ; // === GNet::FdSet::FdSet() // NOLINT cppcoreguidelines-pro-type-member-init = default; fd_set * GNet::FdSet::operator()() { return &m_set_external ; } void GNet::FdSet::invalidate() noexcept { m_valid = false ; } void GNet::FdSet::init( const EventHandlerList & list ) { // if the internal set has been inivalidate()d then re-initialise // it from the event-handler-list -- then copy the internal list // to the external list -- the external list is passed to select() // and modified by it -- this might look klunky but it is well // optimised on the high frequency code paths and it keeps the // choice of select()/fd_set hidden from client code // if( !m_valid ) { // copy the event-handler-list into the internal fd-set m_fdmax = 0 ; FD_ZERO( &m_set_internal ) ; // NOLINT readability-isolate-declaration const EventHandlerList::Iterator end = list.end() ; for( EventHandlerList::Iterator p = list.begin() ; p != end ; ++p ) { G_ASSERT( p.fd().valid() && p.fd().fd() >= 0 ) ; Descriptor fd = p.fd() ; if( fd.fd() < 0 ) continue ; FD_SET( fd.fd() , &m_set_internal ) ; if( (fd.fd()+1) > m_fdmax ) m_fdmax = (fd.fd()+1) ; } m_valid = true ; } m_set_external = m_set_internal ; // fast structure copy } int GNet::FdSet::fdmax( int n ) const { return n > m_fdmax ? n : m_fdmax ; } void GNet::FdSet::raiseEvents( EventHandlerList & list , void (EventHandler::*method)() ) { EventHandlerList::Lock lock( list ) ; // since event handlers may change the list while we iterate const EventHandlerList::Iterator end = list.end() ; for( EventHandlerList::Iterator p = list.begin() ; p != end ; ++p ) { Descriptor fd = p.fd() ; if( fd.fd() >= 0 && FD_ISSET( fd.fd() , &m_set_external ) ) { p.raiseEvent( method ) ; } } } void GNet::FdSet::raiseEvents( EventHandlerList & list , void (EventHandler::*method)(EventHandler::Reason) , EventHandler::Reason reason ) { EventHandlerList::Lock lock( list ) ; // since event handlers may change the list while we iterate const EventHandlerList::Iterator end = list.end() ; for( EventHandlerList::Iterator p = list.begin() ; p != end ; ++p ) { Descriptor fd = p.fd() ; if( fd.fd() >= 0 && FD_ISSET( fd.fd() , &m_set_external ) ) { p.raiseEvent( method , reason ) ; } } } // === std::unique_ptr GNet::EventLoop::create() { return std::make_unique() ; } // === GNet::EventLoopImp::EventLoopImp() : m_read_list("read") , m_write_list("write") , m_other_list("other") { } GNet::EventLoopImp::~EventLoopImp() = default; std::string GNet::EventLoopImp::run() { G::ScopeExitSetFalse running( m_running = true ) ; do { runOnce() ; } while( !m_quit ) ; std::string quit_reason = m_quit_reason ; m_quit_reason.clear() ; m_quit = false ; return quit_reason ; } bool GNet::EventLoopImp::running() const { return m_running ; } void GNet::EventLoopImp::quit( const std::string & reason ) { m_quit = true ; m_quit_reason = reason ; } void GNet::EventLoopImp::quit( const G::SignalSafe & ) { m_quit = true ; } void GNet::EventLoopImp::runOnce() { // build fd-sets from handler lists // m_read_set.init( m_read_list ) ; m_write_set.init( m_write_list ) ; m_other_set.init( m_other_list ) ; int n = m_read_set.fdmax( m_write_set.fdmax(m_other_set.fdmax()) ) ; // get a timeout interval() from TimerList // using Timeval = struct timeval ; Timeval timeout ; Timeval * timeout_p = nullptr ; bool timeout_immediate = false ; if( TimerList::ptr() != nullptr ) { std::pair interval_pair = TimerList::instance().interval() ; G::TimeInterval interval = interval_pair.first ; bool timeout_infinite = interval_pair.second ; timeout_immediate = !timeout_infinite && interval.s() == 0 && interval.us() == 0U ; timeout.tv_sec = interval.s() ; timeout.tv_usec = interval.us() ; timeout_p = timeout_infinite ? nullptr : &timeout ; } if( G::Test::enabled("event-loop-quitfile") ) // esp. for profiling { if( G::File::remove(".quit",std::nothrow) ) m_quit = true ; if( timeout_p == nullptr || timeout.tv_sec > 0 ) { timeout.tv_sec = 0 ; timeout.tv_usec = 999999U ; } timeout_p = &timeout ; } // do the select() // int rc = ::select( n , m_read_set() , m_write_set() , m_other_set() , timeout_p ) ; if( rc < 0 ) { int e = G::Process::errno_() ; if( e != EINTR ) // eg. when profiling throw Error( G::Str::fromInt(e) ) ; } // call the timeout handlers // if( rc == 0 || timeout_immediate ) { //G_DEBUG( "GNet::EventLoopImp::runOnce: select() timeout" ) ; TimerList::instance().doTimeouts() ; } // call the fd event handlers // if( rc > 0 ) { //G_DEBUG( "GNet::EventLoopImp::runOnce: detected event(s) on " << rc << " fd(s)" ) ; m_read_set.raiseEvents( m_read_list , &EventHandler::readEvent ) ; m_write_set.raiseEvents( m_write_list , &EventHandler::writeEvent ) ; m_other_set.raiseEvents( m_other_list , &EventHandler::otherEvent , EventHandler::Reason::other ) ; } if( G::Test::enabled("event-loop-slow") ) { Timeval timeout_slow ; timeout_slow.tv_sec = 0 ; timeout_slow.tv_usec = 100000 ; ::select( 0 , nullptr , nullptr , nullptr , &timeout_slow ) ; } } void GNet::EventLoopImp::addRead( Descriptor fd , EventHandler & handler , ExceptionSink es ) { check( fd.fd() ) ; m_read_list.add( fd , &handler , es ) ; m_read_set.invalidate() ; } void GNet::EventLoopImp::addWrite( Descriptor fd , EventHandler & handler , ExceptionSink es ) { check( fd.fd() ) ; m_write_list.add( fd , &handler , es ) ; m_write_set.invalidate() ; } void GNet::EventLoopImp::addOther( Descriptor fd , EventHandler & handler , ExceptionSink es ) { check( fd.fd() ) ; m_other_list.add( fd , &handler , es ) ; m_other_set.invalidate() ; } void GNet::EventLoopImp::check( int fd ) { if( fd >= FD_SETSIZE ) throw EventLoop::Overflow( "too many open file descriptors for select()" ) ; } void GNet::EventLoopImp::dropRead( Descriptor fd ) noexcept { m_read_list.remove( fd ) ; m_read_set.invalidate() ; } void GNet::EventLoopImp::dropWrite( Descriptor fd ) noexcept { m_write_list.remove( fd ) ; m_write_set.invalidate() ; } void GNet::EventLoopImp::dropOther( Descriptor fd ) noexcept { m_other_list.remove( fd ) ; m_other_set.invalidate() ; } void GNet::EventLoopImp::disarm( ExceptionHandler * p ) noexcept { m_read_list.disarm( p ) ; m_write_list.disarm( p ) ; m_other_list.disarm( p ) ; }