#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 <string>
+#include <iostream>
+#include <tap.h>
+
+#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<THD*>(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 <gtest/gtest.h>
+
+#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<THD*>(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 <my_global.h>
+#include <my_pthread.h>
+
+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 <gtest/gtest.h>
+
+#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();
+}
Attachment: [text/bzr-bundle] bzr/tor.didriksen@sun.com-20090928114344-5gnjg1vf349fy1hj.bundle
| Thread |
|---|
| • bzr commit into mysql-6.0-bugfixing branch (tor.didriksen:2832) | Tor Didriksen | 28 Sep |