E-MailRelay

gresolver.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 gresolver.cpp
19///
20
21#include "gdef.h"
22#include "gresolver.h"
23#include "gresolverfuture.h"
24#include "geventloop.h"
25#include "gtimer.h"
26#include "gfutureevent.h"
27#include "gcleanup.h"
28#include "gtest.h"
29#include "gstr.h"
30#include "glog.h"
31#include "gassert.h"
32
33//| \class GNet::ResolverImp
34/// A private "pimple" implementation class used by GNet::Resolver to do
35/// asynchronous name resolution. The ResolverImp object contains a worker
36/// thread that runs ResolverFuture::run(). The ResolverImp object's
37/// lifetime is dependent on the worker thread, so the best the
38/// GNet::Resolver class can do to cancel a resolve request is to
39/// ask the ResolverImp to delete itself and then forget about it.
40///
41 class GNet::ResolverImp : private FutureEventHandler
42{
43 public:
44 ResolverImp( Resolver & , EventState , const Location & , const Resolver::Config & ) ;
45 // Constructor.
46
47 ~ResolverImp() override ;
48 // Destructor. The destructor will block if the worker thread is
49 // still busy.
50
51 bool zombify() ;
52 // Disarms the callback and schedules a 'delete this' for when
53 // the workder thread has finished.
54
55 static void start( ResolverImp * , HANDLE ) noexcept ;
56 // Static worker-thread function to do name resolution. Calls
57 // ResolverFuture::run() to do the work and then FutureEvent::send()
58 // to signal the main thread. The event plumbing then results in a
59 // call to Resolver::done() on the main thread.
60
61 static std::size_t zcount() noexcept ;
62 // Returns the number of zombify()d objects.
63
64private: // overrides
65 void onFutureEvent() override ; // GNet::FutureEventHandler
66
67public:
68 ResolverImp( const ResolverImp & ) = delete ;
69 ResolverImp( ResolverImp && ) = delete ;
70 ResolverImp & operator=( const ResolverImp & ) = delete ;
71 ResolverImp & operator=( ResolverImp && ) = delete ;
72
73private:
74 void onTimeout() ;
75
76private:
77 Resolver * m_resolver ;
78 std::unique_ptr<FutureEvent> m_future_event ;
79 Timer<ResolverImp> m_timer ;
80 Location m_location ;
81 ResolverFuture m_future ;
82 G::threading::thread_type m_thread ;
83 static std::size_t m_zcount ;
84} ;
85
86 std::size_t GNet::ResolverImp::m_zcount = 0U ;
87
88 GNet::ResolverImp::ResolverImp( Resolver & resolver , EventState es , const Location & location , const Resolver::Config & config ) :
89 m_resolver(&resolver) ,
90 m_future_event(std::make_unique<FutureEvent>(static_cast<FutureEventHandler&>(*this),es)) ,
91 m_timer(*this,&ResolverImp::onTimeout,es) ,
92 m_location(location) ,
93 m_future(location.host(),location.service(),location.family(),config)
94{
95 G_ASSERT( G::threading::works() ) ; // see Resolver::start()
96 G::Cleanup::Block block_signals ;
97 m_thread = G::threading::thread_type( ResolverImp::start , this , m_future_event->handle() ) ;
98}
99
100GNet::ResolverImp::~ResolverImp()
101{
102 try
103 {
104 // (should be already join()ed)
105 if( m_thread.joinable() )
106 m_thread.join() ;
107 }
108 catch(...)
109 {
110 }
111}
112
113std::size_t GNet::ResolverImp::zcount() noexcept
114{
115 return m_zcount ;
116}
117
118 void GNet::ResolverImp::start( ResolverImp * This , HANDLE handle ) noexcept
119{
120 // thread function, spawned from ctor and join()ed from dtor
121 try
122 {
123 This->m_future.run() ;
124 FutureEvent::send( handle ) ;
125 }
126 catch(...) // worker thread outer function
127 {
128 // never gets here -- run and send are noexcept
129 FutureEvent::send( handle ) ;
130 }
131}
132
133 void GNet::ResolverImp::onFutureEvent()
134{
135 G_DEBUG( "GNet::ResolverImp::onFutureEvent: future event: ptr=" << m_resolver ) ;
136
137 ResolverFuture::Result result = m_future.get() ;
138 if( !m_future.error() )
139 m_location.update( result.address ) ;
140
141 if( m_thread.joinable() )
142 m_thread.join() ; // worker thread is finishing, so no delay here
143
144 Resolver * resolver = m_resolver ;
145 m_resolver = nullptr ;
146 if( resolver )
147 resolver->done( std::string(m_future.reason()) , Location(m_location) ) ; // must take copies
148}
149
150 bool GNet::ResolverImp::zombify()
151{
152 m_resolver = nullptr ;
153 m_timer.startTimer( 0U ) ;
154 m_zcount++ ;
155 return true ;
156}
157
158 void GNet::ResolverImp::onTimeout()
159{
160 if( m_thread.joinable() )
161 {
162 m_timer.startTimer( 1U ) ;
163 }
164 else
165 {
166 delete this ; // (previously unique_ptr<>::release()d)
167 m_zcount-- ;
168 }
169}
170
171// ==
172
173 GNet::Resolver::Resolver( Resolver::Callback & callback , EventState es ) :
174 m_callback(callback) ,
175 m_es(es)
176{
177 // lazy imp construction
178}
179
180 GNet::Resolver::~Resolver()
181{
182 if( m_imp && m_imp->zombify() )
183 {
184 G_DEBUG( "GNet::Resolver::dtor: zcount=" << ResolverImp::zcount() ) ;
185 if( ResolverImp::zcount() == 100U )
186 G_WARNING_ONCE( "GNet::Resolver::dtor: large number of threads waiting for dns results" ) ;
187
188 // release the imp to the timer-list until its getaddrinfo() thread completes
189 GDEF_IGNORE_RETURN m_imp.release() ;
190 }
191}
192
193std::string GNet::Resolver::resolve( Location & location )
194{
195 return resolve(location,{}).first ;
196}
197
198std::pair<std::string,std::string> GNet::Resolver::resolve( Location & location , const Config & config )
199{
200 // synchronous resolve
201 using Result = ResolverFuture::Result ;
202 G_DEBUG( "GNet::Resolver::resolve: resolve request [" << location.displayString() << "]"
203 << " (" << location.family() << ")" ) ;
204 ResolverFuture future( location.host() , location.service() , location.family() , config ) ;
205 future.run() ; // blocks until complete
206 Result result = future.get() ;
207 if( future.error() )
208 {
209 G_DEBUG( "GNet::Resolver::resolve: resolve error [" << future.reason() << "]" ) ;
210 return {future.reason(),{}} ;
211 }
212 else
213 {
214 G_DEBUG( "GNet::Resolver::resolve: resolve result [" << result.address.displayString() << "]" ) ;
215 location.update( result.address ) ;
216 return {{},result.canonicalName} ;
217 }
218}
219
220#ifndef G_LIB_SMALL
221GNet::Resolver::AddressList GNet::Resolver::resolve( const std::string & host , const std::string & service ,
222 int family , const Config & config )
223{
224 // synchronous resolve
225 G_DEBUG( "GNet::Resolver::resolve: resolve-request [" << host << "/"
226 << service << "/" << (family==AF_UNSPEC?"ip":(family==AF_INET?"ipv4":"ipv6")) << "]" ) ;
227 ResolverFuture future( host , service , family , config ) ;
228 future.run() ;
229 AddressList list ;
230 future.get( list ) ;
231 G_DEBUG( "GNet::Resolver::resolve: resolve result: list of " << list.size() ) ;
232 return list ;
233}
234#endif
235
236 void GNet::Resolver::start( const Location & location , const Config & config )
237{
238 // asynchronous resolve
239 if( !EventLoop::instance().running() ) throw Error( "no event loop" ) ;
240 if( !async() ) throw Error( "not multi-threaded" ) ; // precondition
241 if( busy() ) throw BusyError() ;
242 G_DEBUG( "GNet::Resolver::start: resolve start [" << location.displayString() << "]" ) ;
243 m_imp = std::make_unique<ResolverImp>( *this , m_es , location , config ) ;
244}
245
246 void GNet::Resolver::done( const std::string & error , const Location & location )
247{
248 // callback from the event loop after worker thread is done
249 G_DEBUG( "GNet::Resolver::done: resolve done: error=[" << error << "] "
250 << "location=[" << location.displayString() << "]" ) ;
251 m_imp.reset() ;
252 m_callback.onResolved( error , location ) ;
253}
254
255 bool GNet::Resolver::busy() const
256{
257 return m_imp != nullptr ;
258}
259
260 bool GNet::Resolver::async()
261{
262 if( G::threading::works() )
263 {
264 return EventLoop::instance().running() ;
265 }
266 else
267 {
268 G_DEBUG( "GNet::Resolver::async: not multi-threaded: using synchronous domain name lookup");
269 return false ;
270 }
271}
272
273GNet::Resolver::Config::Config()
274= default ;
275
GNet::EventLoop::running
virtual bool running() const =0
Returns true if called from within run().
GNet::EventLoop::instance
static EventLoop & instance()
Returns a reference to an instance of the class, if any.
Definition: geventloop.cpp:45
GNet::EventState
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
GNet::FutureEvent::send
static bool send(HANDLE handle, bool close=true) noexcept
Pokes an event into the main event loop so that the FutureEventHandler callback is called asynchronou...
Definition: gfutureevent_unix.cpp:158
GNet::Location
A class that represents the remote target for out-going client connections.
Definition: glocation.h:70
GNet::Location::family
int family() const
Returns the preferred name resolution address family as passed to the constructor.
Definition: glocation.cpp:132
GNet::Location::displayString
std::string displayString() const
Returns a string representation for logging and debug.
Definition: glocation.cpp:182
GNet::Location::service
std::string service() const
Returns the remote service name derived from the constructor parameter.
Definition: glocation.cpp:127
GNet::Location::update
void update(const Address &address)
Updates the address, typically after doing a name lookup on host() and service().
Definition: glocation.cpp:158
GNet::Location::host
std::string host() const
Returns the remote host name derived from the constructor parameter.
Definition: glocation.cpp:122
GNet::ResolverFuture
A 'future' shared-state class for asynchronous name resolution that holds parameters and results of a...
Definition: gresolverfuture.h:58
GNet::ResolverFuture::get
Result get()
Returns the resolved address after run() has completed.
Definition: gresolverfuture.cpp:149
GNet::ResolverFuture::error
bool error() const
Returns true if name resolution failed or no suitable address was returned.
Definition: gresolverfuture.cpp:159
GNet::ResolverFuture::reason
std::string reason() const
Returns the reason for the error().
Definition: gresolverfuture.cpp:164
GNet::ResolverFuture::run
ResolverFuture & run() noexcept
Does the synchronous name resolution and stores the result.
Definition: gresolverfuture.cpp:70
GNet::Resolver::~Resolver
~Resolver()
Destructor.
Definition: gresolver.cpp:180
GNet::Resolver::Resolver
Resolver(Callback &, EventState)
Constructor taking a callback interface reference.
Definition: gresolver.cpp:173
GNet::Resolver::resolve
static std::pair< std::string, std::string > resolve(Location &, const Config &)
Does synchronous name resolution.
Definition: gresolver.cpp:198
GNet::Resolver::start
void start(const Location &, const Config &={})
Starts asynchronous name-to-address resolution.
Definition: gresolver.cpp:236
GNet::Resolver::async
static bool async()
Returns true if the resolver supports asynchronous operation.
Definition: gresolver.cpp:260
GNet::Resolver::busy
bool busy() const
Returns true if there is a pending resolve request.
Definition: gresolver.cpp:255
gassert.h
gcleanup.h
geventloop.h
gfutureevent.h
glog.h
gresolver.h
gresolverfuture.h
gstr.h
gtest.h
gtimer.h
GNet
Network classes.
Definition: gdef.h:1243
G
Low-level classes.
Definition: garg.h:36
std
STL namespace.
GNet::ResolverFuture::Result
Result structure for GNet::ResolverFuture::get().
Definition: gresolverfuture.h:61
GNet::Resolver::Callback
An interface used for GNet::Resolver callbacks.
Definition: gresolver.h:67
GNet::Resolver::Config
A configuration structure for GNet::Resolver.
Definition: gresolver.h:52
G::Cleanup::Block
A RAII class to temporarily block signal delivery.
Definition: gcleanup.h:45