Bug #93593 ODBC memory leak
Submitted: 13 Dec 2018 14:36 Modified: 1 Jun 2020 3:58
Reporter: Bhargava Srinarasi Email Updates:
Status: No Feedback Impact on me:
None 
Category:Connector / ODBC Severity:S2 (Serious)
Version:8.0.13 OS:Windows
Assigned to: CPU Architecture:Any
Tags: memory leak

[13 Dec 2018 14:36] Bhargava Srinarasi
Description:
I'm seeing a memory leak in SQLConnect() on Windows when I call SQLConnect() and SQLDisconnect() repeatedly.

I can see that the memory that's allocated in SQLConnect() never gets released.

A couple of observations that may be interesting keeping the C program below in mind.

1) After the first iteration, hdbc is the same on all iterations!
2) Instead of doing connect and disconnect in the same loop, if I put the handles in an array and disconnect/free them later, I see that there's much less leak. Also hdbc is different in each iteration.

How to repeat:
Here's a very simple C program which leaks around 150kB per iteration.

int main() {
for (int i = 0; i < 10; ++i)
{
    SQLHENV henv;
    SQLHDBC hdbc;
    SQLRETURN retcode;

    // Allocate environment handle  
    retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);

    // Set the ODBC version environment attribute  
    if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
        retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);

        // Allocate connection handle  
        if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
            retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

            // Set login timeout to 5 seconds  
            if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
                SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);

                // Connect to data source  
                retcode = SQLConnect(hdbc, (SQLCHAR*) "MySQL", SQL_NTS, (SQLCHAR*) "test", 0, (SQLCHAR*) "test", 0);

                if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
                    SQLDisconnect(hdbc);
                }

                SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
            }
        }
        SQLFreeHandle(SQL_HANDLE_ENV, henv);
    }
}
[1 May 2020 3:58] MySQL Verification Team
Please try version 8.0.20. Thanks.
[14 May 2020 16:56] Leigh Anderson
I still have this issue in 8.0.20.
If I use 5.3.14 there is no leak.
[2 Jun 2020 1:00] Bugs System
No feedback was provided for this bug for over a month, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
[6 Jun 2020 10:08] Leigh Anderson
I still have this issue with the 8.0.20 driver.
If I use the 5.3.14 driver there is no leak.
[29 Mar 2022 12:08] c t
still existing in mysql-connector-odbc-noinstall-8.0.28-win32

this bug is happening during load/unload of the driver dll.

when you use debugger in VS2017 you can see relation 
between load/alloc and unload/NO free.

VS2017 code used for verification and analysis:

// NOTE: Use Multi-Byte Character Set

// tested with latest ODBC driver
// see output for unload/load
// 'memleak_test.exe' (Win32) : Unloaded 'C:\mysql-connector-odbc-noinstall-8.0.28-win32\lib\myodbc8w.dll'
// 'memleak_test.exe' (Win32) : Loaded 'C:\mysql-connector-odbc-noinstall-8.0.28-win32\lib\myodbc8w.dll'.Symbols loaded.

// REDUCED_OUTPUT can be used to prevent non error output of results of various odbc functions
#define REDUCED_OUTPUT 0
// ODBC_DATASOURCE is the ODBC connection used, configured to MySQL ODBC Unicode driver
#define ODBC_DATASOURCE "test_mycustomer"

#include <iostream>
#include <windows.h>
#include <sqltypes.h>
#include <sqlext.h>
#include <psapi.h>

#pragma comment(lib, "odbc32.lib")

class MemleakTest
{
public:
	MemleakTest(const std::string &ds)
		: odbcDataSource(ds)
	{}

	~MemleakTest()
	{
		disconnect();
	}

