polardbxengine/storage/ndb/ndbapi-examples/ndbapi_async/ndbapi_async.cpp

491 lines
13 KiB
C++

/*
Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* ndbapi_async.cpp:
* Illustrates how to use callbacks and error handling using the asynchronous
* part of the NDBAPI.
*
* Classes and methods in NDBAPI used in this example:
*
* Ndb_cluster_connection
* connect()
* wait_until_ready()
*
* Ndb
* init()
* startTransaction()
* closeTransaction()
* sendPollNdb()
* getNdbError()
*
* NdbConnection
* getNdbOperation()
* executeAsynchPrepare()
* getNdbError()
*
* NdbOperation
* insertTuple()
* equal()
* setValue()
*
*/
#ifdef _WIN32
#include <winsock2.h>
#endif
#include <mysql.h>
#include <mysqld_error.h>
#include <NdbApi.hpp>
#include <stdlib.h>
#include <string.h>
#include <iostream> // Used for cout
#include <config.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
/**
* Helper sleep function
*/
static void
milliSleep(int milliseconds){
struct timeval sleeptime;
sleeptime.tv_sec = milliseconds / 1000;
sleeptime.tv_usec = (milliseconds - (sleeptime.tv_sec * 1000)) * 1000000;
select(0, 0, 0, 0, &sleeptime);
}
/**
* error printout macro
*/
#define PRINT_ERROR(code,msg) \
std::cout << "Error in " << __FILE__ << ", line: " << __LINE__ \
<< ", code: " << code \
<< ", msg: " << msg << "." << std::endl
#define MYSQLERROR(mysql) { \
PRINT_ERROR(mysql_errno(&mysql),mysql_error(&mysql)); \
exit(-1); }
#define APIERROR(error) { \
PRINT_ERROR(error.code,error.message); \
exit(-1); }
#ifndef DOXYGEN_SHOULD_SKIP_INTERNAL
/**
* callback struct.
* transaction : index of the transaction in transaction[] array below
* data : the data that the transaction was modifying.
* retries : counter for how many times the trans. has been retried
*/
typedef struct async_callback_t {
Ndb* const ndb;
int transaction;
int data;
int retries;
async_callback_t(Ndb* _ndb) : ndb(_ndb) {}
} async_callback_t;
/**
* Structure used in "free list" to a NdbTransaction
*/
typedef struct transaction_t {
NdbTransaction* conn;
int used;
transaction_t() : conn(NULL), used(0) {}
} transaction_t;
/**
* Free list holding transactions
*/
transaction_t transaction[1024]; //1024 - max number of outstanding
//transaction in one Ndb object
#endif
static int nPreparedTransactions = 0; //Prepared + asynch executing txn
static int MAX_RETRIES = 10;
static int parallelism = 100;
/**
* prototypes
*/
/**
* Prepare and send transaction
*/
int populate(Ndb * myNdb, int data, async_callback_t * cbData);
/**
* Error handler.
*/
bool asynchErrorHandler(NdbTransaction * trans, Ndb* ndb);
/**
* Exit function
*/
void asynchExitHandler(Ndb * m_ndb) ;
/**
* Helper function used in callback(...)
*/
void closeTransaction(async_callback_t * cb);
/**
* stat. variables
*/
int tempErrors = 0;
int permErrors = 0;
void
closeTransaction(async_callback_t * cb)
{
cb->ndb->closeTransaction(transaction[cb->transaction].conn);
transaction[cb->transaction].conn = 0;
transaction[cb->transaction].used = 0;
}
/**
* Callback executed when transaction has return from NDB
*/
static void
callback(int result, NdbTransaction* trans, void* aObject)
{
async_callback_t * cbData = (async_callback_t *)aObject;
if (result<0)
{
/**
* Error: Temporary or permanent?
*/
const bool retryable = asynchErrorHandler(trans, cbData->ndb);
closeTransaction(cbData);
if (retryable && cbData->retries++ >= MAX_RETRIES)
{
while(populate(cbData->ndb, cbData->data, cbData) < 0)
milliSleep(10);
}
else
{
std::cout << "Unrecoverable error. Exiting..." << std::endl;
Ndb* ndb = cbData->ndb;
delete cbData;
asynchExitHandler(ndb);
}
}
else
{
/**
* OK! close transaction
*/
closeTransaction(cbData);
delete cbData;
}
}
void asynchExitHandler(Ndb * m_ndb)
{
delete m_ndb;
exit(-1);
}
/* returns true if is recoverable (temporary),
* false if it is an error that is permanent.
*/
bool asynchErrorHandler(NdbTransaction * trans, Ndb* ndb)
{
NdbError error = trans->getNdbError();
switch(error.status)
{
case NdbError::Success:
return false;
break;
case NdbError::TemporaryError:
/**
* The error code indicates a temporary error.
* The application should typically retry.
* (Includes classifications: NdbError::InsufficientSpace,
* NdbError::TemporaryResourceError, NdbError::NodeRecoveryError,
* NdbError::OverloadError, NdbError::NodeShutdown
* and NdbError::TimeoutExpired.)
*
* We should sleep for a while and retry, except for insufficient space
*/
if(error.classification == NdbError::InsufficientSpace)
return false;
milliSleep(10);
tempErrors++;
return true;
break;
case NdbError::UnknownResult:
std::cout << error.message << std::endl;
return false;
break;
default:
case NdbError::PermanentError:
switch (error.code)
{
case 499:
case 250:
milliSleep(10);
return true; // SCAN errors that can be retried. Requires restart of scan.
default:
break;
}
//ERROR
std::cout << error.message << std::endl;
return false;
break;
}
return false;
}
/************************************************************************
* populate()
* 1. Prepare 'parallelism' number of insert transactions.
* 2. Send transactions to NDB and wait for callbacks to execute
*/
int populate(Ndb * myNdb, int data, async_callback_t * cbData)
{
NdbOperation* myNdbOperation; // For operations
const NdbDictionary::Dictionary* myDict= myNdb->getDictionary();
const NdbDictionary::Table *myTable= myDict->getTable("api_async");
if (myTable == NULL)
APIERROR(myDict->getNdbError());
async_callback_t * cb = NULL;
int current = 0;
for(int i=0; i<1024; i++)
{
if(transaction[i].used == 0)
{
current = i;
if (cbData == 0)
{
/**
* We already have a callback
* This is an absolutely new transaction
*/
cb = new async_callback_t(myNdb);
}
else
{
/**
* We already have a callback
*/
cb =cbData;
}
/**
* Set data used by the callback
*/
cb->retries = 0;
cb->data = data; //this is the data we want to insert
cb->transaction = current; //This is the number (id) of this transaction
transaction[current].used = 1 ; //Mark the transaction as used
break;
}
}
if(cb == NULL)
return -1;
int retries = 0;
while(retries < MAX_RETRIES)
{
transaction[current].conn = myNdb->startTransaction();
if (transaction[current].conn == NULL) {
/**
* no transaction to close since conn == null
*/
milliSleep(10);
retries++;
continue;
}
myNdbOperation = transaction[current].conn->getNdbOperation(myTable);
if (myNdbOperation == NULL)
{
if (asynchErrorHandler(transaction[current].conn, myNdb))
{
myNdb->closeTransaction(transaction[current].conn);
transaction[current].conn = 0;
milliSleep(10);
retries++;
continue;
}
asynchExitHandler(myNdb);
} // if
char mercedes[20];
char blue[20];
memset(mercedes, 0, sizeof(mercedes));
memset(blue, 0, sizeof(blue));
strcpy(mercedes, "mercedes");
strcpy(blue, "blue");
if(myNdbOperation->insertTuple() < 0 ||
myNdbOperation->equal("REG_NO", data) < 0 ||
myNdbOperation->setValue("BRAND", mercedes) <0 ||
myNdbOperation->setValue("COLOR", blue) < 0)
{
if (asynchErrorHandler(transaction[current].conn, myNdb))
{
myNdb->closeTransaction(transaction[current].conn);
transaction[current].conn = 0;
retries++;
milliSleep(10);
continue;
}
asynchExitHandler(myNdb);
}
/*Prepare transaction (the transaction is NOT yet sent to NDB)*/
transaction[current].conn->executeAsynchPrepare(NdbTransaction::Commit,
&callback,
cb);
/**
* When we have prepared parallelism number of transactions ->
* send the transaction to ndb.
* Next time we will deal with the transactions are in the
* callback. There we will see which ones that were successful
* and which ones to retry.
*/
nPreparedTransactions++;
if (nPreparedTransactions >= parallelism)
{
//-------------------------------------------------------
// Send-poll all transactions
// Now we have defined a set of operations, it is now time
// to execute all of them. Wait for at least 50% to complete.
// Close transaction is done in callback
//-------------------------------------------------------
const int min_execs = nPreparedTransactions/2;
const int nCompleted = myNdb->sendPollNdb(3000, min_execs);
nPreparedTransactions -= nCompleted;
}
return 1;
}
std::cout << "Unable to recover from errors. Exiting..." << std::endl;
asynchExitHandler(myNdb);
return -1;
}
/**************************************************************
* Connect to mysql server and create table *
**************************************************************/
void mysql_connect_and_create(const char * socket) {
MYSQL mysql;
bool ok;
mysql_init(&mysql);
ok = mysql_real_connect(&mysql, "localhost", "root", "", "", 0, socket, 0);
if(ok) {
mysql_query(&mysql, "CREATE DATABASE ndb_examples");
ok = ! mysql_select_db(&mysql, "ndb_examples");
}
if(ok) {
mysql_query(&mysql, "DROP TABLE IF EXISTS api_async");
ok = ! mysql_query(&mysql,
"CREATE TABLE"
" api_async"
" (REG_NO INT UNSIGNED NOT NULL,"
" BRAND CHAR(20) NOT NULL,"
" COLOR CHAR(20) NOT NULL,"
" PRIMARY KEY USING HASH (REG_NO))"
" ENGINE=NDB CHARSET=latin1"
);
}
mysql_close(&mysql);
if(! ok) MYSQLERROR(mysql);
}
void ndb_run_async_inserts(const char * connectstring)
{
/**************************************************************
* Connect to ndb cluster *
**************************************************************/
Ndb_cluster_connection cluster_connection(connectstring);
if (cluster_connection.connect(4, 5, 1))
{
std::cout << "Unable to connect to cluster within 30 secs." << std::endl;
exit(-1);
}
// Optionally connect and wait for the storage nodes (ndbd's)
if (cluster_connection.wait_until_ready(30,0) < 0)
{
std::cout << "Cluster was not ready within 30 secs.\n";
exit(-1);
}
Ndb myNdb( &cluster_connection, "ndb_examples" );
if (myNdb.init(1024) == -1) { // Set max 1024 parallel transactions
APIERROR(myNdb.getNdbError());
}
/**
* Do some insert transactions.
*/
for(int i = 0 ; i < 1234 ; i++)
{
while(populate(&myNdb, i, 0) < 0) // <0, no space on free list. Sleep and try again.
milliSleep(10);
}
/**
* If there are prepared async transactions not yet completed,
* we send them now as part of cleanup.
*/
while (nPreparedTransactions > 0)
{
const int nCompleted = myNdb.sendPollNdb(3000, nPreparedTransactions);
nPreparedTransactions -= nCompleted;
}
std::cout << "Number of temporary errors: " << tempErrors << std::endl;
}
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cout << "Arguments are <socket mysqld> <connect_string cluster>.\n";
exit(-1);
}
const char *mysqld_sock = argv[1];
const char *connectstring = argv[2];
mysql_connect_and_create(mysqld_sock);
ndb_init();
ndb_run_async_inserts(connectstring);
ndb_end(0);
return 0;
}