Bug #12105 Thread-safe client crushes under Windows platform.
Submitted: 22 Jul 2005 8:03 Modified: 28 Jul 2005 9:23
Reporter: [ name withheld ] Email Updates:
Status: Not a Bug Impact on me:
None 
Category:MySQL Server Severity:S1 (Critical)
Version:4.1.12 OS:Windows (Windows)
Assigned to: CPU Architecture:Any

[22 Jul 2005 8:03] [ name withheld ]
Description:
When make any queries to MySQL server in the same time from more than one thread in C application, access violation happens in ntdll.dll file. So the mysqlclient.lib is not thread-safe for Windows platform. But in documentation: "the Windows binaries are by default compiled to be thread-safe". This is not so. When make the same queries from single thread - no any problems.

How to repeat:
1. Create any simple database with any table.
2. Create C application with two or more threads.
3. Run these threads and make queries to the table from every thread.

Suggested fix:
Needs instructions how to make mysqclient.lib thread-safe for Windows platform.
[22 Jul 2005 9:09] Aleksey Kishkin
Can you provide here an exampe? It's hard to reproduce if we dont know how exactly you tried to use mysql API in multithread application. Did you use mysql_thread_init(), mysql_thread_end()?
[22 Jul 2005 12:47] MySQL Verification Team
Are you followed the instructions mentioned at:

http://dev.mysql.com/doc/mysql/en/threaded-clients.html
[22 Jul 2005 14:49] [ name withheld ]
//
// 1. MySQL server and this client are one the same computer.
// 2. If N_THREADS = 1 then everything is ok
//     If N_THREADS > 1 there is access violation
//

#include <windows.h>
#include <process.h>
#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <ctype.h>

#define N_THREADS        2
#define TEST_DB_NAME     "test_db"
#define NUMBER_OF_ITEMS  100

typedef struct
{
    MYSQL   *pMysql;
    HANDLE   hEventExit;
} data_t;

char  *g_szTable = "CREATE TABLE test_tab \
                    ( \
                        col1        INT UNSIGNED NOT NULL PRIMARY KEY, \
                        col2        INT UNSIGNED NOT NULL, \
                        col3        INT UNSIGNED NOT NULL, \
                        col4        INT UNSIGNED NOT NULL  \
                    )";

void show_help( char *szExe );
void press_key( char key, char *sz );

unsigned int __stdcall thread( void *pParameter );

void main( int argc, char **argv )
{
    int              i, err;
    unsigned         n;
    char             szQuery[1024];
    MYSQL           *pMysql         = NULL;
    char            *pszExe         = NULL;
    char            *pszPassword    = NULL;
    char            *pszUser        = NULL;
    char            *pszMysqlIpAddr = NULL;
    unsigned short   mysqlPort      = 0;
    HANDLE           hThread[N_THREADS] = {0};
    data_t           data               = {0};

    err = 1;
    srand( (unsigned int) time(NULL) );

    switch ( argc )
    {
        case 5: pszPassword    = argv[4];
        case 4: pszUser        = argv[3];
        case 3: mysqlPort      = atoi( argv[2] );
        case 2: pszMysqlIpAddr = argv[1];
    }

    pszExe = strrchr( argv[0], '\\' );
    pszExe++;

    do
    {
        //
        // init MySQL
        //
        pMysql = mysql_init( NULL );
        if ( !pMysql )
        {
            printf( "mysql_init() - ERROR\n" );
            break;
        }

        //
        // connect to MySQL server
        //
        if ( !mysql_real_connect( pMysql,
                                  pszMysqlIpAddr,
                                  pszUser,
                                  pszPassword,
                                  NULL,
                                  mysqlPort,
                                  NULL,
                                  0 ) )
        {
            printf( "mysql_real_connect() - ERROR: %s\n", mysql_error( pMysql ) );
            break;
        }

        //
        // delete old database if it exists
        //
        sprintf( szQuery, "DROP DATABASE IF EXISTS %s", TEST_DB_NAME );
        if ( mysql_query( pMysql, szQuery ) )
        {
            printf( "mysql_query( DROP DATABASE ) - ERROR: %s\n", mysql_error( pMysql ) );
        }

        //
        // create new database
        //
        sprintf( szQuery, "CREATE DATABASE %s", TEST_DB_NAME );
        if ( mysql_query( pMysql, szQuery ) )
        {
            printf( "mysql_query( CREATE DATABASE ) - ERROR: %s\n", mysql_error( pMysql ) );
            break;
        }

        //
        // select new database
        //
        if ( mysql_select_db( pMysql, TEST_DB_NAME ) )
        {
            printf( "mysql_select_db() - ERROR: %s\n", mysql_error( pMysql ) );
            break;
        }

        //
        // create table in new database
        //
        if ( mysql_query( pMysql, g_szTable ) )
        {
            printf( "mysql_query( g_szTable ) - ERROR: %s\n", mysql_error( pMysql ) );
            break;
        }

        //
        // fill database
        //
        for ( i=0; i<NUMBER_OF_ITEMS; i++ )
        {
            //
            // test_tab table:
            //   col1        INT UNSIGNED NOT NULL PRIMARY KEY
            //   col2        INT UNSIGNED NOT NULL
            //   col3        INT UNSIGNED NOT NULL
            //   col4        INT UNSIGNED NOT NULL 
            //
            sprintf( szQuery, "INSERT INTO test_tab VALUES( %u, %u, %u, %u )", i, i*2, i*3, i*4 );
            if ( mysql_query( pMysql, szQuery ) )
            {
                printf( "test_tab: mysql_query( szQuery_%i ) - ERROR: %s\n", i, mysql_error( pMysql ) );
                break;
            }
        }

        if ( i == NUMBER_OF_ITEMS )
        {
            printf( "Database '%s' was successfuly created.\n", TEST_DB_NAME );

            if ( mysql_thread_safe() )
            {
                printf( "Client is thread-safe.\n" );
            }
            else
            {
                printf( "Client is not thread-safe.\n" );
            }

            printf( "Start update database...\n" );

            data.pMysql     = pMysql;
            data.hEventExit = CreateEvent( NULL, TRUE, FALSE, NULL );
            if ( !data.hEventExit ) break;

            for ( i=0; i<N_THREADS; i++ )
            {
                hThread[i] = (HANDLE) _beginthreadex( 0, 0, thread, &data, 0, &n );
            }

            err = 0;
        }

    } while (0);

    if ( err )
    {
        show_help( pszExe );
    }

    press_key( 'q', "to quit..." );

    if ( data.hEventExit )
    {
        SetEvent( data.hEventExit );
    }

    for ( i=0; i<N_THREADS; i++ )
    {
        if ( hThread[i] )
        {
            WaitForSingleObject( hThread[i], 10000 );
            CloseHandle( hThread[i] );
            hThread[i] = NULL;
        }
    }

    if ( data.hEventExit )
    {
        CloseHandle( data.hEventExit );
        data.hEventExit = NULL;
    }

    if ( pMysql )
    {
        mysql_close( pMysql );
        pMysql = NULL;
    }
}

