E-MailRelay

gresolver.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 & , ExceptionSink , const Location & ) ;
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 * , FutureEvent::handle_type ) 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
64 private: // overrides
65 void onFutureEvent() override ; // GNet::FutureEventHandler
66
67 public:
68 ResolverImp( const ResolverImp & ) = delete ;
69 ResolverImp( ResolverImp && ) = delete ;
70 void operator=( const ResolverImp & ) = delete ;
71 void operator=( ResolverImp && ) = delete ;
72
73 private:
74 void onTimeout() ;
75
76 private:
77 using Pair = ResolverFuture::Pair ;
78 Resolver * m_resolver ;
79 std::unique_ptr<FutureEvent> m_future_event ;
80 Timer<ResolverImp> m_timer ;
81 Location m_location ;
82 ResolverFuture m_future ;
83 G::threading::thread_type m_thread ;
84 static std::size_t m_zcount ;
85} ;
86
87std::size_t GNet::ResolverImp::m_zcount = 0U ;
88
89GNet::ResolverImp::ResolverImp( Resolver & resolver , ExceptionSink es , const Location & location ) :
90 m_resolver(&resolver) ,
91 m_future_event(std::make_unique<FutureEvent>(static_cast<FutureEventHandler&>(*this),es)) ,
92 m_timer(*this,&ResolverImp::onTimeout,es) ,
93 m_location(location) ,
94 m_future(location.host(),location.service(),location.family(),/*dgram=*/false,true)
95{
96 G_ASSERT( G::threading::works() ) ; // see Resolver::start()
97 G::Cleanup::Block block_signals ;
98 m_thread = G::threading::thread_type( ResolverImp::start , this , m_future_event->handle() ) ;
99}
100
101GNet::ResolverImp::~ResolverImp()
102{
103 try
104 {
105 // (should be already join()ed)
106 if( m_thread.joinable() )
107 m_thread.join() ;
108 }
109 catch(...)
110 {
111 }
112}
113
114std::size_t GNet::ResolverImp::zcount() noexcept
115{
116 return m_zcount ;
117}
118
119 void GNet::ResolverImp::start( ResolverImp * This , FutureEvent::handle_type handle ) noexcept
120{
121 // thread function, spawned from ctor and join()ed from dtor
122 try
123 {
124 This->m_future.run() ;
125 FutureEvent::send( handle ) ;
126 }
127 catch(...) // worker thread outer function
128 {
129 // never gets here -- run and send are noexcept
130 FutureEvent::send( handle ) ;
131 }
132}
133
134 void GNet::ResolverImp::onFutureEvent()
135{
136 G_DEBUG( "GNet::ResolverImp::onFutureEvent: future event: ptr=" << m_resolver ) ;
137
138 Pair result = m_future.get() ;
139 if( !m_future.error() )
140 m_location.update( result.first , result.second ) ;
141
142 if( m_thread.joinable() )
143 m_thread.join() ; // worker thread is finishing, so no delay here
144
145 Resolver * resolver = m_resolver ;
146 m_resolver = nullptr ;
147 if( resolver )
148 resolver->done( std::string(m_future.reason()) , Location(m_location) ) ; // must take copies
149}
150
151 bool GNet::ResolverImp::zombify()
152{
153 m_resolver = nullptr ;
154 m_timer.startTimer( 0U ) ;
155 m_zcount++ ;
156 return true ;
157}
158
159 void GNet::ResolverImp::onTimeout()
160{
161 if( m_thread.joinable() )
162 {
163 m_timer.startTimer( 1U ) ;
164 }
165 else
166 {
167 delete this ;
168 m_zcount-- ;
169 }
170}
171
172// ==
173
174 GNet::Resolver::Resolver( Resolver::Callback & callback , ExceptionSink es ) :
175 m_callback(callback) ,
176 m_es(es)
177{
178 // lazy imp construction
179}
180
181 GNet::Resolver::~Resolver()
182{
183 if( m_imp && m_imp->zombify() )
184 {
185 G_DEBUG( "GNet::Resolver::dtor: zcount=" << ResolverImp::zcount() ) ;
186 if( ResolverImp::zcount() == 100U )
187 G_WARNING_ONCE( "GNet::Resolver::dtor: large number of threads waiting for dns results" ) ;
188
189 // release the imp to the timer-list until its getaddrinfo() thread completes
190 GDEF_IGNORE_RETURN m_imp.release() ;
191 }
192}
193
194std::string GNet::Resolver::resolve( Location & location )
195{
196 // synchronous resolve
197 using Pair = ResolverFuture::Pair ;
198 G_DEBUG( "GNet::Resolver::resolve: resolve request [" << location.displayString() << "]"
199 << " (" << location.family() << ")" ) ;
200 ResolverFuture future( location.host() , location.service() , location.family() , /*dgram=*/false ) ;
201 future.run() ; // blocks until complete
202 Pair result = future.get() ;
203 if( future.error() )
204 {
205 G_DEBUG( "GNet::Resolver::resolve: resolve error [" << future.reason() << "]" ) ;
206 return future.reason() ;
207 }
208 else
209 {
210 G_DEBUG( "GNet::Resolver::resolve: resolve result [" << result.first.displayString() << "]"
211 << "[" << result.second << "]" ) ;
212 location.update( result.first , result.second ) ;
213 return std::string() ;
214 }
215}
216
217GNet::Resolver::AddressList GNet::Resolver::resolve( const std::string & host , const std::string & service ,
218 int family , bool dgram )
219{
220 // synchronous resolve
221 G_DEBUG( "GNet::Resolver::resolve: resolve-request [" << host << "/"
222 << service << "/" << (family==AF_UNSPEC?"ip":(family==AF_INET?"ipv4":"ipv6")) << "]" ) ;
223 ResolverFuture future( host , service , family , dgram ) ;
224 future.run() ;
225 AddressList list ;
226 future.get( list ) ;
227 G_DEBUG( "GNet::Resolver::resolve: resolve result: list of " << list.size() ) ;
228 return list ;
229}
230
231 void GNet::Resolver::start( const Location & location )
232{
233 // asynchronous resolve
234 if( !EventLoop::instance().running() ) throw Error( "no event loop" ) ;
235 if( !async() ) throw Error( "not multi-threaded" ) ; // precondition
236 if( busy() ) throw BusyError() ;
237 G_DEBUG( "GNet::Resolver::start: resolve start [" << location.displayString() << "]" ) ;
238 m_imp = std::make_unique<ResolverImp>( *this , m_es , location ) ;
239}
240
241 void GNet::Resolver::done( const std::string & error , const Location & location )
242{
243 // callback from the event loop after worker thread is done
244 G_DEBUG( "GNet::Resolver::done: resolve done: error=[" << error << "] "
245 << "location=[" << location.displayString() << "]" ) ;
246 m_imp.reset() ;
247 m_callback.onResolved( error , location ) ;
248}
249
250 bool GNet::Resolver::busy() const
251{
252 return m_imp != nullptr ;
253}
254
255 bool GNet::Resolver::async()
256{
257 if( G::threading::works() )
258 {
259 return EventLoop::instance().running() ;
260 }
261 else
262 {
263 G_DEBUG( "GNet::Resolver::async: not multi-threaded: using synchronous domain name lookup");
264 return false ;
265 }
266}
267
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:47
GNet::ExceptionSink
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
Definition: gexceptionsink.h:100
GNet::FutureEventHandler
A callback interface for GNet::FutureEvent.
Definition: gfutureevent.h:113
GNet::FutureEvent
A FutureEvent object can be used to send a one-shot event via the event loop to the relevant event ha...
Definition: gfutureevent.h:72
GNet::FutureEvent::send
static bool send(handle_type 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:157
GNet::Location
A class that represents the remote target for out-going client connections.
Definition: glocation.h:71
GNet::Location::family
int family() const
Returns the preferred name resolution address family as passed to the constructor.
Definition: glocation.cpp:125
GNet::Location::displayString
std::string displayString() const
Returns a string representation for logging and debug.
Definition: glocation.cpp:182
GNet::Location::update
void update(const Address &address, const std::string &canonical_name)
Updates the address and canonical name, typically after doing a name lookup on host() and service().
Definition: glocation.cpp:152
GNet::Location::service
std::string service() const
Returns the remote service name derived from the constructor parameter.
Definition: glocation.cpp:120
GNet::Location::host
std::string host() const
Returns the remote host name derived from the constructor parameter.
Definition: glocation.cpp:115
GNet::ResolverFuture
A 'future' shared-state class for asynchronous name resolution that holds parameters and results of a...
Definition: gresolverfuture.h:57
GNet::ResolverFuture::error
bool error() const
Returns true if name resolution failed or no suitable address was returned.
Definition: gresolverfuture.cpp:146
GNet::ResolverFuture::reason
std::string reason() const
Returns the reason for the error().
Definition: gresolverfuture.cpp:151
GNet::ResolverFuture::get
Pair get()
Returns the resolved address/name pair after run() has completed.
Definition: gresolverfuture.cpp:136
GNet::ResolverFuture::run
ResolverFuture & run() noexcept
Does the synchronous name resolution and stores the result.
Definition: gresolverfuture.cpp:57
GNet::ResolverImp
A private "pimple" implementation class used by GNet::Resolver to do asynchronous name resolution.
Definition: gresolver.cpp:42
GNet::Resolver
A class for synchronous or asynchronous network name to address resolution.
Definition: gresolver.h:44
GNet::Resolver::resolve
static std::string resolve(Location &)
Does synchronous name resolution.
Definition: gresolver.cpp:194
GNet::Resolver::~Resolver
~Resolver()
Destructor.
Definition: gresolver.cpp:181
GNet::Resolver::Resolver
Resolver(Callback &, ExceptionSink)
Constructor taking a callback interface reference.
Definition: gresolver.cpp:174
GNet::Resolver::async
static bool async()
Returns true if the resolver supports asynchronous operation.
Definition: gresolver.cpp:255
GNet::Resolver::start
void start(const Location &)
Starts asynchronous name-to-address resolution.
Definition: gresolver.cpp:231
GNet::Resolver::busy
bool busy() const
Returns true if there is a pending resolve request.
Definition: gresolver.cpp:250
GNet::Timer
A timer class template in which the timeout is delivered to the specified method.
Definition: gtimer.h:129
gassert.h
gcleanup.h
geventloop.h
gfutureevent.h
glog.h
gresolver.h
gresolverfuture.h
gstr.h
gtest.h
gtimer.h
GNet::Resolver::Callback
An interface used for GNet::Resolver callbacks.
Definition: gresolver.h:50
G::Cleanup::Block
A RAII class to temporarily block signal delivery.
Definition: gcleanup.h:44