List:Commits« Previous MessageNext Message »
From:Tor Didriksen Date:September 28 2009 11:43am
Subject:bzr commit into mysql-6.0-bugfixing branch (tor.didriksen:2832)
View as plain text  
#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 Didriksen28 Sep