Asynchronous resolving.

This commit is contained in:
Vincent Richard 2016-03-02 20:33:55 +01:00
parent baec395c8c
commit 194a797055
5 changed files with 147 additions and 32 deletions

View File

@ -1003,6 +1003,19 @@ ELSE(PTHREAD_LIB)
SET(VMIME_HAVE_PTHREAD 0) SET(VMIME_HAVE_PTHREAD 0)
ENDIF(PTHREAD_LIB) ENDIF(PTHREAD_LIB)
# getaddrinfo_a() - GNU libc
LIST(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
LIST(APPEND CMAKE_REQUIRED_LIBRARIES anl)
CHECK_SYMBOL_EXISTS(getaddrinfo_a netdb.h VMIME_HAVE_GETADDRINFO_A)
IF(VMIME_HAVE_GETADDRINFO_A)
TARGET_LINK_LIBRARIES(
${VMIME_LIBRARY_NAME}
${TARGET_LINK_LIBRARIES}
anl
)
ENDIF()
############################################################################## ##############################################################################
# Additional compiler flags # Additional compiler flags

View File

@ -76,6 +76,7 @@ typedef unsigned @VMIME_64BIT_TYPE@ vmime_uint64;
#cmakedefine01 VMIME_PLATFORM_IS_WINDOWS #cmakedefine01 VMIME_PLATFORM_IS_WINDOWS
#cmakedefine01 VMIME_HAVE_PTHREAD #cmakedefine01 VMIME_HAVE_PTHREAD
#cmakedefine01 VMIME_HAVE_GETADDRINFO #cmakedefine01 VMIME_HAVE_GETADDRINFO
#cmakedefine01 VMIME_HAVE_GETADDRINFO_A
#cmakedefine01 VMIME_HAVE_GETTID #cmakedefine01 VMIME_HAVE_GETTID
#cmakedefine01 VMIME_HAVE_SYSCALL #cmakedefine01 VMIME_HAVE_SYSCALL
#cmakedefine01 VMIME_HAVE_SYSCALL_GETTID #cmakedefine01 VMIME_HAVE_SYSCALL_GETTID

View File

@ -1,3 +1,4 @@
#include <ctime>
/** Time out handler. /** Time out handler.
@ -8,19 +9,27 @@ class timeoutHandler : public vmime::net::timeoutHandler
{ {
public: public:
timeoutHandler()
: m_start(time(NULL))
{
}
bool isTimeOut() bool isTimeOut()
{ {
// This is a cancellation point: return true if you want to cancel // This is a cancellation point: return true if you want to cancel
// the current operation. If you return true, handleTimeOut() will // the current operation. If you return true, handleTimeOut() will
// be called just after this, and before actually cancelling the // be called just after this, and before actually cancelling the
// operation // operation
return false;
// 10 seconds timeout
return (time(NULL) - m_start) >= 10; // seconds
} }
void resetTimeOut() void resetTimeOut()
{ {
// Called at the beginning of an operation (eg. connecting, // Called at the beginning of an operation (eg. connecting,
// a read() or a write() on a socket...) // a read() or a write() on a socket...)
m_start = time(NULL);
} }
bool handleTimeOut() bool handleTimeOut()
@ -29,10 +38,14 @@ public:
// allows you to interact with the user, ie. display a prompt to // allows you to interact with the user, ie. display a prompt to
// know whether he wants to cancel the operation. // know whether he wants to cancel the operation.
// If you return true here, the operation will be actually cancelled. // If you return false here, the operation will be actually cancelled.
// If not, the time out is reset and the operation continues. // If true, the time out is reset and the operation continues.
return true; return false;
} }
private:
time_t m_start;
}; };

View File

@ -30,6 +30,10 @@
#include "vmime/platforms/posix/posixSocket.hpp" #include "vmime/platforms/posix/posixSocket.hpp"
#include "vmime/platforms/posix/posixHandler.hpp" #include "vmime/platforms/posix/posixHandler.hpp"
#ifndef _GNU_SOURCE
#define _GNU_SOURCE // for getaddrinfo_a() in <netdb.h>
#endif
#include <unistd.h> #include <unistd.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <arpa/inet.h> #include <arpa/inet.h>
@ -95,42 +99,26 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
#if VMIME_HAVE_GETADDRINFO // use thread-safe and IPv6-aware getaddrinfo() if available #if VMIME_HAVE_GETADDRINFO // use thread-safe and IPv6-aware getaddrinfo() if available
// Resolve address, if needed // Resolve address, if needed
struct ::addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
std::ostringstream portStr;
portStr.imbue(std::locale::classic());
portStr << port;
struct ::addrinfo* res0;
if (::getaddrinfo(address.c_str(), portStr.str().c_str(), &hints, &res0) != 0)
{
// Error: cannot resolve address
throw vmime::exceptions::connection_error("Cannot resolve address.");
}
m_serverAddress = address; m_serverAddress = address;
struct ::addrinfo* addrInfo = NULL; // resolved addresses
resolve(&addrInfo, address, port);
// Connect to host // Connect to host
int sock = -1; int sock = -1;
struct ::addrinfo* res = res0;
int connectErrno = 0; int connectErrno = 0;
if (m_timeoutHandler != NULL) if (m_timeoutHandler != NULL)
m_timeoutHandler->resetTimeOut(); m_timeoutHandler->resetTimeOut();
for ( ; sock == -1 && res != NULL ; res = res->ai_next, connectErrno = ETIMEDOUT) for (struct ::addrinfo* curAddrInfo = addrInfo ;
sock == -1 && curAddrInfo != NULL ;
curAddrInfo = curAddrInfo->ai_next, connectErrno = ETIMEDOUT)
{ {
if (res->ai_family != AF_INET && res->ai_family != AF_INET6) if (curAddrInfo->ai_family != AF_INET && curAddrInfo->ai_family != AF_INET6)
continue; continue;
sock = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol); sock = ::socket(curAddrInfo->ai_family, curAddrInfo->ai_socktype, curAddrInfo->ai_protocol);
if (sock < 0) if (sock < 0)
{ {
@ -152,7 +140,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
{ {
::fcntl(sock, F_SETFL, ::fcntl(sock, F_GETFL) | O_NONBLOCK); ::fcntl(sock, F_SETFL, ::fcntl(sock, F_GETFL) | O_NONBLOCK);
if (::connect(sock, res->ai_addr, res->ai_addrlen) < 0) if (::connect(sock, curAddrInfo->ai_addr, curAddrInfo->ai_addrlen) < 0)
{ {
switch (errno) switch (errno)
{ {
@ -254,7 +242,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
timeval curTime = { 0, 0 }; timeval curTime = { 0, 0 };
gettimeofday(&curTime, /* timezone */ NULL); gettimeofday(&curTime, /* timezone */ NULL);
if (res->ai_next != NULL && if (curAddrInfo->ai_next != NULL &&
curTime.tv_usec - startTime.tv_usec >= tryNextTimeout * 1000) curTime.tv_usec - startTime.tv_usec >= tryNextTimeout * 1000)
{ {
connectErrno = ETIMEDOUT; connectErrno = ETIMEDOUT;
@ -280,7 +268,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
} }
else else
{ {
if (::connect(sock, res->ai_addr, res->ai_addrlen) < 0) if (::connect(sock, curAddrInfo->ai_addr, curAddrInfo->ai_addrlen) < 0)
{ {
connectErrno = errno; connectErrno = errno;
::close(sock); ::close(sock);
@ -290,7 +278,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
} }
} }
::freeaddrinfo(res0); ::freeaddrinfo(addrInfo);
if (sock == -1) if (sock == -1)
{ {
@ -373,6 +361,101 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
} }
void posixSocket::resolve(struct ::addrinfo** addrInfo, const vmime::string& address, const vmime::port_t port)
{
std::ostringstream portStr;
portStr.imbue(std::locale::classic());
portStr << port;
struct ::addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME | AI_NUMERICSERV;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
#if VMIME_HAVE_GETADDRINFO_A
// If getaddrinfo_a() is available, use asynchronous resolving to allow
// the timeout handler to cancel the operation
struct ::gaicb gaiRequest;
memset(&gaiRequest, 0, sizeof(gaiRequest));
gaiRequest.ar_name = address.c_str();
gaiRequest.ar_service = portStr.str().c_str();
gaiRequest.ar_request = &hints;
struct ::gaicb* gaiRequests = &gaiRequest;
int gaiError;
if ((gaiError = getaddrinfo_a(GAI_NOWAIT, &gaiRequests, 1, NULL)) != 0)
{
throw vmime::exceptions::connection_error
("getaddrinfo_a() failed: " + std::string(gai_strerror(gaiError)));
}
if (m_timeoutHandler != NULL)
m_timeoutHandler->resetTimeOut();
while (true)
{
struct timespec gaiTimeout;
gaiTimeout.tv_sec = 1; // query timeout handler every second
gaiTimeout.tv_nsec = 0;
gaiError = gai_suspend(&gaiRequests, 1, &gaiTimeout);
if (gaiError == 0 || gaiError == EAI_ALLDONE)
{
const int ret = gai_error(&gaiRequest);
if (ret != 0)
{
throw vmime::exceptions::connection_error
("getaddrinfo_a() request failed: " + std::string(gai_strerror(ret)));
}
else
{
*addrInfo = gaiRequest.ar_result;
break;
}
}
else if (gaiError != EAI_AGAIN)
{
throw vmime::exceptions::connection_error
("gai_suspend() failed: " + std::string(gai_strerror(gaiError)));
}
// Check for timeout
if (m_timeoutHandler && m_timeoutHandler->isTimeOut())
{
if (!m_timeoutHandler->handleTimeOut())
{
throw exceptions::operation_timed_out();
}
else
{
// Reset timeout and keep waiting for connection
m_timeoutHandler->resetTimeOut();
}
}
}
#else // !VMIME_HAVE_GETADDRINFO_A
if (::getaddrinfo(address.c_str(), portStr.str().c_str(), &hints, addrInfo) != 0)
{
// Error: cannot resolve address
throw vmime::exceptions::connection_error("Cannot resolve address.");
}
#endif // VMIME_HAVE_GETADDRINFO_A
}
bool posixSocket::isConnected() const bool posixSocket::isConnected() const
{ {
if (m_desc == -1) if (m_desc == -1)

View File

@ -34,6 +34,9 @@
#include "vmime/net/socket.hpp" #include "vmime/net/socket.hpp"
struct addrinfo;
namespace vmime { namespace vmime {
namespace platforms { namespace platforms {
namespace posix { namespace posix {
@ -75,6 +78,8 @@ public:
protected: protected:
void resolve(struct ::addrinfo** addrInfo, const vmime::string& address, const vmime::port_t port);
bool waitForData(const bool read, const bool write, const int msecs); bool waitForData(const bool read, const bool write, const int msecs);
static void throwSocketError(const int err); static void throwSocketError(const int err);