Bug #2074 Another bug in Embedded mySql in a multithread application
Submitted: 10 Dec 2003 3:51 Modified: 12 Dec 2003 10:17
Reporter: Cristiano Muzi Email Updates:
Status: Can't repeat Impact on me:
None 
Category:MySQL Server: Embedded Library ( libmysqld ) Severity:S2 (Serious)
Version:4.0.16 patched OS:Windows (Win 2000/XP)
Assigned to: Alexey Botchkov CPU Architecture:Any

[10 Dec 2003 3:51] Cristiano Muzi
Description:
We think we've found another bug in Embedded mySql; we've used the version of libmysqld.dll patched for bug #1959.

We've used the same test program of submission #1959, slightly modified.
The program does theese tasks:
- it creates a database and a table;
- it spawns N (i.e. 50) "inserter threads", each of which continously performs  INSERT queries on the table;
- it spawns N "remover threads", each of which continously performs DELETE queries on the table;
- the main thread continously performs UPDATE queries on the table.

The program is the same of the #1959 submission with this difference: each thread opens the connection before executing the query, and closes the connection after having executed it.

Two behaviours can be observer:

- if mysql_thread_end is called after mysql_close, while the program runs the amount of used memory quickly grows up and after a while an access violation is raised (the error message is: "Unhandled exception in MySqlTest.exe (NTDLL:DLL) 0xC00000005: Access Violation.").

- if mysql_thread_end is not called, the program runs without any access violation, but it seems that there is some lack of memory since the amount of used memory continues to grow up.

So my question is: if a thread opens and closes a connection each time it wants to perform a query, it has or has not to call mysql_thread_end each time it closes the connection?

Thank you,
Cristiano Muzi

How to repeat:
Here is the test code:

--------------------------------------------------------------------------------
// mySqlTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

// This function creates the database and the test table.
void createDatabaseAndTable(void);

// This function connects to the database.
MYSQL* dbConnect(void);

// This function terminates the program, priting an error message.
void die(const char* errorMsg, const char* mysqlErrorMsg = NULL);

// How many threads per type are created.
#define threadCount 50

// Handles of threads.
HANDLE inserterThreadList[threadCount], removerThreadList[threadCount];

// Main procedure of the inserter threads.
DWORD WINAPI inserterThreadProc(LPVOID lpParameter);

// Main procedure of the remover threads.
DWORD WINAPI removerThreadProc(LPVOID lpParameter);

const char *args[] = {"Ignored line", NULL};

int main(int argc, char* argv[])
{
	printf("Starting test program\n");

	// Initializes the server.
	if (mysql_server_init(1, (char**)args, NULL))
		die("mysql_server_init error");
	printf("mysql_server_init OK\n");

	// Creates the dabase and the table.
	createDatabaseAndTable();
	printf("Database and table created.\n");

	// Creates the remover threads.
	for (int i = 0; i < threadCount; i++)
		removerThreadList[i] = CreateThread(NULL, 0, *removerThreadProc, NULL, 0, NULL);
	printf("Remover threads created.\n");

	// Creates the inserter threads.
	for (i = 0; i < threadCount; i++)
		inserterThreadList[i] = CreateThread(NULL, 0, *inserterThreadProc, NULL, 0, NULL);
	printf("Inserter threads created.\n");

	// Repeatedly changes the value of the "passed" flag into the table.

	for (;;) {

		MYSQL *mySql = dbConnect();

		if (mysql_query(mySql, "UPDATE testTable SET passed = 1 WHERE passed = 0"))
			die("UPDATE query error", mysql_error(mySql));

		mysql_close(mySql);
		mysql_thread_end();

	}

	// Closes the server.
	mysql_server_end();
	
	return 0;
}

DWORD WINAPI inserterThreadProc(LPVOID lpParameter)
{
	// Repeatedly insert rows.
	for (;;) {
		
		MYSQL *mySql = dbConnect();

		if (mysql_query(mySql, 
				"INSERT INTO testTable (name, surname, passed) VALUES (\"Mark\", \"Twain\", 0)"))
			die("inserterThread error", mysql_error(mySql));

		mysql_close(mySql);
		mysql_thread_end();
	}
}

DWORD WINAPI removerThreadProc(LPVOID lpParameter)
{

	// Repeatedly insert rows.
	for (;;) {
		MYSQL *mySql = dbConnect();

		if (mysql_query(mySql, 
				"DELETE FROM testTable WHERE passed = 1"))
			die("inserterThread error", mysql_error(mySql));

		mysql_close(mySql);
		mysql_thread_end();
	}

}

MYSQL* dbConnect(void)
{
	MYSQL *mySql = mysql_init(NULL);
	if (!mySql)
		die("mysql_init error");

	if (!mysql_real_connect(mySql, NULL, NULL, NULL, "testDB", 0, NULL, 0))
		die("mysql_real_connect error", mysql_error(mySql));

	return mySql;
}

void createDatabaseAndTable(void)
{
	MYSQL *mySql = mysql_init(NULL);
	if (!mySql)
		die("mysql_init error");

	if (!mysql_real_connect(mySql, NULL, NULL, NULL, NULL, 0, NULL, 0))
		die("mysql_real_connect error");

	if (mysql_query(mySql, "CREATE DATABASE IF NOT EXISTS testDB"))
		die("mysql_query error creating the database.", mysql_error(mySql));

	mysql_close(mySql);
	mySql = dbConnect();

// Defininf the table without indices will arise other types of error.
	if (mysql_query(mySql,
			"CREATE TABLE IF NOT EXISTS testTable (\
			id INTEGER NOT NULL AUTO_INCREMENT, \
			name TEXT NOT NULL, \
			surname TEXT NOT NULL, \
			passed TINYINT, \
			PRIMARY KEY(id))" ))
		die("mysql_query error creating table.", mysql_error(mySql));

	mysql_close(mySql);
}

void die(const char* errorMsg, const char* mysqlErrorMsg)
{
	if (mysqlErrorMsg)
		printf("### DIE\n%s\n%s\n", errorMsg, mysqlErrorMsg);
	else
		printf("### DIE\n%s\n", errorMsg);
	exit(EXIT_FAILURE);
}

-------------------------------------------------------------------------------
[12 Dec 2003 10:17] Alexey Botchkov
mysql_thread_end() is to be called only once after you finish using MySQL in
the thread (usually just before the ending of the thread)

I checked version with
  for {
    mysql_connect(..)
     ...
    mysql_close();
  }

Didn't find any memory leaks. Of cource your program eats memory every time
new thread starts, and mysql sometime allocates internal buffers.
But all this is freed when you close thread or connection.