From: Tor Didriksen Date: September 28 2009 11:43am Subject: bzr commit into mysql-6.0-bugfixing branch (tor.didriksen:2832) List-Archive: http://lists.mysql.com/commits/84830 Message-Id: <20090928114349.D50B44272F@atum07.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============0128269436==" --===============0128269436== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///export/home/didrik/mysqldev-6.0-codebase/6.0-codebase-bf-gtest/part-2/ based on revid:tor.didriksen@stripped 2832 Tor Didriksen 2009-09-28 move gtest changes to 6.0 codebase added: sql/mdl-t.cc sql/mdl_test.cc sql/thread.cc sql/thread.h sql/thread_test.cc modified: sql/Makefile.am === modified file 'sql/Makefile.am' --- a/sql/Makefile.am 2009-09-22 08:50:36 +0000 +++ b/sql/Makefile.am 2009-09-28 11:43:44 +0000 @@ -223,13 +223,30 @@ UNIT_TEST_OBJS= sql_state.o \ # TODO(didrik): Include this target in 'make all' # and use a test runner (ala ../unittest/unit.pl) to run all tests. -test-gunit: sql_list_test +test-gunit: \ + mdl_test \ + mdl-t \ + sql_list_test \ + thread_test # The convention is to have tests for foo.cc in foo_test.cc +mdl_test: mdl_test.cc mdl.o thread.o $(UNIT_TEST_LIBS) $(UNIT_TEST_OBJS) + $(CXX) -o $@ $(UNIT_TEST_FLAGS) $< \ + mdl.o thread.o $(UNIT_TEST_OBJS) $(UNIT_TEST_LIBS) $(LIBS) + +mdl-t: mdl-t.cc mdl.o thread.o $(UNIT_TEST_LIBS) $(UNIT_TEST_OBJS) + $(CXX) -o $@ $(UNIT_TEST_FLAGS) -I$(top_srcdir)/unittest/mytap $< \ + $(top_srcdir)/unittest/mytap/tap.o \ + mdl.o thread.o $(UNIT_TEST_OBJS) $(UNIT_TEST_LIBS) $(LIBS) + sql_list_test: sql_list_test.cc sql_list.o $(UNIT_TEST_LIBS) $(UNIT_TEST_OBJS) $(CXX) -o $@ $(UNIT_TEST_FLAGS) $< \ sql_list.o $(UNIT_TEST_OBJS) $(UNIT_TEST_LIBS) $(LIBS) +thread_test: thread_test.cc thread.o $(UNIT_TEST_LIBS) $(UNIT_TEST_OBJS) + $(CXX) -o $@ $(UNIT_TEST_FLAGS) $< \ + thread.o $(UNIT_TEST_OBJS) $(UNIT_TEST_LIBS) $(LIBS) + # Experimental END === added file 'sql/mdl-t.cc' --- a/sql/mdl-t.cc 1970-01-01 00:00:00 +0000 +++ b/sql/mdl-t.cc 2009-09-28 11:43:44 +0000 @@ -0,0 +1,777 @@ +/* Copyright (C) 2009 Sun Microsystems, Inc. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + This is a port of the corresponding mdl_test.cc (written for googletest) + to mytap. Do a 'tkdiff mdl_test.cc mdl-t.cc' to see the differences. + In order to illustrate (some of) the features of googletest, I have + added some extensions below, notably support for reporting of line + numbers in case of failures. + */ + +#include +#include +#include + +#include "mdl.h" + +#include "thr_malloc.h" +#include "thread.h" + +pthread_key(MEM_ROOT**,THR_MALLOC); +pthread_key(THD*, THR_THD); +pthread_mutex_t LOCK_open; +uint opt_debug_sync_timeout= 0; + +// Reimplemented some macros from googletest, so that the tests below +// could be kept unchanged. No support for streaming of user messages +// in this simplified version. Also no attempt to make this portable +// between compilers. +void print_message(const char* file, int line, const char* message) +{ + std::cout << "# " << file << ":" << line << " " << message << "\n"; +} + +// Some macro tricks to generate names like result123 and result456 +#define CONCAT_TOKEN_(foo, bar) CONCAT_TOKEN_IMPL_(foo, bar) +#define CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar +#define BOOL_VAR_ CONCAT_TOKEN_(result, __LINE__) + +#define MESSAGE_(message) \ + print_message(__FILE__, __LINE__, message) + +// This is where we call the ok() function from mytap!!! +#define TEST_BOOLEAN_(boolexpr, booltext, actual, expected, fail) \ +do { \ + const bool BOOL_VAR_ = boolexpr; \ + ok(BOOL_VAR_, BOOL_VAR_ ? "" : booltext); \ + if (!BOOL_VAR_) \ + fail("\n# Value of: " booltext \ + "\n# Actual: " #actual \ + "\n# Expected: " #expected); \ +} while(0) + +// Boolean assertions. +#define EXPECT_TRUE(condition) \ + TEST_BOOLEAN_(condition, #condition, false, true, MESSAGE_) +#define EXPECT_FALSE(condition) \ + TEST_BOOLEAN_(!(condition), #condition, true, false, MESSAGE_) + + +// Some (very) simplified versions of comparison predicates. +// There is no distinction between ASSERT and EXPECT in mytap. +#define ASSERT_NE(val1, val2) \ + EXPECT_NE(val1, val2) + +// This version will not print expected or actual values for arguments. +#define EXPECT_NE(val1, val2) \ + EXPECT_TRUE(val1 != val2) + +// This version will not print expected or actual values for arguments. +#define EXPECT_EQ(val1, val2) \ + EXPECT_TRUE(val1 == val2) + +#define FAIL() \ + EXPECT_TRUE(1 == 0) + + + +extern "C" void sql_alloc_error_handler(void) +{ + FAIL(); +} + +namespace { +bool notify_thread(THD*); +} + +/* + We need to mock away this global function, because the real version + pulls in a lot of dependencies. + (The @note for the real version of this function indicates that the + coupling between THD and MDL is too tight.) +*/ +extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use) +{ + if (in_use != NULL) + return notify_thread(in_use); + return false; +} + +/* + Mock away this function as well, with an empty function. + TODO(didrik): Consider verifying that the MDL module actually calls + this with correct arguments. +*/ +extern void mysql_ha_flush(THD *) +{ + DBUG_PRINT("mysql_ha_flush", ("mock version")); +} + +/* + We need to mock away this global function, the real version pulls in + too many dependencies. + */ +extern "C" const char *set_thd_proc_info(THD *thd, const char *info, + const char *calling_function, + const char *calling_file, + const unsigned int calling_line) +{ + DBUG_PRINT("proc_info", ("%s:%d %s", calling_file, calling_line, + (info != NULL) ? info : "(null)")); + return info; +} + +/* + Mock away this global function. + We don't need DEBUG_SYNC functionality in a unit test. + */ +void debug_sync(THD *thd, const char *sync_point_name, size_t name_len) +{ + DBUG_PRINT("debug_sync_point", ("hit: '%s'", sync_point_name)); + FAIL(); +} + +// Putting everything in an unnamed namespace prevents any (unintentional) +// name clashes with the code under test. +namespace { + +using thread::Notification; +using thread::Thread; + +const char db_name[] = "some_database"; +const char table_name1[] = "some_table1"; +const char table_name2[] = "some_table2"; +const char table_name3[] = "some_table3"; +const char table_name4[] = "some_table4"; + +class MDL_test +{ +public: + // Utility function to run one test case. + typedef void (MDL_test::* Pmdl_mem)(); + static void run_one_test(Pmdl_mem member_function); + + // Utility function to run all the test cases. + static int RUN_ALL_TESTS(); + +protected: + MDL_test() + : m_thd(NULL), + m_null_ticket(NULL), + m_null_request(NULL) + { + } + + void SetUp() + { + mdl_init(); + init_sql_alloc(&m_mem_root, 1024, 0); + m_mdl_context.init(m_thd); + EXPECT_FALSE(m_mdl_context.has_locks()); + } + + void TearDown() + { + m_mdl_context.destroy(); + mdl_destroy(); + } + + // Returns a MEM_ROOT-allocated request object + // (which cannot be destroyed in the normal C++ fashion). + MDL_request *create_request(const char *table_name) + { + return MDL_request::create(MDL_key::TABLE, + db_name, table_name, MDL_SHARED, &m_mem_root); + } + + // We must list all the individual tests here. + void die_when_m_tickets_nonempty(); + void die_when_holding_global_shared_lock(); + void construct_and_destruct(); + void one_shared(); + void one_shared_high_prio(); + void one_shared_upgradable(); + void one_exclusive(); + void two_shared(); + void shared_locks_between_contexts(); + void upgrade_shared_upgradable(); + void upgrade_exclusive(); + void DISABLED_upgrade_shared(); + void merge(); + void savepoint(); + void concurrent_shared(); + void concurrent_shared_exclusive(); + void concurrent_exclusive_shared(); + void concurrent_upgrade(); + + THD *m_thd; + const MDL_ticket *m_null_ticket; + const MDL_request *m_null_request; + MEM_ROOT m_mem_root; + MDL_context m_mdl_context; +private: + // GTEST_DISALLOW_COPY_AND_ASSIGN_(MDL_test); +}; + + +// Will grab a lock on table_name1 of given type in the run() function. +// The two notifications are for synchronizing with the main thread. +// Does *not* take ownership of the notifications. +class MDL_thread : public Thread +{ +public: + MDL_thread(enum_mdl_type mdl_type, + Notification* lock_grabbed, + Notification* release_locks) + : m_mdl_type(mdl_type), + m_lock_grabbed(lock_grabbed), + m_release_locks(release_locks), + m_thd(reinterpret_cast(this)) // See notify_thread below. + { + m_mdl_context.init(m_thd); + } + + ~MDL_thread() + { + m_mdl_context.destroy(); + } + + virtual void run(); + + bool notify() + { + m_release_locks->notify(); + return true; + } + +private: + enum_mdl_type m_mdl_type; + Notification *m_lock_grabbed; + Notification *m_release_locks; + THD *m_thd; + MDL_context m_mdl_context; +}; + + +// Admittedly an ugly hack, to avoid pulling in the THD in this unit test. +bool notify_thread(THD *thd) +{ + MDL_thread *thread = (MDL_thread*) thd; + return thread->notify(); +} + + +void MDL_thread::run() +{ + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, m_mdl_type); + + if (m_mdl_type == MDL_EXCLUSIVE) + EXPECT_FALSE(m_mdl_context.acquire_exclusive_lock(&request)); + else + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + + // Tell the main thread that we have grabbed our locks. + m_lock_grabbed->notify(); + // Hold on to locks until we are told to release them + m_release_locks->wait_for_notification(); + + m_mdl_context.release_all_locks(); +} + +// googletest recommends DeathTest suffix for classes use in death tests. +typedef MDL_test MDL_DeathTest; + +bool is_lock_owner(MDL_context *context, MDL_request *request) +{ + return + context->is_lock_owner(MDL_key::TABLE, + request->key.db_name(), request->key.name()); +} + + +// Our own (simplified) version of the TEST_F macro. +#define TEST_F(Fixture_class, function_name) \ + void Fixture_class::function_name() + +/* + Verifies that we die with a DBUG_ASSERT if we destry a non-empty MDL_context. + */ +TEST_F(MDL_DeathTest, die_when_m_tickets_nonempty) +{ +// No support for death tests in mytap. +} + + +/* + Verifies that we die with a DBUG_ASSERT if we destry a MDL_context + while holding the global shared lock. + */ +TEST_F(MDL_DeathTest, die_when_holding_global_shared_lock) +{ +// No support for death tests in mytap. +} + + +/* + The most basic test: just construct and destruct our test fixture. + */ +TEST_F(MDL_test, construct_and_destruct) +{ +} + + +/* + Acquires one lock of type MDL_SHARED. + */ +TEST_F(MDL_test, one_shared) +{ + MDL_request *request= create_request(table_name1); + ASSERT_NE(m_null_request, request); + EXPECT_EQ(MDL_SHARED, request->type); + EXPECT_TRUE(request->is_shared()); + EXPECT_EQ(m_null_ticket, request->ticket); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_TRUE(m_mdl_context.has_locks()); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Acquires one lock of type MDL_SHARED_HIGH_PRIO. + */ +TEST_F(MDL_test, one_shared_high_prio) +{ + MDL_request *request= create_request(table_name1); + ASSERT_NE(m_null_request, request); + request->set_type(MDL_SHARED_HIGH_PRIO); + EXPECT_TRUE(request->is_shared()); + EXPECT_EQ(m_null_ticket, request->ticket); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_TRUE(m_mdl_context.has_locks()); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Acquires one lock of type MDL_SHARED_UPGRADABLE. + */ +TEST_F(MDL_test, one_shared_upgradable) +{ + MDL_request *request= create_request(table_name1); + ASSERT_NE(m_null_request, request); + request->set_type(MDL_SHARED_UPGRADABLE); + EXPECT_TRUE(request->is_shared()); + EXPECT_EQ(m_null_ticket, request->ticket); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_TRUE(m_mdl_context.has_locks()); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Acquires one lock of type MDL_EXCLUSIVE. + */ +TEST_F(MDL_test, one_exclusive) +{ + MDL_request *request= create_request(table_name1); + ASSERT_NE(m_null_request, request); + request->set_type(MDL_EXCLUSIVE); + EXPECT_FALSE(request->is_shared()); + EXPECT_EQ(m_null_ticket, request->ticket); + + EXPECT_FALSE(m_mdl_context.acquire_exclusive_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_TRUE(m_mdl_context.has_locks()); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Acquires wo locks of type MDL_SHARED. + Verifies that they are independent. + */ +TEST_F(MDL_test, two_shared) +{ + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name2); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request2)); + EXPECT_TRUE(m_mdl_context.has_locks()); + ASSERT_NE(m_null_ticket, request1->ticket); + ASSERT_NE(m_null_ticket, request2->ticket); + EXPECT_TRUE(request1->ticket->is_shared()); + EXPECT_TRUE(request2->ticket->is_shared()); + + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name2)); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name3)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_lock(request1->ticket); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.has_locks()); + + m_mdl_context.release_lock(request2->ticket); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name2)); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Verifies that two different contexts can acquire a shared lock + on the same table. + */ +TEST_F(MDL_test, shared_locks_between_contexts) +{ + THD *thd2= (THD*) 0x1; + MDL_context mdl_context2; + mdl_context2.init(thd2); + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name1); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(mdl_context2.try_acquire_shared_lock(request2)); + + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(mdl_context2.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + + m_mdl_context.release_all_locks(); + mdl_context2.release_all_locks(); +} + + +/* + Verifies that we can upgrade a shared lock to exclusive. + */ +TEST_F(MDL_test, upgrade_shared_upgradable) +{ + MDL_request *request= create_request(table_name1); + request->set_type(MDL_SHARED_UPGRADABLE); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); + EXPECT_FALSE(request->ticket->upgrade_shared_lock_to_exclusive()); + m_mdl_context.release_lock(request->ticket); +} + + +/* + Verifies that we can grab an exclusive lock, and that it is OK to try + to upgrade it to exclusive. + */ +TEST_F(MDL_test, upgrade_exclusive) +{ + MDL_request *request= create_request(table_name1); + request->set_type(MDL_EXCLUSIVE); + EXPECT_FALSE(m_mdl_context.try_acquire_exclusive_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_FALSE(request->ticket->is_shared()); + EXPECT_FALSE(request->ticket->upgrade_shared_lock_to_exclusive()); + EXPECT_FALSE(request->ticket->is_shared()); + m_mdl_context.release_lock(request->ticket); +} + + +/* + Disable this test, since assert-fails in mdl_destroy(). + (gdb) p global_lock + $1 = {waiting_shared = 0, + active_shared = 0, + active_intention_exclusive = 4294967295} + The upgrade from SHARED to EXCLUSIVE should fail, but it does not, + and something is wrong with the maintenance of active_intention_exclusive. + */ +TEST_F(MDL_test, DISABLED_upgrade_shared) +{ + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name2); + request1->set_type(MDL_SHARED); + request2->set_type(MDL_SHARED_UPGRADABLE); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request2)); + + EXPECT_FALSE(request1->ticket->upgrade_shared_lock_to_exclusive()); + EXPECT_FALSE(request2->ticket->upgrade_shared_lock_to_exclusive()); + m_mdl_context.release_lock(request1->ticket); + m_mdl_context.release_lock(request2->ticket); +} + + +/* + Verifies that we can grab locks in different contexts, and then merge + the locks into one context (releasing them from the other). + */ +TEST_F(MDL_test, merge) +{ + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name2); + MDL_request *request3= create_request(table_name3); + MDL_request *request4= create_request(table_name4); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request2)); + MDL_context mdl_context2; + mdl_context2.init(m_thd); + EXPECT_FALSE(mdl_context2.try_acquire_shared_lock(request3)); + EXPECT_FALSE(mdl_context2.try_acquire_shared_lock(request4)); + EXPECT_TRUE(mdl_context2.has_locks()); + + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request1)); + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request2)); + EXPECT_TRUE(is_lock_owner(&mdl_context2, request3)); + EXPECT_TRUE(is_lock_owner(&mdl_context2, request4)); + + m_mdl_context.merge(&mdl_context2); + EXPECT_FALSE(mdl_context2.has_locks()); + EXPECT_FALSE(is_lock_owner(&mdl_context2, request3)); + EXPECT_FALSE(is_lock_owner(&mdl_context2, request4)); + + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request1)); + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request2)); + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request3)); + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request4)); + + m_mdl_context.release_all_locks(); +} + + +/* + Verfies that locks are released when we roll back to a savepoint. + */ +TEST_F(MDL_test, savepoint) +{ + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name2); + MDL_request *request3= create_request(table_name3); + MDL_request *request4= create_request(table_name4); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request2)); + MDL_ticket *savepoint= m_mdl_context.mdl_savepoint(); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request3)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request4)); + + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name2)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name3)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name4)); + + m_mdl_context.rollback_to_savepoint(savepoint); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name2)); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name3)); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name4)); + + m_mdl_context.release_all_locks(); +} + + +/* + Verifies that we can grab shared locks concurrently, in different threads. + */ +TEST_F(MDL_test, concurrent_shared) +{ + Notification lock_grabbed; + Notification release_locks; + MDL_thread mdl_thread(MDL_SHARED, &lock_grabbed, &release_locks); + mdl_thread.start(Thread::Options()); + lock_grabbed.wait_for_notification(); + + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + + release_locks.notify(); + mdl_thread.join(); + + m_mdl_context.release_all_locks(); +} + + +/* + Verifies that we cannot grab an exclusive lock on something which + is locked with a shared lock in a different thread. + */ +TEST_F(MDL_test, concurrent_shared_exclusive) +{ + Notification lock_grabbed; + Notification release_locks; + MDL_thread mdl_thread(MDL_SHARED, &lock_grabbed, &release_locks); + mdl_thread.start(Thread::Options()); + lock_grabbed.wait_for_notification(); + + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE); + + // We should *not* be able to grab the lock here. + EXPECT_FALSE(m_mdl_context.try_acquire_exclusive_lock(&request)); + EXPECT_EQ(m_null_ticket, request.ticket); + + release_locks.notify(); + mdl_thread.join(); + + // Here we should have the lock. + EXPECT_FALSE(m_mdl_context.try_acquire_exclusive_lock(&request)); + EXPECT_NE(m_null_ticket, request.ticket); + + m_mdl_context.release_all_locks(); +} + + +/* + Verifies that we cannot we cannot grab a shared lock on something which + is locked exlusively in a different thread. + */ +TEST_F(MDL_test, concurrent_exclusive_shared) +{ + Notification lock_grabbed; + Notification release_locks; + MDL_thread mdl_thread(MDL_EXCLUSIVE, &lock_grabbed, &release_locks); + mdl_thread.start(Thread::Options()); + lock_grabbed.wait_for_notification(); + + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED); + + // We should *not* be able to grab the lock here. + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + EXPECT_EQ(m_null_ticket, request.ticket); + + release_locks.notify(); + MDL_request_list mdl_requests; + mdl_requests.push_front(&request); + + // The other thread should eventually release its locks. + EXPECT_FALSE(m_mdl_context.wait_for_locks(&mdl_requests)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + EXPECT_NE(m_null_ticket, request.ticket); + + m_mdl_context.release_all_locks(); +} + + +/* + Verifies the following scenario: + Thread 1: grabs a shared upgradable lock. + Thread 2: grabs a shared lock. + Thread 1: asks for an upgrade to exclusive (needs to wait for thread 2) + Thread 2: gets notified, and releases lock. + Thread 1: gets the exclusive lock. + */ +TEST_F(MDL_test, concurrent_upgrade) +{ + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED_UPGRADABLE); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + Notification lock_grabbed; + Notification release_locks; + MDL_thread mdl_thread(MDL_SHARED, &lock_grabbed, &release_locks); + mdl_thread.start(Thread::Options()); + lock_grabbed.wait_for_notification(); + + EXPECT_FALSE(request.ticket->upgrade_shared_lock_to_exclusive()); + EXPECT_TRUE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); +} + +} // namespace + + +// Creates a new fixture object for each test case. +void MDL_test::run_one_test(Pmdl_mem member_function) +{ + MDL_test *test_object = new MDL_test; + test_object->SetUp(); + (test_object->*member_function)(); + test_object->TearDown(); + delete test_object; +} + + +// We have to invoke each test explicitly here, since we don't have +// the auto-registration support from the TEST and TEST_F macros. +int MDL_test::RUN_ALL_TESTS() +{ + // Execute MDL_test::SetUpTestCase() here, if it is defined. + run_one_test(&MDL_test::construct_and_destruct); + run_one_test(&MDL_test::one_shared); + run_one_test(&MDL_test::one_shared_high_prio); + run_one_test(&MDL_test::one_shared_upgradable); + run_one_test(&MDL_test::one_exclusive); + run_one_test(&MDL_test::two_shared); + run_one_test(&MDL_test::shared_locks_between_contexts); + run_one_test(&MDL_test::upgrade_shared_upgradable); + run_one_test(&MDL_test::upgrade_exclusive); + // googletest will not run tests with names starting with 'DISABLED_' + // run_one_test(&MDL_test::DISABLED_upgrade_shared); + run_one_test(&MDL_test::merge); + run_one_test(&MDL_test::savepoint); + run_one_test(&MDL_test::concurrent_shared); + run_one_test(&MDL_test::concurrent_shared_exclusive); + run_one_test(&MDL_test::concurrent_exclusive_shared); + run_one_test(&MDL_test::concurrent_upgrade); + + // Execute MDL_test::TearDownTestCase() here, if it is defined. + return exit_status(); +} + + +int main(int argc, char **argv) { + // ::testing::InitGoogleTest(&argc, argv); + MY_INIT(argv[0]); + return MDL_test::RUN_ALL_TESTS(); +} === added file 'sql/mdl_test.cc' --- a/sql/mdl_test.cc 1970-01-01 00:00:00 +0000 +++ b/sql/mdl_test.cc 2009-09-28 11:43:44 +0000 @@ -0,0 +1,664 @@ +/* Copyright (C) 2009 Sun Microsystems, Inc. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + This is a unit test for the 'meta data locking' classes. + It is written to illustrate how we can use googletest for unit testing + of MySQL code. + For documentation on googletest, see http://code.google.com/p/googletest/ + and the contained wiki pages GoogleTestPrimer and GoogleTestAdvancedGuide. + The code below should hopefully be (mostly) self-explanatory. + */ + +// Must include gtest first, since MySQL source has macros for min() etc .... +#include + +#include "mdl.h" + +#include "thr_malloc.h" +#include "thread.h" + +pthread_key(MEM_ROOT**,THR_MALLOC); +pthread_key(THD*, THR_THD); +pthread_mutex_t LOCK_open; +uint opt_debug_sync_timeout= 0; + +extern "C" void sql_alloc_error_handler(void) +{ + ADD_FAILURE(); +} + +namespace { +bool notify_thread(THD*); +} + +/* + We need to mock away this global function, because the real version + pulls in a lot of dependencies. + (The @note for the real version of this function indicates that the + coupling between THD and MDL is too tight.) +*/ +extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use) +{ + if (in_use != NULL) + return notify_thread(in_use); + return false; +} + +/* + Mock away this function as well, with an empty function. + TODO(didrik): Consider verifying that the MDL module actually calls + this with correct arguments. +*/ +extern void mysql_ha_flush(THD *) +{ + DBUG_PRINT("mysql_ha_flush", ("mock version")); +} + +/* + We need to mock away this global function, the real version pulls in + too many dependencies. + */ +extern "C" const char *set_thd_proc_info(THD *thd, const char *info, + const char *calling_function, + const char *calling_file, + const unsigned int calling_line) +{ + DBUG_PRINT("proc_info", ("%s:%d %s", calling_file, calling_line, + (info != NULL) ? info : "(null)")); + return info; +} + +/* + Mock away this global function. + We don't need DEBUG_SYNC functionality in a unit test. + */ +void debug_sync(THD *thd, const char *sync_point_name, size_t name_len) +{ + DBUG_PRINT("debug_sync_point", ("hit: '%s'", sync_point_name)); + FAIL() << "Not yet implemented."; +} + +// Putting everything in an unnamed namespace prevents any (unintentional) +// name clashes with the code under test. +namespace { + +using thread::Notification; +using thread::Thread; + +const char db_name[] = "some_database"; +const char table_name1[] = "some_table1"; +const char table_name2[] = "some_table2"; +const char table_name3[] = "some_table3"; +const char table_name4[] = "some_table4"; + +class MDL_test : public ::testing::Test +{ +protected: + MDL_test() + : m_thd(NULL), + m_null_ticket(NULL), + m_null_request(NULL) + { + } + + void SetUp() + { + mdl_init(); + init_sql_alloc(&m_mem_root, 1024, 0); + m_mdl_context.init(m_thd); + EXPECT_FALSE(m_mdl_context.has_locks()); + } + + void TearDown() + { + m_mdl_context.destroy(); + mdl_destroy(); + } + + // Returns a MEM_ROOT-allocated request object + // (which cannot be destroyed in the normal C++ fashion). + MDL_request *create_request(const char *table_name) + { + return MDL_request::create(MDL_key::TABLE, + db_name, table_name, MDL_SHARED, &m_mem_root); + } + + THD *m_thd; + const MDL_ticket *m_null_ticket; + const MDL_request *m_null_request; + MEM_ROOT m_mem_root; + MDL_context m_mdl_context; +private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MDL_test); +}; + + +// Will grab a lock on table_name1 of given type in the run() function. +// The two notifications are for synchronizing with the main thread. +// Does *not* take ownership of the notifications. +class MDL_thread : public Thread +{ +public: + MDL_thread(enum_mdl_type mdl_type, + Notification* lock_grabbed, + Notification* release_locks) + : m_mdl_type(mdl_type), + m_lock_grabbed(lock_grabbed), + m_release_locks(release_locks), + m_thd(reinterpret_cast(this)) // See notify_thread below. + { + m_mdl_context.init(m_thd); + } + + ~MDL_thread() + { + m_mdl_context.destroy(); + } + + virtual void run(); + + bool notify() + { + m_release_locks->notify(); + return true; + } + +private: + enum_mdl_type m_mdl_type; + Notification *m_lock_grabbed; + Notification *m_release_locks; + THD *m_thd; + MDL_context m_mdl_context; +}; + + +// Admittedly an ugly hack, to avoid pulling in the THD in this unit test. +bool notify_thread(THD *thd) +{ + MDL_thread *thread = (MDL_thread*) thd; + return thread->notify(); +} + + +void MDL_thread::run() +{ + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, m_mdl_type); + + if (m_mdl_type == MDL_EXCLUSIVE) + EXPECT_FALSE(m_mdl_context.acquire_exclusive_lock(&request)); + else + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + + // Tell the main thread that we have grabbed our locks. + m_lock_grabbed->notify(); + // Hold on to locks until we are told to release them + m_release_locks->wait_for_notification(); + + m_mdl_context.release_all_locks(); +} + +// googletest recommends DeathTest suffix for classes use in death tests. +typedef MDL_test MDL_DeathTest; + +bool is_lock_owner(MDL_context *context, MDL_request *request) +{ + return + context->is_lock_owner(MDL_key::TABLE, + request->key.db_name(), request->key.name()); +} + + +/* + Verifies that we die with a DBUG_ASSERT if we destry a non-empty MDL_context. + */ +TEST_F(MDL_DeathTest, die_when_m_tickets_nonempty) +{ + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + MDL_request *request= create_request(table_name1); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); +#if !defined(DBUG_OFF) + EXPECT_DEATH(m_mdl_context.destroy(), ".*Assertion .*m_tickets.is_empty.*"); +#endif + m_mdl_context.release_all_locks(); +} + + +/* + Verifies that we die with a DBUG_ASSERT if we destry a MDL_context + while holding the global shared lock. + */ +TEST_F(MDL_DeathTest, die_when_holding_global_shared_lock) +{ + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + EXPECT_FALSE(m_mdl_context.acquire_global_shared_lock()); +#if !defined(DBUG_OFF) + EXPECT_DEATH(m_mdl_context.destroy(), + ".*Assertion .*has_global_shared_lock.*"); +#endif + m_mdl_context.release_global_shared_lock(); +} + + +/* + The most basic test: just construct and destruct our test fixture. + */ +TEST_F(MDL_test, construct_and_destruct) +{ +} + + +/* + Acquires one lock of type MDL_SHARED. + */ +TEST_F(MDL_test, one_shared) +{ + MDL_request *request= create_request(table_name1); + ASSERT_NE(m_null_request, request); + EXPECT_EQ(MDL_SHARED, request->type); + EXPECT_TRUE(request->is_shared()); + EXPECT_EQ(m_null_ticket, request->ticket); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_TRUE(m_mdl_context.has_locks()); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Acquires one lock of type MDL_SHARED_HIGH_PRIO. + */ +TEST_F(MDL_test, one_shared_high_prio) +{ + MDL_request *request= create_request(table_name1); + ASSERT_NE(m_null_request, request); + request->set_type(MDL_SHARED_HIGH_PRIO); + EXPECT_TRUE(request->is_shared()); + EXPECT_EQ(m_null_ticket, request->ticket); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_TRUE(m_mdl_context.has_locks()); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Acquires one lock of type MDL_SHARED_UPGRADABLE. + */ +TEST_F(MDL_test, one_shared_upgradable) +{ + MDL_request *request= create_request(table_name1); + ASSERT_NE(m_null_request, request); + request->set_type(MDL_SHARED_UPGRADABLE); + EXPECT_TRUE(request->is_shared()); + EXPECT_EQ(m_null_ticket, request->ticket); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_TRUE(m_mdl_context.has_locks()); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Acquires one lock of type MDL_EXCLUSIVE. + */ +TEST_F(MDL_test, one_exclusive) +{ + MDL_request *request= create_request(table_name1); + ASSERT_NE(m_null_request, request); + request->set_type(MDL_EXCLUSIVE); + EXPECT_FALSE(request->is_shared()); + EXPECT_EQ(m_null_ticket, request->ticket); + + EXPECT_FALSE(m_mdl_context.acquire_exclusive_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_TRUE(m_mdl_context.has_locks()); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Acquires wo locks of type MDL_SHARED. + Verifies that they are independent. + */ +TEST_F(MDL_test, two_shared) +{ + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name2); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request2)); + EXPECT_TRUE(m_mdl_context.has_locks()); + ASSERT_NE(m_null_ticket, request1->ticket); + ASSERT_NE(m_null_ticket, request2->ticket); + EXPECT_TRUE(request1->ticket->is_shared()); + EXPECT_TRUE(request2->ticket->is_shared()); + + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name2)); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name3)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_lock(request1->ticket); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.has_locks()); + + m_mdl_context.release_lock(request2->ticket); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name2)); + EXPECT_FALSE(m_mdl_context.has_locks()); +} + + +/* + Verifies that two different contexts can acquire a shared lock + on the same table. + */ +TEST_F(MDL_test, shared_locks_between_contexts) +{ + THD *thd2= (THD*) 0x1; + MDL_context mdl_context2; + mdl_context2.init(thd2); + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name1); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(mdl_context2.try_acquire_shared_lock(request2)); + + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(mdl_context2.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + + m_mdl_context.release_all_locks(); + mdl_context2.release_all_locks(); +} + + +/* + Verifies that we can upgrade a shared lock to exclusive. + */ +TEST_F(MDL_test, upgrade_shared_upgradable) +{ + MDL_request *request= create_request(table_name1); + request->set_type(MDL_SHARED_UPGRADABLE); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request)); + EXPECT_FALSE(request->ticket->upgrade_shared_lock_to_exclusive()); + m_mdl_context.release_lock(request->ticket); +} + + +/* + Verifies that we can grab an exclusive lock, and that it is OK to try + to upgrade it to exclusive. + */ +TEST_F(MDL_test, upgrade_exclusive) +{ + MDL_request *request= create_request(table_name1); + request->set_type(MDL_EXCLUSIVE); + EXPECT_FALSE(m_mdl_context.try_acquire_exclusive_lock(request)); + EXPECT_NE(m_null_ticket, request->ticket); + EXPECT_FALSE(request->ticket->is_shared()); + EXPECT_FALSE(request->ticket->upgrade_shared_lock_to_exclusive()); + EXPECT_FALSE(request->ticket->is_shared()); + m_mdl_context.release_lock(request->ticket); +} + + +/* + Disable this test, since assert-fails in mdl_destroy(). + (gdb) p global_lock + $1 = {waiting_shared = 0, + active_shared = 0, + active_intention_exclusive = 4294967295} + The upgrade from SHARED to EXCLUSIVE should fail, but it does not, + and something is wrong with the maintenance of active_intention_exclusive. + */ +TEST_F(MDL_test, DISABLED_upgrade_shared) +{ + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name2); + request1->set_type(MDL_SHARED); + request2->set_type(MDL_SHARED_UPGRADABLE); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request2)); + + EXPECT_FALSE(request1->ticket->upgrade_shared_lock_to_exclusive()); + EXPECT_FALSE(request2->ticket->upgrade_shared_lock_to_exclusive()); + m_mdl_context.release_lock(request1->ticket); + m_mdl_context.release_lock(request2->ticket); +} + + +/* + Verifies that we can grab locks in different contexts, and then merge + the locks into one context (releasing them from the other). + */ +TEST_F(MDL_test, merge) +{ + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name2); + MDL_request *request3= create_request(table_name3); + MDL_request *request4= create_request(table_name4); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request2)); + MDL_context mdl_context2; + mdl_context2.init(m_thd); + EXPECT_FALSE(mdl_context2.try_acquire_shared_lock(request3)); + EXPECT_FALSE(mdl_context2.try_acquire_shared_lock(request4)); + EXPECT_TRUE(mdl_context2.has_locks()); + + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request1)); + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request2)); + EXPECT_TRUE(is_lock_owner(&mdl_context2, request3)); + EXPECT_TRUE(is_lock_owner(&mdl_context2, request4)); + + m_mdl_context.merge(&mdl_context2); + EXPECT_FALSE(mdl_context2.has_locks()); + EXPECT_FALSE(is_lock_owner(&mdl_context2, request3)); + EXPECT_FALSE(is_lock_owner(&mdl_context2, request4)); + + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request1)); + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request2)); + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request3)); + EXPECT_TRUE(is_lock_owner(&m_mdl_context, request4)); + + m_mdl_context.release_all_locks(); +} + + +/* + Verfies that locks are released when we roll back to a savepoint. + */ +TEST_F(MDL_test, savepoint) +{ + MDL_request *request1= create_request(table_name1); + MDL_request *request2= create_request(table_name2); + MDL_request *request3= create_request(table_name3); + MDL_request *request4= create_request(table_name4); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request1)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request2)); + MDL_ticket *savepoint= m_mdl_context.mdl_savepoint(); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request3)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(request4)); + + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name2)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name3)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name4)); + + m_mdl_context.rollback_to_savepoint(savepoint); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name2)); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name3)); + EXPECT_FALSE(m_mdl_context.is_lock_owner(MDL_key::TABLE, + db_name, table_name4)); + + m_mdl_context.release_all_locks(); +} + + +/* + Verifies that we can grab shared locks concurrently, in different threads. + */ +TEST_F(MDL_test, concurrent_shared) +{ + Notification lock_grabbed; + Notification release_locks; + MDL_thread mdl_thread(MDL_SHARED, &lock_grabbed, &release_locks); + mdl_thread.start(Thread::Options()); + lock_grabbed.wait_for_notification(); + + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED); + + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + EXPECT_TRUE(m_mdl_context.is_lock_owner(MDL_key::TABLE, db_name, table_name1)); + + release_locks.notify(); + mdl_thread.join(); + + m_mdl_context.release_all_locks(); +} + + +/* + Verifies that we cannot grab an exclusive lock on something which + is locked with a shared lock in a different thread. + */ +TEST_F(MDL_test, concurrent_shared_exclusive) +{ + Notification lock_grabbed; + Notification release_locks; + MDL_thread mdl_thread(MDL_SHARED, &lock_grabbed, &release_locks); + mdl_thread.start(Thread::Options()); + lock_grabbed.wait_for_notification(); + + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE); + + // We should *not* be able to grab the lock here. + EXPECT_FALSE(m_mdl_context.try_acquire_exclusive_lock(&request)); + EXPECT_EQ(m_null_ticket, request.ticket); + + release_locks.notify(); + mdl_thread.join(); + + // Here we should have the lock. + EXPECT_FALSE(m_mdl_context.try_acquire_exclusive_lock(&request)); + EXPECT_NE(m_null_ticket, request.ticket); + + m_mdl_context.release_all_locks(); +} + + +/* + Verifies that we cannot we cannot grab a shared lock on something which + is locked exlusively in a different thread. + */ +TEST_F(MDL_test, concurrent_exclusive_shared) +{ + Notification lock_grabbed; + Notification release_locks; + MDL_thread mdl_thread(MDL_EXCLUSIVE, &lock_grabbed, &release_locks); + mdl_thread.start(Thread::Options()); + lock_grabbed.wait_for_notification(); + + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED); + + // We should *not* be able to grab the lock here. + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + EXPECT_EQ(m_null_ticket, request.ticket); + + release_locks.notify(); + MDL_request_list mdl_requests; + mdl_requests.push_front(&request); + + // The other thread should eventually release its locks. + EXPECT_FALSE(m_mdl_context.wait_for_locks(&mdl_requests)); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + EXPECT_NE(m_null_ticket, request.ticket); + + m_mdl_context.release_all_locks(); +} + + +/* + Verifies the following scenario: + Thread 1: grabs a shared upgradable lock. + Thread 2: grabs a shared lock. + Thread 1: asks for an upgrade to exclusive (needs to wait for thread 2) + Thread 2: gets notified, and releases lock. + Thread 1: gets the exclusive lock. + */ +TEST_F(MDL_test, concurrent_upgrade) +{ + MDL_request request; + request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED_UPGRADABLE); + EXPECT_FALSE(m_mdl_context.try_acquire_shared_lock(&request)); + EXPECT_FALSE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + Notification lock_grabbed; + Notification release_locks; + MDL_thread mdl_thread(MDL_SHARED, &lock_grabbed, &release_locks); + mdl_thread.start(Thread::Options()); + lock_grabbed.wait_for_notification(); + + EXPECT_FALSE(request.ticket->upgrade_shared_lock_to_exclusive()); + EXPECT_TRUE(m_mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db_name, table_name1)); + + m_mdl_context.release_all_locks(); +} + +} // namespace + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + MY_INIT(argv[0]); + return RUN_ALL_TESTS(); +} === added file 'sql/thread.cc' --- a/sql/thread.cc 1970-01-01 00:00:00 +0000 +++ b/sql/thread.cc 2009-09-28 11:43:44 +0000 @@ -0,0 +1,121 @@ +/* Copyright (C) 2009 Sun Microsystems, Inc. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "thread.h" + +namespace thread { + +namespace { +void *thread_start_routine(void *arg) +{ + Thread *thread= (Thread*) arg; + Thread::run_wrapper(thread); + return NULL; +} +} // namespace + +Thread::~Thread() +{ +} + + +int Thread::start(const Options &options) +{ + m_options = options; + DBUG_ASSERT(m_options.stack_size() >= PTHREAD_STACK_MIN); + pthread_attr_t attr; + pthread_attr_init(&attr); + if (options.detached()) + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); + pthread_attr_setstacksize(&attr, m_options.stack_size()); + const int error= + pthread_create(&m_thread_id, &attr, thread_start_routine, this); + pthread_attr_destroy(&attr); + return error; +} + + +void Thread::join() +{ + DBUG_ASSERT(!m_options.detached()); + int failed= pthread_join(m_thread_id, NULL); + DBUG_ASSERT(!failed); +} + + +void Thread::run_wrapper(Thread *thread) +{ + const my_bool error= my_thread_init(); + DBUG_ASSERT(!error); + thread->run(); + my_thread_end(); +} + + +Mutex_lock::Mutex_lock(pthread_mutex_t *mutex) : m_mutex(mutex) +{ + pthread_mutex_lock(m_mutex); +} + + +Mutex_lock::~Mutex_lock() +{ + const int failed= pthread_mutex_unlock(m_mutex); + DBUG_ASSERT(!failed); +} + + +Notification::Notification() : m_notified(false) +{ + const int failed1= pthread_cond_init(&m_cond, NULL); + DBUG_ASSERT(!failed1); + const int failed2= pthread_mutex_init(&m_mutex, MY_MUTEX_INIT_FAST); + DBUG_ASSERT(!failed2); +} + +Notification::~Notification() +{ + const int failed1= pthread_mutex_destroy(&m_mutex); + DBUG_ASSERT(!failed1); + const int failed2= pthread_cond_destroy(&m_cond); + DBUG_ASSERT(!failed2); +} + +bool Notification::has_been_notified() +{ + Mutex_lock lock(&m_mutex); + return m_notified; +} + +void Notification::wait_for_notification() +{ + Mutex_lock lock(&m_mutex); + while (!m_notified) { + const int failed= pthread_cond_wait(&m_cond, &m_mutex); + DBUG_ASSERT(!failed); + } +} + +void Notification::notify() +{ + Mutex_lock lock(&m_mutex); + m_notified= true; + const int failed= pthread_cond_broadcast(&m_cond); + DBUG_ASSERT(!failed); +} + + +} // namespace thread === added file 'sql/thread.h' --- a/sql/thread.h 1970-01-01 00:00:00 +0000 +++ b/sql/thread.h 2009-09-28 11:43:44 +0000 @@ -0,0 +1,124 @@ +/* Copyright (C) 2009 Sun Microsystems, Inc. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef SQL_THREAD_INCLUDED +#define SQL_THREAD_INCLUDED + +#include +#include + +namespace thread { + +/* + An abstract class for creating/running/joining threads. + Thread::start() will create a new pthread, and execute the run() function. +*/ +class Thread +{ +public: + Thread() : m_thread_id(0) {} + virtual ~Thread(); + + // Options for starting the thread. + struct Options + { + Options() : m_detached(false), m_stack_size(PTHREAD_STACK_MIN) {} + + void set_datched(bool val) { m_detached= val; } + bool detached() const { return m_detached; } + + void set_stack_size(size_t val) { m_stack_size= val; } + size_t stack_size() const { return m_stack_size; } + private: + bool m_detached; + size_t m_stack_size; + }; + + /* + Will create a new pthread, and invoke run(); + Returns the value from pthread_create(). + */ + int start(const Options &options); + + /* + You may invoke this to wait for the thread to finish. + You cannot join() a thread which runs in detached mode. + */ + void join(); + + // The id of the thread (valid only if it is actually running). + pthread_t thread_id() const { return m_thread_id; } + + /* + A wrapper for the run() function. + Users should *not* call this function directly, they should rather + invoke the start() function. + */ + static void run_wrapper(Thread*); + +protected: + /* + Define this function in derived classes. + Users should *not* call this function directly, they should rather + invoke the start() function. + */ + virtual void run() = 0; + +private: + pthread_t m_thread_id; + Options m_options; + + Thread(const Thread&); /* Not copyable. */ + void operator=(const Thread&); /* Not assignable. */ +}; + + +// A simple wrapper around a mutex: +// Grabs the mutex in the CTOR, releases it in the DTOR. +class Mutex_lock +{ +public: + Mutex_lock(pthread_mutex_t *mutex); + ~Mutex_lock(); +private: + pthread_mutex_t *m_mutex; + + Mutex_lock(const Mutex_lock&); /* Not copyable. */ + void operator=(const Mutex_lock&); /* Not assignable. */ +}; + + +// A barrier which can be used for one-time synchronization between threads. +class Notification +{ +public: + Notification(); + ~Notification(); + + bool has_been_notified(); + void wait_for_notification(); + void notify(); +private: + bool m_notified; + pthread_cond_t m_cond; + pthread_mutex_t m_mutex; + + Notification(const Notification&); /* Not copyable. */ + void operator=(const Notification&); /* Not assignable. */ +}; + +} // namespace thread + +#endif // SQL_THREAD_INCLUDED === added file 'sql/thread_test.cc' --- a/sql/thread_test.cc 1970-01-01 00:00:00 +0000 +++ b/sql/thread_test.cc 2009-09-28 11:43:44 +0000 @@ -0,0 +1,122 @@ +/* Copyright (C) 2009 Sun Microsystems, Inc. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// Must include gtest first, since MySQL source has macros for min() etc .... +#include + +#include "thread.h" + +#include "mdl.h" + +pthread_key(MEM_ROOT**,THR_MALLOC); +pthread_key(THD*, THR_THD); + +extern "C" void sql_alloc_error_handler(void) +{ + ADD_FAILURE(); +} + +using thread::Mutex_lock; +using thread::Notification; +using thread::Thread; + +namespace { + +const int counter_start_value= 42; + +class Notification_thread : public Thread +{ +public: + Notification_thread(Notification *start_notification, + Notification *end_notfication, + int *counter) + : m_start_notification(start_notification), + m_end_notification(end_notfication), + m_counter(counter) + { + } + + virtual void run() + { + // Verify counter, increment it, notify the main thread. + EXPECT_EQ(counter_start_value, *m_counter); + (*m_counter)+= 1; + m_start_notification->notify(); + + // Wait for notification from other thread. + m_end_notification->wait_for_notification(); + EXPECT_EQ(counter_start_value, *m_counter); + + // Set counter again before returning from thread. + (*m_counter)+= 1; + } + +private: + Notification *m_start_notification; + Notification *m_end_notification; + int *m_counter; + + Notification_thread(const Notification_thread&); // Not copyable. + void operator=(const Notification_thread&); // Not assignable. +}; + + +/* + A basic, single-threaded test of Notification. + */ +TEST(Notification, notify) +{ + Notification notification; + EXPECT_FALSE(notification.has_been_notified()); + notification.notify(); + EXPECT_TRUE(notification.has_been_notified()); +} + +/* + Starts a thread, and verifies that the notification/synchronization + mechanism works. + */ +TEST(Notification_thread, start_and_wait) +{ + Notification start_notification; + Notification end_notfication; + int counter= counter_start_value; + Notification_thread + notification_thread(&start_notification, &end_notfication, &counter); + notification_thread.start(Thread::Options()); + + // Wait for the other thread to increment counter, and notify us. + start_notification.wait_for_notification(); + EXPECT_EQ(counter_start_value + 1, counter); + EXPECT_TRUE(start_notification.has_been_notified()); + + // Reset counter, and notify other thread. + counter= counter_start_value; + end_notfication.notify(); + notification_thread.join(); + + // We should see the final results of the thread we have joined. + EXPECT_EQ(counter_start_value + 1, counter); +} + + +} // namespace + + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + MY_INIT(argv[0]); + return RUN_ALL_TESTS(); +} --===============0128269436== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/tor.didriksen@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: tor.didriksen@stripped # target_branch: file:///export/home/didrik/mysqldev-6.0-codebase/6.0-\ # codebase-bf-gtest/part-2/ # testament_sha1: 610b855891e1d952c19adeaf4e2ce038330efc60 # timestamp: 2009-09-28 13:43:49 +0200 # base_revision_id: tor.didriksen@stripped\ # 7tvht4bsub940i8d # # Begin bundle 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 kyR+YtWlajwVV/8XckU4UJAMjJws --===============0128269436==--