	bool connect()
	{
		const auto envAllocResult = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
#if !REDUCED_OUTPUT
		std::cout << "envAllocResult: " << envAllocResult << std::endl;
#endif
		if (SQL_SUCCEEDED(envAllocResult))
		{
			const auto setEnvAttrResult = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
#if !REDUCED_OUTPUT
			std::cout << "setEnvAttrResult: " << setEnvAttrResult << std::endl;
#endif
			if (SQL_SUCCEEDED(envAllocResult))
			{
				const auto hdbcAllocResult = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
#if !REDUCED_OUTPUT
				std::cout << "hdbcAllocResult: " << hdbcAllocResult << std::endl;
#endif
				if (SQL_SUCCEEDED(hdbcAllocResult))
				{
					const auto setConnAttrResult = SQLSetConnectAttr(hDbc, SQL_LOGIN_TIMEOUT, (void*)5, 0);
#if !REDUCED_OUTPUT
					std::cout << "setConnAttrResult: " << setConnAttrResult << std::endl;
#endif
					if (SQL_SUCCEEDED(setConnAttrResult))
					{
						// Connect to data source //
						const auto connectResult = SQLConnect(hDbc, (SQLCHAR*)odbcDataSource.c_str(), SQL_NTS, (SQLCHAR*) "", SQL_NTS, (SQLCHAR*) "", SQL_NTS);
#if !REDUCED_OUTPUT
						std::cout << "connectResult: " << connectResult << std::endl;
#endif
						if (SQL_SUCCEEDED(connectResult))
						{
#if !REDUCED_OUTPUT
							std::cout << "CONNECTED" << std::endl;
#endif
							return true;
						}
						else
						{
							std::cerr << "SQLConnect failed: " << connectResult << std::endl;
						}
					}
					else
					{
						std::cerr << "SQLSetConnectAttr failed: " << setConnAttrResult << std::endl;
					}

					FreeHandle(SQL_HANDLE_DBC, hDbc);
				}
				else
				{
					std::cerr << "SQLAllocHandle hDbc failed: " << hdbcAllocResult << std::endl;
				}
			}
			else
			{
				std::cerr << "SQLSetEnvAttr failed: " << setEnvAttrResult << std::endl;
			}

			FreeHandle(SQL_HANDLE_ENV, hEnv);
		}
		else
		{
			std::cerr << "SQLAllocHandle HENV failed: " << envAllocResult << std::endl;
		}

		return false;
	}

	void disconnect()
	{
		if (hDbc)
		{
			const auto disconnectResult = SQLDisconnect(hDbc);
#if !REDUCED_OUTPUT
			std::cout << "disconnectResult: " << disconnectResult << std::endl;
#endif
			if (!SQL_SUCCEEDED(disconnectResult))
			{
				std::cerr << "SQLDisconnect failed: " << disconnectResult << std::endl;
			}

			FreeHandle(SQL_HANDLE_DBC, hDbc);
		}
		if (hEnv)
		{
			FreeHandle(SQL_HANDLE_ENV, hEnv);
		}

#if !REDUCED_OUTPUT
		std::cout << "DISCONNECTED" << std::endl;
#endif
	}

private:
	std::string odbcDataSource;
	SQLHANDLE hEnv = nullptr;
	SQLHANDLE hDbc = nullptr;

	bool FreeHandle(SQLSMALLINT type, SQLHANDLE &handle)
	{
		if (nullptr == handle)
		{
			std::cerr << "invalid handle given" << std::endl;
			return false;
		}

		const auto freeHandleResult = SQLFreeHandle(type, handle);
		if (!SQL_SUCCEEDED(freeHandleResult))
		{
			std::cerr << "SQLFreeHandle(type " << type << ", handle " << handle << ") failed: " << freeHandleResult << std::endl;
			return false;
		}
		handle = nullptr;
		return true;
	}
};

void printMemory()
{
	const HANDLE hProcess = GetCurrentProcess();
	PROCESS_MEMORY_COUNTERS pmc;

	if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
	{
		printf("\tWorkingSetSize: 0x%08X\n", pmc.WorkingSetSize);
	}

	CloseHandle(hProcess);
}

int main()
{
	std::cout << "***START ";
	printMemory();

	for (uint32_t counter = 0; counter < 500; ++counter)
	{
		MemleakTest memleakTest(ODBC_DATASOURCE);
		memleakTest.connect();
		memleakTest.disconnect();

		std::cout << "#" << counter;
		printMemory();

		Sleep(500);		// dont let firewall/server think its DDoS attack
	}

	std::cout << "***END ";
	printMemory();

	return 0;
}
[30 Mar 2022 15:32] c t
Add.: used system:

Edition	Windows 10 Enterprise
Version	20H2
OS build	19042.1586
Experience	Windows Feature Experience Pack 120.2212.4170.0