如何修复C ++ ODBC连接/断开过程中的句柄泄漏?

时间:2017-05-30 23:50:13

标签: c++ sql-server windows-7 odbc windows-10

症状

在对我的C ++应用程序进行负载测试时,使用和ODBC连接不断(并且大量)与SQL Server实例的接口,我开始注意到Windows任务管理器中的句柄泄漏。以前没有这些(请遏制你的怀疑和阅读),我怀疑他们在进行负载测试时已经发展。

在备用计算机上运行完全相同的二进制文件不会显示相同的症状,而是与泄漏开始之前标准和常见的预期行为相关联。即在另一台机器上没有相同二进制文件的泄漏。

我将此句柄泄漏跟踪到SQL连接/断开连接过程,并且能够使用仅 打开的控制台应用程序重新创建并关闭与SQL Server实例的连接,模拟{{3 }}。代码应如下所示。

  • 并非所有已分配的句柄似乎都已释放。
  • 返回值显示代码中的所有SQL操作都执行且没有错误。

所用组件的技术细节

  • 开发机器(泄漏)。 Windows 7专业版SP1。
    • 尝试使用ODBC驱动程序:
    • SQL Server 6.01.7601.17514
    • 用于SQL Server 2015.130.1601.05的ODBC驱动程序13。
    • 用于SQL Server 2015.131.4413.46的ODBC驱动程序13。
  • 具有更新操作系统(泄漏)的开发机器。 Windows 10专业版
    • 尝试使用ODBC驱动程序:
    • SQL Server 10.00.15063.00
    • 用于SQL Server 2017.140.500.272的ODBC驱动程序13。
  • 备用机器(无泄漏)。 Windows Server 2012 R2。
    • ODBC驱动程序:Sql Server 6.03.9600.17415
  • Visual Studio 2013(v120)控制台应用程序。
    • 标准窗口库
    • 未设置字符集。
    • 没有CLR支持。
    • 没有整个程序或C ++优化。
  • 从Dev机器尝试SQL Server连接。
    • Express 9.0.5。
    • 13.0.1728.2。

