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

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.041 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.