症状
在对我的C ++应用程序进行负载测试时,使用和ODBC连接不断(并且大量)与SQL Server实例的接口,我开始注意到Windows任务管理器中的句柄泄漏。以前没有这些(请遏制你的怀疑和阅读),我怀疑他们在进行负载测试时已经发展。
在备用计算机上运行完全相同的二进制文件不会显示相同的症状,而是与泄漏开始之前标准和常见的预期行为相关联。即在另一台机器上没有相同二进制文件的泄漏。
我将此句柄泄漏跟踪到SQL连接/断开连接过程,并且能够使用仅 打开的控制台应用程序重新创建并关闭与SQL Server实例的连接,模拟{{3 }}。代码应如下所示。
所用组件的技术细节
未成功尝试的内容
连接到另一个SQL服务器实例。
使用其他ODBC驱动程序。
修复Visual Studio 2013 Pro并重建二进制文件。
重新安装Visual Studio 2013 Pro并重建二进制文件。
重新安装SSMS(尝试刷新本地内置驱动程序)。
卸载包含" SQL"的所有组件。从PC安装最新的SSMS(试图减少组件冲突)。
仅将可疑组件提取到自己的控制台应用程序
使用默认系统DSN而不是SQLDriverConnect()尝试SQLConnect(),并明确指定所有参数。
更新了SQL Server ODBC驱动程序13到2015.131.4413.46。
控制台应用程序源
// 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;
}
观察
知道导致泄漏的原因是什么?
非常感谢任何见解。
亲切的问候。
编辑6/7/2017:更新和更改论坛问题的目标是面向修复。
更新6/7/2017:
调试上面的应用程序,试图找到进程的哪个部分没有释放所有句柄。
(A)Win 10 Pro,VS2013,VC ++ v120,SQL Server ODBC v10.00.15063.00 - 4次迭代,每次迭代平均1次句柄泄漏
(B)Win Server 2012 R2,VS2013,VC ++ v120,SQL Server ODBC 6.03.9600.17415 - 4次迭代 - 净句柄使用常量
获得相当小的样本量: