Bug #65136 my_thread_var not getting initialized
Submitted: 27 Apr 2012 16:35 Modified: 4 May 2012 10:53
Reporter: Steven Cain Email Updates:
Status: Verified Impact on me:
None 
Category:Connector / ODBC Severity:S3 (Non-critical)
Version:5.5.23 OS:Microsoft Windows
Assigned to: Bogdan Degtyariov CPU Architecture:Any

[27 Apr 2012 16:35] Steven Cain
Description:
We connect to our CentOS based MySQL servers with the Windows Connector/ODBC from our Windows application.  Our application creates multiple connections to the MySQL server on application startup using the Connector/ODBC driver.  If the thread that calls my_thread_global_init first is not the first thread to call mysql_server_init then a thread will not initialize my_thread_var correctly.  A subsequent call to my_read while doing a LOAD DATA LOCAL INFILE causes an access violation in my_read when setting my_errno to any value.  my_errno is a define that uses my_thread_global.

This problem occurs because the call to my_thread_init in mysql_server_init is skipped in a thread if the thread is the first thread to call mysql_server_init.  The first thread to call my_thread_global_init calls my_thread_init so it is assumed that the first thread to call my_thread_global_init will also call mysql_server_init first.

Example 1:
Correct behavior.
Thread A calls my_thread_global_init first and calls my_thread_init to initialize my_thread_global.
Thread A calls mysql_server_init first and skips the call of my_thread_init.
Thread B calls my_thread_global_init second and skips the call of  my_thread_init.
Thread B calls mysql_server_init second and calls my_thread_init to initialize my_thread_global.

Example 2:
Incorrect behavior.
Thread A calls my_thread_global_init first and calls my_thread_init to initialize my_thread_global.
Thread B calls my_thread_global_init second and skips the call of  my_thread_init.
Thread B calls mysql_server_init first and skips the call of my_thread_init.
Thread A calls mysql_server_init second and calls my_thread_init to initialize my_thread_global again.
The result is Thread A called my_thread_init 2 times and Thread B call my_thread_init 0 times.

How to repeat:
Create an application that will open multiple connections to a server at once(our client opens 10 connections).  Occasionally different threads will call my_thread_global_init and mysql_server_init first.  I added logging to these functions so I could see when this occurred on application startup.  To cause the access violation perform a LOAD DATA LOCAL INFILE statement in the thread that called mysql_server_init first but did not call my_thread_global_init first.

Suggested fix:
Add
if (my_thread_var == 0)
{
      result= (int)my_thread_init();         /* Init if new thread */
}

at the end of

if (!mysql_client_init)

in mysql_server_init.

This patch works for our application but I think the thread that calls my_thread_global_init first should be forced to complete any other required initialization functions before other threads are allowed to proceed in order to guarantee proper initialization.
[30 Apr 2012 9:31] Bogdan Degtyariov
Which version of Connector/ODBC you are using?
5.5.23 is the version of the server or client library, but not ODBC driver.
[30 Apr 2012 15:09] Steven Cain
I am using Connector/ODBC version 5.1.10.
[30 Apr 2012 15:10] Steven Cain
When I compiled the driver I used Connector/ODBC version 5.1.10 and mysql source code version 5.5.23 to compile libmysql.
[1 May 2012 3:49] Bogdan Degtyariov
Thanks for your reply.
Can you send us the output from the following shell command:

ldd libmyodbc5.so

I want to find out whether it is trying to load libmysqlclient_r.so shared library or the client code was statically linked into the driver binary file.
[1 May 2012 15:10] Steven Cain
The driver that I am compiling and running is for Windows.  I am building the driver in Visual Studio 2010 and replacing the myodbc5.dll on my client machine with the one that I compiled.
[2 May 2012 4:38] Bogdan Degtyariov
Steven,

Thanks for your reply.
Please note that my_thread_global_init() and mysql_server_init() are not called
explicitly anywhere in Connector/ODBC code.

The driver only calls my_init() function upon loading the library, which should
be sufficient for initializing all mysql client internals:

my_init() initializes some global variables that MySQL needs. It also calls mysql_thread_init() for this thread.

It is necessary for my_init() to be called early in the initialization phase of
a program's use of the MySQL library. However, my_init() is automatically
called by mysql_init(), mysql_library_init(), mysql_server_init(), and
mysql_connect(). If you ensure that your program invokes one of those functions
before any other MySQL calls, there is no need to invoke my_init() explicitly.

http://dev.mysql.com/doc/refman/5.5/en/my-init.html

NOTE: this comment from Connector/ODBC source might be essential for 
      understanding how the threading is designed in the driver:

  /*
     We don't explicitly call my_thread_init() to avoid initialization in
     threads that may not even make ODBC calls. my_thread_init() will be
     called implicitly when mysys calls are made from the thread.
  */

Also, my_init() cannot be called twice in two different threads. The second
call of my_init() can only be possible if myodbc5.dll library is unloaded from
memory and then loaded again. This is different from what you have showed in
your scheme how to repeat the problem.

So, I have two questions:

 1. Since you built the driver on your own and we do not support the custom
    builds, have you tried the release binary version for Windows OS?

 2. As I explained above, I do not completely understand how to repeat the 
    problem. Maybe you could provide a simplified C/C++ test case?

Thanks.
[2 May 2012 15:34] Steven Cain
I compiled this program using Visual Studio 6.  I get an access violation most of the time that I run it.  Let me know how it performs for you.  temp.csv contains the numbers 0-9 each on their own line.

Here's some test code:

// BugTest.cpp : Defines the entry point for the console application.
//

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers

