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///
41class GNet::ResolverImp : private FutureEventHandler
42{
43public:
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
86std::size_t GNet::ResolverImp::m_zcount = 0U ;
87
88GNet::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
118void 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
133void 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
150bool GNet::ResolverImp::zombify()
151{
152 m_resolver = nullptr ;
153 m_timer.startTimer( 0U ) ;
154 m_zcount++ ;
155 return true ;
156}
157
158void 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
174 m_callback(callback) ,
175 m_es(es)
176{
177 // lazy imp construction
178}
179
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
236void 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
246void 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
256{
257 return m_imp != nullptr ;
258}
259
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
virtual bool running() const =0
Returns true if called from within run().
static EventLoop & instance()
Returns a reference to an instance of the class, if any.
Definition: geventloop.cpp:45
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
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...
A class that represents the remote target for out-going client connections.
Definition: glocation.h:70
int family() const
Returns the preferred name resolution address family as passed to the constructor.
Definition: glocation.cpp:132
std::string displayString() const
Returns a string representation for logging and debug.
Definition: glocation.cpp:182
std::string service() const
Returns the remote service name derived from the constructor parameter.
Definition: glocation.cpp:127
void update(const Address &address)
Updates the address, typically after doing a name lookup on host() and service().
Definition: glocation.cpp:158
std::string host() const
Returns the remote host name derived from the constructor parameter.
Definition: glocation.cpp:122
A 'future' shared-state class for asynchronous name resolution that holds parameters and results of a...
Result get()
Returns the resolved address after run() has completed.
bool error() const
Returns true if name resolution failed or no suitable address was returned.
std::string reason() const
Returns the reason for the error().
ResolverFuture & run() noexcept
Does the synchronous name resolution and stores the result.
~Resolver()
Destructor.
Definition: gresolver.cpp:180
Resolver(Callback &, EventState)
Constructor taking a callback interface reference.
Definition: gresolver.cpp:173
static std::pair< std::string, std::string > resolve(Location &, const Config &)
Does synchronous name resolution.
Definition: gresolver.cpp:198
void start(const Location &, const Config &={})
Starts asynchronous name-to-address resolution.
Definition: gresolver.cpp:236
static bool async()
Returns true if the resolver supports asynchronous operation.
Definition: gresolver.cpp:260
bool busy() const
Returns true if there is a pending resolve request.
Definition: gresolver.cpp:255
Network classes.
Definition: gdef.h:1243
Low-level classes.
Definition: garg.h:36
STL namespace.
Result structure for GNet::ResolverFuture::get().
An interface used for GNet::Resolver callbacks.
Definition: gresolver.h:67
A configuration structure for GNet::Resolver.
Definition: gresolver.h:52
A RAII class to temporarily block signal delivery.
Definition: gcleanup.h:45