emailrelay/src/gnet/geventloop_select.cpp
Graeme Walker 27c01949fa v2.2.1
2022-04-03 12:00:00 +00:00

365 lines
9.4 KiB
C++

//
// Copyright (C) 2001-2021 Graeme Walker <graeme_walker@users.sourceforge.net>
//
// 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 <http://www.gnu.org/licenses/>.
// ===
///
/// \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 <sstream>
#include <sys/types.h>
#include <sys/time.h>
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> GNet::EventLoop::create()
{
return std::make_unique<EventLoopImp>() ;
}
// ===
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<G::TimeInterval,bool> 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 ) ;
}