#include <stdio.h>
#include <windows.h>
#include <winbase.h>
#include <process.h>
#include <sqlext.h>

int g_bProcess = 1;

unsigned __stdcall SQLThread( void * )
{
  char szConnect[] = "DRIVER=MySQL ODBC 5.1 Driver;SERVER=youserver;TRUSTED_CONNECTION=no;NETWORK=dbmssocn;OPTION=67108864;DATABASE=test;UID=user;PWD=pass;";
  char szConnectOut[1024];
  // CREATE TABLE `test_load_data` (
  // `test_load_data` int(11) NOT NULL AUTO_INCREMENT,
  // `Message` varchar(32) DEFAULT NULL,
  // PRIMARY KEY (`test_load_data`)
  // ) ENGINE=MyISAM AUTO_INCREMENT=113211 DEFAULT CHARSET=latin1
  char szSQLStatement[] = "LOAD DATA LOCAL INFILE 'c:\\\\temp\\\\test.csv' INTO TABLE test.test_load_data FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\r\n' (Message);";
  SQLHENV hEnv = NULL;
  SQLHDBC hDbc = NULL;
  SQLHSTMT hStmt = NULL;
  SQLSMALLINT iConnectOutLengthOut;
  int iReturnValue;
  int bReconnect = 0;
  int bLoadData = 1;
  int bGetMoreResults;
  int bFirstLoop = 1;

  while(g_bProcess)
  {
    if(hDbc == NULL)
    {
      if(bFirstLoop)
      {
        bFirstLoop = 0;
      }
      else
      {
        Sleep(10000);
      }

      printf("Initializing\n");
      SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
      SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
      SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);

      printf("Connecting\n");
      iReturnValue = SQLDriverConnect(hDbc,
                  NULL,
                  (SQLCHAR*)szConnect,
                  strlen(szConnect),
                  (SQLCHAR*)szConnectOut,
                  0,
                  &iConnectOutLengthOut,
                  SQL_DRIVER_NOPROMPT);

      if(iReturnValue != SQL_SUCCESS && iReturnValue != SQL_SUCCESS_WITH_INFO)
      {
        printf("Could not connect to %s\n", szConnect);

        SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
        hDbc = NULL;
        SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
        hEnv = NULL;
        continue;
      }
    }

    while(bLoadData && g_bProcess)
    {
      printf("Allocating statement\n");
      SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);

      printf("Executing statement\n");
      iReturnValue = SQLExecDirect(hStmt, (SQLCHAR*)(szSQLStatement), SQL_NTS);
      if(iReturnValue == SQL_SUCCESS || iReturnValue == SQL_NO_DATA)
      {
        bGetMoreResults = 1;
        while(bGetMoreResults)
        {
          printf("Fetching results\n");
          iReturnValue = SQLFetch(hStmt);
          if(iReturnValue == SQL_SUCCESS || iReturnValue == SQL_SUCCESS_WITH_INFO)
          {
            // Store data from the resultset.
          }

          printf("Getting more results\n");
          iReturnValue = SQLMoreResults(hStmt);
          if(iReturnValue == SQL_SUCCESS || iReturnValue == SQL_SUCCESS_WITH_INFO)
          {
            continue;
          }
          else if(iReturnValue == SQL_NO_DATA)
          {
            bGetMoreResults = 0;
          }
          else
          {
            // Error.
            bGetMoreResults = 0;
            bLoadData = 0;
            bReconnect = 1;
          }
        }
      }
      else
      {
        printf("Error executing statement %s\n", szSQLStatement);
        bLoadData = 0;
        bReconnect = 1;
      }

      printf("Freeing statement\n");
      SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
      hStmt = NULL;
      Sleep(100);
    }

    if(bReconnect || !g_bProcess)
    {
      printf("Disconnecting\n");
      SQLDisconnect(hDbc);

      printf("Uninitialization\n");
      SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
      hDbc = NULL;
      SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
      hEnv = NULL;
    }
  }

  return 0;
}

int main(int argc, char* argv[])
{
  unsigned long hThread;
  unsigned uThreadId;
  int iThreadsCreated = 0;

  printf("Start\n");

  // Create some threads that will start using the driver all at once.
  for(int i = 0; i < 10; i ++)
  {
    hThread = _beginthreadex(NULL, 
      0, 
      SQLThread, 
      NULL, 
      0,
      &uThreadId);
    if(hThread != 0)
      iThreadsCreated ++;
  }

  if(iThreadsCreated)
  {
    printf("Let the threads do stuff\n");
    Sleep(20000);

    printf("Tell the threads to stop\n");
    g_bProcess = 0;

    Sleep(15000);
  }

  printf("All done\n");

	return 0;
}
[4 May 2012 7:08] Bogdan Degtyariov
Thanks for the test case.
First of all, we do not recommend using the unrecognized option names for ODBC driver:

TRUSTED_CONNECTION=no;NETWORK=dbmssocn;

These two options are just ignored.

Another thing is the driver name, which should be enclosed in {}:

DRIVER={MySQL ODBC 5.1 Driver}

Nevertheless, I will try running it and then let you know the results.
[4 May 2012 10:25] Bogdan Degtyariov
one more thing:

   after executing LOAD DATA INFILE it is not correct to get results using
   SQLFetch() because no resultset is returned at all.
   I inserted the error handling and got the following error message:

   Error: [MySQL][ODBC 5.1 Driver][mysqld-5.5.15-log]Fetch without a SELECT
[4 May 2012 10:53] Bogdan Degtyariov
There is no crash if SQLFetch() is not called.
This is incorrect to call SQLFetch() when no result set is expected, but the driver should not crash anyway...
Need to check this.