//*********************************************************
unsigned int __stdcall thread( void *pParameter )
{
    int      i;
    char     szQuery[1024];
    data_t  *pData = (data_t *) pParameter;
    DWORD    id    = GetCurrentThreadId();

    printf( "thread id=%u started\n", id );

    if ( mysql_thread_init() )
    {
        printf( "thread id=%u: mysql_thread_init() - ERROR\n", id );
    }

    i = 0;

    while ( WaitForSingleObject( pData->hEventExit, 20 ) != WAIT_OBJECT_0 )
    {
        sprintf( szQuery, "UPDATE test_tab SET col2 = %u, col3 = %u, col4 = %u WHERE col1 = %u", i*4, i*3, i*2, i );

        if ( mysql_query( pData->pMysql, szQuery ) )
        {
            printf( "thread id=%u mysql_query(%u) - ERROR: %s\n", id, i, mysql_error( pData->pMysql ) );
        }

        i++;
        if ( i == NUMBER_OF_ITEMS ) i = 0;
    }

    mysql_thread_end();

    printf( "thread id=%u finished\n", id );

    return 0;
}

//*********************************************************
void show_help( char *szExe )
{
    printf( "How to use:\n" );
    printf( ">%s [mysql_ip_addr] [mysql_port] [user] [password]\n", szExe );
    printf( "    mysql_ip_addr - ip address of mysql server\n" );
    printf( "    mysql_port    - port of mysql server\n" );
    printf( "    user          - user name to connect to mysql server\n" );
    printf( "    password      - password to connect to mysql server\n" );
    printf( "\n" );
}

//*********************************************************
void press_key( char key, char *sz )
{
    if ( sz )
        printf( "\nPress '%c' key %s\n", key, sz );
    else
        printf( "\nTo continue press '%c' key...\n",key );

    while ( toupper(_getch()) != toupper((int) key) );
}
[23 Jul 2005 7:30] Vasily Kishkin
I tested the test case on Windows 2003 and got error message:

Database 'test_db' was successfuly created.
Client is thread-safe.
Start update database...

Press 'q' key to quit...
thread id=3168 started
thread id=3172 started
thread id=3168 mysql_query(0) - ERROR: Lost connection to MySQL server during query
[23 Jul 2005 12:12] [ name withheld ]
I have the same results but on Windows XP Professional (SP2).
After this error client application crushed because of access violation
in "ntdll.dll" file.

>I tested the test case on Windows 2003 and got error message:

>Database 'test_db' was successfuly created.
>Client is thread-safe.
>Start update database...

>Press 'q' key to quit...
>thread id=3168 started
>thread id=3172 started
>thread id=3168 mysql_query(0) - ERROR: Lost connection to MySQL server
>during query
[28 Jul 2005 9:23] Aleksey Kishkin
Well. functions of mysql api _are_ thread-safe, but if you want to share _one_ connection in the all threads, you must lock mysql_query and mysql_store_results  by mutex (or any similar things). If you open connection for each thread, you don't need to lock any section of code.  It's in the documentation that Miguel mentioned. (http://dev.mysql.com/doc/mysql/en/threaded-clients.html).
[28 Jul 2005 9:38] [ name withheld ]
Thank you. I was not careful with documentation. Now I see that mysqlclient.lib
is thread-safe only per connection for each thread. Several threads can't share the same connection simultaneously without of synchronization objects.