Bug #81092 NDB API multithreading issue
Submitted: 15 Apr 2016 9:41 Modified: 9 Feb 2018 16:03
Reporter: Miljen Mikić Email Updates:
Status: Not a Bug Impact on me:
None 
Category:MySQL Cluster: NDB API Severity:S3 (Non-critical)
Version:MySQL Cluster 5.6.28 / NDB 7.4.9 OS:Windows (Windows 7 Enterprise N SP 1)
Assigned to: MySQL Verification Team CPU Architecture:Any

[15 Apr 2016 9:41] Miljen Mikić
Description:
I have faced an interesting issue that happens only on Windows, while on Linux everything works just fine. I made a simple C++ application based on your official documentation (https://dev.mysql.com/doc/ndbapi/en/ndb-examples.html) where I perform NDB initialization in one thread, and read data in another. The error I get while trying to read data is  „write access violation _my_thread_var(...) returned nullptr”. However, if I do everything in a single thread – it works. My C++ application references „ndbclient_static.lib“ and „mysqlclient.lib“ from the /lib folder of MySQL Cluster installation. Code is attached below.

This is a really simple scenario, where as a part of a larger project you wish to call ndb_init() and get Ndb_cluster_connection object in one thread (let's call it init thread), and perform NDB read/writes in another thread (let's call it worker thread). Worker thread should not obtain Ndb_cluster_connection every time when runs, because this is expensive and there is no performance benefit, which is otherwise tremendous.

How to repeat:
Build and run a C++ program that contains two methods listed below (first replace "my_database_name" and "my_table_name" with real values). Invoke init() method in one thread, and read() method in another thread and you should get the exception under Windows, while under Linux everything works fine.

#include <iostream>
#include <mysql.h>
#include <NdbApi.hpp>

Ndb_cluster_connection* init(char* connection_string) {

    ndb_init();

    // Object representing the cluster
    Ndb_cluster_connection* cluster_connection = new Ndb_cluster_connection(connection_string); 

    if (cluster_connection->connect(3, 2, 1)) {
        std::cout << "Cluster management server was not ready within 30 secs,error:" << cluster_connection->get_latest_error_msg() << "\n";
        exit(-1);
    }
    if (cluster_connection->wait_until_ready(10, 0) < 0) {
        std::cout << "Cluster not ready within 10 secs, error:" << cluster_connection->get_latest_error_msg() << std::endl;
    }

    return cluster_connection;
}

char** read(Ndb_cluster_connection* cluster_connection) {
    int count = 10;
    char** ret_val = new char*[count];
    Ndb* ndb_obj = new Ndb(cluster_connection, "my_database_name");
    if(ndb_obj->init()) {
        std::cout << "Ndb not initialized" << std::endl;
        exit(-1);
    }

    NdbRecAttr **attr = new NdbRecAttr*[count];

    const NdbDictionary::Dictionary* dict = ndb_obj->getDictionary();
    const NdbDictionary::Table *table = dict->getTable("my_table_name"); // THIS IS THE LINE THAT THROWS ACCESS VIOLATION EXCEPTION
//    ..
    return ret_val;
}

Suggested fix:
Based on the suggestion of Mauritz Sundell from MySQL Cluster mailing list, adding mysql_thread_init() in a worker thread before invoking read() resolves this issue. However, this is not documented anywhere and it is not clear why should we do this.
[9 Feb 2018 16:03] MySQL Verification Team
Hi,
You should have mysql_thread_init() on linux too. As per our documentation:
[quote]
This function must be called early within each created thread to initialize thread-specific variables. 
[/quote]

Documentation on ndb-api is not ideal so if you have suggestions we are ready to hear them :)

all best
Bogdan