未成功尝试的内容

  1. 连接到另一个SQL服务器实例。

  2. 使用其他ODBC驱动程序。

  3. 修复Visual Studio 2013 Pro并重建二进制文件。

  4. 重新安装Visual Studio 2013 Pro并重建二进制文件。

  5. 重新安装SSMS(尝试刷新本地内置驱动程序)。

  6. 卸载包含" SQL"的所有组件。从PC安装最新的SSMS(试图减少组件冲突)。

  7. 仅将可疑组件提取到自己的控制台应用程序

  8. 使用默认系统DSN而不是SQLDriverConnect()尝试SQLConnect(),并明确指定所有参数。

  9. 更新了SQL Server ODBC驱动程序13到2015.131.4413.46。

  10. 控制台应用程序源

    // ConsoleTest.cpp : Defines the entry point for the console application.
    //
    #include <windows.h>
    #include <stdio.h>
    #include <tchar.h>
    #include <string>
    #include <sqlext.h>
    #include <sqltypes.h>
    #include <iostream>
    #include "MyTypes.h"
    
    #pragma comment(lib, "ODBC32.lib")
    
    static s32 PrintSqlDiagRecords(SQLRETURN _sqlRet, SQLSMALLINT _sqlHandleType, SQLHANDLE _sqlHandle);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        std::cout << "Starting test" << std::endl;
    
        SQLHANDLE m_sqlHndlEnvironment = NULL;
        SQLHANDLE m_sqlHndlConnection = NULL;
    
        for (int k = 0; k < 500; ++k)
        {       
            //Allocate environment handle
            SQLRETURN ssiResult = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_sqlHndlEnvironment);
            if ((ssiResult != SQL_SUCCESS) && (ssiResult != SQL_SUCCESS_WITH_INFO))
            {
                std::cout << "Error allocating ENV handle" << std::endl;
                return -1;
            }
    
            //Set environment attribute
            ssiResult = SQLSetEnvAttr(m_sqlHndlEnvironment, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
            if ((ssiResult != SQL_SUCCESS) && (ssiResult != SQL_SUCCESS_WITH_INFO))
            {
                std::cout << "Error setting ODBC version" << std::endl;
                return -1;
            }
    
            //Allocate connection handle
            ssiResult = SQLAllocHandle(SQL_HANDLE_DBC, m_sqlHndlEnvironment, &m_sqlHndlConnection);
            if ((ssiResult != SQL_SUCCESS) && (ssiResult != SQL_SUCCESS_WITH_INFO))
            {
                std::cout << "Error allocating DBC handle" << std::endl;
                return -1;
            }
    
            (void)SQLSetConnectAttr(m_sqlHndlConnection, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
    
            //Connect SQL
            SQLCHAR acOutmsg[1024] = { 0 };
            ssiResult = SQLDriverConnect(
                m_sqlHndlConnection, 
                NULL,           
                (SQLCHAR*)"My Verified Connection String",
                SQL_NTSL, 
                acOutmsg, 
                sizeof(acOutmsg), 
                NULL, 
                SQL_DRIVER_NOPROMPT);       
            if (ssiResult != SQL_SUCCESS)
            {
                (void) PrintSqlDiagRecords(ssiResult, SQL_HANDLE_DBC, m_sqlHndlConnection);
                //If success with info, just dump diagnostic info
                if (ssiResult == SQL_SUCCESS_WITH_INFO)  {/*Do nothing for now*/ }
                //Else error
                else
                {
                    std::cout << "Error connecting to DB" << std::endl;
                    return -1;
                }
            }       
    
            //Leave out actual DB operation to simplify execution path, but spin here for a while
            Sleep(10);
    
            //Free handles
            SQLRETURN sqlRet = SQL_SUCCESS;
            if (m_sqlHndlConnection != NULL)
            {
                sqlRet = SQLDisconnect(m_sqlHndlConnection);
                if (sqlRet != SQL_SUCCESS)
                {               
                    (void)PrintSqlDiagRecords(ssiResult, SQL_HANDLE_DBC, m_sqlHndlConnection);
                }
    
                sqlRet = SQLFreeHandle(SQL_HANDLE_DBC, m_sqlHndlConnection);
                if (sqlRet != SQL_SUCCESS)
                {
                    std::cout << "Error freeing DBC handle" << std::endl;
                    return -1;
                }
    
                m_sqlHndlConnection = NULL;
            }
    
            //Free environment handle
            if (m_sqlHndlEnvironment != NULL)
            {
                sqlRet = SQLFreeHandle(SQL_HANDLE_ENV, m_sqlHndlEnvironment);
                if (sqlRet != SQL_SUCCESS)
                {
                    std::cout << "Error freeing ENV handle" << std::endl;
                    return -1;
                }
    
                m_sqlHndlEnvironment = NULL;
            }
        }
    
        std::cout << "Test complete" << std::endl;
        Sleep(5000);
        return 0;
    }
    
    s32 PrintSqlDiagRecords(SQLRETURN _sqlRet, SQLSMALLINT _sqlHandleType, SQLHANDLE _sqlHandle)
    {
        SQLRETURN sqlRetDiag = SQL_SUCCESS;
            SQLINTEGER sqliNativeError = SQL_SUCCESS;
            SQLSMALLINT sqlsiMsgLen = 0;
            SQLCHAR acOutmsg[1024] = { 0 };
    
            SQLCHAR acSqlState[1024] = { 0 };
            int i = 1;
            sqlRetDiag = SQLGetDiagRec(_sqlHandleType, _sqlHandle, i, acSqlState, &sqliNativeError, acOutmsg, sizeof(acOutmsg), &sqlsiMsgLen);
            while ((sqlRetDiag != SQL_NO_DATA) && (i < 100))
            {
                //std::cout << "Msg[" << i <<"]: " << acOutmsg << "State: " << acSqlState << std::endl;
                ++i;
                memset(acOutmsg, 0, sizeof(acOutmsg));
                memset(acSqlState, 0, sizeof(acSqlState));
                sqlRetDiag = SQLGetDiagRec(_sqlHandleType, _sqlHandle, i, acSqlState, &sqliNativeError, acOutmsg, sizeof(acOutmsg), &sqlsiMsgLen);
            }
    
            return 0;
    }
    

    观察

    1. 在不显示这些泄漏的其他计算机上的相同二进制文件表示本地化到开发者PC的问题。
    2. 使用不同版本的SQL Server驱动程序进行重新创建可降低与ODBC驱动程序相关的可能性。
    3. 更新ODBC驱动程序后重新创建支持2.,因为它可以使用新安装更新过时/损坏的驱动程序文件。
    4. 重复连接/断开过程500次,在应用程序退出之前留下~580个句柄。 4.1每次迭代后中断执行显示1个句柄泄漏/迭代。
    5. 备用计算机上的测试控制台应用程序执行显示在执行期间和所有迭代完成后,句柄计数稳定在~135。
    6. 知道导致泄漏的原因是什么?

      非常感谢任何见解。

      亲切的问候。

      编辑6/7/2017:更新和更改论坛问题的目标是面向修复。

      • 原始问题背景旨在指出原因,所以我可以解决它。现在重点转向寻找解决方案并推断原因。
      • 根据@JeroenMostert的输入,我做了一个粗略的检查,尝试将原因归结为可能导致问题的操作。

      更新6/7/2017:

      • 忽略了我最近在症状出现前不久将我的Win 7 Pro OS恢复到SSD上。操作系统移动后的前几天不存在手柄泄漏(更多信息表示上述症状)。
      • 怀疑可能链接到Win 7 - SSD组合,因为上面的控制台应用程序在Windows 8.1或Windows 10计算机上尝试时没有显示任何泄漏(与上面提到的Windows Server 2012 R2相同)。
      • 在开发计算机上完成Windows 10 Pro的全新安装。操作系统安装后不久,控制台应用程序和原始原始应用程序二进制文件的初步测试成功。
      • 已安装VS2017并保持原始VC ++ lib和目标平台设置不变。
      • 安装日常操作所需的各种组件。
      • 恢复开发时,句柄泄漏再次出现,症状相同,本地机器上的二进制文件的反应与在其他可信计算机上运行的二进制文件不同。
      • 在操作系统更新之前构建并在其他测试计算机上验证的Pristine控制台应用程序仅在我的计算机上显示相同的泄漏行为。
      • 使用SQL Server ODBC v10.00.15063.00的控制台应用程序和SQL Server 2017.140.500.272的ODBC驱动程序13显示漏洞行为。
      • 目前临时解决方案是假设我无法信任我的开发机器的处理消耗(至少就SQL连接而言),并验证我们的登台服务器上的句柄使用情况

      调试上面的应用程序,试图找到进程的哪个部分没有释放所有句柄。

      (A)Win 10 Pro,VS2013,VC ++ v120,SQL Server ODBC v10.00.15063.00 - 4次迭代,每次迭代平均1次句柄泄漏

      • 连接:+5; +6; +6; 6
      • 断开连接:-1; -1; -1; -1
      • DBC free:-1; -2; -2; -2
      • ENV free:-1; -2; -2; -1

      (B)Win Server 2012 R2,VS2013,VC ++ v120,SQL Server ODBC 6.03.9600.17415 - 4次迭代 - 净句柄使用常量

      • 连接:+10; +11; +10; 10
      • 断开连接:-1; -1; -1; -1
      • DBC free:-2; -2; -3; -2
      • ENV免费:-7; -7; -7; -7

      获得相当小的样本量:

      • 怀疑ENV处理免费操作是罪魁祸首。在工作情况(B)中使用是恒定的,在(A)中使用更多。
      • 在两种情况下,断开操作似乎都是不变的。
      • 在两种情况下,DBC free看起来相当稳定(4次迭代中有1次更改)

0 个答案:

没有答案