From: John David Duncan Date: June 16 2011 5:16am Subject: bzr commit into mysql-5.1-telco-7.2 branch (john.duncan:4182) List-Archive: http://lists.mysql.com/commits/139259 Message-Id: <201106160516.p5G5GGp6018213@acsmt356.oracle.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============0248230458==" --===============0248230458== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///Users/jdd/bzr-repo/working/cluster-7.2-multiwait/ based on revid:john.duncan@stripped 4182 John David Duncan 2011-06-15 This is a revised version of the Async API Multi-wait patch. The top-level API is moved out of Ndb_cluster_connection into a new class NdbPollGroup. The NdbPollGroup (rather than the application code) owns the array of Ndb objects used for waiting. The "waiter" Ndb object is a dedicated Ndb owned by the NdbPollGroup (rather than the first Ndb in the user's list). There is still only one NdbPollGroup allowed per cluster connection, but now that restriction could be lifted in a way that would not break existing code. added: storage/ndb/include/ndbapi/NdbPollGroup.hpp storage/ndb/src/ndbapi/NdbPollGroup.cpp storage/ndb/src/ndbapi/WakeupHandler.cpp storage/ndb/src/ndbapi/WakeupHandler.hpp storage/ndb/test/ndbapi/testAsynchMultiwait.cpp modified: storage/ndb/include/ndbapi/Ndb.hpp storage/ndb/include/ndbapi/NdbApi.hpp storage/ndb/include/ndbapi/ndb_cluster_connection.hpp storage/ndb/include/transporter/TransporterCallback.hpp storage/ndb/src/common/transporter/TransporterRegistry.cpp storage/ndb/src/ndbapi/API.hpp storage/ndb/src/ndbapi/Makefile.am storage/ndb/src/ndbapi/NdbImpl.hpp storage/ndb/src/ndbapi/Ndbif.cpp storage/ndb/src/ndbapi/Ndbinit.cpp storage/ndb/src/ndbapi/TransporterFacade.cpp storage/ndb/src/ndbapi/TransporterFacade.hpp storage/ndb/src/ndbapi/ndb_cluster_connection_impl.hpp storage/ndb/src/ndbapi/trp_client.hpp storage/ndb/test/ndbapi/Makefile.am storage/ndb/test/run-test/daily-devel-tests.txt === modified file 'storage/ndb/include/ndbapi/Ndb.hpp' --- a/storage/ndb/include/ndbapi/Ndb.hpp 2011-04-18 23:20:12 +0000 +++ b/storage/ndb/include/ndbapi/Ndb.hpp 2011-06-16 05:09:51 +0000 @@ -1074,6 +1074,9 @@ class Ndb friend class PollGuard; friend class NdbQueryImpl; friend class NdbQueryOperationImpl; + friend class DefaultWakeupHandler; + friend class MultiNdbWakeupHandler; + friend class NdbPollGroup; #endif public: === modified file 'storage/ndb/include/ndbapi/NdbApi.hpp' --- a/storage/ndb/include/ndbapi/NdbApi.hpp 2011-02-01 23:27:25 +0000 +++ b/storage/ndb/include/ndbapi/NdbApi.hpp 2011-06-16 05:09:51 +0000 @@ -35,4 +35,5 @@ #include "NdbEventOperation.hpp" #include "NdbPool.hpp" #include "NdbBlob.hpp" +#include "NdbPollGroup.hpp" #endif === added file 'storage/ndb/include/ndbapi/NdbPollGroup.hpp' --- a/storage/ndb/include/ndbapi/NdbPollGroup.hpp 1970-01-01 00:00:00 +0000 +++ b/storage/ndb/include/ndbapi/NdbPollGroup.hpp 2011-06-16 05:09:51 +0000 @@ -0,0 +1,104 @@ +/* + Copyright (c) 2003, 2010, 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 as published by + the Free Software Foundation; version 2 of the License. + + 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef NdbPollGroup_H +#define NdbPollGroup_H + +class Ndb_cluster_connection; +class Ndb; +class MultiNdbWakeupHandler; + +/* NdbPollGroup extends the Asynchronous NDB API, allowing the user to + wait for asynchronous operations to complete on multiple Ndb objects + with a single call. + + All Ndb objects within a poll group must belong to the same cluster connection. + In the current implementation, only one poll group per cluster connection + is supported. The user can instantiate this poll group using + ndb_cluster_connection::get_asynch_poll_group(). + + Once Ndb::sendPreparedTransactions() has been used to send an async operation + on a particular Ndb object, that Ndb can be added to the poll group. + + NdbPollGroup::wait() returns when some Ndb's are ready for polling; at this + point the user can call pollNdb(0, 1) on the ones that are ready. +*/ + +class NdbPollGroup { +friend class Ndb_cluster_connection; // can use the protected constructor + +public: + /** Construct an NdbPollGroup for cluster connection conn + * with up to max_ndb_objects objects. + * + * The current implementation supports only one NdbPollGroup per + * cluster connection; any attempt to create a second one will result + * in a failed assertion in NdbMultiWakeupHandler(). + */ + NdbPollGroup(Ndb_cluster_connection *conn, int max_ndb_objects); + + ~NdbPollGroup(); + + /** Add an Ndb object to the group. + The client thread uses this method to add Ndb objects to the group + before calling waitNdbs() on them. + + Returns true on success, false on error. + */ + bool addNdb(Ndb *); + + + /** Remove an Ndb object from the group. + This restores normal + + /** Wake up the thread that is polling this group. + This can be used by other threads to signal a condition to the + waiting thread. + */ + void wakeup(); + + + /** wait for Ndbs to be ready. + The call will return when: + (a) at least min_ready Ndbs are ready for polling, or + (b) timeout milliseconds have elapsed, or + (c) another thread has called NdbPollGroup::wakeup() + + The return value is the number of Ndb objects ready for polling, + with 0 indicating a timeout or wakeup, or negative on error. + + On return, arrayHead is set to the head of the list of Ndb objectss that + are ready for polling, and those objects have been implicitly removed + from the group. + */ + int wait(Ndb ** & arrayHead, Uint32 timeout_millis, Uint32 min_ready = 1 ); + +private: /* private methods */ + int topDownIdx(int n) { return m_array_size - n; } + +private: /* private instance variables */ + Ndb_cluster_connection *m_conn; + MultiNdbWakeupHandler *m_multiWaitHandler; + Ndb *m_wakeNdb; + Ndb **m_array; + int m_array_size; + int m_count; +}; + + +#endif + === modified file 'storage/ndb/include/ndbapi/ndb_cluster_connection.hpp' --- a/storage/ndb/include/ndbapi/ndb_cluster_connection.hpp 2011-02-04 18:34:09 +0000 +++ b/storage/ndb/include/ndbapi/ndb_cluster_connection.hpp 2011-06-16 05:09:51 +0000 @@ -206,6 +206,7 @@ private: friend class NdbImpl; friend class Ndb_cluster_connection_impl; friend class SignalSender; + friend class NdbPollGroup; class Ndb_cluster_connection_impl & m_impl; Ndb_cluster_connection(Ndb_cluster_connection_impl&); === modified file 'storage/ndb/include/transporter/TransporterCallback.hpp' --- a/storage/ndb/include/transporter/TransporterCallback.hpp 2011-02-01 23:27:25 +0000 +++ b/storage/ndb/include/transporter/TransporterCallback.hpp 2011-06-16 05:09:51 +0000 @@ -402,6 +402,14 @@ public: */ /** + * Notify upper layer of explicit wakeup request + * + * The is called from the thread holding receiving data from the + * transporter, under the protection of the transporter lock. + */ + virtual void reportWakeup() { } + + /** * Ask upper layer to supply a list of struct iovec's with data to * send to a node. * === modified file 'storage/ndb/src/common/transporter/TransporterRegistry.cpp' --- a/storage/ndb/src/common/transporter/TransporterRegistry.cpp 2011-02-01 23:27:25 +0000 +++ b/storage/ndb/src/common/transporter/TransporterRegistry.cpp 2011-06-16 05:09:51 +0000 @@ -1278,6 +1278,9 @@ TransporterRegistry::consume_extra_socke ret = my_recv(sock, buf, sizeof(buf), 0); err = my_socket_errno(); } while (ret == sizeof(buf) || (ret == -1 && err == EINTR)); + + /* Notify upper layer of explicit wakeup */ + callbackObj->reportWakeup(); } void === modified file 'storage/ndb/src/ndbapi/API.hpp' --- a/storage/ndb/src/ndbapi/API.hpp 2011-03-30 13:26:02 +0000 +++ b/storage/ndb/src/ndbapi/API.hpp 2011-06-16 05:09:51 +0000 @@ -40,6 +40,7 @@ #include #include #include +#include #include #include "NdbEventOperationImpl.hpp" === modified file 'storage/ndb/src/ndbapi/Makefile.am' --- a/storage/ndb/src/ndbapi/Makefile.am 2011-02-04 11:45:24 +0000 +++ b/storage/ndb/src/ndbapi/Makefile.am 2011-06-16 05:09:51 +0000 @@ -43,6 +43,7 @@ libndbapi_la_SOURCES = \ NdbOperationInt.cpp \ NdbOperationDefine.cpp \ NdbOperationExec.cpp \ + NdbPollGroup.cpp \ NdbScanOperation.cpp \ NdbScanFilter.cpp \ NdbIndexOperation.cpp \ @@ -64,9 +65,10 @@ libndbapi_la_SOURCES = \ NdbInfo.cpp \ NdbInfoScanOperation.cpp \ ndb_internal.cpp \ - trp_client.cpp \ + trp_client.cpp \ trp_node.cpp \ - trp_buffer.cpp + trp_buffer.cpp \ + WakeupHandler.cpp INCLUDES_LOC = -I$(top_srcdir)/storage/ndb/src/mgmapi === modified file 'storage/ndb/src/ndbapi/NdbImpl.hpp' --- a/storage/ndb/src/ndbapi/NdbImpl.hpp 2011-04-18 23:20:12 +0000 +++ b/storage/ndb/src/ndbapi/NdbImpl.hpp 2011-06-16 05:09:51 +0000 @@ -31,6 +31,7 @@ #include "trp_client.hpp" #include "trp_node.hpp" #include "NdbWaiter.hpp" +#include "WakeupHandler.hpp" template struct Ndb_free_list_t @@ -80,7 +81,11 @@ public: Uint32 the_release_ind[MAX_NDB_NODES]; NdbWaiter theWaiter; - + + DefaultWakeupHandler normalWakeHandler; + WakeupHandler* wakeHandler; + Uint32 wakeContext; + NdbEventOperationImpl *m_ev_op; int m_optimized_node_selection; @@ -190,6 +195,7 @@ public: */ virtual void trp_deliver_signal(const NdbApiSignal*, const LinearSectionPtr p[3]); + virtual void trp_wakeup(); virtual void recordWaitTimeNanos(Uint64 nanos); // Is node available for running transactions bool get_node_alive(NodeId nodeId) const; @@ -599,4 +605,13 @@ NdbImpl::sendFragmentedSignal(NdbApiSign } return -1; } + +inline +void +NdbImpl::trp_wakeup() +{ + wakeHandler->notifyWakeup(); +} + + #endif === added file 'storage/ndb/src/ndbapi/NdbPollGroup.cpp' --- a/storage/ndb/src/ndbapi/NdbPollGroup.cpp 1970-01-01 00:00:00 +0000 +++ b/storage/ndb/src/ndbapi/NdbPollGroup.cpp 2011-06-16 05:09:51 +0000 @@ -0,0 +1,115 @@ +/* + Copyright (c) 2004, 2010, 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 as published by + the Free Software Foundation; version 2 of the License. + + 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include "NdbPollGroup.hpp" +#include "WakeupHandler.hpp" +#include +#include "TransporterFacade.hpp" +#include "ndb_cluster_connection_impl.hpp" +#include "NdbImpl.hpp" + +NdbPollGroup::NdbPollGroup(Ndb_cluster_connection *_conn, int _ndbs) : + m_conn(_conn), + m_array_size(_ndbs), + m_count(0), + m_multiWaitHandler(0) +{ + /* Allocate the array of Ndbs */ + m_array = new Ndb *[m_array_size]; + + /* Call into the TransporterFacade to set up wakeups */ + bool rc = m_conn->m_impl.m_transporter_facade->setupWakeup(); + assert(rc); + + /* Get a new Ndb object to be the dedicated "wakeup object" for the group */ + m_wakeNdb = new Ndb(m_conn); + assert(m_wakeNdb); + m_wakeNdb->init(4); + + /* Get a wakeup handler */ + m_multiWaitHandler = new MultiNdbWakeupHandler(m_wakeNdb); +} + + +NdbPollGroup::~NdbPollGroup() +{ + while(m_count > 0) + { + m_multiWaitHandler->unregisterNdb(m_array[topDownIdx(m_count--)]); + } + + delete[] m_array; + delete m_multiWaitHandler; +} + + +bool NdbPollGroup::addNdb(Ndb *ndb) +{ + if(unlikely(& ndb->theImpl->m_ndb_cluster_connection != m_conn)) + { + abort(); + } + + if(unlikely(topDownIdx(m_count) == 0)) + { + return false; + } + + m_count++; + m_array[topDownIdx(m_count)] = ndb; + return true; +} + + +void NdbPollGroup::wakeup() +{ + m_conn->m_impl.m_transporter_facade->requestWakeup(); +} + + +int NdbPollGroup::wait(Ndb ** & arrayHead /* out */, + Uint32 timeout_millis, + Uint32 min_ready) +{ + arrayHead = m_array + topDownIdx(m_count); + m_multiWaitHandler->setWakeThreshold(min_ready); + + /* Update the statistics on the Ndb waiter */ + m_wakeNdb->theImpl->incClientStat(Ndb::WaitExecCompleteCount, 1); + + int wait_rc; + int nready; + { + PollGuard pg(* m_wakeNdb->theImpl); // get ready to poll + m_multiWaitHandler->setNdbList(arrayHead, m_count); + wait_rc = m_multiWaitHandler->waitForInput(& pg, timeout_millis); + nready = m_multiWaitHandler->getNumReadyNdbs(); + + if(wait_rc == 0) // success. remove ready Ndbs from poll list. + { + for(int i = 0 ; i < nready ; i++) + { + m_multiWaitHandler->unregisterNdb(m_array[topDownIdx(m_count)]); + m_count--; + } + } + } /* release PollGuard */ + + return wait_rc ? -1 : nready; +} + === modified file 'storage/ndb/src/ndbapi/Ndbif.cpp' --- a/storage/ndb/src/ndbapi/Ndbif.cpp 2011-02-28 12:25:52 +0000 +++ b/storage/ndb/src/ndbapi/Ndbif.cpp 2011-06-16 05:09:51 +0000 @@ -1002,12 +1002,8 @@ Ndb::completedTransaction(NdbTransaction theNoOfSentTransactions = tNoSentTransactions - 1; aCon->theListState = NdbTransaction::InCompletedList; aCon->handleExecuteCompletion(); - if ((theMinNoOfEventsToWakeUp != 0) && - (theNoOfCompletedTransactions >= theMinNoOfEventsToWakeUp)) { - theMinNoOfEventsToWakeUp = 0; - theImpl->theWaiter.signal(NO_WAIT); - return; - }//if + + theImpl->wakeHandler->notifyTransactionCompleted(this); } else { ndbout << "theNoOfSentTransactions = " << (int) theNoOfSentTransactions; ndbout << " theListState = " << (int) aCon->theListState; @@ -1235,7 +1231,7 @@ Ndb::sendPrepTrans(int forceSend) insert_completed_list(a_con); }//for theNoOfPreparedTransactions = 0; - theImpl->do_forceSend(forceSend); + theImpl->do_forceSend(forceSend); return; }//Ndb::sendPrepTrans() === modified file 'storage/ndb/src/ndbapi/Ndbinit.cpp' --- a/storage/ndb/src/ndbapi/Ndbinit.cpp 2011-04-18 23:20:12 +0000 +++ b/storage/ndb/src/ndbapi/Ndbinit.cpp 2011-06-16 05:09:51 +0000 @@ -192,6 +192,8 @@ NdbImpl::NdbImpl(Ndb_cluster_connection 1024,1024), theNoOfDBnodes(0), theWaiter(this), + wakeHandler(&normalWakeHandler), + wakeContext(~Uint32(0)), m_ev_op(0), customDataPtr(0) { === modified file 'storage/ndb/src/ndbapi/TransporterFacade.cpp' --- a/storage/ndb/src/ndbapi/TransporterFacade.cpp 2011-03-30 22:07:57 +0000 +++ b/storage/ndb/src/ndbapi/TransporterFacade.cpp 2011-06-16 05:09:51 +0000 @@ -533,6 +533,7 @@ TransporterFacade::TransporterFacade(Glo theClusterMgr(NULL), checkCounter(4), currentSendLimit(1), + dozer(NULL), theStopReceive(0), theSendThread(NULL), theReceiveThread(NULL), @@ -2031,3 +2032,64 @@ TransporterFacade::ext_doConnect(int aNo theClusterMgr->unlock(); } +bool +TransporterFacade::setupWakeup() +{ + /* Ask TransporterRegistry to setup wakeup sockets */ + bool rc; + lock_mutex(); + { + rc = theTransporterRegistry->setup_wakeup_socket(); + } + unlock_mutex(); + return rc; +} + +bool +TransporterFacade::registerForWakeup(trp_client* _dozer) +{ + /* Called with Transporter lock */ + /* In future use a DLList for dozers. + * Ideally with some way to wake one rather than all + * For now, we just have one/TransporterFacade + */ + if (dozer != NULL) + return false; + + dozer = _dozer; + return true; +} + +bool +TransporterFacade::unregisterForWakeup(trp_client* _dozer) +{ + /* Called with Transporter lock */ + if (dozer != _dozer) + return false; + + dozer = NULL; + return true; +} + +void +TransporterFacade::requestWakeup() +{ + /* Forward to TransporterRegistry + * No need for locks, assuming only one client at a time will use + */ + theTransporterRegistry->wakeup(); +} + + +void +TransporterFacade::reportWakeup() +{ + /* Explicit wakeup callback + * Called with Transporter Mutex held + */ + /* Notify interested parties */ + if (dozer != NULL) + { + dozer->trp_wakeup(); + }; +} === modified file 'storage/ndb/src/ndbapi/TransporterFacade.hpp' --- a/storage/ndb/src/ndbapi/TransporterFacade.hpp 2011-02-28 12:25:52 +0000 +++ b/storage/ndb/src/ndbapi/TransporterFacade.hpp 2011-06-16 05:09:51 +0000 @@ -197,6 +197,21 @@ public: { theTransporterRegistry->reset_send_buffer(node, should_be_empty); } + /** + * Wakeup + * + * Clients normally block waiting for a pattern of signals, + * or until a timeout expires. + * This Api allows them to be woken early. + * To use it, a setupWakeup() call must be made once prior + * to using the Apis in any client. + * + */ + bool setupWakeup(); + bool registerForWakeup(trp_client* dozer); + bool unregisterForWakeup(trp_client* dozer); + void requestWakeup(); + void reportWakeup(); private: @@ -223,6 +238,11 @@ private: void calculateSendLimit(); + /* Single dozer supported currently. + * In future, use a DLList to support > 1 + */ + trp_client * dozer; + // Declarations for the receive and send thread int theStopReceive; === added file 'storage/ndb/src/ndbapi/WakeupHandler.cpp' --- a/storage/ndb/src/ndbapi/WakeupHandler.cpp 1970-01-01 00:00:00 +0000 +++ b/storage/ndb/src/ndbapi/WakeupHandler.cpp 2011-06-16 05:09:51 +0000 @@ -0,0 +1,223 @@ + +/* + Copyright (c) 2003, 2011, 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 as published by + the Free Software Foundation; version 2 of the License. + + 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "WakeupHandler.hpp" +#include "Ndb.hpp" +#include "NdbImpl.hpp" +#include "trp_client.hpp" + +// ****** Default handler **** +void DefaultWakeupHandler::notifyTransactionCompleted(Ndb *from) +{ + if ((from->theMinNoOfEventsToWakeUp != 0) && + (from->theNoOfCompletedTransactions >= from->theMinNoOfEventsToWakeUp)) { + from->theMinNoOfEventsToWakeUp = 0; + from->theImpl->theWaiter.signal(NO_WAIT); + } +} + +void DefaultWakeupHandler::notifyWakeup() +{ +} + + + +// ***** Multiwait handler **** + +/** + * An instance of this class is used when a single thread + * wants to wait for the asynchronous completion of transactions + * on multiple Ndb objects. + * When the thread starts waiting, all Ndb objects are checked + * for CompletedTransactions, and their wakeHandler is set to + * poin to the same MultiNdbWakeupHandler object. The thread + * is then put to sleep / polls on a designated Ndb object. + * + * As transactions complete, the MultiNdbWakeHandler object + * moves their Ndb objects to the start of the passed Ndb + * object list and determines whether enough have completed + * to wake the waiting thread. + * When enough have completed, the waiting thread is woken via + * the designated Ndb object. + */ + +MultiNdbWakeupHandler::MultiNdbWakeupHandler(Ndb* _wakeNdb) + : wakeNdb(_wakeNdb), + woken(false), + minCompletedTransToWake(1) +{ + /* Register the waiter Ndb to receive wakeups for all Ndbs in the group */ + PollGuard pg(* wakeNdb->theImpl); // Hold mutex before calling into Facade + bool rc = wakeNdb->theImpl->m_transporter_facade->registerForWakeup(wakeNdb->theImpl); + assert(rc); + + /* And set its wakeHandler */ + wakeNdb->theImpl->wakeHandler = this; +} + + +bool MultiNdbWakeupHandler::setNdbList(Ndb** _objs, Uint32 _cnt) +{ + numNdbsWithCompletedTrans = 0; + numCompletedTrans = 0; + objs = _objs; + cnt = _cnt; + for (Uint32 ndbcnt = 0; ndbcnt < cnt; ndbcnt ++) + { + Ndb* obj = objs [ndbcnt]; + + /* Set each Ndb object's wakeHandler */ + obj->theImpl->wakeHandler = this; + + /* Store the object's list position in its wake context */ + obj->theImpl->wakeContext = ndbcnt; + + /* An Ndb may already have some completed transactions */ + if (obj->theNoOfCompletedTransactions) + { + numCompletedTrans += obj->theNoOfCompletedTransactions; + /* Move that ndb to the start of the array */ + swapNdbsInArray(ndbcnt, numNdbsWithCompletedTrans); + numNdbsWithCompletedTrans++; + } + } + + return true; +} + + +MultiNdbWakeupHandler::~MultiNdbWakeupHandler() +{ + PollGuard pg(* wakeNdb->theImpl); // Hold mutex before calling into Facade + bool rc = wakeNdb->theImpl->m_transporter_facade-> + unregisterForWakeup(wakeNdb->theImpl); + assert(rc); +} + + +Uint32 MultiNdbWakeupHandler::getNumReadyNdbs() const +{ + return numNdbsWithCompletedTrans; +} + + +bool MultiNdbWakeupHandler::isReadyToWake() const +{ + return (numCompletedTrans >= minCompletedTransToWake) || woken; +} + + +int MultiNdbWakeupHandler::waitForInput(PollGuard* pg, int timeout_millis) +{ + // TODO : Check wakeNdb is the Ndb in pg + wakeNdb->theImpl->theWaiter.set_node(0); + wakeNdb->theImpl->theWaiter.set_state(WAIT_TRANS); + + /* We need to wait for some event(s) to fire */ + NDB_TICKS currTime = NdbTick_CurrentMillisecond(); + NDB_TICKS maxTime = currTime + (NDB_TICKS) timeout_millis; + + int maxsleep = timeout_millis > 10 ? 10 : timeout_millis; + + do { + /* PollGuard will put us to sleep until something relevant happens */ + pg->wait_for_input(maxsleep); + if (isReadyToWake()) + { + return 0; + } + timeout_millis = (int) (maxTime - NdbTick_CurrentMillisecond()); + } while (timeout_millis > 0); + + return -1; +} + + +void MultiNdbWakeupHandler::unregisterNdb(Ndb *obj) { + assert(obj->theImpl->wakeHandler == this); + + obj->theImpl->wakeHandler = & obj->theImpl->normalWakeHandler; + obj->theImpl->wakeContext = 0; +} + + + +void MultiNdbWakeupHandler::swapNdbsInArray(Uint32 indexA, Uint32 indexB) +{ + /* Generally used to move an Ndb object down the list + * (bubble sort), so that it is part of a contiguous + * list of Ndbs with completed transactions to return + * to caller. + * If it's already in the given position, no effect + */ + assert(indexA < cnt); + assert(indexB < cnt); + + Ndb* a = objs[ indexA ]; + Ndb* b = objs[ indexB ]; + + assert(a->theImpl->wakeContext == indexA); + assert(b->theImpl->wakeContext == indexB); + + objs[ indexA ] = b; + b->theImpl->wakeContext = indexA; + + objs[ indexB ] = a; + a->theImpl->wakeContext = indexB; +} + + +void MultiNdbWakeupHandler::notifyTransactionCompleted(Ndb* from) +{ + // TODO : assert tp lock held. + /* Some Ndb object has just completed another transaction. + Ensure that it's in the completed Ndbs list + */ + assert(from != wakeNdb); + + Uint32 completedNdbListPos = from->theImpl->wakeContext; + assert(completedNdbListPos < cnt); + if (completedNdbListPos >= numNdbsWithCompletedTrans) + { + /* It's not, swap it with Ndb in 'next' position */ + swapNdbsInArray(completedNdbListPos, numNdbsWithCompletedTrans); + + numNdbsWithCompletedTrans ++; + } + + numCompletedTrans ++; + + if (numCompletedTrans >= minCompletedTransToWake) + { + /* Wakeup client thread, using 'waiter' Ndb */ + assert(wakeNdb->theImpl->wakeHandler == this); + wakeNdb->theImpl->theWaiter.signal(NO_WAIT); + } + + return; +} + + +void MultiNdbWakeupHandler::notifyWakeup() +{ + woken = true; + + /* Wakeup client thread, using 'waiter' Ndb */ + assert(wakeNdb->theImpl->wakeHandler == this); + wakeNdb->theImpl->theWaiter.signal(NO_WAIT); +} === added file 'storage/ndb/src/ndbapi/WakeupHandler.hpp' --- a/storage/ndb/src/ndbapi/WakeupHandler.hpp 1970-01-01 00:00:00 +0000 +++ b/storage/ndb/src/ndbapi/WakeupHandler.hpp 2011-06-16 05:09:51 +0000 @@ -0,0 +1,86 @@ +/* + Copyright (c) 2003, 2011, 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 as published by + the Free Software Foundation; version 2 of the License. + + 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef WakeupHandler_H +#define WakeupHandler_H + +#include +class Ndb; +class Ndb_cluster_connection; +class PollGuard; + +/** + * WakeupHandler + * + * Help Ndb objects respond to wakeups from the TransporterFacade + * when transactions have completed. + * + * Each Ndb will own an instance of the DefaultWakeupHandler, + * and each NdbPollGroup will create an instance of a more specialized + * WakeupHandler. + */ + +class WakeupHandler +{ + public: + virtual void notifyTransactionCompleted(Ndb* from) = 0; + virtual void notifyWakeup() = 0; + virtual ~WakeupHandler() {}; +}; + + +class DefaultWakeupHandler : public WakeupHandler +{ + void notifyTransactionCompleted(Ndb* from); + void notifyWakeup(); +}; + + +class MultiNdbWakeupHandler : public WakeupHandler +{ + public: + MultiNdbWakeupHandler(Ndb* _wakeNdb); + bool setNdbList(Ndb** _objs, Uint32 _cnt); + void unregisterNdb(Ndb *); + void notifyTransactionCompleted(Ndb* from); + void notifyWakeup(); + ~MultiNdbWakeupHandler(); + + void setWakeThreshold(int n); + Uint32 getNumReadyNdbs() const; + bool isReadyToWake() const; + int waitForInput(PollGuard* pg, int timeout_millis); + + private: // private methods + void swapNdbsInArray(Uint32 indexA, Uint32 indexB); + + private: // private instance variables + Uint32 numNdbsWithCompletedTrans; + Uint32 numCompletedTrans; + Uint32 minCompletedTransToWake; + Ndb* wakeNdb; + Ndb** objs; + Uint32 cnt; + bool woken; +}; + + +inline void MultiNdbWakeupHandler::setWakeThreshold(int n) { + minCompletedTransToWake = n; +} + +#endif === modified file 'storage/ndb/src/ndbapi/ndb_cluster_connection_impl.hpp' --- a/storage/ndb/src/ndbapi/ndb_cluster_connection_impl.hpp 2011-03-31 20:51:28 +0000 +++ b/storage/ndb/src/ndbapi/ndb_cluster_connection_impl.hpp 2011-06-16 05:09:51 +0000 @@ -73,6 +73,7 @@ public: private: friend class Ndb; friend class NdbImpl; + friend class NdbPollGroup; friend void* run_ndb_cluster_connection_connect_thread(void*); friend class Ndb_cluster_connection; friend class NdbEventBuffer; === modified file 'storage/ndb/src/ndbapi/trp_client.hpp' --- a/storage/ndb/src/ndbapi/trp_client.hpp 2011-02-24 22:07:05 +0000 +++ b/storage/ndb/src/ndbapi/trp_client.hpp 2011-06-16 05:09:51 +0000 @@ -35,6 +35,8 @@ public: virtual void trp_deliver_signal(const NdbApiSignal *, const LinearSectionPtr ptr[3]) = 0; + virtual void trp_wakeup() + {}; Uint32 open(class TransporterFacade*, int blockNo = -1); void close(); === modified file 'storage/ndb/test/ndbapi/Makefile.am' --- a/storage/ndb/test/ndbapi/Makefile.am 2011-02-04 11:45:24 +0000 +++ b/storage/ndb/test/ndbapi/Makefile.am 2011-06-16 05:09:51 +0000 @@ -29,6 +29,7 @@ flexTT \ testBackup \ testBasic \ testBasicAsynch \ +testAsynchMultiwait \ testBlobs \ testDataBuffers \ testDict \ @@ -89,6 +90,7 @@ testBackup_SOURCES = testBackup.cpp testBasic_SOURCES = testBasic.cpp testSpj_SOURCES = testSpj.cpp testBasicAsynch_SOURCES = testBasicAsynch.cpp +testAsynchMultiwait_SOURCES = testAsynchMultiwait.cpp testBlobs_SOURCES = testBlobs.cpp testDataBuffers_SOURCES = testDataBuffers.cpp testDict_SOURCES = testDict.cpp === added file 'storage/ndb/test/ndbapi/testAsynchMultiwait.cpp' --- a/storage/ndb/test/ndbapi/testAsynchMultiwait.cpp 1970-01-01 00:00:00 +0000 +++ b/storage/ndb/test/ndbapi/testAsynchMultiwait.cpp 2011-06-16 05:09:51 +0000 @@ -0,0 +1,296 @@ +/* + Copyright (C) 2003-2006, 2008 MySQL AB + All rights reserved. Use is subject to license terms. + + 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; version 2 of the License. + + 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "NDBT_Test.hpp" +#include "NDBT_ReturnCodes.h" +#include "HugoTransactions.hpp" +#include "HugoAsynchTransactions.hpp" +#include "UtilTransactions.hpp" +#include "random.h" + +NdbPollGroup * global_poll_group; + +#define check(b, e) \ + if (!(b)) { g_err << "ERR: " << step->getName() << " failed on line " \ + << __LINE__ << ": " << e.getNdbError() << endl; return NDBT_FAILED; } + + +int runSetup(NDBT_Context* ctx, NDBT_Step* step){ + + int records = ctx->getNumRecords(); + int batchSize = ctx->getProperty("BatchSize", 1); + int transactions = (records / 100) + 1; + int operations = (records / transactions) + 1; + + HugoAsynchTransactions hugoTrans(*ctx->getTab()); + if (hugoTrans.loadTableAsynch(GETNDB(step), records, batchSize, + transactions, operations) != 0){ + return NDBT_FAILED; + } + + Ndb* pNdb = GETNDB(step); + Ndb_cluster_connection* conn = &pNdb->get_ndb_cluster_connection(); + global_poll_group = new NdbPollGroup(conn, 1000); + + if(global_poll_group == 0) { + return NDBT_FAILED; + } + + return NDBT_OK; +} + +int runCleanup(NDBT_Context* ctx, NDBT_Step* step){ + int records = ctx->getNumRecords(); + int batchSize = ctx->getProperty("BatchSize", 1); + int transactions = (records / 100) + 1; + int operations = (records / transactions) + 1; + + HugoAsynchTransactions hugoTrans(*ctx->getTab()); + if (hugoTrans.pkDelRecordsAsynch(GETNDB(step), records, batchSize, + transactions, operations) != 0){ + return NDBT_FAILED; + } + + delete global_poll_group; + + return NDBT_OK; +} + + +int runPkReadMultiBasic(NDBT_Context* ctx, NDBT_Step* step){ + int loops = ctx->getNumLoops(); + int records = ctx->getNumRecords(); + const int MAX_NDBS = 200; + Ndb* pNdb = GETNDB(step); + Ndb_cluster_connection* conn = &pNdb->get_ndb_cluster_connection(); + + int i = 0; + HugoOperations hugoOps(*ctx->getTab()); + + Ndb* ndbObjs[ MAX_NDBS ]; + NdbTransaction* transArray[ MAX_NDBS ]; + Ndb ** ready_ndbs; + + for (int j=0; j < MAX_NDBS; j++) + { + Ndb* ndb = new Ndb(conn); + check(ndb->init() == 0, (*ndb)); + ndbObjs[ j ] = ndb; + } + + while (istartTransaction(); + check(trans != NULL, (*ndb)); + NdbOperation* readOp = trans->getNdbOperation(ctx->getTab()); + check(readOp != NULL, (*trans)); + check(readOp->readTuple() == 0, (*readOp)); + check(hugoOps.equalForRow(readOp, recordsLeft) == 0, hugoOps); + + /* Read all other cols */ + for (int k=0; k < ctx->getTab()->getNoOfColumns(); k++) + { + check(readOp->getValue(ctx->getTab()->getColumn(k)) != NULL, + (*readOp)); + } + + /* Now send em off */ + trans->executeAsynchPrepare(NdbTransaction::Commit, + NULL, + NULL, + NdbOperation::AbortOnError); + ndb->sendPreparedTransactions(); + + transArray[ndbcnt] = trans; + global_poll_group->addNdb(ndb); + + ndbcnt++; + pollcnt++; + recordsLeft--; + lumpsize--; + }; + + /* Ok, now wait for the Ndbs to complete */ + while (pollcnt) + { + /* Occasionally check with no timeout */ + Uint32 timeout_millis = myRandom48(2)?10000:0; + int count = global_poll_group->wait(ready_ndbs, timeout_millis); + + if (count > 0) + { + for (int y=0; y < count; y++) + { + Ndb *ndb = ready_ndbs[y]; + check(ndb->pollNdb(0, 1) != 0, (*ndb)); + } + pollcnt -= count; + } + } + + /* Ok, now close the transactions */ + for (int t=0; t < ndbcnt; t++) + { + transArray[t]->close(); + } + } while (recordsLeft); + + i++; + } + + for (int j=0; j < MAX_NDBS; j++) + { + delete ndbObjs[ j ]; + } + + return NDBT_OK; +} + +int runPkReadMultiWakeupT1(NDBT_Context* ctx, NDBT_Step* step) +{ + HugoOperations hugoOps(*ctx->getTab()); + Ndb* ndb = GETNDB(step); + Uint32 phase = ctx->getProperty("PHASE"); + + if (phase != 0) + { + ndbout << "Thread 1 : Error, initial phase should be 0 not " << phase << endl; + return NDBT_FAILED; + }; + + /* We now start a transaction, locking row 0 */ + ndbout << "Thread 1 : Starting transaction locking row 0..." << endl; + check(hugoOps.startTransaction(ndb) == 0, hugoOps); + check(hugoOps.pkReadRecord(ndb, 0, 1, NdbOperation::LM_Exclusive) == 0, + hugoOps); + check(hugoOps.execute_NoCommit(ndb) == 0, hugoOps); + + ndbout << "Thread 1 : Lock taken." << endl; + ndbout << "Thread 1 : Triggering Thread 2 by move to phase 1" << endl; + /* Ok, now get thread 2 to try to read row */ + ctx->incProperty("PHASE"); /* Set to 1 */ + + /* Here, loop waking up waiter on the cluster connection */ + /* Check the property has not moved to phase 2 */ + ndbout << "Thread 1 : Performing async wakeup until phase changes to 2" + << endl; + while (ctx->getProperty("PHASE") != 2) + { + global_poll_group->wakeup(); + NdbSleep_MilliSleep(500); + } + + ndbout << "Thread 1 : Phase changed to 2, committing transaction " + << "and releasing lock" << endl; + + /* Ok, give them a break, commit transaction */ + check(hugoOps.execute_Commit(ndb) ==0, hugoOps); + hugoOps.closeTransaction(ndb); + + ndbout << "Thread 1 : Finished" << endl; + return NDBT_OK; +} + +int runPkReadMultiWakeupT2(NDBT_Context* ctx, NDBT_Step* step) +{ + ndbout << "Thread 2 : Waiting for phase 1 notification from Thread 1" << endl; + ctx->getPropertyWait("PHASE", 1); + + /* Ok, now thread 1 has locked row 1, we'll attempt to read + * it, using the multi_ndb_wait Api to block + */ + HugoOperations hugoOps(*ctx->getTab()); + Ndb* ndb = GETNDB(step); + + ndbout << "Thread 2 : Starting async transaction to read row" << endl; + check(hugoOps.startTransaction(ndb) == 0, hugoOps); + check(hugoOps.pkReadRecord(ndb, 0, 1, NdbOperation::LM_Exclusive) == 0, + hugoOps); + /* Prepare, Send */ + check(hugoOps.execute_async(ndb, + NdbTransaction::Commit, + NdbOperation::AbortOnError) == 0, + hugoOps); + + global_poll_group->addNdb(ndb); + Ndb ** ready_ndbs; + int wait_rc = 0; + int acknowledged = 0; + do + { + ndbout << "Thread 2 : Calling NdbPollGroup::wait()" << endl; + wait_rc = global_poll_group->wait(ready_ndbs, 10000); + ndbout << " Result : " << wait_rc << endl; + if (wait_rc == 0) + { + if (!acknowledged) + { + ndbout << "Thread 2 : Woken up, moving to phase 2" << endl; + ctx->incProperty("PHASE"); + acknowledged = 1; + } + } + else if (wait_rc > 0) + { + ndbout << "Thread 2 : Transaction completed" << endl; + ndb->pollNdb(1,0); + hugoOps.closeTransaction(ndb); + } + } while (wait_rc == 0); + + return (wait_rc == 1 ? NDBT_OK : NDBT_FAILED); +} + +NDBT_TESTSUITE(testAsynchMultiwait); +TESTCASE("PkReadAsynchMultiBasic", + "Verify wait-multi-ndb api using read ops") { + INITIALIZER(runSetup); + STEP(runPkReadMultiBasic); + FINALIZER(runCleanup); +} +TESTCASE("PkReadAsynchMultiWakeup", + "Verify wait-multi-ndb wakeup Api code") { + INITIALIZER(runSetup); + TC_PROPERTY("PHASE", Uint32(0)); + STEP(runPkReadMultiWakeupT1); + STEP(runPkReadMultiWakeupT2); + FINALIZER(runCleanup); +} +NDBT_TESTSUITE_END(testAsynchMultiwait); + +int main(int argc, const char** argv){ + ndb_init(); + NDBT_TESTSUITE_INSTANCE(testAsynchMultiwait); + return testAsynchMultiwait.execute(argc, argv); +} + === modified file 'storage/ndb/test/run-test/daily-devel-tests.txt' --- a/storage/ndb/test/run-test/daily-devel-tests.txt 2011-02-01 08:36:25 +0000 +++ b/storage/ndb/test/run-test/daily-devel-tests.txt 2011-06-16 05:09:51 +0000 @@ -129,3 +129,13 @@ max-time: 1800 cmd: testDict args: -n SchemaTrans -l 1 +# async api extensions + +max-time : 500 +cmd : testAsynchMultiwait +args: -nPkReadAsynchMultiBasic T1 + +max-time : 500 +cmd : testAsynchMultiwait +args: -nPkReadAsynchMultiWakeup T1 + --===============0248230458== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/john.duncan@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: john.duncan@stripped # target_branch: file:///Users/jdd/bzr-repo/working/cluster-7.2-\ # multiwait/ # testament_sha1: d0e9905482d44eebf5f184ebdc48114e7691e489 # timestamp: 2011-06-15 22:16:06 -0700 # base_revision_id: john.duncan@stripped\ # 23qc6paiavucscrk # # Begin bundle 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 u5JfzT/4u5IpwoSDdWvfaA== --===============0248230458==--