From ac5a2ead922b9c2cda0751ea13e24a587ee6440a Mon Sep 17 00:00:00 2001 From: unknown <pekka@mysql.com> Date: Tue, 29 Jun 2004 14:53:15 +0200 Subject: [PATCH] wl1822: verify locks are flushed --- ndb/test/ndbapi/Makefile.am | 3 + ndb/test/ndbapi/testDeadlock.cpp | 514 +++++++++++++++++++++++++++++++ 2 files changed, 517 insertions(+) create mode 100644 ndb/test/ndbapi/testDeadlock.cpp diff --git a/ndb/test/ndbapi/Makefile.am b/ndb/test/ndbapi/Makefile.am index a0a3692bfd4..6776ba966c1 100644 --- a/ndb/test/ndbapi/Makefile.am +++ b/ndb/test/ndbapi/Makefile.am @@ -28,6 +28,7 @@ testScanInterpreter \ testSystemRestart \ testTimeout \ testTransactions \ +testDeadlock \ test_event #flexTimedAsynch @@ -61,6 +62,7 @@ testScanInterpreter_SOURCES = testScanInterpreter.cpp testSystemRestart_SOURCES = testSystemRestart.cpp testTimeout_SOURCES = testTimeout.cpp testTransactions_SOURCES = testTransactions.cpp +testDeadlock_SOURCES = testDeadlock.cpp test_event_SOURCES = test_event.cpp INCLUDES_LOC = -I$(top_srcdir)/ndb/include/kernel @@ -77,3 +79,4 @@ testBackup_LDADD = $(LDADD) bank/libbank.a # Don't update the files from bitkeeper %::SCCS/s.% + diff --git a/ndb/test/ndbapi/testDeadlock.cpp b/ndb/test/ndbapi/testDeadlock.cpp new file mode 100644 index 00000000000..f51b3cea1e5 --- /dev/null +++ b/ndb/test/ndbapi/testDeadlock.cpp @@ -0,0 +1,514 @@ +/* Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <ndb_global.h> +#include <NdbMain.h> +#include <NdbApi.hpp> +#include <NdbOut.hpp> +#include <NdbMutex.h> +#include <NdbCondition.h> +#include <NdbThread.h> +#include <NdbTest.hpp> + +struct Opt { + bool m_dbg; + const char* m_scan; + const char* m_tname; + const char* m_xname; + Opt() : + m_dbg(true), + m_scan("tx"), + m_tname("T"), + m_xname("X") + {} +}; + +static void +printusage() +{ + Opt d; + ndbout + << "usage: testDeadlock" << endl + << "-scan tx scan table, index [" << d.m_scan << "]" << endl + ; +} + +static Opt g_opt; + +static NdbMutex ndbout_mutex = NDB_MUTEX_INITIALIZER; + +#define DBG(x) \ + do { \ + if (! g_opt.m_dbg) break; \ + NdbMutex_Lock(&ndbout_mutex); \ + ndbout << "line " << __LINE__ << " " << x << endl; \ + NdbMutex_Unlock(&ndbout_mutex); \ + } while (0) + +#define CHK(x) \ + do { \ + if (x) break; \ + ndbout << "line " << __LINE__ << ": " << #x << " failed" << endl; \ + return -1; \ + } while (0) + +#define CHN(p, x) \ + do { \ + if (x) break; \ + ndbout << "line " << __LINE__ << ": " << #x << " failed" << endl; \ + ndbout << (p)->getNdbError() << endl; \ + return -1; \ + } while (0) + +// threads + +typedef int (*Runstep)(struct Thr& thr); + +struct Thr { + enum State { Wait, Start, Stop, Stopped, Exit }; + State m_state; + int m_no; + Runstep m_runstep; + int m_ret; + NdbMutex* m_mutex; + NdbCondition* m_cond; + NdbThread* m_thread; + void* m_status; + Ndb* m_ndb; + NdbConnection* m_con; + NdbScanOperation* m_scanop; + NdbIndexScanOperation* m_indexscanop; + NdbResultSet* m_rs; + // + Thr(int no); + ~Thr(); + int run(); + void start(Runstep runstep); + void stop(); + void stopped(); + void lock() { NdbMutex_Lock(m_mutex); } + void unlock() { NdbMutex_Unlock(m_mutex); } + void wait() { NdbCondition_Wait(m_cond, m_mutex); } + void signal() { NdbCondition_Signal(m_cond); } + void exit(); + void join() { NdbThread_WaitFor(m_thread, &m_status); } +}; + +static NdbOut& +operator<<(NdbOut& out, const Thr& thr) { + out << "thr " << thr.m_no; + return out; +} + +extern "C" { static void* runthread(void* arg); } + +Thr::Thr(int no) +{ + m_state = Wait; + m_no = no; + m_runstep = 0; + m_ret = 0; + m_mutex = NdbMutex_Create(); + m_cond = NdbCondition_Create(); + assert(m_mutex != 0 && m_cond != 0); + const unsigned stacksize = 256 * 1024; + const NDB_THREAD_PRIO prio = NDB_THREAD_PRIO_LOW; + m_thread = NdbThread_Create(runthread, (void**)this, stacksize, "me", prio); + if (m_thread == 0) { + DBG("create thread failed: errno=" << errno); + m_ret = -1; + } + m_status = 0; + m_ndb = 0; + m_con = 0; + m_scanop = 0; + m_indexscanop = 0; + m_rs = 0; +} + +Thr::~Thr() +{ + if (m_thread != 0) + NdbThread_Destroy(&m_thread); + if (m_cond != 0) + NdbCondition_Destroy(m_cond); + if (m_mutex != 0) + NdbMutex_Destroy(m_mutex); +} + +static void* +runthread(void* arg) { + Thr& thr = *(Thr*)arg; + thr.run(); + return 0; +} + +int +Thr::run() +{ + DBG(*this << " run"); + while (true) { + lock(); + while (m_state != Start && m_state != Exit) { + wait(); + } + if (m_state == Exit) { + DBG(*this << " exit"); + unlock(); + break; + } + m_ret = (*m_runstep)(*this); + m_state = Stopped; + signal(); + unlock(); + if (m_ret != 0) { + DBG(*this << " error exit"); + break; + } + } + delete m_ndb; + m_ndb = 0; + return 0; +} + +void +Thr::start(Runstep runstep) +{ + lock(); + m_state = Start; + m_runstep = runstep; + signal(); + unlock(); +} + +void +Thr::stopped() +{ + lock(); + while (m_state != Stopped) { + wait(); + } + m_state = Wait; + unlock(); +} + +void +Thr::exit() +{ + lock(); + m_state = Exit; + signal(); + unlock(); +} + +// general + +static int +runstep_connect(Thr& thr) +{ + Ndb* ndb = thr.m_ndb = new Ndb("TEST_DB"); + CHN(ndb, ndb->init() == 0); + CHN(ndb, ndb->waitUntilReady() == 0); + DBG(thr << " connected"); + return 0; +} + +static int +runstep_starttx(Thr& thr) +{ + Ndb* ndb = thr.m_ndb; + assert(ndb != 0); + CHN(ndb, (thr.m_con = ndb->startTransaction()) != 0); + DBG("thr " << thr.m_no << " tx started"); + return 0; +} + +/* + * WL1822 flush locks + * + * Table T with 3 tuples X, Y, Z. + * Two transactions (* = lock wait). + * + * - tx1 reads and locks Z + * - tx2 scans X, Y, *Z + * - tx2 returns X, Y before lock wait on Z + * - tx1 reads and locks *X + * - api asks for next tx2 result + * - LQH unlocks X via ACC or TUX [*] + * - tx1 gets lock on X + * - tx1 returns X to api + * - api commits tx1 + * - tx2 gets lock on Z + * - tx2 returs Z to api + * + * The point is deadlock is avoided due to [*]. + * The test is for 1 db node and 1 fragment table. + */ + +static char wl1822_scantx = 0; + +static const Uint32 wl1822_valA[3] = { 0, 1, 2 }; +static const Uint32 wl1822_valB[3] = { 3, 4, 5 }; + +static Uint32 wl1822_bufA = ~0; +static Uint32 wl1822_bufB = ~0; + +// map scan row to key (A) and reverse +static unsigned wl1822_r2k[3] = { 0, 0, 0 }; +static unsigned wl1822_k2r[3] = { 0, 0, 0 }; + +static int +wl1822_createtable(Thr& thr) +{ + Ndb* ndb = thr.m_ndb; + assert(ndb != 0); + NdbDictionary::Dictionary* dic = ndb->getDictionary(); + // drop T + if (dic->getTable(g_opt.m_tname) != 0) + CHN(dic, dic->dropTable(g_opt.m_tname) == 0); + // create T + NdbDictionary::Table tab(g_opt.m_tname); + tab.setFragmentType(NdbDictionary::Object::FragAllSmall); + { NdbDictionary::Column col("A"); + col.setType(NdbDictionary::Column::Unsigned); + col.setPrimaryKey(true); + tab.addColumn(col); + } + { NdbDictionary::Column col("B"); + col.setType(NdbDictionary::Column::Unsigned); + col.setPrimaryKey(false); + tab.addColumn(col); + } + CHN(dic, dic->createTable(tab) == 0); + // create X + NdbDictionary::Index ind(g_opt.m_xname); + ind.setTable(g_opt.m_tname); + ind.setType(NdbDictionary::Index::OrderedIndex); + ind.setLogging(false); + ind.addColumn("B"); + CHN(dic, dic->createIndex(ind) == 0); + DBG("created " << g_opt.m_tname << ", " << g_opt.m_xname); + return 0; +} + +static int +wl1822_insertrows(Thr& thr) +{ + // insert X, Y, Z + Ndb* ndb = thr.m_ndb; + assert(ndb != 0); + NdbConnection* con; + NdbOperation* op; + for (unsigned k = 0; k < 3; k++) { + CHN(ndb, (con = ndb->startTransaction()) != 0); + CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0); + CHN(op, op->insertTuple() == 0); + CHN(op, op->equal("A", (char*)&wl1822_valA[k]) == 0); + CHN(op, op->setValue("B", (char*)&wl1822_valB[k]) == 0); + CHN(con, con->execute(Commit) == 0); + ndb->closeTransaction(con); + } + DBG("inserted X, Y, Z"); + return 0; +} + +static int +wl1822_getscanorder(Thr& thr) +{ + // cheat, table order happens to be key order in my test + wl1822_r2k[0] = 0; + wl1822_r2k[1] = 1; + wl1822_r2k[2] = 2; + wl1822_k2r[0] = 0; + wl1822_k2r[1] = 1; + wl1822_k2r[2] = 2; + DBG("scan order determined"); + return 0; +} + +static int +wl1822_tx1_readZ(Thr& thr) +{ + // tx1 read Z with exclusive lock + NdbConnection* con = thr.m_con; + assert(con != 0); + NdbOperation* op; + CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0); + CHN(op, op->readTupleExclusive() == 0); + CHN(op, op->equal("A", wl1822_valA[wl1822_r2k[2]]) == 0); + wl1822_bufB = ~0; + CHN(op, op->getValue("B", (char*)&wl1822_bufB) != 0); + CHN(con, con->execute(NoCommit) == 0); + CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[2]]); + DBG("tx1 locked Z"); + return 0; +} + +static int +wl1822_tx2_scanXY(Thr& thr) +{ + // tx2 scan X, Y with exclusive lock + NdbConnection* con = thr.m_con; + assert(con != 0); + NdbScanOperation* scanop; + NdbIndexScanOperation* indexscanop; + NdbResultSet* rs; + if (wl1822_scantx == 't') { + CHN(con, (scanop = thr.m_scanop = con->getNdbScanOperation(g_opt.m_tname)) != 0); + DBG("tx2 scan exclusive " << g_opt.m_tname); + } + if (wl1822_scantx == 'x') { + CHN(con, (scanop = thr.m_scanop = indexscanop = thr.m_indexscanop = con->getNdbIndexScanOperation(g_opt.m_xname, g_opt.m_tname)) != 0); + DBG("tx2 scan exclusive " << g_opt.m_xname); + } + CHN(scanop, (rs = thr.m_rs = scanop->readTuplesExclusive(16)) != 0); + CHN(scanop, scanop->getValue("A", (char*)&wl1822_bufA) != 0); + CHN(scanop, scanop->getValue("B", (char*)&wl1822_bufB) != 0); + CHN(con, con->execute(NoCommit) == 0); + unsigned row = 0; + while (row < 2) { + DBG("before row " << row); + int ret; + wl1822_bufA = wl1822_bufB = ~0; + CHN(con, (ret = rs->nextResult(true)) == 0); + DBG("got row " << row << " a=" << wl1822_bufA << " b=" << wl1822_bufB); + CHK(wl1822_bufA == wl1822_valA[wl1822_r2k[row]]); + CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[row]]); + row++; + } + return 0; +} + +static int +wl1822_tx1_readX_commit(Thr& thr) +{ + // tx1 read X with exclusive lock and commit + NdbConnection* con = thr.m_con; + assert(con != 0); + NdbOperation* op; + CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0); + CHN(op, op->readTupleExclusive() == 0); + CHN(op, op->equal("A", wl1822_valA[wl1822_r2k[2]]) == 0); + wl1822_bufB = ~0; + CHN(op, op->getValue("B", (char*)&wl1822_bufB) != 0); + CHN(con, con->execute(NoCommit) == 0); + CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[2]]); + DBG("tx1 locked X"); + CHN(con, con->execute(Commit) == 0); + DBG("tx1 commit"); + return 0; +} + +static int +wl1822_tx2_scanZ_close(Thr& thr) +{ + // tx2 scan Z with exclusive lock and close scan + Ndb* ndb = thr.m_ndb; + NdbConnection* con = thr.m_con; + NdbScanOperation* scanop = thr.m_scanop; + NdbResultSet* rs = thr.m_rs; + assert(ndb != 0 && con != 0 && scanop != 0 && rs != 0); + unsigned row = 2; + while (true) { + DBG("before row " << row); + int ret; + wl1822_bufA = wl1822_bufB = ~0; + CHN(con, (ret = rs->nextResult(true)) == 0 || ret == 1); + if (ret == 1) + break; + DBG("got row " << row << " a=" << wl1822_bufA << " b=" << wl1822_bufB); + CHK(wl1822_bufA == wl1822_valA[wl1822_r2k[row]]); + CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[row]]); + row++; + } + ndb->closeTransaction(con); + CHK(row == 3); + return 0; +} + +// threads are synced between each step +static Runstep wl1822_step[][2] = { + { runstep_connect, runstep_connect }, + { wl1822_createtable, 0 }, + { wl1822_insertrows, 0 }, + { wl1822_getscanorder, 0 }, + { runstep_starttx, runstep_starttx }, + { wl1822_tx1_readZ, 0 }, + { 0, wl1822_tx2_scanXY }, + { wl1822_tx1_readX_commit, wl1822_tx2_scanZ_close } +}; +const unsigned wl1822_stepcount = sizeof(wl1822_step)/sizeof(wl1822_step[0]); + +static int +wl1822_main(char scantx) +{ + wl1822_scantx = scantx; + static const unsigned thrcount = 2; + // create threads for tx1 and tx2 + Thr* thrlist[2]; + for (int n = 0; n < thrcount; n++) { + Thr& thr = *(thrlist[n] = new Thr(1 + n)); + CHK(thr.m_ret == 0); + } + // run the steps + for (unsigned i = 0; i < wl1822_stepcount; i++) { + DBG("step " << i << " start"); + for (int n = 0; n < thrcount; n++) { + Thr& thr = *thrlist[n]; + Runstep runstep = wl1822_step[i][n]; + if (runstep != 0) + thr.start(runstep); + } + for (int n = 0; n < thrcount; n++) { + Thr& thr = *thrlist[n]; + Runstep runstep = wl1822_step[i][n]; + if (runstep != 0) + thr.stopped(); + } + } + // delete threads + for (int n = 0; n < thrcount; n++) { + Thr& thr = *thrlist[n]; + thr.exit(); + thr.join(); + delete &thr; + } + return 0; +} + +NDB_COMMAND(testOdbcDriver, "testDeadlock", "testDeadlock", "testDeadlock", 65535) +{ + while (++argv, --argc > 0) { + const char* arg = argv[0]; + if (strcmp(arg, "-scan") == 0) { + if (++argv, --argc > 0) { + g_opt.m_scan = strdup(argv[0]); + continue; + } + } + printusage(); + return NDBT_ProgramExit(NDBT_WRONGARGS); + } + if ( + strchr(g_opt.m_scan, 't') != 0 && wl1822_main('t') == -1 || + strchr(g_opt.m_scan, 'x') != 0 && wl1822_main('x') == -1 + ) { + return NDBT_ProgramExit(NDBT_FAILED); + } + return NDBT_ProgramExit(NDBT_OK); +} + +// vim: set sw=2 et: -- 2.30.9