You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2017/11/21 12:10:00 UTC

[06/47] ignite git commit: IGNITE-6835 ODBC driver now handles ungraceful TCP disconnects This closes #2997

IGNITE-6835 ODBC driver now handles ungraceful TCP disconnects
This closes #2997


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/46c480b4
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/46c480b4
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/46c480b4

Branch: refs/heads/ignite-zk
Commit: 46c480b46cb6c86ebcc3a94659c689057f9e1464
Parents: 9303845
Author: Igor Sapego <is...@gridgain.com>
Authored: Tue Nov 14 15:48:02 2017 +0300
Committer: Igor Sapego <is...@gridgain.com>
Committed: Tue Nov 14 15:49:04 2017 +0300

----------------------------------------------------------------------
 .../include/ignite/odbc/system/socket_client.h  |  20 +-
 .../odbc/os/linux/src/system/socket_client.cpp  | 140 ++++++++++++-
 .../odbc/os/win/src/system/socket_client.cpp    | 194 ++++++++++++++++++-
 modules/platforms/cpp/odbc/src/connection.cpp   |   2 +-
 4 files changed, 341 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/46c480b4/modules/platforms/cpp/odbc/include/ignite/odbc/system/socket_client.h
----------------------------------------------------------------------
diff --git a/modules/platforms/cpp/odbc/include/ignite/odbc/system/socket_client.h b/modules/platforms/cpp/odbc/include/ignite/odbc/system/socket_client.h
index ee58927..946605e 100644
--- a/modules/platforms/cpp/odbc/include/ignite/odbc/system/socket_client.h
+++ b/modules/platforms/cpp/odbc/include/ignite/odbc/system/socket_client.h
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ignite/common/common.h"
+#include "ignite/odbc/diagnostic/diagnosable.h"
 
 namespace ignite
 {
@@ -34,6 +35,15 @@ namespace ignite
             class SocketClient
             {
             public:
+                /** Buffers size */
+                enum { BUFFER_SIZE = 0x10000 };
+
+                /** The time in seconds the connection needs to remain idle before starts sending keepalive probes. */
+                enum { KEEP_ALIVE_IDLE_TIME = 60 };
+
+                /** The time in seconds between individual keepalive probes. */
+                enum { KEEP_ALIVE_PROBES_PERIOD = 1 };
+
                 /**
                  * Constructor.
                  */
@@ -49,9 +59,10 @@ namespace ignite
                  *
                  * @param hostname Remote host name.
                  * @param port TCP service port.
+                 * @param diag Diagnostics collector.
                  * @return True on success.
                  */
-                bool Connect(const char* hostname, uint16_t port);
+                bool Connect(const char* hostname, uint16_t port, diagnostic::Diagnosable& diag);
 
                 /**
                  * Close established connection.
@@ -73,7 +84,7 @@ namespace ignite
                 /**
                  * Receive data from established connection.
                  *
-                 * @param data Pointer to data buffer.
+                 * @param buffer Pointer to data buffer.
                  * @param size Size of the buffer in bytes.
                  * @return Number of bytes that have been received on success and negative
                  *         value on failure.
@@ -81,6 +92,11 @@ namespace ignite
                 int Receive(int8_t* buffer, size_t size);
 
             private:
+                /**
+                 * Tries set socket options.
+                 */
+                void TrySetOptions(diagnostic::Diagnosable& diag);
+
                 intptr_t socketHandle;
 
                 IGNITE_NO_COPY_ASSIGNMENT(SocketClient)

http://git-wip-us.apache.org/repos/asf/ignite/blob/46c480b4/modules/platforms/cpp/odbc/os/linux/src/system/socket_client.cpp
----------------------------------------------------------------------
diff --git a/modules/platforms/cpp/odbc/os/linux/src/system/socket_client.cpp b/modules/platforms/cpp/odbc/os/linux/src/system/socket_client.cpp
index 9bdf1d7..5a9b03a 100644
--- a/modules/platforms/cpp/odbc/os/linux/src/system/socket_client.cpp
+++ b/modules/platforms/cpp/odbc/os/linux/src/system/socket_client.cpp
@@ -17,7 +17,7 @@
 
 #include <sys/socket.h>
 #include <sys/types.h>
-#include <sys/socket.h>
+#include <netinet/tcp.h>
 #include <netdb.h>
 #include <unistd.h>
 
@@ -31,6 +31,32 @@
 
 #define SOCKET_ERROR (-1)
 
+namespace
+{
+    /**
+     * Get last socket error message.
+     * @return Last socket error message string.
+     */
+    std::string GetLastSocketErrorMessage()
+    {
+        int lastError = errno;
+        std::stringstream res;
+
+        res << "error_code=" << lastError;
+
+        if (lastError == 0)
+            return res.str();
+
+        char buffer[1024] = "";
+
+        strerror_r(lastError, buffer, sizeof(buffer));
+
+        res << ", msg=" << buffer;
+
+        return res.str();
+    }
+}
+
 namespace ignite
 {
     namespace odbc
@@ -48,11 +74,12 @@ namespace ignite
                 Close();
             }
 
-            bool SocketClient::Connect(const char* hostname, uint16_t port)
+            bool SocketClient::Connect(const char* hostname, uint16_t port, diagnostic::Diagnosable& diag)
             {
                 LOG_MSG("Host: " << hostname << ", port: " << port);
 
                 addrinfo hints;
+
                 memset(&hints, 0, sizeof(hints));
                 hints.ai_family = AF_UNSPEC;
                 hints.ai_socktype = SOCK_STREAM;
@@ -66,26 +93,44 @@ namespace ignite
                 int res = getaddrinfo(hostname, converter.str().c_str(), &hints, &result);
 
                 if (res != 0)
+                {
+                    LOG_MSG("Address resolving failed: " << gai_strerror(res));
+
+                    diag.AddStatusRecord(SqlState::SHY000_GENERAL_ERROR, "Can not resolve host address.");
+
                     return false;
+                }
 
                 // Attempt to connect to an address until one succeeds
-                for (addrinfo *it = result; it != NULL; it = it->ai_next) 
+                for (addrinfo *it = result; it != NULL; it = it->ai_next)
                 {
-                    LOG_MSG("Addr: " << it->ai_addr->sa_data[2] << "."
-                                     << it->ai_addr->sa_data[3] << "."
-                                     << it->ai_addr->sa_data[4] << "."
-                                     << it->ai_addr->sa_data[5]);
+                    LOG_MSG("Addr: " << (it->ai_addr->sa_data[2] & 0xFF) << "."
+                                     << (it->ai_addr->sa_data[3] & 0xFF) << "."
+                                     << (it->ai_addr->sa_data[4] & 0xFF) << "."
+                                     << (it->ai_addr->sa_data[5] & 0xFF));
 
                     // Create a SOCKET for connecting to server
                     socketHandle = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
 
                     if (socketHandle == SOCKET_ERROR)
+                    {
+                        LOG_MSG("Socket creation failed: " << GetLastSocketErrorMessage());
+
+                        diag.AddStatusRecord(SqlState::SHY000_GENERAL_ERROR, "Can not create new socket.");
+
                         return false;
+                    }
+
+                    diag.GetDiagnosticRecords().Reset();
+
+                    TrySetOptions(diag);
 
                     // Connect to server.
-                    res = connect(socketHandle, it->ai_addr, (int)it->ai_addrlen);
-                    if (res == SOCKET_ERROR) 
+                    res = connect(socketHandle, it->ai_addr, static_cast<int>(it->ai_addrlen));
+                    if (SOCKET_ERROR == res)
                     {
+                        LOG_MSG("Connection failed: " << GetLastSocketErrorMessage());
+
                         Close();
 
                         continue;
@@ -117,6 +162,83 @@ namespace ignite
             {
                 return recv(socketHandle, reinterpret_cast<char*>(buffer), static_cast<int>(size), 0);
             }
+
+            void SocketClient::TrySetOptions(diagnostic::Diagnosable& diag)
+            {
+                int trueOpt = 1;
+                int bufSizeOpt = BUFFER_SIZE;
+                int idleOpt = KEEP_ALIVE_IDLE_TIME;
+                int idleRetryOpt = KEEP_ALIVE_PROBES_PERIOD;
+
+                int res = setsockopt(socketHandle, SOL_SOCKET, SO_SNDBUF,
+                    reinterpret_cast<char*>(&bufSizeOpt), sizeof(bufSizeOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP socket send buffer size setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP socket send buffer size");
+                }
+
+                res = setsockopt(socketHandle, SOL_SOCKET, SO_RCVBUF,
+                    reinterpret_cast<char*>(&bufSizeOpt), sizeof(bufSizeOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP socket receive buffer size setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP socket receive buffer size");
+                }
+
+                res = setsockopt(socketHandle, IPPROTO_TCP, TCP_NODELAY,
+                    reinterpret_cast<char*>(&trueOpt), sizeof(trueOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP no-delay mode setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP no-delay mode");
+                }
+
+                res = setsockopt(socketHandle, SOL_SOCKET, SO_KEEPALIVE,
+                    reinterpret_cast<char*>(&trueOpt), sizeof(trueOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP keep-alive mode setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP keep-alive mode");
+
+                    // There is no sense in configuring keep alive params if we faileed to set up keep alive mode.
+                    return;
+                }
+
+                res = setsockopt(socketHandle, IPPROTO_TCP, TCP_KEEPIDLE,
+                    reinterpret_cast<char*>(&idleOpt), sizeof(idleOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP keep-alive idle timeout setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP keep-alive idle timeout");
+                }
+
+                res = setsockopt(socketHandle, IPPROTO_TCP, TCP_KEEPINTVL,
+                    reinterpret_cast<char*>(&idleRetryOpt), sizeof(idleRetryOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP keep-alive probes period setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP keep-alive probes period");
+                }
+            }
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/46c480b4/modules/platforms/cpp/odbc/os/win/src/system/socket_client.cpp
----------------------------------------------------------------------
diff --git a/modules/platforms/cpp/odbc/os/win/src/system/socket_client.cpp b/modules/platforms/cpp/odbc/os/win/src/system/socket_client.cpp
index 4c440e2..30fb7d7 100644
--- a/modules/platforms/cpp/odbc/os/win/src/system/socket_client.cpp
+++ b/modules/platforms/cpp/odbc/os/win/src/system/socket_client.cpp
@@ -21,6 +21,7 @@
 #include <windows.h>
 #include <winsock2.h>
 #include <ws2tcpip.h>
+#include <mstcpip.h>
 
 #include <cstring>
 
@@ -30,6 +31,54 @@
 #include "ignite/odbc/utility.h"
 #include "ignite/odbc/log.h"
 
+namespace
+{
+    /**
+     * Get last socket error message.
+     * @return Last socket error message string.
+     */
+    std::string GetLastSocketErrorMessage()
+    {
+        HRESULT lastError = WSAGetLastError();
+        std::stringstream res;
+
+        res << "error_code=" << lastError;
+
+        if (lastError == 0)
+            return res.str();
+
+        LPTSTR errorText = NULL;
+
+        DWORD len = FormatMessage(
+            // use system message tables to retrieve error text
+            FORMAT_MESSAGE_FROM_SYSTEM
+            // allocate buffer on local heap for error text
+            | FORMAT_MESSAGE_ALLOCATE_BUFFER
+            // We're not passing insertion parameters
+            | FORMAT_MESSAGE_IGNORE_INSERTS,
+            // unused with FORMAT_MESSAGE_FROM_SYSTEM
+            NULL,
+            lastError,
+            MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
+            // output
+            reinterpret_cast<LPTSTR>(&errorText),
+            // minimum size for output buffer
+            0,
+            // arguments - see note
+            NULL);
+
+        if (NULL != errorText)
+        {
+            if (len != 0)
+                res << ", msg=" << std::string(errorText, len);
+
+            LocalFree(errorText);
+        }
+
+        return res.str();
+    }
+}
+
 namespace ignite
 {
     namespace odbc
@@ -47,7 +96,7 @@ namespace ignite
                 Close();
             }
 
-            bool SocketClient::Connect(const char* hostname, uint16_t port)
+            bool SocketClient::Connect(const char* hostname, uint16_t port, diagnostic::Diagnosable& diag)
             {
                 static bool networkInited = false;
 
@@ -59,10 +108,15 @@ namespace ignite
                     networkInited = (WSAStartup(MAKEWORD(2, 2), &wsaData) == 0);
 
                     if (!networkInited)
+                    {
+                        LOG_MSG("Networking initialisation failed: " << GetLastSocketErrorMessage());
+
+                        diag.AddStatusRecord(SqlState::SHY000_GENERAL_ERROR, "Can not initialize Windows networking.");
+
                         return false;
+                    }
                 }
 
-                addrinfo *result = NULL;
                 addrinfo hints;
 
                 LOG_MSG("Host: " << hostname << " port: " << port);
@@ -76,10 +130,17 @@ namespace ignite
                 converter << port;
 
                 // Resolve the server address and port
+                addrinfo *result = NULL;
                 int res = getaddrinfo(hostname, converter.str().c_str(), &hints, &result);
 
                 if (res != 0)
+                {
+                    LOG_MSG("Address resolving failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::SHY000_GENERAL_ERROR, "Can not resolve host address.");
+
                     return false;
+                }
 
                 // Attempt to connect to an address until one succeeds
                 for (addrinfo *it = result; it != NULL; it = it->ai_next)
@@ -93,12 +154,24 @@ namespace ignite
                     socketHandle = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
 
                     if (socketHandle == INVALID_SOCKET)
+                    {
+                        LOG_MSG("Socket creation failed: " << GetLastSocketErrorMessage());
+
+                        diag.AddStatusRecord(SqlState::SHY000_GENERAL_ERROR, "Can not create new socket.");
+
                         return false;
+                    }
+
+                    diag.GetDiagnosticRecords().Reset();
+
+                    TrySetOptions(diag);
 
                     // Connect to server.
                     res = connect(socketHandle, it->ai_addr, static_cast<int>(it->ai_addrlen));
-                    if (res == SOCKET_ERROR)
+                    if (SOCKET_ERROR == res)
                     {
+                        LOG_MSG("Connection failed: " << GetLastSocketErrorMessage());
+
                         Close();
 
                         continue;
@@ -130,6 +203,121 @@ namespace ignite
             {
                 return recv(socketHandle, reinterpret_cast<char*>(buffer), static_cast<int>(size), 0);
             }
+
+            void SocketClient::TrySetOptions(diagnostic::Diagnosable& diag)
+            {
+                BOOL trueOpt = TRUE;
+                int bufSizeOpt = BUFFER_SIZE;
+
+                int res = setsockopt(socketHandle, SOL_SOCKET, SO_SNDBUF,
+                    reinterpret_cast<char*>(&bufSizeOpt), sizeof(bufSizeOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP socket send buffer size setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP socket send buffer size");
+                }
+
+                res = setsockopt(socketHandle, SOL_SOCKET, SO_RCVBUF,
+                    reinterpret_cast<char*>(&bufSizeOpt), sizeof(bufSizeOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP socket receive buffer size setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP socket receive buffer size");
+                }
+
+                res = setsockopt(socketHandle, IPPROTO_TCP, TCP_NODELAY,
+                    reinterpret_cast<char*>(&trueOpt), sizeof(trueOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP no-delay mode setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP no-delay mode");
+                }
+
+                res = setsockopt(socketHandle, SOL_SOCKET, SO_KEEPALIVE,
+                    reinterpret_cast<char*>(&trueOpt), sizeof(trueOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP keep-alive mode setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP keep-alive mode");
+
+                    // There is no sense in configuring keep alive params if we faileed to set up keep alive mode.
+                    return;
+                }
+
+                // This option is available starting with Windows 10, version 1709.
+#if defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL)
+                DWORD idleOpt = KEEP_ALIVE_IDLE_TIME;
+                DWORD idleRetryOpt = KEEP_ALIVE_PROBES_PERIOD;
+
+                res = setsockopt(socketHandle, IPPROTO_TCP, TCP_KEEPIDLE,
+                    reinterpret_cast<char*>(&idleOpt), sizeof(idleOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP keep-alive idle timeout setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP keep-alive idle timeout");
+                }
+
+                res = setsockopt(socketHandle, IPPROTO_TCP, TCP_KEEPINTVL,
+                    reinterpret_cast<char*>(&idleRetryOpt), sizeof(idleRetryOpt));
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP keep-alive probes period setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP keep-alive probes period");
+                }
+#else // use old hardcore WSAIoctl
+
+                // WinSock structure for KeepAlive timing settings
+                struct tcp_keepalive settings = {0};
+                settings.onoff = 1;
+                settings.keepalivetime = KEEP_ALIVE_IDLE_TIME * 1000;
+                settings.keepaliveinterval = KEEP_ALIVE_PROBES_PERIOD * 1000;
+
+                // pointers for WinSock call
+                DWORD bytesReturned;
+                WSAOVERLAPPED overlapped;
+                overlapped.hEvent = NULL;
+
+                // Set KeepAlive settings
+                res = WSAIoctl(
+                    socketHandle,
+                    SIO_KEEPALIVE_VALS,
+                    &settings,
+                    sizeof(struct tcp_keepalive),
+                    NULL,
+                    0,
+                    &bytesReturned,
+                    &overlapped,
+                    NULL
+                );
+
+                if (SOCKET_ERROR == res)
+                {
+                    LOG_MSG("TCP keep-alive params setup failed: " << GetLastSocketErrorMessage());
+
+                    diag.AddStatusRecord(SqlState::S01S02_OPTION_VALUE_CHANGED,
+                        "Can not set up TCP keep-alive idle timeout and probes period");
+                }
+#endif
+            }
+
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/46c480b4/modules/platforms/cpp/odbc/src/connection.cpp
----------------------------------------------------------------------
diff --git a/modules/platforms/cpp/odbc/src/connection.cpp b/modules/platforms/cpp/odbc/src/connection.cpp
index 8f4bf14..b99d768 100644
--- a/modules/platforms/cpp/odbc/src/connection.cpp
+++ b/modules/platforms/cpp/odbc/src/connection.cpp
@@ -126,7 +126,7 @@ namespace ignite
                 return SqlResult::AI_ERROR;
             }
 
-            connected = socket.Connect(cfg.GetHost().c_str(), cfg.GetTcpPort());
+            connected = socket.Connect(cfg.GetHost().c_str(), cfg.GetTcpPort(), *this);
 
             if (!connected)
             {