E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gresolver.cpp
1 //
2 // Copyright (C) 2001-2018 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 // 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 "gtest.h"
28 #include "gstr.h"
29 #include "gdebug.h"
30
31 /// \class GNet::ResolverImp
32 /// A private "pimple" implementation class used by GNet::Resolver to do
33 /// asynchronous name resolution. The object contains a worker thread using
34 /// the future/promise pattern. Its lifetime is dependent on the worker
35 /// thread, so the GNet::Resolver can only ask it to delete itself and
36 /// then forget about it.
37 ///
38 class GNet::ResolverImp : private FutureEventHandler
39 {
40 public:
41 ResolverImp( Resolver & , ExceptionHandler & , const Location & ) ;
42 // Constructor.
43
44 virtual ~ResolverImp() ;
45 // Destructor. The destructor will block if the worker thread is
46 // still busy.
47
48 void doDelete() ;
49 // Schedules a 'delete this'. The callback is disarmed.
50
51 static void start( ResolverImp * , FutureEvent::handle_type ) ;
52 // Static worker-thread function to do name resolution. Calls
53 // ResolverFuture::run() to do the work and then FutureEvent::send()
54 // to signal the main thread. The event plumbing then results in a
55 // call to Resolver::done() on the main thread.
56
57 static size_t count() ;
58 // Returns the number of objects.
59
60 private:
61 ResolverImp( const ResolverImp & ) ;
62 void operator=( const ResolverImp & ) ;
63 virtual void onFutureEvent() override ; // GNet::FutureEventHandler
64 void onTimeout() ;
65
66 private:
67 typedef ResolverFuture::Pair Pair ;
68 Resolver * m_resolver ;
69 unique_ptr<FutureEvent> m_future_event ;
70 Timer<ResolverImp> m_timer ;
71 Location m_location ;
72 ResolverFuture m_future ;
73 G::threading::thread_type m_thread ;
74 bool m_busy ;
75 static size_t m_instance_count ;
76 } ;
77
78 size_t GNet::ResolverImp::m_instance_count = 0U ;
79
80 GNet::ResolverImp::ResolverImp( Resolver & resolver , ExceptionHandler & eh , const Location & location ) :
81 m_resolver(&resolver) ,
82 m_future_event(new FutureEvent(*this,eh)) ,
83 m_timer(*this,&ResolverImp::onTimeout,eh) ,
84 m_location(location) ,
85 m_future(location.host(),location.service(),location.family(),location.dgram(),true) ,
86 m_thread(ResolverImp::start,this,m_future_event->handle()) ,
87 m_busy(true)
88 {
89 m_instance_count++ ;
90 }
91
92 GNet::ResolverImp::~ResolverImp()
93 {
94 m_timer.cancelTimer() ;
95 if( m_thread.joinable() )
96 {
97 G_WARNING( "ResolverImp::dtor: waiting for getaddrinfo thread to complete" ) ;
98 m_thread.join() ;
99 }
100 m_instance_count-- ;
101 }
102
103 size_t GNet::ResolverImp::count()
104 {
105 return m_instance_count ;
106 }
107
108 void GNet::ResolverImp::start( ResolverImp * This , FutureEvent::handle_type handle )
109 {
110 // thread function, spawned from ctor and join()ed from dtor
111 try
112 {
113 This->m_future.run() ;
114 FutureEvent::send( handle ) ;
115 }
116 catch(...) // worker thread outer function
117 {
118 FutureEvent::send( handle ) ; // nothrow
119 }
120 }
121
122 void GNet::ResolverImp::onFutureEvent()
123 {
124 G_DEBUG( "GNet::ResolverImp::onFutureEvent: future event: ptr=" << m_resolver ) ;
125 G_ASSERT( m_busy ) ; if( !m_busy ) return ;
126 if( m_resolver == nullptr ) return ;
127
128 m_thread.join() ; // worker thread is finishing, so no delay here
129 m_busy = false ;
130 Resolver * resolver = m_resolver ;
131 m_resolver = nullptr ;
132 m_timer.startTimer( 0U ) ;
133
134 Pair result = m_future.get() ;
135 if( !m_future.error() )
136 m_location.update( result.first , result.second ) ;
137
138 G_DEBUG( "GNet::ResolverImp::onFutureEvent: [" << m_future.reason() << "][" << m_location.displayString() << "]" ) ;
139 resolver->done( m_future.reason() , m_location ) ;
140 }
141
142 void GNet::ResolverImp::doDelete()
143 {
144 m_resolver = nullptr ;
145 m_timer.startTimer( 0U ) ;
146 }
147
148 void GNet::ResolverImp::onTimeout()
149 {
150 if( m_busy )
151 m_timer.startTimer( 1U ) ;
152 else
153 delete this ;
154 }
155
156 // ==
157
158 GNet::Resolver::Resolver( Resolver::Callback & callback , ExceptionHandler & eh ) :
159 m_callback(callback) ,
160 m_eh(eh)
161 {
162 // lazy imp construction
163 }
164
165 GNet::Resolver::~Resolver()
166 {
167 const size_t sanity_limit = 50U ;
168 if( m_imp.get() != nullptr && ResolverImp::count() < sanity_limit )
169 {
170 // release the imp to an independent lifetime until its getaddrinfo() completes
171 G_DEBUG( "GNet::Resolver::dtor: releasing still-busy thread: " << ResolverImp::count() ) ;
172 m_imp->doDelete() ;
173 m_imp.release() ;
174 }
175 }
176
177 std::string GNet::Resolver::resolve( Location & location )
178 {
179 // synchronous resolve
180 typedef ResolverFuture::Pair Pair ;
181 G_DEBUG( "GNet::Resolver::resolve: resolve request [" << location.displayString() << "] (" << location.family() << ")" ) ;
182 ResolverFuture future( location.host() , location.service() , location.family() , location.dgram() ) ;
183 future.run() ; // blocks until complete
184 Pair result = future.get() ;
185 if( future.error() )
186 {
187 G_DEBUG( "GNet::Resolver::resolve: resolve error [" << future.reason() << "]" ) ;
188 return future.reason() ;
189 }
190 else
191 {
192 G_DEBUG( "GNet::Resolver::resolve: resolve result [" << result.first.displayString() << "][" << result.second << "]" ) ;
193 location.update( result.first , result.second ) ;
194 return std::string() ;
195 }
196 }
197
198 GNet::Resolver::AddressList GNet::Resolver::resolve( const std::string & host , const std::string & service , int family , bool dgram )
199 {
200 // synchronous resolve
201 G_DEBUG( "GNet::Resolver::resolve: resolve-request [" << host << "/" << service << "/" << (family==AF_UNSPEC?"ip":(family==AF_INET?"ipv4":"ipv6")) << "]" ) ;
202 ResolverFuture future( host , service , family , dgram ) ;
203 future.run() ;
204 AddressList list ;
205 future.get( list ) ;
206 G_DEBUG( "GNet::Resolver::resolve: resolve result: list of " << list.size() ) ;
207 return list ;
208 }
209
210 void GNet::Resolver::start( const Location & location )
211 {
212 // asynchronous resolve
213 if( !EventLoop::instance().running() ) throw Error("no event loop") ;
214 if( busy() ) throw BusyError() ;
215 G_DEBUG( "GNet::Resolver::start: resolve start [" << location.displayString() << "]" ) ;
216 m_imp.reset( new ResolverImp(*this,m_eh,location) ) ;
217 }
218
219 void GNet::Resolver::done( std::string error , Location location )
220 {
221 // callback from the event loop after worker thread is done
222 G_DEBUG( "GNet::Resolver::done: resolve done: error=[" << error << "] location=[" << location.displayString() << "]" ) ;
223 m_imp.reset() ;
224 m_callback.onResolved( error , location ) ;
225 }
226
227 bool GNet::Resolver::busy() const
228 {
229 return m_imp.get() != nullptr ;
230 }
231
232 bool GNet::Resolver::async()
233 {
234 if( G::threading::works() )
235 {
236 return EventLoop::instance().running() ;
237 }
238 else
239 {
240 G_DEBUG( "GNet::Resolver::async: not multi-threaded: using synchronous domain name lookup");
241 return false ;
242 }
243 }
244
245 // ==
246
247 GNet::Resolver::Callback::~Callback()
248 {
249 }
250
GNet::Resolver::~Resolver
~Resolver()
Destructor.
Definition: gresolver.cpp:165
GNet::Resolver::start
void start(const Location &)
Starts asynchronous name-to-address resolution.
Definition: gresolver.cpp:210
GNet::FutureEventHandler
A callback interface for GNet::FutureEvent.
Definition: gfutureevent.h:100
GNet::FutureEvent
An object that hooks into the event loop and calls back to the client code.
Definition: gfutureevent.h:62
GNet::Resolver
A class for synchronous or asynchronous network name to address resolution.
Definition: gresolver.h:42
geventloop.h
gtest.h
GNet::Location::displayString
std::string displayString() const
Returns a string representation for logging and debug.
Definition: glocation.cpp:169
GNet::Resolver::Callback
An interface used for GNet::Resolver callbacks.
Definition: gresolver.h:48
GNet::ExceptionHandler
An abstract interface for handling exceptions thrown out of event-loop callbacks (socket events...
Definition: gexceptionhandler.h:44
GNet::Location
A class that holds a host/service name pair and the preferred address family (if any), and also the results of a name-to-address lookup, ie.
Definition: glocation.h:50
GNet::Location::dgram
bool dgram() const
Returns true if the name resolution should be specifically for datagram sockets.
Definition: glocation.cpp:131
gstr.h
GNet::EventLoop::running
virtual bool running() const =0
Returns true if called from within run().
gtimer.h
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:153
GNet::Location::service
std::string service() const
Returns the remote service name, as passed in to the constructor.
Definition: glocation.cpp:121
GNet::ResolverFuture::get
Pair get()
Returns the resolved address/name pair.
Definition: gresolverfuture.cpp:136
GNet::Resolver::async
static bool async()
Returns true if the resolver supports asynchronous operation.
Definition: gresolver.cpp:232
GNet::FutureEvent::send
static bool send(handle_type handle) g__noexcept
Pokes an event into the main event loop so that the FutureEventHandler callback is called asynchronou...
Definition: gfutureevent_unix.cpp:146
gresolverfuture.h
GNet::ResolverFuture::run
void run()
Does the name resolution.
Definition: gresolverfuture.cpp:64
GNet::Resolver::busy
bool busy() const
Returns true if there is a pending resolve request.
Definition: gresolver.cpp:227
GNet::Resolver::resolve
static std::string resolve(Location &)
Does synchronous name resolution.
Definition: gresolver.cpp:177
GNet::Resolver::Resolver
Resolver(Callback &, ExceptionHandler &)
Constructor taking a callback interface reference.
Definition: gresolver.cpp:158
GNet::Location::family
int family() const
Returns the preferred name resolution address family, or AF_UNSPEC.
Definition: glocation.cpp:126
GNet::ResolverFuture
A 'future' object for asynchronous name resolution, in practice holding just enough state for a singl...
Definition: gresolverfuture.h:56
GNet::Timer
A timer class template in which the timeout is delivered to the specified method. ...
Definition: gtimer.h:115
GNet::ResolverImp
A private "pimple" implementation class used by GNet::Resolver to do asynchronous name resolution...
Definition: gresolver.cpp:38
gfutureevent.h
GNet::Location::host
std::string host() const
Returns the remote host name, as passed in to the constructor.
Definition: glocation.cpp:116
GNet::EventLoop::instance
static EventLoop & instance()
Returns a reference to an instance of the class, if any.
Definition: geventloop.cpp:42
gdebug.h
gresolver.h