polardbxengine/storage/ndb/memcache/src/schedulers/Scheduler73.cc

505 lines
14 KiB
C++

/*
Copyright (c) 2013, 2016, 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
*/
#include <stdio.h>
#include <ctype.h>
#include <stdio.h>
#include <sys/errno.h>
#include <inttypes.h>
/* Memcache headers */
#include "memcached/types.h"
#include <memcached/extension_loggers.h>
#include "timing.h"
#include "debug.h"
#include "Configuration.h"
#include "thread_identifier.h"
#include "workitem.h"
#include "ndb_worker.h"
#include "ndb_engine_errors.h"
#include "ndb_error_logger.h"
#include "Scheduler73.h"
extern EXTENSION_LOGGER_DESCRIPTOR *logger;
/* Scheduler Global singleton */
static Scheduler73::Global * s_global;
/* SchedulerGlobal methods */
Scheduler73::Global::Global(int _nthreads) :
GlobalConfigManager(_nthreads)
{
}
void Scheduler73::Global::init(const scheduler_options *sched_opts) {
DEBUG_ENTER_METHOD("Scheduler73::Global::init");
/* Set member variables */
options.max_clients = sched_opts->max_clients;
parse_config_string(sched_opts->config_string);
/* Fetch or initialize clusters */
nclusters = conf->nclusters;
clusters = new Cluster * [nclusters];
for(int i = 0 ; i < nclusters ; i++) {
ClusterConnectionPool *pool = conf->getConnectionPoolById(i);
Cluster *c = (Cluster *) pool->getCustomData();
if(c == 0) {
c = new Cluster(this, i);
pool->setCustomData(c);
}
clusters[i] = c;
}
/* Initialize the WorkerConnections */
for(int t = 0 ; t < nthreads ; t++) {
for(int c = 0 ; c < nclusters ; c++) {
WorkerConnection **wc_cell = getWorkerConnectionPtr(t, c);
* wc_cell = new WorkerConnection(this, clusters[c], t, nthreads);
}
}
configureSchedulers();
/* Start the wait thread for each connection */
for(int i = 0 ; i < nclusters ; i++)
clusters[i]->startThreads();
/* Log message for startup */
logger->log(LOG_WARNING, 0, "Scheduler 73: starting ...");
/* Now Running */
running = true;
}
void Scheduler73::Global::parse_config_string(const char *str) {
/* Initialize the configuration default values */
options.separate_send = true;
if(str) {
const char *s = str;
char letter;
int value;
/* tolerate a ':' at the start of the string */
if( *s == ':') s++;
while(*s != '\0' && sscanf(s, "%c%d", &letter, &value) == 2) {
switch(letter) {
case 's':
options.separate_send = value;
break;
}
/* Skip over the part just read */
s += 1; // the letter
while(isdigit(*s)) s++; // the value
/* Now tolerate a comma */
if(*s == ',') s++;
}
}
/* Test validity of configuration */
}
void Scheduler73::Global::shutdown() {
if(running) {
logger->log(LOG_INFO, 0, "Shutting down scheduler 73.");
/* Release each WorkerConnection */
for(int i = 0; i < nclusters ; i++) {
for(int j = 0; j < nthreads ; j++) {
WorkerConnection *wc = * (getWorkerConnectionPtr(j, i));
delete wc;
}
}
/* Release each Cluster */
for(int i = 0; i < nclusters ; i++) {
delete clusters[i];
conf->getConnectionPoolById(i)->setCustomData(0);
}
/* Shutdown now */
logger->log(LOG_WARNING, 0, "Scheduler 73 shutdown completed.");
running = false;
}
}
void Scheduler73::Global::add_stats(const char *stat_key,
ADD_STAT add_stat,
const void *cookie) {
if(strncasecmp(stat_key, "reconf", 6) == 0) {
WorkerConnection ** wc = getWorkerConnectionPtr(0,0);
(* wc)->add_stats(stat_key, add_stat, cookie);
}
else {
DEBUG_PRINT(" scheduler");
for(int c = 0 ; c < nclusters ; c++) {
clusters[c]->add_stats(stat_key, add_stat, cookie);
}
}
}
/* SchedulerWorker methods */
void Scheduler73::Worker::init(int my_thread,
const scheduler_options * options) {
/* On the first call in, initialize the SchedulerGlobal.
*/
if(my_thread == 0) {
s_global = new Global(options->nthreads);
s_global->init(options);
}
/* Initialize member variables */
id = my_thread;
}
void Scheduler73::Worker::shutdown() {
if(id == 0)
s_global->shutdown();
}
Scheduler73::Worker::~Worker() {
if(id == 0)
delete s_global;
}
ENGINE_ERROR_CODE Scheduler73::Worker::schedule(workitem *item) {
int c = item->prefix_info.cluster_id;
WorkerConnection *wc = * (s_global->getWorkerConnectionPtr(id, c));
return wc->schedule(item);
}
void Scheduler73::Worker::prepare(NdbTransaction * tx,
NdbTransaction::ExecType execType,
NdbAsynchCallback callback,
workitem * item, prepare_flags flags) {
Ndb *ndb = tx->getNdb();
Uint64 nwaitsPre = ndb->getClientStat(Ndb::WaitExecCompleteCount);
if(s_global->options.separate_send)
tx->executeAsynchPrepare(execType, callback, (void *) item);
else
tx->executeAsynch(execType, callback, (void *) item);
Uint64 nwaitsPost = ndb->getClientStat(Ndb::WaitExecCompleteCount);
assert(nwaitsPost == nwaitsPre);
if(flags == RESCHEDULE) item->base.reschedule = 1;
}
void Scheduler73::Worker::close(NdbTransaction *tx, workitem *item) {
Uint64 nwaits_pre, nwaits_post;
Ndb * & ndb = item->ndb_instance->db;
nwaits_pre = ndb->getClientStat(Ndb::WaitExecCompleteCount);
tx->close();
nwaits_post = ndb->getClientStat(Ndb::WaitExecCompleteCount);
if(nwaits_post > nwaits_pre)
log_app_error(& AppError29023_SyncClose);
}
/* Release the resources used by an operation.
Unlink the NdbInstance from the workitem, and return it to the free list
(or free it, if the scheduler is shutting down).
*/
void Scheduler73::Worker::release(workitem *item) {
NdbInstance *inst = item->ndb_instance;
if(inst) {
inst->unlink_workitem(item);
int c = item->prefix_info.cluster_id;
WorkerConnection * wc = * (s_global->getWorkerConnectionPtr(id, c));
if(wc) {
wc->release(inst);
}
else {
/* We are in the midst of shutting down (and possibly reconfiguring) */
delete inst;
}
}
}
bool Scheduler73::Worker::global_reconfigure(Configuration *new_cf) {
return s_global->reconfigure(new_cf);
}
void Scheduler73::Worker::add_stats(const char *stat_key,
ADD_STAT add_stat,
const void *cookie) {
s_global->add_stats(stat_key, add_stat, cookie);
}
/* WorkerConnection methods */
Scheduler73::WorkerConnection::WorkerConnection(Global *global,
Cluster * _cl,
int _worker_id,
int nthreads) :
SchedulerConfigManager(_worker_id, _cl->id),
cluster(_cl)
{
/* How many NDB instances to start initially */
instances.initial = cluster->instances.initial / nthreads;
/* Upper bound on NDB instances */
instances.max = global->options.max_clients / nthreads;
/* Build the freelist */
freelist = 0;
for(instances.current = 0; instances.current < instances.initial; ) {
NdbInstance *inst = newNdbInstance();
inst->next = freelist;
freelist = inst;
}
DEBUG_PRINT("Cluster %d / worker %d: %d NDBs.",
cluster->id, thread, instances.current);
/* Hoard a transaction (an API connect record) for each Ndb object. This
* first call to startTransaction() will send TC_SEIZEREQ and wait for a
* reply, but later at runtime startTransaction() should return immediately.
*/
NdbTransaction ** txlist = new NdbTransaction * [instances.current];
int i = 0;
// Open them all.
for(NdbInstance *inst = freelist; inst != 0 ;inst=inst->next, i++) {
NdbTransaction *tx;
tx = inst->db->startTransaction();
if(! tx) log_ndb_error(inst->db->getNdbError());
txlist[i] = tx;
}
// Close them all.
for(i = 0 ; i < instances.current ; i++) {
if(txlist[i])
txlist[i]->close();
}
// Free the list.
delete[] txlist;
}
inline NdbInstance * Scheduler73::WorkerConnection::newNdbInstance() {
NdbInstance * inst = new NdbInstance(cluster->ndb_conn, 2);
if(inst) {
instances.current++;
inst->id = ((worker_id + 1) * 10000) + instances.current;
}
return inst;
}
ENGINE_ERROR_CODE Scheduler73::WorkerConnection::schedule(workitem *item) {
ENGINE_ERROR_CODE response_code;
NdbInstance *inst = 0;
if(freelist) { /* Get the next NDB from the freelist. */
inst = freelist;
freelist = inst->next;
}
else if(instances.current < instances.max) {
inst = newNdbInstance();
log_app_error(& AppError29024_autogrow);
}
else {
/* We have hit a hard maximum. Eventually Scheduler::io_completed()
will run _in this thread_ and return an NDB to the freelist.
But no other thread can free one, so here we return an error.
*/
log_app_error(& AppError29002_NoNDBs);
return ENGINE_TMPFAIL;
}
assert(inst);
inst->link_workitem(item);
// Fetch the query plan for this prefix.
setQueryPlanInWorkitem(item);
if(! item->plan) {
DEBUG_PRINT("getPlanForPrefix() failure");
return ENGINE_FAILED;
}
// Build the NDB transaction
op_status_t op_status = worker_prepare_operation(item);
if(op_status == op_prepared) {
/* Success */
if(s_global->options.separate_send)
inst->db->sendPreparedTransactions(false);
cluster->pollgroup->push(inst->db);
cluster->pollgroup->wakeup();
response_code = ENGINE_EWOULDBLOCK;
}
else {
/* Status is not op_prepared, but rather some error status */
response_code = item->status->status;
}
return response_code;
}
inline void Scheduler73::WorkerConnection::release(NdbInstance *inst) {
inst->next = freelist;
freelist = inst;
}
Scheduler73::WorkerConnection::~WorkerConnection() {
DEBUG_ENTER_METHOD("WorkerConnection::~WorkerConnection");
/* Delete all of the Ndbs that are not currently in use */
NdbInstance *inst = freelist;
while(inst != 0) {
NdbInstance *next = inst->next;
delete inst;
inst = next;
}
}
/* Cluster methods */
Scheduler73::Cluster::Cluster(Global *global, int _id) :
running(false),
id(_id)
{
DEBUG_PRINT("%d", id);
Configuration *conf = global->conf;
ndb_conn = conf->getConnectionPoolById(id)->getMainConnection();
node_id = ndb_conn->node_id();
/* Set the timer on the adaptive send thread */
ndb_conn->set_max_adaptive_send_time(1);
/* How many NDB objects are needed for the desired performance? */
instances.initial = (int) conf->figureInFlightTransactions(id);
while(instances.initial % global->nthreads) instances.initial++; // round up
/* Get a multi-wait Poll Group */
pollgroup = ndb_conn->create_ndb_wait_group(instances.initial);
}
/* Threads are started only once and persist across reconfiguration.
But, this method will be called again for each reconf. */
void Scheduler73::Cluster::startThreads() {
if(running == false) {
running = true;
pthread_create( & wait_thread_id, NULL, run_ndb_wait_thread, (void *) this);
}
}
void Scheduler73::Cluster::add_stats(const char *stat_key,
ADD_STAT add_stat,
const void *cookie) {
return;
}
Scheduler73::WorkerConnection ** Scheduler73::Cluster::getWorkerConnectionPtr(int thd) const {
return s_global->getWorkerConnectionPtr(thd, id);
}
void * Scheduler73::Cluster::run_wait_thread() {
/* Set thread identity */
thread_identifier tid;
tid.pipeline = 0;
snprintf(tid.name, THD_ID_NAME_LEN, "cl%d.wait", id);
set_thread_id(&tid);
DEBUG_ENTER();
NdbInstance *inst;
int wait_timeout_millisec = 5000;
while(running) {
/* Wait until something is ready to poll */
int nwaiting = pollgroup->wait(wait_timeout_millisec, 25);
/* Poll the ones that are ready */
while(nwaiting-- > 0) {
Ndb *db = pollgroup->pop();
inst = (NdbInstance *) db->getCustomData();
DEBUG_PRINT_DETAIL("Polling %d.%d", inst->wqitem->pipeline->id, inst->wqitem->id);
db->pollNdb(0, 1);
if(inst->wqitem->base.reschedule) {
DEBUG_PRINT_DETAIL("Rescheduling %d.%d", inst->wqitem->pipeline->id, inst->wqitem->id);
inst->wqitem->base.reschedule = 0;
if(s_global->options.separate_send)
db->sendPreparedTransactions(false);
pollgroup->push(db);
}
else { // Operation is complete
item_io_complete(inst->wqitem);
}
}
}
return 0;
}
Scheduler73::Cluster::~Cluster() {
DEBUG_PRINT("Shutting down cluster %d", id);
running = false;
pollgroup->wakeup();
pthread_join(wait_thread_id, NULL);
ndb_conn->release_ndb_wait_group(pollgroup);
}
void * run_ndb_wait_thread(void *v) {
Scheduler73::Cluster *c = (Scheduler73::Cluster *) v;
return c->run_wait_thread();
}