用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4380 lines
144 KiB

/* Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
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, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/**
This is a unit test for the 'meta data locking' classes.
It is written to illustrate how we can use Google Test for unit testing
of MySQL code.
For documentation on Google Test, see http://code.google.com/p/googletest/
and the contained wiki pages GoogleTestPrimer and GoogleTestAdvancedGuide.
The code below should hopefully be (mostly) self-explanatory.
*/
#include <sstream>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <stddef.h>
#include <sys/types.h>
#include "my_dbug.h"
#include "my_inttypes.h"
#include "mysqld_error.h"
#include "sql/mdl.h"
#include "sql/mysqld.h"
#include "sql/thr_malloc.h"
#include "unittest/gunit/benchmark.h"
#include "unittest/gunit/test_mdl_context_owner.h"
#include "unittest/gunit/thread_utils.h"
/*
Mock thd_wait_begin/end functions
*/
void thd_wait_begin(THD *, int) {}
void thd_wait_end(THD *) {}
/*
A mock error handler.
*/
static uint expected_error = 0;
extern "C" void test_error_handler_hook(uint err, const char *str, myf) {
EXPECT_EQ(expected_error, err) << str;
}
/*
Mock away this global function.
We don't need DEBUG_SYNC functionality in a unit test.
*/
void debug_sync(THD *, const char *sync_point_name MY_ATTRIBUTE((unused)),
size_t) {
DBUG_PRINT("debug_sync_point", ("hit: '%s'", sync_point_name));
FAIL() << "Not yet implemented.";
}
/*
Putting everything in a namespace prevents any (unintentional)
name clashes with the code under test.
*/
namespace mdl_unittest {
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";
const ulong zero_timeout = 0;
const ulong long_timeout = (ulong)3600L * 24L * 365L;
class MDLTest : public ::testing::Test, public Test_MDL_context_owner {
protected:
MDLTest() : m_null_ticket(NULL), m_null_request(NULL) {}
static void SetUpTestCase() {
/* Save original and install our custom error hook. */
m_old_error_handler_hook = error_handler_hook;
error_handler_hook = test_error_handler_hook;
}
static void TearDownTestCase() {
error_handler_hook = m_old_error_handler_hook;
}
void SetUp() {
expected_error = 0;
mdl_locks_unused_locks_low_water = MDL_LOCKS_UNUSED_LOCKS_LOW_WATER_DEFAULT;
max_write_lock_count = ULONG_MAX;
mdl_init();
m_mdl_context.init(this);
EXPECT_FALSE(m_mdl_context.has_locks());
m_charset = system_charset_info;
system_charset_info = &my_charset_utf8_bin;
EXPECT_TRUE(system_charset_info != nullptr);
MDL_REQUEST_INIT(&m_global_request, MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE, MDL_TRANSACTION);
}
void TearDown() {
system_charset_info = m_charset;
m_mdl_context.destroy();
mdl_destroy();
}
virtual void notify_shared_lock(MDL_context_owner *in_use,
bool needs_thr_lock_abort) {
in_use->notify_shared_lock(NULL, needs_thr_lock_abort);
}
// A utility member for testing single lock requests.
void test_one_simple_shared_lock(enum_mdl_type lock_type);
const MDL_ticket *m_null_ticket;
const MDL_request *m_null_request;
MDL_context m_mdl_context;
MDL_request m_request;
MDL_request m_global_request;
MDL_request_list m_request_list;
private:
CHARSET_INFO *m_charset;
GTEST_DISALLOW_COPY_AND_ASSIGN_(MDLTest);
static void (*m_old_error_handler_hook)(uint, const char *, myf);
};
void (*MDLTest::m_old_error_handler_hook)(uint, const char *, myf);
/*
Will grab a lock on table_name 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 Test_MDL_context_owner {
public:
MDL_thread(const char *table_name, enum_mdl_type mdl_type,
Notification *lock_grabbed, Notification *release_locks,
Notification *lock_blocked, Notification *lock_released)
: m_table_name(table_name),
m_mdl_type(mdl_type),
m_lock_grabbed(lock_grabbed),
m_release_locks(release_locks),
m_lock_blocked(lock_blocked),
m_lock_released(lock_released),
m_enable_release_on_notify(false) {
m_mdl_context.init(this);
}
~MDL_thread() { m_mdl_context.destroy(); }
virtual void run();
void enable_release_on_notify() { m_enable_release_on_notify = true; }
virtual void notify_shared_lock(MDL_context_owner *in_use,
bool needs_thr_lock_abort) {
if (in_use)
in_use->notify_shared_lock(NULL, needs_thr_lock_abort);
else if (m_enable_release_on_notify && m_release_locks)
m_release_locks->notify();
}
virtual void enter_cond(mysql_cond_t *cond, mysql_mutex_t *mutex,
const PSI_stage_info *stage,
PSI_stage_info *old_stage, const char *src_function,
const char *src_file, int src_line) {
Test_MDL_context_owner::enter_cond(cond, mutex, stage, old_stage,
src_function, src_file, src_line);
/*
No extra checks needed here since MDL uses enter_con only when thread
is blocked.
*/
if (m_lock_blocked) m_lock_blocked->notify();
return;
}
MDL_context &get_mdl_context() { return m_mdl_context; }
private:
const char *m_table_name;
enum_mdl_type m_mdl_type;
Notification *m_lock_grabbed;
Notification *m_release_locks;
Notification *m_lock_blocked;
Notification *m_lock_released;
bool m_enable_release_on_notify;
MDL_context m_mdl_context;
};
void MDL_thread::run() {
MDL_request request;
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, m_table_name, m_mdl_type,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, m_table_name, m_mdl_type));
// Tell the main thread that we have grabbed our locks.
if (m_lock_grabbed) m_lock_grabbed->notify();
// Hold on to locks until we are told to release them
if (m_release_locks) m_release_locks->wait_for_notification();
m_mdl_context.release_transactional_locks();
// Tell the main thread that grabbed lock is released.
if (m_lock_released) m_lock_released->notify();
}
// Google Test recommends DeathTest suffix for classes use in death tests.
typedef MDLTest MDLDeathTest;
/*
Verifies that we die with a DBUG_ASSERT if we destry a non-empty MDL_context.
*/
#if GTEST_HAS_DEATH_TEST && !defined(DBUG_OFF)
TEST_F(MDLDeathTest, DieWhenMTicketsNonempty) {
::testing::FLAGS_gtest_death_test_style = "threadsafe";
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_DEATH(m_mdl_context.destroy(),
".*Assertion.*m_ticket_store.*is_empty.*");
m_mdl_context.release_transactional_locks();
}
#endif // GTEST_HAS_DEATH_TEST && !defined(DBUG_OFF)
/*
The most basic test: just construct and destruct our test fixture.
*/
TEST_F(MDLTest, ConstructAndDestruct) {}
void MDLTest::test_one_simple_shared_lock(enum_mdl_type lock_type) {
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, lock_type,
MDL_TRANSACTION);
EXPECT_EQ(lock_type, m_request.type);
EXPECT_EQ(m_null_ticket, m_request.ticket);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_NE(m_null_ticket, m_request.ticket);
EXPECT_TRUE(m_mdl_context.has_locks());
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, lock_type));
MDL_request request_2;
MDL_REQUEST_INIT_BY_KEY(&request_2, &m_request.key, lock_type,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
EXPECT_EQ(m_request.ticket, request_2.ticket);
m_mdl_context.release_transactional_locks();
EXPECT_FALSE(m_mdl_context.has_locks());
}
/*
Acquires one lock of type MDL_SHARED.
*/
TEST_F(MDLTest, OneShared) { test_one_simple_shared_lock(MDL_SHARED); }
/*
Acquires one lock of type MDL_SHARED_HIGH_PRIO.
*/
TEST_F(MDLTest, OneSharedHighPrio) {
test_one_simple_shared_lock(MDL_SHARED_HIGH_PRIO);
}
/*
Acquires one lock of type MDL_SHARED_READ.
*/
TEST_F(MDLTest, OneSharedRead) { test_one_simple_shared_lock(MDL_SHARED_READ); }
/*
Acquires one lock of type MDL_SHARED_WRITE.
*/
TEST_F(MDLTest, OneSharedWrite) {
test_one_simple_shared_lock(MDL_SHARED_WRITE);
}
/*
Acquires one lock of type MDL_EXCLUSIVE.
*/
TEST_F(MDLTest, OneExclusive) {
const enum_mdl_type lock_type = MDL_EXCLUSIVE;
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, lock_type,
MDL_TRANSACTION);
EXPECT_EQ(m_null_ticket, m_request.ticket);
m_request_list.push_front(&m_request);
m_request_list.push_front(&m_global_request);
EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
EXPECT_NE(m_null_ticket, m_request.ticket);
EXPECT_NE(m_null_ticket, m_global_request.ticket);
EXPECT_TRUE(m_mdl_context.has_locks());
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, lock_type));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE));
EXPECT_TRUE(m_request.ticket->is_upgradable_or_exclusive());
m_mdl_context.release_transactional_locks();
EXPECT_FALSE(m_mdl_context.has_locks());
}
/*
Acquires two locks, on different tables, of type MDL_SHARED.
Verifies that they are independent.
*/
TEST_F(MDLTest, TwoShared) {
MDL_request request_2;
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_EXPLICIT);
MDL_REQUEST_INIT(&request_2, MDL_key::TABLE, db_name, table_name2, MDL_SHARED,
MDL_EXPLICIT);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
EXPECT_TRUE(m_mdl_context.has_locks());
ASSERT_NE(m_null_ticket, m_request.ticket);
ASSERT_NE(m_null_ticket, request_2.ticket);
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
m_mdl_context.release_lock(m_request.ticket);
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
EXPECT_TRUE(m_mdl_context.has_locks());
m_mdl_context.release_lock(request_2.ticket);
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
EXPECT_FALSE(m_mdl_context.has_locks());
}
/*
Verifies that two different contexts can acquire a shared lock
on the same table.
*/
TEST_F(MDLTest, SharedLocksBetweenContexts) {
MDL_context mdl_context2;
mdl_context2.init(this);
MDL_request request_2;
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
MDL_REQUEST_INIT(&request_2, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_FALSE(mdl_context2.try_acquire_lock(&request_2));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
EXPECT_TRUE(mdl_context2.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
m_mdl_context.release_transactional_locks();
mdl_context2.release_transactional_locks();
}
/*
Verifies that we can upgrade a shared lock to exclusive.
*/
TEST_F(MDLTest, UpgradeSharedUpgradable) {
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_UPGRADABLE, MDL_TRANSACTION);
m_request_list.push_front(&m_request);
m_request_list.push_front(&m_global_request);
EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
EXPECT_FALSE(m_mdl_context.upgrade_shared_lock(m_request.ticket,
MDL_EXCLUSIVE, long_timeout));
EXPECT_EQ(MDL_EXCLUSIVE, m_request.ticket->get_type());
// Another upgrade should be a no-op.
EXPECT_FALSE(m_mdl_context.upgrade_shared_lock(m_request.ticket,
MDL_EXCLUSIVE, long_timeout));
EXPECT_EQ(MDL_EXCLUSIVE, m_request.ticket->get_type());
m_mdl_context.release_transactional_locks();
}
/*
Verfies that locks are released when we roll back to a savepoint.
*/
TEST_F(MDLTest, SavePoint) {
MDL_request request_2;
MDL_request request_3;
MDL_request request_4;
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
MDL_REQUEST_INIT(&request_2, MDL_key::TABLE, db_name, table_name2, MDL_SHARED,
MDL_TRANSACTION);
MDL_REQUEST_INIT(&request_3, MDL_key::TABLE, db_name, table_name3, MDL_SHARED,
MDL_TRANSACTION);
MDL_REQUEST_INIT(&request_4, MDL_key::TABLE, db_name, table_name4, MDL_SHARED,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
MDL_savepoint savepoint = m_mdl_context.mdl_savepoint();
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_3));
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_4));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name4, MDL_SHARED));
m_mdl_context.rollback_to_savepoint(savepoint);
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name4, MDL_SHARED));
m_mdl_context.release_transactional_locks();
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
}
/*
Verifies that we can grab shared locks concurrently, in different threads.
*/
TEST_F(MDLTest, ConcurrentShared) {
Notification lock_grabbed;
Notification release_locks;
MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks,
NULL, NULL);
mdl_thread.start();
lock_grabbed.wait_for_notification();
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
release_locks.notify();
mdl_thread.join();
m_mdl_context.release_transactional_locks();
}
/*
Verifies that we cannot grab an exclusive lock on something which
is locked with a shared lock in a different thread.
*/
TEST_F(MDLTest, ConcurrentSharedExclusive) {
expected_error = ER_LOCK_WAIT_TIMEOUT;
Notification lock_grabbed;
Notification release_locks;
MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks,
NULL, NULL);
mdl_thread.start();
lock_grabbed.wait_for_notification();
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
m_request_list.push_front(&m_request);
m_request_list.push_front(&m_global_request);
// We should *not* be able to grab the lock here.
EXPECT_TRUE(m_mdl_context.acquire_locks(&m_request_list, zero_timeout));
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE));
release_locks.notify();
mdl_thread.join();
// Now we should be able to grab the lock.
EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, zero_timeout));
EXPECT_NE(m_null_ticket, m_request.ticket);
m_mdl_context.release_transactional_locks();
}
/*
Verifies that we cannot we cannot grab a shared lock on something which
is locked exlusively in a different thread.
*/
TEST_F(MDLTest, ConcurrentExclusiveShared) {
Notification lock_grabbed;
Notification release_locks;
MDL_thread mdl_thread(table_name1, MDL_EXCLUSIVE, &lock_grabbed,
&release_locks, NULL, NULL);
mdl_thread.start();
lock_grabbed.wait_for_notification();
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
// We should *not* be able to grab the lock here.
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
release_locks.notify();
// The other thread should eventually release its locks.
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
EXPECT_NE(m_null_ticket, m_request.ticket);
mdl_thread.join();
m_mdl_context.release_transactional_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(MDLTest, ConcurrentUpgrade) {
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_UPGRADABLE, MDL_TRANSACTION);
m_request_list.push_front(&m_request);
m_request_list.push_front(&m_global_request);
EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_SHARED_UPGRADABLE));
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE));
Notification lock_grabbed;
Notification release_locks;
MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks,
NULL, NULL);
mdl_thread.enable_release_on_notify();
mdl_thread.start();
lock_grabbed.wait_for_notification();
EXPECT_FALSE(m_mdl_context.upgrade_shared_lock(m_request.ticket,
MDL_EXCLUSIVE, long_timeout));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE));
mdl_thread.join();
m_mdl_context.release_transactional_locks();
}
TEST_F(MDLTest, UpgradableConcurrency) {
MDL_request request_2;
MDL_request_list request_list;
Notification lock_grabbed;
Notification release_locks;
MDL_thread mdl_thread(table_name1, MDL_SHARED_UPGRADABLE, &lock_grabbed,
&release_locks, NULL, NULL);
mdl_thread.start();
lock_grabbed.wait_for_notification();
// We should be able to take a SW lock.
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_WRITE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_NE(m_null_ticket, m_request.ticket);
// But SHARED_UPGRADABLE is not compatible with itself
expected_error = ER_LOCK_WAIT_TIMEOUT;
MDL_REQUEST_INIT(&request_2, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_UPGRADABLE, MDL_TRANSACTION);
request_list.push_front(&m_global_request);
request_list.push_front(&request_2);
EXPECT_TRUE(m_mdl_context.acquire_locks(&request_list, zero_timeout));
EXPECT_EQ(m_null_ticket, request_2.ticket);
release_locks.notify();
mdl_thread.join();
m_mdl_context.release_transactional_locks();
}
/*
Test compatibility matrice for MDL_SHARED_WRITE_LOW_PRIO lock.
*/
TEST_F(MDLTest, SharedWriteLowPrioCompatibility) {
enum_mdl_type compatible[] = {
MDL_SHARED, MDL_SHARED_HIGH_PRIO, MDL_SHARED_READ,
MDL_SHARED_WRITE, MDL_SHARED_WRITE_LOW_PRIO, MDL_SHARED_UPGRADABLE};
enum_mdl_type incompatible[] = {MDL_SHARED_READ_ONLY, MDL_SHARED_NO_WRITE,
MDL_SHARED_NO_READ_WRITE, MDL_EXCLUSIVE};
enum_mdl_type higher_prio[] = {MDL_SHARED_READ_ONLY, MDL_SHARED_NO_WRITE,
MDL_SHARED_NO_READ_WRITE, MDL_EXCLUSIVE};
Notification lock_grabbed;
Notification release_lock;
MDL_thread mdl_thread(table_name1, MDL_SHARED_WRITE_LOW_PRIO, &lock_grabbed,
&release_lock, NULL, NULL);
uint i;
// Start thread which will acquire SWLP lock and pause.
mdl_thread.start();
lock_grabbed.wait_for_notification();
// We should be able to take all locks from compatible list
for (i = 0; i < sizeof(compatible) / sizeof(enum_mdl_type); ++i) {
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
compatible[i], MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_NE(m_null_ticket, m_request.ticket);
m_mdl_context.release_transactional_locks();
}
// But none of the locks from incompatible list
for (i = 0; i < sizeof(incompatible) / sizeof(enum_mdl_type); ++i) {
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
incompatible[i], MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
}
release_lock.notify();
mdl_thread.join();
// Check that SWLP lock can be acquired when any of compatible locks is active
for (i = 0; i < sizeof(compatible) / sizeof(enum_mdl_type); ++i) {
Notification second_grabbed;
Notification second_release;
MDL_thread mdl_thread2(table_name1, compatible[i], &second_grabbed,
&second_release, NULL, NULL);
// Start thread that will acquire one of locks from compatible list
mdl_thread2.start();
second_grabbed.wait_for_notification();
// Acquisition of SWLP should succeed
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_WRITE_LOW_PRIO, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_NE(m_null_ticket, m_request.ticket);
m_mdl_context.release_transactional_locks();
second_release.notify();
mdl_thread2.join();
}
/*
Check that SWLP lock can't be acquired when any of incompatible locks
is active.
*/
for (i = 0; i < sizeof(incompatible) / sizeof(enum_mdl_type); ++i) {
Notification third_grabbed;
Notification third_release;
MDL_thread mdl_thread3(table_name1, incompatible[i], &third_grabbed,
&third_release, NULL, NULL);
// Start thread that will acquire one of locks from incompatible list
mdl_thread3.start();
third_grabbed.wait_for_notification();
// Acquisition of SWLP should fail.
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_WRITE_LOW_PRIO, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
third_release.notify();
mdl_thread3.join();
}
/*
Check that SWLP lock can't be acquired if one of higher-prio locks is
pending.
*/
for (i = 0; i < sizeof(higher_prio) / sizeof(enum_mdl_type); ++i) {
Notification fourth_grabbed;
Notification fourth_release;
Notification fifth_blocked;
MDL_thread mdl_thread4(table_name1, MDL_SHARED_WRITE, &fourth_grabbed,
&fourth_release, NULL, NULL);
MDL_thread mdl_thread5(table_name1, higher_prio[i], NULL, NULL,
&fifth_blocked, NULL);
// Acquire SW lock on the table.
mdl_thread4.start();
fourth_grabbed.wait_for_notification();
// Ensure that there is pending high-prio lock.
mdl_thread5.start();
fifth_blocked.wait_for_notification();
// Acquisition of SWLP should fail because there is pending high-prio lock.
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_WRITE_LOW_PRIO, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
fourth_release.notify();
mdl_thread4.join();
mdl_thread5.join();
}
/*
Check that higher-prio locks can be acquired even if there
is pending SWLP lock.
*/
for (i = 0; i < sizeof(higher_prio) / sizeof(enum_mdl_type); ++i) {
Notification sixth_grabbed;
Notification sixth_release;
Notification seventh_blocked;
Notification seventh_grabbed;
Notification eighth_blocked;
Notification eighth_release;
MDL_thread mdl_thread6(table_name1, MDL_EXCLUSIVE, &sixth_grabbed,
&sixth_release, NULL, NULL);
MDL_thread mdl_thread7(table_name1, higher_prio[i], &seventh_grabbed, NULL,
&seventh_blocked, NULL);
MDL_thread mdl_thread8(table_name1, MDL_SHARED_WRITE_LOW_PRIO, NULL,
&eighth_release, &eighth_blocked, NULL);
// Acquire X lock on the table.
mdl_thread6.start();
sixth_grabbed.wait_for_notification();
// Ensure that there is pending high-prio lock.
mdl_thread7.start();
seventh_blocked.wait_for_notification();
// Ensure that there is pending SWLP after it.
mdl_thread8.start();
eighth_blocked.wait_for_notification();
// Release X lock.
sixth_release.notify();
mdl_thread6.join();
/*
This should unblock high-prio lock and not SWLP (otherwise we will
wait for SWLP release.
*/
seventh_grabbed.wait_for_notification();
mdl_thread7.join();
// After this SWLP lock will be granted and can be released
eighth_release.notify();
mdl_thread8.join();
}
}
/*
Test compatibility matrice for MDL_SHARED_READ_ONLY lock.
*/
TEST_F(MDLTest, SharedReadOnlyCompatibility) {
enum_mdl_type compatible[] = {MDL_SHARED, MDL_SHARED_HIGH_PRIO,
MDL_SHARED_READ, MDL_SHARED_UPGRADABLE,
MDL_SHARED_READ_ONLY, MDL_SHARED_NO_WRITE};
enum_mdl_type incompatible[] = {MDL_SHARED_WRITE, MDL_SHARED_WRITE_LOW_PRIO,
MDL_SHARED_NO_READ_WRITE, MDL_EXCLUSIVE};
enum_mdl_type higher_prio[] = {MDL_SHARED_WRITE, MDL_SHARED_NO_READ_WRITE,
MDL_EXCLUSIVE};
Notification lock_grabbed;
Notification release_lock;
MDL_thread mdl_thread(table_name1, MDL_SHARED_READ_ONLY, &lock_grabbed,
&release_lock, NULL, NULL);
uint i;
// Start thread which will acquire SRO lock and pause.
mdl_thread.start();
lock_grabbed.wait_for_notification();
// We should be able to take all locks from compatible list
for (i = 0; i < sizeof(compatible) / sizeof(enum_mdl_type); ++i) {
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
compatible[i], MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_NE(m_null_ticket, m_request.ticket);
m_mdl_context.release_transactional_locks();
}
// But none of the locks from incompatible list
for (i = 0; i < sizeof(incompatible) / sizeof(enum_mdl_type); ++i) {
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
incompatible[i], MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
}
release_lock.notify();
mdl_thread.join();
// Check that SRO lock can be acquired when any of compatible locks is active
for (i = 0; i < sizeof(compatible) / sizeof(enum_mdl_type); ++i) {
Notification second_grabbed;
Notification second_release;
MDL_thread mdl_thread2(table_name1, compatible[i], &second_grabbed,
&second_release, NULL, NULL);
// Start thread that will acquire one of locks from compatible list
mdl_thread2.start();
second_grabbed.wait_for_notification();
// Acquisition of SRO should succeed
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_READ_ONLY, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_NE(m_null_ticket, m_request.ticket);
m_mdl_context.release_transactional_locks();
second_release.notify();
mdl_thread2.join();
}
/*
Check that SRO lock can't be acquired when any of incompatible locks
is active.
*/
for (i = 0; i < sizeof(incompatible) / sizeof(enum_mdl_type); ++i) {
Notification third_grabbed;
Notification third_release;
MDL_thread mdl_thread3(table_name1, incompatible[i], &third_grabbed,
&third_release, NULL, NULL);
// Start thread that will acquire one of locks from incompatible list
mdl_thread3.start();
third_grabbed.wait_for_notification();
// Acquisition of SRO should fail.
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_READ_ONLY, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
third_release.notify();
mdl_thread3.join();
}
/*
Check that SRO lock can't be acquired if one of higher-prio locks is
pending.
*/
for (i = 0; i < sizeof(higher_prio) / sizeof(enum_mdl_type); ++i) {
Notification fourth_grabbed;
Notification fourth_release;
Notification fifth_blocked;
MDL_thread mdl_thread4(table_name1, MDL_SHARED_READ_ONLY, &fourth_grabbed,
&fourth_release, NULL, NULL);
MDL_thread mdl_thread5(table_name1, higher_prio[i], NULL, NULL,
&fifth_blocked, NULL);
// Acquire SRO lock on the table.
mdl_thread4.start();
fourth_grabbed.wait_for_notification();
// Ensure that there is pending high-prio lock.
mdl_thread5.start();
fifth_blocked.wait_for_notification();
// Acquisition of SRO should fail because there is pending high-prio lock.
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_READ_ONLY, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
fourth_release.notify();
mdl_thread4.join();
mdl_thread5.join();
}
// Check that SRO lock can be acquired if there is pending SWLP request.
Notification sixth_grabbed;
Notification sixth_release;
Notification seventh_blocked;
MDL_thread mdl_thread6(table_name1, MDL_SHARED_READ_ONLY, &sixth_grabbed,
&sixth_release, NULL, NULL);
MDL_thread mdl_thread7(table_name1, MDL_SHARED_WRITE_LOW_PRIO, NULL, NULL,
&seventh_blocked, NULL);
// Acquire SRO lock on the table.
mdl_thread6.start();
sixth_grabbed.wait_for_notification();
// Ensure that there is pending SWLP lock.
mdl_thread7.start();
seventh_blocked.wait_for_notification();
// Acquisition of SRO should succeed despite pending SWLP is present
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_READ_ONLY, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_NE(m_null_ticket, m_request.ticket);
m_mdl_context.release_transactional_locks();
sixth_release.notify();
mdl_thread6.join();
mdl_thread7.join();
/*
Check that higher-prio locks can be acquired even if there
is pending SRO lock.
*/
for (i = 0; i < sizeof(higher_prio) / sizeof(enum_mdl_type); ++i) {
Notification eighth_grabbed;
Notification eighth_release;
Notification nineth_blocked;
Notification nineth_grabbed;
Notification tenth_blocked;
Notification tenth_release;
MDL_thread mdl_thread8(table_name1, MDL_EXCLUSIVE, &eighth_grabbed,
&eighth_release, NULL, NULL);
MDL_thread mdl_thread9(table_name1, higher_prio[i], &nineth_grabbed, NULL,
&nineth_blocked, NULL);
MDL_thread mdl_thread10(table_name1, MDL_SHARED_READ_ONLY, NULL,
&tenth_release, &tenth_blocked, NULL);
// Acquire X lock on the table.
mdl_thread8.start();
eighth_grabbed.wait_for_notification();
// Ensure that there is pending high-prio lock.
mdl_thread9.start();
nineth_blocked.wait_for_notification();
// Ensure that there is pending SRO after it.
mdl_thread10.start();
tenth_blocked.wait_for_notification();
// Release X lock.
eighth_release.notify();
mdl_thread8.join();
/*
This should unblock high-prio lock and not SRO (otherwise we will
wait for SRO release.
*/
nineth_grabbed.wait_for_notification();
mdl_thread9.join();
// After this SRO lock will be granted and can be released
tenth_release.notify();
mdl_thread10.join();
}
// Check that SWLP lock can't be acquired if there is pending SRO request.
Notification eleventh_grabbed;
Notification eleventh_release;
Notification twelveth_blocked;
MDL_thread mdl_thread11(table_name1, MDL_SHARED_WRITE, &eleventh_grabbed,
&eleventh_release, NULL, NULL);
MDL_thread mdl_thread12(table_name1, MDL_SHARED_READ_ONLY, NULL, NULL,
&twelveth_blocked, NULL);
// Acquire SW lock on the table.
mdl_thread11.start();
eleventh_grabbed.wait_for_notification();
// Ensure that there is pending SRO lock.
mdl_thread12.start();
twelveth_blocked.wait_for_notification();
// Acquisition of SWLP should fail.
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_WRITE_LOW_PRIO, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
eleventh_release.notify();
mdl_thread11.join();
mdl_thread12.join();
}
/*
Verifies following scenario,
Low priority lock requests starvation. Lock is granted to high priority
lock request in wait queue always as max_write_lock_count is a large value.
- max_write_lock_count == default value i.e ~(ulong)0L
- THREAD 1: Acquires X lock on the table.
- THREAD 2: Requests for SR lock on the table.
- THREAD 3: Requests for SW lock on the table.
- THREAD 4: Requests for SNRW on the table.
- THREAD 1: Releases X lock.
- THREAD 5: Requests for SNRW lock on the table.
- THREAD 4: Releases SNRW lock.
- THREAD 2,3: Check whether THREADs got lock on the table.
Though, THREAD 2,3 requested lock before THREAD 4's SNRW lock and
THREAD 5's SNRW lock, lock is granted for THREAD 4 and 5.
*/
TEST_F(MDLTest, HogLockTest1) {
Notification thd_lock_grabbed[5];
Notification thd_release_locks[5];
Notification thd_lock_blocked[5];
Notification thd_lock_released[5];
/* Locks taken by the threads */
enum { THD1_X, THD2_SR, THD3_SW, THD4_SNRW, THD5_SNRW };
/*
THREAD1: Acquiring X lock on table.
Lock Wait Queue: <empty>
Lock granted: <empty>
*/
MDL_thread mdl_thread1(table_name1, MDL_EXCLUSIVE, &thd_lock_grabbed[THD1_X],
&thd_release_locks[THD1_X], &thd_lock_blocked[THD1_X],
&thd_lock_released[THD1_X]);
mdl_thread1.start();
thd_lock_grabbed[THD1_X].wait_for_notification();
/*
THREAD2: Requesting SR lock on table.
Lock Wait Queue: SR
Lock granted: X
*/
MDL_thread mdl_thread2(
table_name1, MDL_SHARED_READ, &thd_lock_grabbed[THD2_SR],
&thd_release_locks[THD2_SR], &thd_lock_blocked[THD2_SR],
&thd_lock_released[THD2_SR]);
mdl_thread2.start();
thd_lock_blocked[THD2_SR].wait_for_notification();
/*
THREAD3: Requesting SW lock on table.
Lock Wait Queue: SR<--SW
Lock granted: X
*/
MDL_thread mdl_thread3(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD3_SW],
&thd_release_locks[THD3_SW], &thd_lock_blocked[THD3_SW],
&thd_lock_released[THD3_SW]);
mdl_thread3.start();
thd_lock_blocked[THD3_SW].wait_for_notification();
/*
THREAD4: Requesting SNRW lock on table.
Lock Wait Queue: SR<--SW<--SNRW
Lock granted: X
*/
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD4_SNRW],
&thd_release_locks[THD4_SNRW], &thd_lock_blocked[THD4_SNRW],
&thd_lock_released[THD4_SNRW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SNRW].wait_for_notification();
/* THREAD 1: Release X lock. */
thd_release_locks[THD1_X].notify();
thd_lock_released[THD1_X].wait_for_notification();
/*
Lock Wait Queue: SR<--SW
Lock granted: SNRW
*/
thd_lock_grabbed[THD4_SNRW].wait_for_notification();
/*
THREAD 5: Requests SNRW lock on the table.
Lock Wait Queue: SR<--SW<--SNRW
Lock granted: SNRW
*/
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD5_SNRW],
&thd_release_locks[THD5_SNRW], &thd_lock_blocked[THD5_SNRW],
&thd_lock_released[THD5_SNRW]);
mdl_thread5.start();
thd_lock_blocked[THD5_SNRW].wait_for_notification();
/* THREAD 4: Release SNRW lock */
thd_release_locks[THD4_SNRW].notify();
thd_lock_released[THD4_SNRW].wait_for_notification();
/* THREAD 2: Is Lock granted to me? */
EXPECT_FALSE((mdl_thread2.get_mdl_context())
.owns_equal_or_stronger_lock(MDL_key::TABLE, db_name,
table_name1, MDL_SHARED_READ));
/* THREAD 3: Is Lock granted to me? */
EXPECT_FALSE((mdl_thread3.get_mdl_context())
.owns_equal_or_stronger_lock(MDL_key::TABLE, db_name,
table_name1, MDL_SHARED_WRITE));
/*
THREAD 5: Lock is granted to THREAD 5 as priority is higher.
Lock Wait Queue: SR<--SW
Lock granted: SNRW
*/
thd_lock_grabbed[THD5_SNRW].wait_for_notification();
thd_release_locks[THD5_SNRW].notify();
thd_lock_released[THD5_SNRW].wait_for_notification();
/*CLEANUP*/
thd_lock_grabbed[THD2_SR].wait_for_notification();
thd_release_locks[THD2_SR].notify();
thd_lock_released[THD2_SR].wait_for_notification();
thd_lock_grabbed[THD3_SW].wait_for_notification();
thd_release_locks[THD3_SW].notify();
thd_lock_released[THD3_SW].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
}
/*
Verifies following scenario,
After granting max_write_lock_count(=1) number of times for high priority
lock request, lock is granted to starving low priority lock request
in wait queue.
- max_write_lock_count= 1
- THREAD 1: Acquires X lock on the table.
- THREAD 2: Requests for SR lock on the table.
- THREAD 3: Requests for SW lock on the table.
- THREAD 4: Requests for SNRW on the table.
- THREAD 1: Releases X lock. m_hog_lock_count= 1
- THREAD 5: Requests for SNRW lock on the table.
- THREAD 4: Releases SNRW lock.
- THREAD 2,3: Release lock.
While releasing X held by THREAD-1, m_hog_lock_count becomes 1 and while
releasing SNRW lock in THREAD 4, lock is granted to starving low priority
locks as m_hog_lock_count == max_write_lock_count.
So THREAD 2, 3 gets lock here instead of THREAD 5.
*/
TEST_F(MDLTest, HogLockTest2) {
Notification thd_lock_grabbed[5];
Notification thd_release_locks[5];
Notification thd_lock_blocked[5];
Notification thd_lock_released[5];
/* Locks taken by the threads */
enum { THD1_X, THD2_SR, THD3_SW, THD4_SNRW, THD5_SNRW };
max_write_lock_count = 1;
/*
THREAD1: Acquiring X lock on table.
Lock Wait Queue: <empty>
Lock Granted: <empty>
*/
MDL_thread mdl_thread1(table_name1, MDL_EXCLUSIVE, &thd_lock_grabbed[THD1_X],
&thd_release_locks[THD1_X], &thd_lock_blocked[THD1_X],
&thd_lock_released[THD1_X]);
mdl_thread1.start();
thd_lock_grabbed[THD1_X].wait_for_notification();
/*
THREAD2: Requesting SR lock on table.
Lock Wait Queue: SR
Lock Granted: X
*/
MDL_thread mdl_thread2(
table_name1, MDL_SHARED_READ, &thd_lock_grabbed[THD2_SR],
&thd_release_locks[THD2_SR], &thd_lock_blocked[THD2_SR],
&thd_lock_released[THD2_SR]);
mdl_thread2.start();
thd_lock_blocked[THD2_SR].wait_for_notification();
/*
THREAD3: Requesting SW lock on table.
Lock Wait Queue: SR<--SW
Lock Granted: X
*/
MDL_thread mdl_thread3(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD3_SW],
&thd_release_locks[THD3_SW], &thd_lock_blocked[THD3_SW],
&thd_lock_released[THD3_SW]);
mdl_thread3.start();
thd_lock_blocked[THD3_SW].wait_for_notification();
/*
THREAD4: Requesting SNRW lock on table.
Lock Wait Queue: SR<--SW<--SNRW
Lock Granted: X
*/
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD4_SNRW],
&thd_release_locks[THD4_SNRW], &thd_lock_blocked[THD4_SNRW],
&thd_lock_released[THD4_SNRW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SNRW].wait_for_notification();
/*
THREAD 1: Release X lock.
Lock Wait Queue: SR<--SW
Lock Granted: SNRW
m_hog_lock_count= 1
*/
thd_release_locks[THD1_X].notify();
thd_lock_released[THD1_X].wait_for_notification();
/* Lock is granted to THREAD 4 */
thd_lock_grabbed[THD4_SNRW].wait_for_notification();
/*
THREAD 5: Requests SNRW lock on the table.
Lock Wait Queue: SR<--SW<--SNRW
Lock Granted: SNRW
*/
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD5_SNRW],
&thd_release_locks[THD5_SNRW], &thd_lock_blocked[THD5_SNRW],
&thd_lock_released[THD5_SNRW]);
mdl_thread5.start();
thd_lock_blocked[THD5_SNRW].wait_for_notification();
/* THREAD 4: Release SNRW lock */
thd_release_locks[THD4_SNRW].notify();
thd_lock_released[THD4_SNRW].wait_for_notification();
/*
THREAD 2: Since max_write_lock_count == m_hog_lock_count, Lock is granted to
THREAD 2 and 3 instead of THREAD 5.
Lock Wait Queue: SNRW
Lock Granted: SR, SW
*/
thd_lock_grabbed[THD2_SR].wait_for_notification();
thd_lock_grabbed[THD3_SW].wait_for_notification();
thd_release_locks[THD2_SR].notify();
thd_lock_released[THD2_SR].wait_for_notification();
thd_release_locks[THD3_SW].notify();
thd_lock_released[THD3_SW].wait_for_notification();
/* Cleanup */
thd_lock_grabbed[THD5_SNRW].wait_for_notification();
thd_release_locks[THD5_SNRW].notify();
thd_lock_released[THD5_SNRW].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
}
/*
Verifies locks priorities,
X has priority over--> S, SR, SW, SU, (SNW, SNRW)
SNRW has priority over--> SR, SW
SNW has priority over--> SW
- max_write_lock_count contains default value i.e ~(ulong)0L
- THREAD 1: Acquires X lock on the table.
- THREAD 2: Requests for S lock on the table.
- THREAD 3: Requests for SR lock on the table.
- THREAD 4: Requests for SW lock on the table.
- THREAD 5: Requests for SU lock on the table.
- THREAD 6: Requests for SNRW on the table.
- THREAD 1: Releases X lock.
Lock is granted THREAD 2, THREAD 5.
- THREAD 5: RELEASE SU lock.
Lock is granted to THREAD 6.
- THREAD 7: Requests for SNW lock on the table.
- THREAD 6: Releases SNRW lock.
Lock is granted to THREAD 4 & THREAD 7.
- THREAD 4: Check whether THREAD got lock on the table.
At each locks release, locks of equal priorities are granted.
At the end only SW will be in wait queue as lock is granted to SNW
lock request.
*/
TEST_F(MDLTest, LockPriorityTest) {
Notification thd_lock_grabbed[7];
Notification thd_release_locks[7];
Notification thd_lock_blocked[7];
Notification thd_lock_released[7];
/* Locks taken by the threads */
enum { THD1_X, THD2_S, THD3_SR, THD4_SW, THD5_SU, THD6_SNRW, THD7_SNW };
/*THREAD1: Acquiring X lock on table */
MDL_thread mdl_thread1(table_name1, MDL_EXCLUSIVE, &thd_lock_grabbed[THD1_X],
&thd_release_locks[THD1_X], &thd_lock_blocked[THD1_X],
&thd_lock_released[THD1_X]);
mdl_thread1.start();
thd_lock_grabbed[THD1_X].wait_for_notification();
/*
THREAD2: Requesting S lock on table.
Lock Wait Queue: S
Lock Granted: X
*/
MDL_thread mdl_thread2(table_name1, MDL_SHARED, &thd_lock_grabbed[THD2_S],
&thd_release_locks[THD2_S], &thd_lock_blocked[THD2_S],
&thd_lock_released[THD2_S]);
mdl_thread2.start();
thd_lock_blocked[THD2_S].wait_for_notification();
/*
THREAD3: Requesting SR lock on table.
Lock Wait Queue: S<--SR
Lock Granted: X
*/
MDL_thread mdl_thread3(
table_name1, MDL_SHARED_READ, &thd_lock_grabbed[THD3_SR],
&thd_release_locks[THD3_SR], &thd_lock_blocked[THD3_SR],
&thd_lock_released[THD3_SR]);
mdl_thread3.start();
thd_lock_blocked[THD3_SR].wait_for_notification();
/*
THREAD4: Requesting SW lock on table.
Lock Wait Queue: S<--SR<--SW
Lock Granted: X
*/
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD4_SW],
&thd_release_locks[THD4_SW], &thd_lock_blocked[THD4_SW],
&thd_lock_released[THD4_SW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SW].wait_for_notification();
/*
THREAD5: Requesting SU lock on table
Lock Wait Queue: S<--SR<--SW<--SU
Lock Granted: X
*/
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_UPGRADABLE, &thd_lock_grabbed[THD5_SU],
&thd_release_locks[THD5_SU], &thd_lock_blocked[THD5_SU],
&thd_lock_released[THD5_SU]);
mdl_thread5.start();
thd_lock_blocked[THD5_SU].wait_for_notification();
/*
THREAD6: Requesting SNRW lock on table
Lock Wait Queue: S<--SR<--SW<--SU<--SNRW
Lock Granted: X
*/
MDL_thread mdl_thread6(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD6_SNRW],
&thd_release_locks[THD6_SNRW], &thd_lock_blocked[THD6_SNRW],
&thd_lock_released[THD6_SNRW]);
mdl_thread6.start();
thd_lock_blocked[THD6_SNRW].wait_for_notification();
/*
Lock wait Queue status: S<--SR<--SW<--SU<--SNRW
THREAD 1: Release X lock.
*/
thd_release_locks[THD1_X].notify();
thd_lock_released[THD1_X].wait_for_notification();
/*
THREAD 5: Verify and Release lock.
Lock wait Queue status: SR<--SW<--SNRW
Lock Granted: S, SU
*/
thd_lock_grabbed[THD2_S].wait_for_notification();
thd_release_locks[THD2_S].notify();
thd_lock_released[THD2_S].wait_for_notification();
thd_lock_grabbed[THD5_SU].wait_for_notification();
thd_release_locks[THD5_SU].notify();
thd_lock_released[THD5_SU].wait_for_notification();
/* Now Lock Granted to THREAD 6 SNRW lock type request*/
thd_lock_grabbed[THD6_SNRW].wait_for_notification();
/*
THREAD 7: Requests SNW lock on the table.
Lock wait Queue status: SR<--SW<--SNW
Lock Granted: SNRW
*/
MDL_thread mdl_thread7(
table_name1, MDL_SHARED_NO_WRITE, &thd_lock_grabbed[THD7_SNW],
&thd_release_locks[THD7_SNW], &thd_lock_blocked[THD7_SNW],
&thd_lock_released[THD7_SNW]);
mdl_thread7.start();
thd_lock_blocked[THD7_SNW].wait_for_notification();
/* THREAD 6: Release SNRW lock */
thd_release_locks[THD6_SNRW].notify();
thd_lock_released[THD6_SNRW].wait_for_notification();
/* Now lock is granted to THREAD 3 & 7 */
thd_lock_grabbed[THD7_SNW].wait_for_notification();
thd_lock_grabbed[THD3_SR].wait_for_notification();
/*
THREAD 3: Release SR lock
Lock wait Queue status: SW
Lock Granted: SR, SNW
*/
thd_release_locks[THD3_SR].notify();
thd_lock_released[THD3_SR].wait_for_notification();
/* THREAD 4: Verify whether lock is granted or not*/
EXPECT_FALSE((mdl_thread4.get_mdl_context())
.owns_equal_or_stronger_lock(MDL_key::TABLE, db_name,
table_name1, MDL_SHARED_WRITE));
/*CLEANUP*/
thd_release_locks[THD7_SNW].notify();
thd_lock_released[THD7_SNW].wait_for_notification();
thd_lock_grabbed[THD4_SW].wait_for_notification();
thd_release_locks[THD4_SW].notify();
thd_lock_released[THD4_SW].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
mdl_thread6.join();
mdl_thread7.join();
}
/*
Verifies locks priorities when max_write_lock_count= 1
X has priority over--> S, SR, SW, SU, (SNW, SNRW)
SNRW has priority over--> SR, SW
SNW has priority over--> SW
- max_write_lock_count= 1
- THREAD 1: Acquires X lock on the table.
- THREAD 2: Requests for S lock on the table.
- THREAD 3: Requests for SR lock on the table.
- THREAD 4: Requests for SW lock on the table.
- THREAD 5: Requests for SU lock on the table.
- THREAD 6: Requests for X on the table.
- THREAD 1: Releases X lock.
Lock is granted THREAD 6.
- THREAD 7: Requests SNRW lock.
- THREAD 6: Releases X lock.
Lock is granted to THREAD 2,3,4,5.
- THREAD 7: Check Whether lock is granted or not.
*/
TEST_F(MDLTest, HogLockTest3) {
Notification thd_lock_grabbed[7];
Notification thd_release_locks[7];
Notification thd_lock_blocked[7];
Notification thd_lock_released[7];
enum { THD1_X, THD2_S, THD3_SR, THD4_SW, THD5_SU, THD6_X, THD7_SNRW };
max_write_lock_count = 1;
/* THREAD1: Acquiring X lock on table. */
MDL_thread mdl_thread1(table_name1, MDL_EXCLUSIVE, &thd_lock_grabbed[THD1_X],
&thd_release_locks[THD1_X], &thd_lock_blocked[THD1_X],
&thd_lock_released[THD1_X]);
mdl_thread1.start();
thd_lock_grabbed[THD1_X].wait_for_notification();
/*
THREAD2: Requesting S lock on table.
Lock Wait Queue: S
Lock Granted: X
*/
MDL_thread mdl_thread2(table_name1, MDL_SHARED, &thd_lock_grabbed[THD2_S],
&thd_release_locks[THD2_S], &thd_lock_blocked[THD2_S],
&thd_lock_released[THD2_S]);
mdl_thread2.start();
thd_lock_blocked[THD2_S].wait_for_notification();
/*
THREAD3: Requesting SR lock on table.
Lock Wait Queue: S<--SR
Lock Granted: X
*/
MDL_thread mdl_thread3(
table_name1, MDL_SHARED_READ, &thd_lock_grabbed[THD3_SR],
&thd_release_locks[THD3_SR], &thd_lock_blocked[THD3_SR],
&thd_lock_released[THD3_SR]);
mdl_thread3.start();
thd_lock_blocked[THD3_SR].wait_for_notification();
/*
THREAD4: Requesting SW lock on table.
Lock Wait Queue: S<--SR<--SW.
Lock Granted: X
*/
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD4_SW],
&thd_release_locks[THD4_SW], &thd_lock_blocked[THD4_SW],
&thd_lock_released[THD4_SW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SW].wait_for_notification();
/*
THREAD5: Requesting SU lock on table.
Lock Wait Queue: S<--SR<--SW<--SU
Lock Granted: X
*/
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_UPGRADABLE, &thd_lock_grabbed[THD5_SU],
&thd_release_locks[THD5_SU], &thd_lock_blocked[THD5_SU],
&thd_lock_released[THD5_SU]);
mdl_thread5.start();
thd_lock_blocked[THD5_SU].wait_for_notification();
/*
THREAD6: Requesting X lock on table
Lock Wait Queue: S<--SR<--SW<--SU<--X
Lock Granted: X
*/
MDL_thread mdl_thread6(table_name1, MDL_EXCLUSIVE, &thd_lock_grabbed[THD6_X],
&thd_release_locks[THD6_X], &thd_lock_blocked[THD6_X],
&thd_lock_released[THD6_X]);
mdl_thread6.start();
thd_lock_blocked[THD6_X].wait_for_notification();
/*
Lock wait Queue status: S<--SR<--SW<--SU<--X
Lock Granted: X
THREAD 1: Release X lock.
*/
thd_release_locks[THD1_X].notify();
thd_lock_released[THD1_X].wait_for_notification();
/* Lock is granted to THREAD 6*/
thd_lock_grabbed[THD6_X].wait_for_notification();
/*
THREAD7: Requesting SNRW lock on table
Lock wait Queue status: S<--SR<--SW<--SU
Lock Granted: X
*/
MDL_thread mdl_thread7(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD7_SNRW],
&thd_release_locks[THD7_SNRW], &thd_lock_blocked[THD7_SNRW],
&thd_lock_released[THD7_SNRW]);
mdl_thread7.start();
thd_lock_blocked[THD7_SNRW].wait_for_notification();
/* THREAD 6: Release X lock. */
thd_release_locks[THD6_X].notify();
thd_lock_released[THD6_X].wait_for_notification();
/* Lock is granted to THREAD 2, 3, 4, 5*/
thd_lock_grabbed[THD2_S].wait_for_notification();
thd_lock_grabbed[THD3_SR].wait_for_notification();
thd_lock_grabbed[THD4_SW].wait_for_notification();
thd_lock_grabbed[THD5_SU].wait_for_notification();
/*
Lock wait Queue status: <empty>
Lock Granted: <empty>
THREAD 7: high priority SNRW lock is still waiting.
*/
EXPECT_FALSE((mdl_thread7.get_mdl_context())
.owns_equal_or_stronger_lock(MDL_key::TABLE, db_name,
table_name1,
MDL_SHARED_NO_READ_WRITE));
/* CLEAN UP */
thd_release_locks[THD2_S].notify();
thd_lock_released[THD2_S].wait_for_notification();
thd_release_locks[THD3_SR].notify();
thd_lock_released[THD3_SR].wait_for_notification();
thd_release_locks[THD4_SW].notify();
thd_lock_released[THD4_SW].wait_for_notification();
thd_release_locks[THD5_SU].notify();
thd_lock_released[THD5_SU].wait_for_notification();
thd_lock_grabbed[THD7_SNRW].wait_for_notification();
thd_release_locks[THD7_SNRW].notify();
thd_lock_released[THD7_SNRW].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
mdl_thread6.join();
mdl_thread7.join();
}
/*
Verifies whether m_hog_lock_count is resets or not,
when there are no low priority lock request.
- max_write_lock_count= 1
- THREAD 1: Acquires X lock on the table.
- THREAD 2: Requests for SU lock on the table.
- THREAD 3: Requests for X lock on the table.
- THREAD 1: Releases X lock.
Lock is granted to THREAD 3
m_hog_lock_count= 1;
- THREAD 3: Releases X lock.
Lock is granted to THRED 2.
m_hog_lock_count= 0;
- THREAD 4: Requests for SNRW lock.
- THREAD 5: Requests for R lock.
- THREAD 2: Releases SU lock.
Lock is granted to THREAD 4.
*/
TEST_F(MDLTest, HogLockTest4) {
Notification thd_lock_grabbed[5];
Notification thd_release_locks[5];
Notification thd_lock_blocked[5];
Notification thd_lock_released[5];
/* Locks taken by the threads */
enum { THD1_X, THD2_SU, THD3_X, THD4_SNRW, THD5_SR };
max_write_lock_count = 1;
/* THREAD1: Acquiring X lock on table */
MDL_thread mdl_thread1(table_name1, MDL_EXCLUSIVE, &thd_lock_grabbed[THD1_X],
&thd_release_locks[THD1_X], &thd_lock_blocked[THD1_X],
&thd_lock_released[THD1_X]);
mdl_thread1.start();
thd_lock_grabbed[THD1_X].wait_for_notification();
/* THREAD2: Requesting SU lock on table */
MDL_thread mdl_thread2(
table_name1, MDL_SHARED_UPGRADABLE, &thd_lock_grabbed[THD2_SU],
&thd_release_locks[THD2_SU], &thd_lock_blocked[THD2_SU],
&thd_lock_released[THD2_SU]);
mdl_thread2.start();
thd_lock_blocked[THD2_SU].wait_for_notification();
/* THREAD3: Requesting X lock on table */
MDL_thread mdl_thread3(table_name1, MDL_EXCLUSIVE, &thd_lock_grabbed[THD3_X],
&thd_release_locks[THD3_X], &thd_lock_blocked[THD3_X],
&thd_lock_released[THD3_X]);
mdl_thread3.start();
thd_lock_blocked[THD3_X].wait_for_notification();
/*
THREAD1: Release X lock.
Lock Request Queue: SU<--X
Lock Grant: X
m_hog_lock_count= 1
*/
thd_release_locks[THD1_X].notify();
thd_lock_released[THD1_X].wait_for_notification();
/* Lock is granted to THREAD 3 */
thd_lock_grabbed[THD3_X].wait_for_notification();
/*
THREAD3: Release X lock.
Lock Request Queue: <empty>
Lock Grant: SU
m_hog_lock_count= 0
*/
thd_release_locks[THD3_X].notify();
thd_lock_released[THD3_X].wait_for_notification();
/*Lock is granted to THREAD 2 */
thd_lock_grabbed[THD2_SU].wait_for_notification();
/*
THREAD4: Requesting SNRW lock on table.
Lock Request Queue: SNRW
Lock Grant: SU
*/
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD4_SNRW],
&thd_release_locks[THD4_SNRW], &thd_lock_blocked[THD4_SNRW],
&thd_lock_released[THD4_SNRW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SNRW].wait_for_notification();
/*
THREAD5: Requesting SR lock on table.
Lock Request Queue: SNRW<--SR
Lock Grant: SU
*/
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_READ, &thd_lock_grabbed[THD5_SR],
&thd_release_locks[THD5_SR], &thd_lock_blocked[THD5_SR],
&thd_lock_released[THD5_SR]);
mdl_thread5.start();
thd_lock_blocked[THD5_SR].wait_for_notification();
/* THREAD 2: Release lock. */
thd_release_locks[THD2_SU].notify();
thd_lock_released[THD2_SU].wait_for_notification();
/*
Lock Request Queue: SR
Lock Grant: SNRW
Lock is granted to THREAD 5 if m_hog_lock_count is not reset.
*/
thd_lock_grabbed[THD4_SNRW].wait_for_notification();
/* THREAD5: Lock is not granted */
EXPECT_FALSE((mdl_thread5.get_mdl_context())
.owns_equal_or_stronger_lock(MDL_key::TABLE, db_name,
table_name1, MDL_SHARED_READ));
/* CLEAN UP */
thd_release_locks[THD4_SNRW].notify();
thd_lock_released[THD4_SNRW].wait_for_notification();
thd_lock_grabbed[THD5_SR].wait_for_notification();
thd_release_locks[THD5_SR].notify();
thd_lock_released[THD5_SR].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
}
/*
Verifies resetting of m_hog_lock_count when only few of
the waiting low priority locks are granted and queue has
some more low priority lock requests in queue.
m_hog_lock_count should not be reset to 0 when few low priority
lock requests are granted.
- max_write_lock_count= 1
- THREAD 1: Acquires X lock on the table.
- THREAD 2: Requests for SNW lock on the table.
- THREAD 3: Requests for SR lock on the table.
- THREAD 4: Requests for SW lock on the table.
- THREAD 5: Requests for SU lock on the table.
- THREAD 1: Releases X lock.
Lock is granted THREAD 2, 3 as they are of same priority.
- THREAD 6: Requests for SNRW lock.
- THREAD 2: Releases SNW lock.
Lock shoule be granted to THREAD 4, 5 as
m_hog_lock_count == max_write_lock_count.
- THREAD 3: Check Whether lock is granted or not.
*/
TEST_F(MDLTest, HogLockTest5) {
Notification thd_lock_grabbed[6];
Notification thd_release_locks[6];
Notification thd_lock_blocked[6];
Notification thd_lock_released[6];
/* Locks taken by the threads */
enum { THD1_X, THD2_SNW, THD3_SR, THD4_SW, THD5_SU, THD6_SNRW };
max_write_lock_count = 1;
/* THREAD1: Acquiring X lock on table. */
MDL_thread mdl_thread1(table_name1, MDL_EXCLUSIVE, &thd_lock_grabbed[THD1_X],
&thd_release_locks[THD1_X], &thd_lock_blocked[THD1_X],
&thd_lock_released[THD1_X]);
mdl_thread1.start();
thd_lock_grabbed[THD1_X].wait_for_notification();
/* THREAD2: Requesting SNW lock on table. */
MDL_thread mdl_thread2(
table_name1, MDL_SHARED_NO_WRITE, &thd_lock_grabbed[THD2_SNW],
&thd_release_locks[THD2_SNW], &thd_lock_blocked[THD2_SNW],
&thd_lock_released[THD2_SNW]);
mdl_thread2.start();
thd_lock_blocked[THD2_SNW].wait_for_notification();
/* THREAD3: Requesting SR lock on table. */
MDL_thread mdl_thread3(
table_name1, MDL_SHARED_READ, &thd_lock_grabbed[THD3_SR],
&thd_release_locks[THD3_SR], &thd_lock_blocked[THD3_SR],
&thd_lock_released[THD3_SR]);
mdl_thread3.start();
thd_lock_blocked[THD3_SR].wait_for_notification();
/* THREAD4: Requesting SW lock on table. */
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD4_SW],
&thd_release_locks[THD4_SW], &thd_lock_blocked[THD4_SW],
&thd_lock_released[THD4_SW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SW].wait_for_notification();
/* THREAD5: Requesting SNW lock on table. */
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_UPGRADABLE, &thd_lock_grabbed[THD5_SU],
&thd_release_locks[THD5_SU], &thd_lock_blocked[THD5_SU],
&thd_lock_released[THD5_SU]);
mdl_thread5.start();
thd_lock_blocked[THD5_SU].wait_for_notification();
/*
Lock wait Queue status: SNW<--SR<--SW<--SU
Lock Granted: X
THREAD 1: Release X lock.
*/
thd_release_locks[THD1_X].notify();
thd_lock_released[THD1_X].wait_for_notification();
/*
Lock wait Queue status: SW<--SU
Lock Granted: SR, SNW
Lock is granted for Thread 2, 3
*/
thd_lock_grabbed[THD2_SNW].wait_for_notification();
thd_lock_grabbed[THD3_SR].wait_for_notification();
/*
THREAD5: Requesting SNRW lock on table.
Lock wait Queue status: SW<--SU<--SNRW
Lock Granted: SR, SNW
*/
MDL_thread mdl_thread6(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD6_SNRW],
&thd_release_locks[THD6_SNRW], &thd_lock_blocked[THD6_SNRW],
&thd_lock_released[THD6_SNRW]);
mdl_thread6.start();
thd_lock_blocked[THD6_SNRW].wait_for_notification();
/* Thread 2: Release SNW lock */
thd_release_locks[THD2_SNW].notify();
thd_lock_released[THD2_SNW].wait_for_notification();
/*
Lock wait Queue status: SNRW
Lock Granted: SR, SW, SU
Lock is granted to Thread 4,5 instead of Thread 6
THREAD6: Lock is not granted
*/
EXPECT_FALSE((mdl_thread6.get_mdl_context())
.owns_equal_or_stronger_lock(MDL_key::TABLE, db_name,
table_name1,
MDL_SHARED_NO_READ_WRITE));
thd_lock_grabbed[THD4_SW].wait_for_notification();
thd_release_locks[THD4_SW].notify();
thd_lock_released[THD4_SW].wait_for_notification();
thd_lock_grabbed[THD5_SU].wait_for_notification();
thd_release_locks[THD5_SU].notify();
thd_lock_released[THD5_SU].wait_for_notification();
/* CLEANUP */
thd_release_locks[THD3_SR].notify();
thd_lock_released[THD3_SR].wait_for_notification();
thd_lock_grabbed[THD6_SNRW].wait_for_notification();
thd_release_locks[THD6_SNRW].notify();
thd_lock_released[THD6_SNRW].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
mdl_thread6.join();
}
/*
Verifies that pending "obtrusive" lock correctly blocks later
"unobtrusive" lock, which can't use "fast path" in this case.
Also verifies that release of "fast path" "unobtrusive" lock
unblocks waiting "obtrusive" lock and release of "obtrusive" lock
unblocks pending "unobtrusive" lock.
*/
TEST_F(MDLTest, ConcurrentSharedExclusiveShared) {
Notification first_shared_grabbed;
Notification first_shared_release;
Notification exclusive_blocked;
Notification exclusive_grabbed;
Notification exclusive_release;
Notification second_shared_grabbed;
Notification second_shared_blocked;
MDL_thread mdl_thread1(table_name1, MDL_SHARED, &first_shared_grabbed,
&first_shared_release, NULL, NULL);
MDL_thread mdl_thread2(table_name1, MDL_EXCLUSIVE, &exclusive_grabbed,
&exclusive_release, &exclusive_blocked, NULL);
MDL_thread mdl_thread3(table_name1, MDL_SHARED, &second_shared_grabbed, NULL,
&second_shared_blocked, NULL);
/* Start thread which will acquire S lock. */
mdl_thread1.start();
first_shared_grabbed.wait_for_notification();
/* Start thread which will try to acquire X lock and will block. */
mdl_thread2.start();
exclusive_blocked.wait_for_notification();
/*
Start thread which will try to acquire another S lock.
It should not use "fast path" because of pending X lock
and should be blocked.
*/
mdl_thread3.start();
second_shared_blocked.wait_for_notification();
/*
Unblock 2nd thread by releasing the first S lock.
Here S lock acquiring on "fast path" should unblock
pending "obtrusive" - X lock.
*/
first_shared_release.notify();
exclusive_grabbed.wait_for_notification();
/*
Release X lock and thus unblock the second S lock.
This tests that release of X lock which uses "slow path"
unblocks pending locks.
*/
exclusive_release.notify();
second_shared_grabbed.wait_for_notification();
/* Wrap-up. */
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
}
/*
Verify that active X lock will block incoming X lock.
This also covers general situation when "obtrusive" lock is
acquired when other "obtrusive" lock is active.
Also covers case when release of "obtrusive" lock
unblocks another "obtrusive" lock.
*/
TEST_F(MDLTest, ConcurrentExclusiveExclusive) {
Notification first_exclusive_grabbed;
Notification first_exclusive_release;
Notification second_exclusive_blocked;
Notification second_exclusive_grabbed;
MDL_thread mdl_thread1(table_name1, MDL_EXCLUSIVE, &first_exclusive_grabbed,
&first_exclusive_release, NULL, NULL);
MDL_thread mdl_thread2(table_name1, MDL_EXCLUSIVE, &second_exclusive_grabbed,
NULL, &second_exclusive_blocked, NULL);
/* Start thread which will acquire X lock. */
mdl_thread1.start();
first_exclusive_grabbed.wait_for_notification();
/* Start thread which will try to acquire X lock and will block. */
mdl_thread2.start();
second_exclusive_blocked.wait_for_notification();
/* Releasing the first lock. This should unblock the second X request.*/
first_exclusive_release.notify();
second_exclusive_grabbed.wait_for_notification();
/* Wrap-up. */
mdl_thread1.join();
mdl_thread2.join();
}
/*
Verify that during rescheduling we correctly handle
situation when still there are conflicting "fast path"
lock (i.e. test that MDL_lock::can_grant_lock() correctly
works in this case).
*/
TEST_F(MDLTest, ConcurrentSharedSharedExclusive) {
Notification first_shared_grabbed;
Notification first_shared_release;
Notification second_shared_grabbed;
Notification second_shared_release;
Notification exclusive_blocked;
Notification exclusive_grabbed;
MDL_thread mdl_thread1(table_name1, MDL_SHARED, &first_shared_grabbed,
&first_shared_release, NULL, NULL);
MDL_thread mdl_thread2(table_name1, MDL_SHARED, &second_shared_grabbed,
&second_shared_release, NULL, NULL);
MDL_thread mdl_thread3(table_name1, MDL_EXCLUSIVE, &exclusive_grabbed, NULL,
&exclusive_blocked, NULL);
/* Start two threads which will acquire S locks. */
mdl_thread1.start();
first_shared_grabbed.wait_for_notification();
mdl_thread2.start();
second_shared_grabbed.wait_for_notification();
/* Start 3rd thread which will try to acquire X lock and will block. */
mdl_thread3.start();
exclusive_blocked.wait_for_notification();
/* Release one S lock. */
first_shared_release.notify();
/*
Rescheduling which happens in this case should still see that X lock
is waiting.
*/
EXPECT_FALSE((mdl_thread3.get_mdl_context())
.owns_equal_or_stronger_lock(MDL_key::TABLE, db_name,
table_name1, MDL_EXCLUSIVE));
/* Release the second S lock to unblock request for X lock. */
second_shared_release.notify();
/* Wrap-up. */
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
}
/*
Verify that we correctly handle situation when one thread tries
consequitively to acquire locks which conflict with each other
(this is again to check that MDL_lock::can_grant_lock() handles
such situations correctly).
*/
TEST_F(MDLTest, SelfConflict) {
/* The first scenario: "unobtrusive" lock first, then "obtrusive". */
/* Acquire S lock, it will be acquired using "fast path" algorithm. */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/* Acquire global IX lock to be able acquire X lock later. */
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_global_request, long_timeout));
/*
Acquire X lock on the same table. MDL subsystem should be able to detect
that conflicting S lock belongs to the same context even though it was
was acquired using "fast path".
*/
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
m_mdl_context.release_transactional_locks();
/*
The second scenario: "obtrusive" lock first then "unobtrusive".
Let us try to acquire global S lock.
*/
MDL_REQUEST_INIT(&m_global_request, MDL_key::GLOBAL, "", "", MDL_SHARED,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_global_request, long_timeout));
/*
Now let us acquire IX lock. Note that S lock is not exactly stronger
than IX lock, so IX lock can't be acquired using find_ticket()
optimization.
*/
MDL_REQUEST_INIT(&m_global_request, MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_global_request, long_timeout));
m_mdl_context.release_transactional_locks();
}
/*
Verifies that we correctly account for "fast path" locks in
clone_ticket() operation.
*/
TEST_F(MDLTest, CloneSharedExclusive) {
MDL_ticket *initial_ticket;
Notification lock_blocked;
MDL_thread mdl_thread(table_name1, MDL_EXCLUSIVE, NULL, NULL, &lock_blocked,
NULL);
/* Acquire SHARED lock, it will be acquired using "fast path" algorithm. */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_EXPLICIT);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/*
Save initial ticket and create its clone.
This operation should correctly update MDL_lock's "fast path"
counter.
*/
initial_ticket = m_request.ticket;
EXPECT_FALSE(m_mdl_context.clone_ticket(&m_request));
/* Release the original lock. This will decrement "fast path" counter. */
m_mdl_context.release_lock(initial_ticket);
/*
Now try to acquire EXCLUSIVE lock. This request should be blocked
because "fast path" counter is non-zero.
*/
mdl_thread.start();
lock_blocked.wait_for_notification();
/* Release remaining SHARED lock to unblock request for EXCLUSIVE lock. */
m_mdl_context.release_lock(m_request.ticket);
mdl_thread.join();
}
/*
Verifies that we correctly account for "obtrusive" locks in
clone_ticket() operation.
*/
TEST_F(MDLTest, CloneExclusiveShared) {
MDL_ticket *initial_ticket;
Notification lock_blocked;
MDL_thread mdl_thread(table_name1, MDL_SHARED, NULL, NULL, &lock_blocked,
NULL);
/* Acquire EXCLUSIVE lock, counter of "obtrusive" locks is increased. */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_EXPLICIT);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/*
Save initial ticket and create its clone.
This operation should correctly increase counter of "obtrusive" locks.
*/
initial_ticket = m_request.ticket;
EXPECT_FALSE(m_mdl_context.clone_ticket(&m_request));
/*
Release the original lock. This will decrement count of "obtrusive" locks.
*/
m_mdl_context.release_lock(initial_ticket);
/*
Now try to acquire SHARED lock. This request should be blocked
because "obtrusive" counter still should be non-zero.
*/
mdl_thread.start();
lock_blocked.wait_for_notification();
/* Release remaining EXCLUSIVE lock to unblock request for SHARED lock. */
m_mdl_context.release_lock(m_request.ticket);
mdl_thread.join();
}
/**
Verify that we correctly notify owners of "unobtrusive" locks when "obtrusive"
locks are acquired, even though the former can be initially acquired
using "fast path".
*/
TEST_F(MDLTest, NotifyScenarios) {
/*
The first scenario: Check that notification works properly if for
"unobtrusive" lock owner set_needs_thr_lock_abort(true)
was called after lock acquisition.
*/
Notification first_shared_grabbed, first_shared_release;
Notification first_shared_released, first_exclusive_grabbed;
MDL_thread mdl_thread1(table_name1, MDL_SHARED, &first_shared_grabbed,
&first_shared_release, NULL, &first_shared_released);
MDL_thread mdl_thread2(table_name1, MDL_EXCLUSIVE, &first_exclusive_grabbed,
NULL, NULL, NULL);
/* Acquire S lock which will be granted using "fast path". */
mdl_thread1.enable_release_on_notify();
mdl_thread1.start();
first_shared_grabbed.wait_for_notification();
/*
In order for notification to work properly for such locks
context should be marked as requiring lock abort.
*/
mdl_thread1.get_mdl_context().set_needs_thr_lock_abort(true);
/*
Now try to acquire X lock. This attemptshould notify owner of S lock.
In our unit test such notification causes S lock release, so X lock
should be successfully granted after that.
*/
mdl_thread2.start();
first_shared_released.wait_for_notification();
first_exclusive_grabbed.wait_for_notification();
/* Wrap-up of the first scenario. */
mdl_thread1.join();
mdl_thread2.join();
/*
The second scenario: Check the same works fine when
set_needs_thr_lock_abort(true) was called before
lock acquisition.
*/
Notification second_shared_grabbed, second_shared_release;
Notification second_shared_released, second_exclusive_grabbed;
MDL_thread mdl_thread3(table_name1, MDL_SHARED, &second_shared_grabbed,
&second_shared_release, NULL, &second_shared_released);
MDL_thread mdl_thread4(table_name1, MDL_EXCLUSIVE, &second_exclusive_grabbed,
NULL, NULL, NULL);
/*
In order for notification to work properly context should be marked
as requiring lock abort.
*/
mdl_thread3.get_mdl_context().set_needs_thr_lock_abort(true);
mdl_thread3.enable_release_on_notify();
/* Acquire S lock which will be granted using "fast path". */
mdl_thread3.start();
second_shared_grabbed.wait_for_notification();
/*
Now try to acquire X lock. This attemptshould notify owner of S lock.
In our unit test such notification causes S lock release, so X lock
should be successfully granted after that.
*/
mdl_thread4.start();
second_shared_released.wait_for_notification();
second_exclusive_grabbed.wait_for_notification();
/* Wrap-up of the second scenario. */
mdl_thread3.join();
mdl_thread4.join();
}
/*
Verify that upgrade to "obtrusive" lock doesn't leave any wrong traces
in MDL subsystem.
*/
TEST_F(MDLTest, UpgradeScenarios) {
/*
The first scenario: Upgrade to X lock and then its release, should not
leave traces blocking further S locks.
*/
/* Acquire S lock to be upgraded. */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/* Acquire IX lock to be able to upgrade. */
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_global_request, long_timeout));
/* Upgrade S lock to X lock. */
EXPECT_FALSE(m_mdl_context.upgrade_shared_lock(m_request.ticket,
MDL_EXCLUSIVE, long_timeout));
/*
Ensure that there is pending S lock, so release of X lock won't destroy
MDL_lock object.
*/
Notification first_blocked;
Notification first_release;
MDL_thread mdl_thread1(table_name1, MDL_SHARED, NULL, &first_release,
&first_blocked, NULL);
mdl_thread1.start();
first_blocked.wait_for_notification();
/*
Now release IX and X locks.
The latter should clear "obtrusive" locks counter so it should be
possible to acquire S lock using "fast path" after that.
*/
m_mdl_context.release_transactional_locks();
/* Check that we can acquire S lock. */
Notification second_grabbed;
MDL_thread mdl_thread2(table_name1, MDL_SHARED, &second_grabbed, NULL, NULL,
NULL);
mdl_thread2.start();
second_grabbed.wait_for_notification();
/* Wrap-up of the first scenario. */
first_release.notify();
mdl_thread1.join();
mdl_thread2.join();
/*
The second scenario: Upgrade from S to X, should not block further X
requests if upgraded lock is released.
*/
/* Acquire S lock to be upgraded. */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/* Upgrade S lock to X lock. */
EXPECT_FALSE(m_mdl_context.upgrade_shared_lock(m_request.ticket,
MDL_EXCLUSIVE, long_timeout));
/*
Try to acquire X lock, it will block.
*/
Notification third_blocked;
Notification third_grabbed;
MDL_thread mdl_thread3(table_name1, MDL_EXCLUSIVE, &third_grabbed, NULL,
&third_blocked, NULL);
mdl_thread3.start();
third_blocked.wait_for_notification();
/*
Now release IX and X locks. Upgrade should not leave any traces
from original S lock (e.g. no "fast path" counter), so this should
unblock pending X lock.
*/
m_mdl_context.release_transactional_locks();
third_grabbed.wait_for_notification();
/* Wrap-up of the second scenario. */
mdl_thread3.join();
/*
The third scenario: After upgrade from SU to X lock and release of the
latter there should not be traces preventing "fast
path" acquisition of S lock. Main difference from
the first scenario is that we upgrade from "obtrusive"
lock, so different path is triggered.
*/
/* Acquire SU lock to be upgraded. */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_UPGRADABLE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/* Upgrade SU lock to X lock. */
EXPECT_FALSE(m_mdl_context.upgrade_shared_lock(m_request.ticket,
MDL_EXCLUSIVE, long_timeout));
/*
Ensure that there is pending S lock, so release of X lock won't destroy
MDL_lock object.
*/
Notification fourth_blocked;
Notification fourth_release;
MDL_thread mdl_thread4(table_name1, MDL_SHARED, NULL, &fourth_release,
&fourth_blocked, NULL);
mdl_thread4.start();
fourth_blocked.wait_for_notification();
/*
Now release IX and X locks.
The latter should clear "obtrusive" locks counter so it should be
possible to acquire S lock using "fast path" after that.
*/
m_mdl_context.release_transactional_locks();
/* Check that we can acquire S lock. */
Notification fifth_grabbed;
MDL_thread mdl_thread5(table_name1, MDL_SHARED, &fifth_grabbed, NULL, NULL,
NULL);
mdl_thread5.start();
fifth_grabbed.wait_for_notification();
/* Wrap-up of the third scenario. */
fourth_release.notify();
mdl_thread4.join();
mdl_thread5.join();
}
/**
Verify that MDL subsystem can correctly handle deadlock
in which one of participated locks was initially granted
using "fast path".
*/
TEST_F(MDLTest, Deadlock) {
Notification lock_blocked;
MDL_thread mdl_thread(table_name1, MDL_EXCLUSIVE, NULL, NULL, &lock_blocked,
NULL);
/* Acquire SR lock which will be granted using "fast path". */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_READ, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/* Now try to request X lock. This will blocked. */
mdl_thread.start();
lock_blocked.wait_for_notification();
/*
Try to acquire SW lock.
Because of pending X lock this will lead to deadlock.
Which should be correctly detected and reported even though SR
lock was originally granted using "fast path".
*/
expected_error = ER_LOCK_DEADLOCK;
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_WRITE, MDL_TRANSACTION);
EXPECT_TRUE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/* Wrap-up. */
m_mdl_context.release_transactional_locks();
mdl_thread.join();
}
/**
Verify that downgrade correctly handles "obtrusive" locks by decrementing
"obtrusive"-lock counter when necessary.
*/
TEST_F(MDLTest, DowngradeShared) {
Notification lock_grabbed;
MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, NULL, NULL,
NULL);
/* Acquire global IX lock first to satisfy MDL asserts. */
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_global_request, long_timeout));
/* Acquire "obtrusive" X lock. */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/* Downgrade lock to S lock. */
m_request.ticket->downgrade_lock(MDL_SHARED);
/*
It should be possible to acquire S lock now in another thread since
downgrade operation should decrement counter of "obtrusive" locks.
*/
mdl_thread.start();
lock_grabbed.wait_for_notification();
/* Wrap-up. */
m_mdl_context.release_transactional_locks();
mdl_thread.join();
}
/**
Verify that rescheduling of "obtrusive" lock correctly handles
"obtrusive" lock counter and further attempts to acquire "unobtrusive"
locks are not blocked.
*/
TEST_F(MDLTest, RescheduleSharedNoWrite) {
Notification shared_grabbed;
Notification shared_release;
Notification first_shared_write_grabbed;
Notification first_shared_write_release;
Notification shared_no_write_grabbed;
Notification shared_no_write_blocked;
Notification second_shared_write_grabbed;
MDL_thread mdl_thread1(table_name1, MDL_SHARED, &shared_grabbed,
&shared_release, NULL, NULL);
MDL_thread mdl_thread2(table_name1, MDL_SHARED_WRITE,
&first_shared_write_grabbed,
&first_shared_write_release, NULL, NULL);
MDL_thread mdl_thread3(table_name1, MDL_SHARED_NO_WRITE,
&shared_no_write_grabbed, NULL,
&shared_no_write_blocked, NULL);
MDL_thread mdl_thread4(table_name1, MDL_SHARED_WRITE,
&second_shared_write_grabbed, NULL, NULL, NULL);
/* Start thread which will acquire S lock. */
mdl_thread1.start();
shared_grabbed.wait_for_notification();
/* Start thread which will acquire SW lock. */
mdl_thread2.start();
first_shared_write_grabbed.wait_for_notification();
/* Now start thread which will try to acquire SNW lock and will block. */
mdl_thread3.start();
shared_no_write_blocked.wait_for_notification();
/*
Now we will release the first SW lock. This should schedule SNW lock.
In the process or rescheduling counter of waiting and granted "obtrusive"
locks should stay the same. So release of SNW, which happens after that,
will allow to acquire more SW locks.
*/
first_shared_write_release.notify();
/* It should be possible to acquire SW lock without problems now. */
mdl_thread4.start();
second_shared_write_grabbed.wait_for_notification();
/* Wrap-up. */
shared_release.notify();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
}
/**
Verify that try_acquire_lock() operation for lock correctly
cleans up after itself.
*/
TEST_F(MDLTest, ConcurrentSharedTryExclusive) {
Notification first_grabbed, second_grabbed, third_grabbed;
Notification first_release, second_release;
MDL_thread mdl_thread1(table_name1, MDL_SHARED, &first_grabbed,
&first_release, NULL, NULL);
MDL_thread mdl_thread2(table_name1, MDL_SHARED, &second_grabbed,
&second_release, NULL, NULL);
MDL_thread mdl_thread3(table_name1, MDL_SHARED, &third_grabbed, NULL, NULL,
NULL);
/* Start the first thread which will acquire S lock. */
mdl_thread1.start();
first_grabbed.wait_for_notification();
/* Acquire global IX lock to satisfy asserts in MDL subsystem. */
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_global_request, long_timeout));
EXPECT_NE(m_null_ticket, m_global_request.ticket);
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
/*
Attempt to acquire X lock on table should fail.
But it should correctly cleanup after itself.
*/
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
first_release.notify();
mdl_thread1.join();
/* After S lock is released. MDL_lock should be counted as unused. */
EXPECT_EQ(1, mdl_get_unused_locks_count());
/* Start the second thread which will acquire S lock again. */
mdl_thread2.start();
second_grabbed.wait_for_notification();
/* Attempt to acquire X lock on table should fail again. */
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
EXPECT_EQ(m_null_ticket, m_request.ticket);
/* Grabbing another S lock after that should not be a problem. */
mdl_thread3.start();
third_grabbed.wait_for_notification();
second_release.notify();
mdl_thread2.join();
mdl_thread3.join();
m_mdl_context.release_transactional_locks();
}
/**
Basic test which checks that unused MDL_lock objects are freed at all.
*/
TEST_F(MDLTest, UnusedBasic) {
mdl_locks_unused_locks_low_water = 0;
EXPECT_EQ(0, mdl_get_unused_locks_count());
test_one_simple_shared_lock(MDL_SHARED);
EXPECT_EQ(0, mdl_get_unused_locks_count());
}
/**
More complex test in which we test freeing couple of unused
MDL_lock objects while having another one still used and around.
*/
TEST_F(MDLTest, UnusedConcurrentThree) {
Notification first_grabbed, first_release, second_grabbed, second_release,
third_grabbed, third_release;
MDL_thread mdl_thread1(table_name1, MDL_SHARED, &first_grabbed,
&first_release, NULL, NULL);
MDL_thread mdl_thread2(table_name2, MDL_SHARED, &second_grabbed,
&second_release, NULL, NULL);
MDL_thread mdl_thread3(table_name3, MDL_SHARED_UPGRADABLE, &third_grabbed,
&third_release, NULL, NULL);
mdl_locks_unused_locks_low_water = 0;
/* Start thread which will use one MDL_lock object and will keep it used. */
mdl_thread1.start();
first_grabbed.wait_for_notification();
EXPECT_EQ(0, mdl_get_unused_locks_count());
/*
Start thread which will acquire lock on another table, i.e. will use
another MDL_lock object.
*/
mdl_thread2.start();
second_grabbed.wait_for_notification();
EXPECT_EQ(0, mdl_get_unused_locks_count());
/* Now let 2nd thread to release its lock and thus unuse MDL_lock object. */
second_release.notify();
mdl_thread2.join();
EXPECT_EQ(0, mdl_get_unused_locks_count());
/*
Start 3rd thread which will acquire "slow path" lock and release it.
By doing this we test freeing of unused on "slow path" release.
*/
mdl_thread3.start();
third_grabbed.wait_for_notification();
EXPECT_EQ(0, mdl_get_unused_locks_count());
/* Now let 3nd thread to release its lock and thus unuse MDL_lock object. */
third_release.notify();
mdl_thread3.join();
EXPECT_EQ(0, mdl_get_unused_locks_count());
/*
Let the 1st thread to release its lock and unuse its MDL_lock object
as well.
*/
first_release.notify();
mdl_thread1.join();
EXPECT_EQ(0, mdl_get_unused_locks_count());
}
/**
Finally test which involves many threads using, unusing and
freeing MDL_lock objects.
*/
TEST_F(MDLTest, UnusedConcurrentMany) {
const uint THREADS = 50, TABLES = 10;
const char *table_names_group_a[TABLES] = {"0", "1", "2", "3", "4",
"5", "6", "7", "8", "9"};
const char *table_names_group_b[TABLES] = {"a", "b", "c", "d", "e",
"f", "g", "h", "i", "j"};
MDL_thread *mdl_thread_group_a[THREADS];
MDL_thread *mdl_thread_group_b[THREADS];
Notification group_a_grabbed[THREADS], group_a_release,
group_b_grabbed[THREADS], group_b_release;
uint i;
for (i = 0; i < THREADS; ++i)
mdl_thread_group_a[i] = new MDL_thread(
table_names_group_a[i % TABLES],
/*
To make things more interesting for the
first 2 tables in the group on of threads
will also acquire SU lock.
*/
((i % TABLES < 2) && (i % TABLES == i)) ? MDL_SHARED_UPGRADABLE
: MDL_SHARED,
&group_a_grabbed[i], &group_a_release, NULL, NULL);
for (i = 0; i < THREADS; ++i)
mdl_thread_group_b[i] = new MDL_thread(
table_names_group_b[i % TABLES],
/*
To make things more interesting for the
first 2 tables in the group on of threads
will also acquire SU lock.
*/
((i % TABLES < 2) && (i % TABLES == i)) ? MDL_SHARED_UPGRADABLE
: MDL_SHARED,
&group_b_grabbed[i], &group_b_release, NULL, NULL);
mdl_locks_unused_locks_low_water = 0;
/* Start both groups of threads. */
for (i = 0; i < THREADS; ++i) mdl_thread_group_a[i]->start();
for (i = 0; i < THREADS; ++i) mdl_thread_group_b[i]->start();
/* Wait until both groups acquire their locks. */
for (i = 0; i < THREADS; ++i) group_a_grabbed[i].wait_for_notification();
for (i = 0; i < THREADS; ++i) group_b_grabbed[i].wait_for_notification();
EXPECT_EQ(0, mdl_get_unused_locks_count());
/*
Now let the second group to release its locks and free unused MDL_lock
objects. The first group will keep its objects used.
*/
group_b_release.notify();
/* Wait until it is done. */
for (i = 0; i < THREADS; ++i) {
mdl_thread_group_b[i]->join();
delete mdl_thread_group_b[i];
}
/*
At this point we should not have more than
TABLES*MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO/(1-MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO
unused objects. It is OK to have less.
*/
EXPECT_GE((TABLES * MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO /
(1 - MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO)),
mdl_get_unused_locks_count());
/* Now let the first group go as well. */
group_a_release.notify();
for (i = 0; i < THREADS; ++i) {
mdl_thread_group_a[i]->join();
delete mdl_thread_group_a[i];
}
EXPECT_EQ(0, mdl_get_unused_locks_count());
}
/**
Test that unused locks low water threshold works as expected,
i.e. that we don't free unused objects unless their number
exceeds threshold.
*/
TEST_F(MDLTest, UnusedLowWater) {
const uint TABLES = 10;
const char *table_names[TABLES] = {"0", "1", "2", "3", "4",
"5", "6", "7", "8", "9"};
MDL_request request;
uint i;
mdl_locks_unused_locks_low_water = 4;
EXPECT_EQ(0, mdl_get_unused_locks_count());
/* Acquire some locks and then release them all at once. */
for (i = 0; i < TABLES; ++i) {
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_names[i],
MDL_SHARED, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
}
EXPECT_EQ(0, mdl_get_unused_locks_count());
/* Release all locks. */
m_mdl_context.release_transactional_locks();
/* Number of unused lock objects should not go below threshold. */
EXPECT_EQ(4, mdl_get_unused_locks_count());
}
/**
Test that we don't free unused MDL_lock objects if unused/total objects
ratio is below MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO.
*/
TEST_F(MDLTest, UnusedMinRatio) {
const uint TABLES = 10;
const char *table_names_a[TABLES] = {"0", "1", "2", "3", "4",
"5", "6", "7", "8", "9"};
const char *table_names_b[TABLES] = {"0", "1", "2", "3", "4",
"5", "6", "7", "8", "9"};
MDL_request request;
uint i;
mdl_locks_unused_locks_low_water = 0;
EXPECT_EQ(0, mdl_get_unused_locks_count());
/* Acquire some locks. */
for (i = 0; i < TABLES; ++i) {
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_names_a[i],
MDL_SHARED, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
}
EXPECT_EQ(0, mdl_get_unused_locks_count());
/* Take a savepoint to be able to release part of the locks in future. */
MDL_savepoint savepoint = m_mdl_context.mdl_savepoint();
/* Acquire a few more locks. */
for (i = 0; i < TABLES; ++i) {
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_names_b[i],
MDL_SHARED, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
}
/* Now release part of the locks we hold. */
m_mdl_context.rollback_to_savepoint(savepoint);
/*
At this point we should not have more than
TABLES*MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO/(1-MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO
unused objects.
This is equivalent to:
"unused objects" <= (("used objects (i.e.TABLES)" + "unused objects") *
MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO).
*/
EXPECT_GE((TABLES * MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO /
(1 - MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO)),
mdl_get_unused_locks_count());
/* Release all locks. */
m_mdl_context.release_transactional_locks();
/*
There should be no unused MDL_lock objects after this
since low water threshold is zero.
*/
EXPECT_EQ(0, mdl_get_unused_locks_count());
}
/*
Verifies following scenario,
After granting max_write_lock_count(=1) number of times for SW
lock request, lock is granted to starving SRO lock request
in wait queue. After SRO lock has been granted SW locks again
get higher priority than SRO locks until max_write_lock_count(=1)
SW lock requests has been satisfied.
- max_write_lock_count= 1
- THREAD 1: Acquires SW lock on the table.
- THREAD 2: Requests for SRO lock on the table, blocked.
- THREAD 3: Acquires SW lock on the table (m_piglet_lock_count= 1).
- THREAD 4: Requests for SW on the table, blocked.
- THREAD 5: Requests for SRO lock on the table, blocked.
- THREAD 1,3: Release SW locks.
- THREAD 2,5: Get SRO locks, (m_piglet_lock_count= 0)
THREAD 6: Requests SRO lock, blocked.
- THREAD 2,5: Release SRO locks.
- THREAD 4: Gets SW lock (m_piglet_lock_count= 1).
- THREAD 7: Requests SW lock, blocked.
- THREAD 4: Releases SW lock.
- THREAD 6: Gets SRO lock. Releases it.
- THREAD 7: Gets SW lock.
*/
TEST_F(MDLTest, PigletLockTest) {
Notification thd_lock_grabbed[7];
Notification thd_release_locks[7];
Notification thd_lock_blocked[7];
Notification thd_lock_released[7];
/* Locks taken by the threads */
enum { THD1_SW, THD2_SRO, THD3_SW, THD4_SW, THD5_SRO, THD6_SRO, THD7_SW };
max_write_lock_count = 1;
/*
THREAD1: Acquires SW lock on table.
Lock Wait Queue: <empty>
Lock Granted: SW
*/
MDL_thread mdl_thread1(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD1_SW],
&thd_release_locks[THD1_SW], &thd_lock_blocked[THD1_SW],
&thd_lock_released[THD1_SW]);
mdl_thread1.start();
thd_lock_grabbed[THD1_SW].wait_for_notification();
/*
THREAD2: Requesting SRO lock on table.
Lock Wait Queue: SRO
Lock Granted: SW
*/
MDL_thread mdl_thread2(
table_name1, MDL_SHARED_READ_ONLY, &thd_lock_grabbed[THD2_SRO],
&thd_release_locks[THD2_SRO], &thd_lock_blocked[THD2_SRO],
&thd_lock_released[THD2_SRO]);
mdl_thread2.start();
thd_lock_blocked[THD2_SRO].wait_for_notification();
/*
THREAD3: Acquires SW lock on table. m_piglet_lock_count becomes 1.
Lock Wait Queue: SRO
Lock Granted: SW, SW
*/
MDL_thread mdl_thread3(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD3_SW],
&thd_release_locks[THD3_SW], &thd_lock_blocked[THD3_SW],
&thd_lock_released[THD3_SW]);
mdl_thread3.start();
thd_lock_grabbed[THD3_SW].wait_for_notification();
/*
THREAD4: Requesting SW lock on table.
Blocks because m_piglet_lock_count == max_write_lock_count.
Lock Wait Queue: SRO <-- SW
Lock Granted: SW, SW
m_piglet_lock_count == 1
*/
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD4_SW],
&thd_release_locks[THD4_SW], &thd_lock_blocked[THD4_SW],
&thd_lock_released[THD4_SW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SW].wait_for_notification();
/*
THREAD 5: Requests SRO lock on the table.
Lock Wait Queue: SRO <-- SW <--SRO
Lock Granted: SW, SW
m_piglet_lock_count == 1
*/
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_READ_ONLY, &thd_lock_grabbed[THD5_SRO],
&thd_release_locks[THD5_SRO], &thd_lock_blocked[THD5_SRO],
&thd_lock_released[THD5_SRO]);
mdl_thread5.start();
thd_lock_blocked[THD5_SRO].wait_for_notification();
/*
THREAD 1, 3: Release SW locks. Both SRO locks are granted since
m_piglet_lock_count == max_write_lock_count. After that
m_piglet_lock_count is reset to 0.
Lock Wait Queue: SW
Lock Granted: SRO, SRO
*/
thd_release_locks[THD1_SW].notify();
thd_lock_released[THD1_SW].wait_for_notification();
thd_release_locks[THD3_SW].notify();
thd_lock_released[THD3_SW].wait_for_notification();
/* Locks are granted to THREAD 2 and THREAD 5 */
thd_lock_grabbed[THD2_SRO].wait_for_notification();
thd_lock_grabbed[THD5_SRO].wait_for_notification();
/*
THREAD 6: Requests SRO lock on the table.
Blocked because m_piglet_lock_count == 0.
Lock Wait Queue: SW <-- SRO
Lock Granted: SRO, SRO
*/
MDL_thread mdl_thread6(
table_name1, MDL_SHARED_READ_ONLY, &thd_lock_grabbed[THD6_SRO],
&thd_release_locks[THD6_SRO], &thd_lock_blocked[THD6_SRO],
&thd_lock_released[THD6_SRO]);
mdl_thread6.start();
thd_lock_blocked[THD6_SRO].wait_for_notification();
/* THREAD 2 and THREAD 5: Release SRO locks */
thd_release_locks[THD2_SRO].notify();
thd_lock_released[THD2_SRO].wait_for_notification();
thd_release_locks[THD5_SRO].notify();
thd_lock_released[THD5_SRO].wait_for_notification();
/*
THREAD 4: Gets SW lock.
Since there is pending SRO, m_piglet_lock_count becomes 1.
Lock Wait Queue: SRO
Lock Granted: SW
*/
thd_lock_grabbed[THD4_SW].wait_for_notification();
/*
THREAD 7: Requests SW lock on the table.
Blocked because m_piglet_lock_count == max_write_lock_count.
Lock Wait Queue: SRO <-- SW
Lock Granted: SW
*/
MDL_thread mdl_thread7(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD7_SW],
&thd_release_locks[THD7_SW], &thd_lock_blocked[THD7_SW],
&thd_lock_released[THD7_SW]);
mdl_thread7.start();
thd_lock_blocked[THD7_SW].wait_for_notification();
/* THREAD 4: Release SW lock */
thd_release_locks[THD4_SW].notify();
thd_lock_released[THD4_SW].wait_for_notification();
/* THREAD 6: Gets SRO lock. */
thd_lock_grabbed[THD6_SRO].wait_for_notification();
/* Cleanup */
thd_release_locks[THD6_SRO].notify();
thd_lock_released[THD6_SRO].wait_for_notification();
thd_lock_grabbed[THD7_SW].wait_for_notification();
thd_release_locks[THD7_SW].notify();
thd_lock_released[THD7_SW].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
mdl_thread6.join();
mdl_thread7.join();
}
/*
Verifies interaction of "piglet" and "hog" lock requests.
Check situation when we first reach limit on successive grants
of "piglet" and then "hog" locks. Notice that once both these
limits are reached we give a way to SRO locks and reset both
counters even though there are still pending SW locks. This is
allows to avoid stream of concurrent SRO and SW starving out
"hog" locks.
- max_write_lock_count= 1
- THREAD 1: Acquires SW lock on the table.
- THREAD 2: Requests for SRO lock on the table, blocked.
- THREAD 3: Acquires SW lock on the table (m_piglet_lock_count= 1).
- THREAD 4: Requests for SW on the table, blocked.
- THREAD 5: Requests for SNRW lock on the table, blocked.
- THREAD 6: Requests for SRO lock on the table, blocked.
- THREAD 7: Requests for SNRW lock on the table, blocked.
- THREAD 8: Requests for SW lock on the table, blocked.
- THREAD 1, 3: Release SW locks.
- THREAD 5: Gets SNRW lock on the table (m_hog_lock_count= 1).
- THREAD 5: Releases SNRW lock.
- THREAD 2, 6: Get SRO locks on the table. (m_piglet_lock_count= 0,
m_hog_lock_count= 0)
- THREAD 9: Requests for SRO lock on the table, blocked.
- THREAD 2, 6: Release SRO locks on the table.
- THREAD 7: Gets SNRW lock (m_hog_lock_count= 1).
- THREAD 7: Releases SNRW lock.
- THREAD 4: Gets SW lock on the table. (m_piglet_lock_count= 1)
- THREAD 4: Releases SW lock.
- THREAD 9: Gets SRO lock. (m_piglet_lock_count= 0,
m_hog_lock_count= 0).
- THREAD 9: Releases SRO lock.
- THREAD 8: Gets SW lock.
*/
TEST_F(MDLTest, PigletThenHogLockTest) {
Notification thd_lock_grabbed[9];
Notification thd_release_locks[9];
Notification thd_lock_blocked[9];
Notification thd_lock_released[9];
/* Locks taken by the threads */
enum {
THD1_SW,
THD2_SRO,
THD3_SW,
THD4_SW,
THD5_SNRW,
THD6_SRO,
THD7_SNRW,
THD8_SW,
THD9_SRO
};
max_write_lock_count = 1;
/*
THREAD1: Acquires SW lock on table.
Lock Wait Queue: <empty>
Lock Granted: SW
*/
MDL_thread mdl_thread1(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD1_SW],
&thd_release_locks[THD1_SW], &thd_lock_blocked[THD1_SW],
&thd_lock_released[THD1_SW]);
mdl_thread1.start();
thd_lock_grabbed[THD1_SW].wait_for_notification();
/*
THREAD2: Requesting SRO lock on table.
Lock Wait Queue: SRO
Lock Granted: SW
*/
MDL_thread mdl_thread2(
table_name1, MDL_SHARED_READ_ONLY, &thd_lock_grabbed[THD2_SRO],
&thd_release_locks[THD2_SRO], &thd_lock_blocked[THD2_SRO],
&thd_lock_released[THD2_SRO]);
mdl_thread2.start();
thd_lock_blocked[THD2_SRO].wait_for_notification();
/*
THREAD3: Acquires SW lock on table. m_piglet_lock_count becomes 1.
Lock Wait Queue: SRO
Lock Granted: SW, SW
*/
MDL_thread mdl_thread3(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD3_SW],
&thd_release_locks[THD3_SW], &thd_lock_blocked[THD3_SW],
&thd_lock_released[THD3_SW]);
mdl_thread3.start();
thd_lock_grabbed[THD3_SW].wait_for_notification();
/*
THREAD4: Requesting SW lock on table.
Blocks because m_piglet_lock_count == max_write_lock_count.
Lock Wait Queue: SRO <-- SW
Lock Granted: SW, SW
m_piglet_lock_count == 1
*/
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD4_SW],
&thd_release_locks[THD4_SW], &thd_lock_blocked[THD4_SW],
&thd_lock_released[THD4_SW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SW].wait_for_notification();
/*
THREAD 5: Requests SNRW lock on the table.
Lock Wait Queue: SRO <-- SW <-- SNRW
Lock Granted: SW, SW
m_piglet_lock_count == 1
*/
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD5_SNRW],
&thd_release_locks[THD5_SNRW], &thd_lock_blocked[THD5_SNRW],
&thd_lock_released[THD5_SNRW]);
mdl_thread5.start();
thd_lock_blocked[THD5_SNRW].wait_for_notification();
/*
THREAD 6: Requests SRO lock on the table.
Blocked because m_hog_lock_count == 0.
Lock Wait Queue: SRO <-- SW <-- SNRW <-- SRO
Lock Granted: SW, SW
m_piglet_lock_count == 1
*/
MDL_thread mdl_thread6(
table_name1, MDL_SHARED_READ_ONLY, &thd_lock_grabbed[THD6_SRO],
&thd_release_locks[THD6_SRO], &thd_lock_blocked[THD6_SRO],
&thd_lock_released[THD6_SRO]);
mdl_thread6.start();
thd_lock_blocked[THD6_SRO].wait_for_notification();
/*
THREAD 7: Requests SNRW lock on the table.
Lock Wait Queue: SRO <-- SW <-- SNRW <-- SRO <-- SNRW
Lock Granted: SW, SW
m_piglet_lock_count == 1
*/
MDL_thread mdl_thread7(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD7_SNRW],
&thd_release_locks[THD7_SNRW], &thd_lock_blocked[THD7_SNRW],
&thd_lock_released[THD7_SNRW]);
mdl_thread7.start();
thd_lock_blocked[THD7_SNRW].wait_for_notification();
/*
THREAD 8: Requests SW lock on the table.
Blocked because of pending SRO and SNRW locks.
Lock Wait Queue: SRO <-- SW <-- SNRW <-- SRO <-- SNRW <-- SW
Lock Granted: SW, SW
m_piglet_lock_count == 1
*/
MDL_thread mdl_thread8(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD8_SW],
&thd_release_locks[THD8_SW], &thd_lock_blocked[THD8_SW],
&thd_lock_released[THD8_SW]);
mdl_thread8.start();
thd_lock_blocked[THD8_SW].wait_for_notification();
/*
THREAD 1, 3: Release SW locks. The first SNRW lock is granted.
Lock Wait Queue: SRO <-- SW <-- SRO <-- SNRW <-- SW
Lock Granted: SNRW
m_piglet_lock_count == 1
m_hog_lock_count == 1
*/
thd_release_locks[THD1_SW].notify();
thd_lock_released[THD1_SW].wait_for_notification();
thd_release_locks[THD3_SW].notify();
thd_lock_released[THD3_SW].wait_for_notification();
/* Lock is granted to THREAD 5 */
thd_lock_grabbed[THD5_SNRW].wait_for_notification();
/*
THREAD 5: Release SNRW lock.
Since m_piglet_lock_count == 1 and m_hog_lock_count == 1 this
ublocks SRO locks.
*/
thd_release_locks[THD5_SNRW].notify();
thd_lock_released[THD5_SNRW].wait_for_notification();
/*
THREAD 2,6: Get SRO locks.
m_piglet_lock_count and m_hog_lock_count are reset to 0.
Lock Wait Queue: SW <-- SNRW <-- SW
Lock Granted: SRO, SRO
m_piglet_lock_count == 0
m_hog_lock_count == 0
*/
thd_lock_grabbed[THD2_SRO].wait_for_notification();
thd_lock_grabbed[THD6_SRO].wait_for_notification();
/*
THREAD 9: Requests SRO lock on the table.
Blocked because m_piglet_lock_count == 0 and m_hog_lock_count == 0.
Lock Wait Queue: SW <-- SNRW <-- SW <-- SRO
Lock Granted: SRO, SRO
m_piglet_lock_count == 0
m_hog_lock_count == 0
*/
MDL_thread mdl_thread9(
table_name1, MDL_SHARED_READ_ONLY, &thd_lock_grabbed[THD9_SRO],
&thd_release_locks[THD9_SRO], &thd_lock_blocked[THD9_SRO],
&thd_lock_released[THD9_SRO]);
mdl_thread9.start();
thd_lock_blocked[THD9_SRO].wait_for_notification();
/*
THREAD 2,6: Release SRO locks.
Since m_piglet_lock_count == 0 and m_hog_lock_count == 0 this
unblocks SNRW lock request,
*/
thd_release_locks[THD2_SRO].notify();
thd_lock_released[THD2_SRO].wait_for_notification();
thd_release_locks[THD6_SRO].notify();
thd_lock_released[THD6_SRO].wait_for_notification();
/*
THREAD 7: Gets SNRW lock.
m_hog_lock_count is set to 1 since there is pending SW and SRO locks.
Lock Wait Queue: SW <-- SW <-- SRO
Lock Granted: SNW
m_piglet_lock_count == 0
m_hog_lock_count == 1
*/
thd_lock_grabbed[THD7_SNRW].wait_for_notification();
/* THREAD 7: Releases SNRW lock. This will unblock one of SW locks. */
thd_release_locks[THD7_SNRW].notify();
thd_lock_released[THD7_SNRW].wait_for_notification();
/*
THREAD 4: Gets SW locks.
m_piglet_lock_count is set to 1 since there is pending SRO lock.
Lock Wait Queue: SW <-- SRO
Lock Granted: SW
m_piglet_lock_count == 1
m_hog_lock_count == 0
*/
thd_lock_grabbed[THD4_SW].wait_for_notification();
/*
THREAD 4: Release SW locks.
Since m_piglet_lock_count == 1 this unblocks SRO lock request,
*/
thd_release_locks[THD4_SW].notify();
thd_lock_released[THD4_SW].wait_for_notification();
/*
THREAD 9: Gets SRO lock.
m_piglet_lock_count is reset to 0.
Lock Wait Queue: SW
Lock Granted: SRO
m_piglet_lock_count == 0
m_hog_lock_count == 0
*/
thd_lock_grabbed[THD9_SRO].wait_for_notification();
/*
THREAD 9: Release SRO lock.
Since m_hog_lock_count == 1 this unblocks SW lock request.
*/
thd_release_locks[THD9_SRO].notify();
thd_lock_released[THD9_SRO].wait_for_notification();
/* THREAD 8: Gets SW lock. */
thd_lock_grabbed[THD8_SW].wait_for_notification();
/* Cleanup */
thd_release_locks[THD8_SW].notify();
thd_lock_released[THD8_SW].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
mdl_thread6.join();
mdl_thread7.join();
mdl_thread8.join();
mdl_thread9.join();
}
/*
Another test for interaction of "piglet" and "hog" lock requests.
Check situation when we first reach limit on successive grants
of "hog" and then "piglet" locks. Again once both these limits
are reached we give a way to SRO locks and reset both counters
even though there are still pending SW locks in order to avoid
starvation of "hog" requests.
- max_write_lock_count= 1
- THREAD 1: Acquires SW lock on the table.
- THREAD 2: Requests for SNRW lock on the table, blocked.
- THREAD 3: Requests for SW on the table, blocked.
- THREAD 1: Releases SW lock.
- THREAD 2: Gets SNRW lock (m_hog_lock_count= 1).
- THREAD 4: Requests for SNRW lock on the table, blocked.
- THREAD 5: Requests for SRO lock on the table, blocked.
- THREAD 2: Releases SNRW lock.
- THREAD 3: Gets SW lock (m_piglet_lock_count= 1).
- THREAD 6: Requests for SW lock on the table, blocked.
- THREAD 3: Releases SW lock.
- THREAD 5: Gets SRO lock (m_piglet_lock_count=0, m_hog_lock_count= 0).
- THREAD 7: Requests SR lock on the table, blocked.
- THREAD 5: Releases SRO lock.
- THREAD 4: Gets SNRW lock (m_hog_lock_count= 1)
- THREAD 4: Releases SNRW lock.
- THREAD 6,7: Get SW and SR locks.
*/
TEST_F(MDLTest, HogThenPigletLockTest) {
Notification thd_lock_grabbed[7];
Notification thd_release_locks[7];
Notification thd_lock_blocked[7];
Notification thd_lock_released[7];
/* Locks taken by the threads */
enum { THD1_SW, THD2_SNRW, THD3_SW, THD4_SNRW, THD5_SRO, THD6_SW, THD7_SR };
max_write_lock_count = 1;
/*
THREAD1: Acquires SW lock on table.
Lock Wait Queue: <empty>
Lock Granted: SW
*/
MDL_thread mdl_thread1(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD1_SW],
&thd_release_locks[THD1_SW], &thd_lock_blocked[THD1_SW],
&thd_lock_released[THD1_SW]);
mdl_thread1.start();
thd_lock_grabbed[THD1_SW].wait_for_notification();
/*
THREAD2: Requesting SNRW lock on table.
Lock Wait Queue: SNRW
Lock Granted: SW
*/
MDL_thread mdl_thread2(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD2_SNRW],
&thd_release_locks[THD2_SNRW], &thd_lock_blocked[THD2_SNRW],
&thd_lock_released[THD2_SNRW]);
mdl_thread2.start();
thd_lock_blocked[THD2_SNRW].wait_for_notification();
/*
THREAD3: Requesting SW lock on table.
Lock Wait Queue: SNRW <-- SW
Lock Granted: SW
*/
MDL_thread mdl_thread3(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD3_SW],
&thd_release_locks[THD3_SW], &thd_lock_blocked[THD3_SW],
&thd_lock_released[THD3_SW]);
mdl_thread3.start();
thd_lock_blocked[THD3_SW].wait_for_notification();
/*
THREAD 1: Release SW lock. The SNRW lock is granted.
m_hog_lock_count is set to 1.
Lock Wait Queue: SW
Lock Granted: SNRW
m_piglet_lock_count == 0
m_hog_lock_count == 1
*/
thd_release_locks[THD1_SW].notify();
thd_lock_released[THD1_SW].wait_for_notification();
/* THREAD 2: Gets SNRW. */
thd_lock_grabbed[THD2_SNRW].wait_for_notification();
/*
THREAD4: Requesting SNRW lock on table.
Blocks because m_hog_lock_count == 1.
Lock Wait Queue: SW <-- SNRW
Lock Granted: SNRW
m_piglet_lock_count == 0
m_hog_lock_count == 1
*/
MDL_thread mdl_thread4(
table_name1, MDL_SHARED_NO_READ_WRITE, &thd_lock_grabbed[THD4_SNRW],
&thd_release_locks[THD4_SNRW], &thd_lock_blocked[THD4_SNRW],
&thd_lock_released[THD4_SNRW]);
mdl_thread4.start();
thd_lock_blocked[THD4_SNRW].wait_for_notification();
/*
THREAD 5: Requests SRO lock on the table.
Lock Wait Queue: SW <-- SNRW <-- SRO
Lock Granted: SNRW
m_piglet_lock_count == 0
m_hog_lock_count == 1
*/
MDL_thread mdl_thread5(
table_name1, MDL_SHARED_READ_ONLY, &thd_lock_grabbed[THD5_SRO],
&thd_release_locks[THD5_SRO], &thd_lock_blocked[THD5_SRO],
&thd_lock_released[THD5_SRO]);
mdl_thread5.start();
thd_lock_blocked[THD5_SRO].wait_for_notification();
/*
THREAD 2: Releases SNRW lock. This unblocks SW lock
since m_hog_lock_count == 1.
*/
thd_release_locks[THD2_SNRW].notify();
thd_lock_released[THD2_SNRW].wait_for_notification();
/*
THREAD 3: Gets SW lock on the table.
m_piglet_lock_count is set to 1.
Lock Wait Queue: SNRW <-- SRO
Lock Granted: SW
m_piglet_lock_count == 1
m_hog_lock_count == 1
*/
thd_lock_grabbed[THD3_SW].wait_for_notification();
/*
THREAD 6: Requests SW lock on the table.
Blocked because m_piglet_lock_count == 1.
Lock Wait Queue: SNRW <-- SRO <-- SW
Lock Granted: SW
m_piglet_lock_count == 1
m_hog_lock_count == 1
*/
MDL_thread mdl_thread6(
table_name1, MDL_SHARED_WRITE, &thd_lock_grabbed[THD6_SW],
&thd_release_locks[THD6_SW], &thd_lock_blocked[THD6_SW],
&thd_lock_released[THD6_SW]);
mdl_thread6.start();
thd_lock_blocked[THD6_SW].wait_for_notification();
/*
THREAD 3: Releases SW lock. SRO lock is granted.
Both m_piglet_lock_count and m_hog_lock_count are reset to 0.
Lock Wait Queue: SNRW <-- SW
Lock Granted: SRO
m_piglet_lock_count == 0
m_hog_lock_count == 0
*/
thd_release_locks[THD3_SW].notify();
thd_lock_released[THD3_SW].wait_for_notification();
/* THREAD 5: Gets SRO lock. */
thd_lock_grabbed[THD5_SRO].wait_for_notification();
/*
THREAD 7: Requests SR lock on the table. Blocked.
Lock Wait Queue: SNRW <-- SW <-- SR
Lock Granted: SRO
m_piglet_lock_count == 0
m_hog_lock_count == 0
*/
MDL_thread mdl_thread7(
table_name1, MDL_SHARED_READ, &thd_lock_grabbed[THD7_SR],
&thd_release_locks[THD7_SR], &thd_lock_blocked[THD7_SR],
&thd_lock_released[THD7_SR]);
mdl_thread7.start();
thd_lock_blocked[THD7_SR].wait_for_notification();
/* THREAD 5: Releases SRO lock. */
thd_release_locks[THD5_SRO].notify();
thd_lock_released[THD5_SRO].wait_for_notification();
/*
THREAD 4: Gets SNRW lock. m_hog_lock_count is set to 1.
Lock Wait Queue: SW <-- SR
Lock Granted: SNRW
m_piglet_lock_count == 0
m_hog_lock_count == 1
*/
thd_lock_grabbed[THD4_SNRW].wait_for_notification();
/* THREAD 4: Releases SNRW lock. */
thd_release_locks[THD4_SNRW].notify();
thd_lock_released[THD4_SNRW].wait_for_notification();
/* THREAD 6, 8: Get SW and SR locks. */
thd_lock_grabbed[THD6_SW].wait_for_notification();
thd_lock_grabbed[THD7_SR].wait_for_notification();
/* Cleanup */
thd_release_locks[THD6_SW].notify();
thd_lock_released[THD6_SW].wait_for_notification();
thd_release_locks[THD7_SR].notify();
thd_lock_released[THD7_SR].wait_for_notification();
mdl_thread1.join();
mdl_thread2.join();
mdl_thread3.join();
mdl_thread4.join();
mdl_thread5.join();
mdl_thread6.join();
mdl_thread7.join();
}
/**
Auxiliary thread class which simulates connection which does
LOCK TABLES t1 WRITE, t1 READ/UNLOCK TABLES in a loop.
*/
class MDL_SRO_SNRW_thread : public Thread, public Test_MDL_context_owner {
public:
MDL_SRO_SNRW_thread() { m_mdl_context.init(this); }
~MDL_SRO_SNRW_thread() { m_mdl_context.destroy(); }
virtual void run();
virtual void notify_shared_lock(MDL_context_owner *, bool) {}
private:
MDL_context m_mdl_context;
};
void MDL_SRO_SNRW_thread::run() {
for (int i = 0; i < 100; ++i) {
MDL_request request1, request2;
MDL_request_list request_list;
MDL_REQUEST_INIT(&request1, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_NO_READ_WRITE, MDL_TRANSACTION);
MDL_REQUEST_INIT(&request2, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_READ_ONLY, MDL_TRANSACTION);
/*
Simulate LOCK TABLES t1 WRITE, t1 READ, by putting SRO lock
to the front of the list and SNRW to its back.
*/
request_list.push_front(&request1);
request_list.push_front(&request2);
/*
Lock acquisition should succeed and never deadlock. I.e. we should
always acquire SNRW first and only then SRO.
*/
EXPECT_FALSE(m_mdl_context.acquire_locks(&request_list, long_timeout));
m_mdl_context.release_transactional_locks();
}
}
/*
Check that MDL_context::acquire_locks() takes type of lock requested
into account when it sorts requests in order to avoid/reduce number
of deadlocks.
Also provides additional coverage for case when we concurrently create
and destroy MDL_lock instances for the same object.
*/
TEST_F(MDLTest, AcquireLocksTypeOrder) {
MDL_SRO_SNRW_thread thread1, thread2;
/* Force immediate destruction of unused MDL_lock object. */
mdl_locks_unused_locks_low_water = 0;
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
/**
Thread class for testing MDL_context::set_force_dml_deadlock_weight()
method.
*/
class MDL_weight_thread : public Thread, public Test_MDL_context_owner {
public:
MDL_weight_thread(Notification *first_grabbed, Notification *go_for_second,
Notification *lock_blocked)
: m_first_grabbed(first_grabbed),
m_go_for_second(go_for_second),
m_lock_blocked(lock_blocked) {
m_mdl_context.init(this);
}
~MDL_weight_thread() { m_mdl_context.destroy(); }
virtual void run();
virtual void notify_shared_lock(MDL_context_owner *, bool) {}
virtual void enter_cond(mysql_cond_t *cond, mysql_mutex_t *mutex,
const PSI_stage_info *stage,
PSI_stage_info *old_stage, const char *src_function,
const char *src_file, int src_line) {
Test_MDL_context_owner::enter_cond(cond, mutex, stage, old_stage,
src_function, src_file, src_line);
m_lock_blocked->notify();
return;
}
private:
MDL_context m_mdl_context;
Notification *m_first_grabbed;
Notification *m_go_for_second;
Notification *m_lock_blocked;
};
void MDL_weight_thread::run() {
MDL_request request1, request2;
MDL_REQUEST_INIT(&request1, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_NO_READ_WRITE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request1, long_timeout));
m_first_grabbed->notify();
m_go_for_second->wait_for_notification();
MDL_REQUEST_INIT(&request2, MDL_key::TABLE, db_name, table_name2,
MDL_SHARED_NO_READ_WRITE, MDL_TRANSACTION);
/* Mark current thread as preferred deadlock victim. */
m_mdl_context.set_force_dml_deadlock_weight(true);
/*
Wait for the second table should end-up in a deadlock with
thread being chosen as victim.
*/
expected_error = ER_LOCK_DEADLOCK;
EXPECT_TRUE(m_mdl_context.acquire_lock(&request2, long_timeout));
m_mdl_context.release_transactional_locks();
}
/**
Test coverage for MDL_context::set_force_dml_deadlock_weight() method.
*/
TEST_F(MDLTest, ForceDMLDeadlockWeight) {
Notification first_grabbed, go_for_second, second_blocked;
MDL_weight_thread thread(&first_grabbed, &go_for_second, &second_blocked);
/*
Start the concurrent thread and wait until it acquires SNRW lock on
table_name1 and gets suspended.
*/
thread.start();
first_grabbed.wait_for_notification();
/* Now let us grab SNRW lock table_name2. */
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name2,
MDL_SHARED_NO_READ_WRITE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
/*
Resume the concurrent thread. It should try to acquire SNRW lock on
table_name2. Wait for it to get blocked.
*/
go_for_second.notify();
second_blocked.wait_for_notification();
/*
Now let us try to grab SNRW lock table_name1.
This should lead to deadlock.
Normally this context will be chosen as a victim since all waits
happen for the same type request - SNRW and it has joined waiters
graph last.
But since another thread uses MDL_context::set_force_dml_deadlock_weight()
method it will be chosen as a victim instead.
*/
MDL_REQUEST_INIT(&m_request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_NO_READ_WRITE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
m_mdl_context.release_transactional_locks();
thread.join();
}
/** Simple MDL_context_visitor which stores pointer to context visited. */
class MDLTestContextVisitor : public MDL_context_visitor {
public:
MDLTestContextVisitor() : m_visited_ctx(NULL) {}
virtual void visit_context(const MDL_context *ctx) { m_visited_ctx = ctx; }
const MDL_context *get_visited_ctx() { return m_visited_ctx; }
private:
const MDL_context *m_visited_ctx;
};
/**
Test coverage for MDL_context::find_lock_owner() method.
*/
TEST_F(MDLTest, FindLockOwner) {
Notification first_grabbed, first_release;
Notification second_blocked, second_grabbed, second_release;
MDL_thread thread1(table_name1, MDL_EXCLUSIVE, &first_grabbed, &first_release,
NULL, NULL);
MDL_thread thread2(table_name1, MDL_EXCLUSIVE, &second_grabbed,
&second_release, &second_blocked, NULL);
MDL_key mdl_key(MDL_key::TABLE, db_name, table_name1);
/* There should be no lock owner before we have started any threads. */
MDLTestContextVisitor visitor1;
EXPECT_FALSE(m_mdl_context.find_lock_owner(&mdl_key, &visitor1));
const MDL_context *null_context = NULL;
EXPECT_EQ(null_context, visitor1.get_visited_ctx());
/*
Start the first thread and wait until it grabs the lock.
This thread should be found as lock owner.
*/
thread1.start();
first_grabbed.wait_for_notification();
EXPECT_FALSE(m_mdl_context.find_lock_owner(&mdl_key, &visitor1));
EXPECT_EQ(&thread1.get_mdl_context(), visitor1.get_visited_ctx());
/*
Start the second thread and wait unti it is blocked waiting for the lock.
The first thread still should be reported as lock owner.
*/
MDLTestContextVisitor visitor2;
thread2.start();
second_blocked.wait_for_notification();
EXPECT_FALSE(m_mdl_context.find_lock_owner(&mdl_key, &visitor2));
EXPECT_EQ(&thread1.get_mdl_context(), visitor2.get_visited_ctx());
/*
Let the first thread to release lock and wait until the second thread
acquires it. The second thread should be reported as lock owner now.
*/
MDLTestContextVisitor visitor3;
first_release.notify();
second_grabbed.wait_for_notification();
EXPECT_FALSE(m_mdl_context.find_lock_owner(&mdl_key, &visitor3));
EXPECT_EQ(&thread2.get_mdl_context(), visitor3.get_visited_ctx());
/* Release the lock. Wait until both threads have stopped. */
second_release.notify();
thread1.join();
thread2.join();
/* There should be no lock owners again. */
MDLTestContextVisitor visitor4;
EXPECT_FALSE(m_mdl_context.find_lock_owner(&mdl_key, &visitor4));
EXPECT_EQ(null_context, visitor4.get_visited_ctx());
}
/** Test class for SE notification testing. */
class MDLHtonNotifyTest : public MDLTest {
protected:
MDLHtonNotifyTest() {}
void SetUp() {
MDLTest::SetUp();
reset_counts_and_keys();
}
void TearDown() { MDLTest::TearDown(); }
virtual bool notify_hton_pre_acquire_exclusive(const MDL_key *mdl_key,
bool *victimized) {
*victimized = false;
m_pre_acquire_count++;
m_pre_acquire_key.mdl_key_init(mdl_key);
return m_refuse_acquire;
}
virtual void notify_hton_post_release_exclusive(const MDL_key *mdl_key) {
m_post_release_key.mdl_key_init(mdl_key);
m_post_release_count++;
}
uint pre_acquire_count() const { return m_pre_acquire_count; }
uint post_release_count() const { return m_post_release_count; }
const MDL_key &pre_acquire_key() const { return m_pre_acquire_key; }
const MDL_key &post_release_key() const { return m_post_release_key; }
void reset_counts_and_keys() {
m_pre_acquire_count = 0;
m_post_release_count = 0;
m_pre_acquire_key.reset();
m_post_release_key.reset();
m_refuse_acquire = false;
}
void set_refuse_acquire() { m_refuse_acquire = true; }
private:
GTEST_DISALLOW_COPY_AND_ASSIGN_(MDLHtonNotifyTest);
uint m_pre_acquire_count, m_post_release_count;
MDL_key m_pre_acquire_key, m_post_release_key;
bool m_refuse_acquire;
};
/**
Test that SE notification is performed only for prescribed namespaces and
not others.
*/
TEST_F(MDLHtonNotifyTest, NotifyNamespaces) {
bool notify_or_not[MDL_key::NAMESPACE_END] = {
false, // GLOBAL
true, // TABLESPACE
true, // SCHEMA
true, // TABLE
true, // FUNCTION
true, // PROCEDURE
true, // TRIGGER
true, // EVENT
false, // COMMIT
false, // USER_LEVEL_LOCK
false // LOCKING_SERVICE
};
for (uint i = 0; i < static_cast<uint>(MDL_key::NAMESPACE_END); i++) {
MDL_request request;
if (static_cast<MDL_key::enum_mdl_namespace>(i) == MDL_key::FUNCTION ||
static_cast<MDL_key::enum_mdl_namespace>(i) == MDL_key::PROCEDURE ||
static_cast<MDL_key::enum_mdl_namespace>(i) == MDL_key::TRIGGER ||
static_cast<MDL_key::enum_mdl_namespace>(i) == MDL_key::EVENT ||
static_cast<MDL_key::enum_mdl_namespace>(i) ==
MDL_key::RESOURCE_GROUPS) {
MDL_key mdl_key;
mdl_key.mdl_key_init(static_cast<MDL_key::enum_mdl_namespace>(i), "", "",
0, "");
MDL_REQUEST_INIT_BY_KEY(&request, &mdl_key, MDL_EXCLUSIVE,
MDL_TRANSACTION);
} else {
MDL_REQUEST_INIT(&request, static_cast<MDL_key::enum_mdl_namespace>(i),
"",
"", // To work with GLOBAL/COMMIT spaces
MDL_EXCLUSIVE, MDL_TRANSACTION);
}
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
m_mdl_context.release_transactional_locks();
if (notify_or_not[i]) {
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
reset_counts_and_keys();
} else {
EXPECT_EQ(0U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
}
}
}
/**
Test that SE notification is performed only for X requests and not others.
*/
TEST_F(MDLHtonNotifyTest, NotifyLockTypes) {
MDL_request request;
// IX type can only be used for scoped locks. Doesn't cause notification.
MDL_REQUEST_INIT(&request, MDL_key::SCHEMA, db_name, "",
MDL_INTENTION_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
m_mdl_context.release_transactional_locks();
EXPECT_EQ(0U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
/*
All other lock types can be used with tables and don't cause
notification except X requests.
*/
for (uint i = static_cast<uint>(MDL_INTENTION_EXCLUSIVE) + 1;
i < static_cast<uint>(MDL_EXCLUSIVE); i++) {
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_name1,
static_cast<enum_mdl_type>(i), MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
m_mdl_context.release_transactional_locks();
EXPECT_EQ(0U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
}
// X lock causes notifications.
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
m_mdl_context.release_transactional_locks();
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
// There are no other lock types!
DBUG_ASSERT(static_cast<uint>(MDL_EXCLUSIVE) + 1 ==
static_cast<uint>(MDL_TYPE_END));
}
/**
Test for SE notification in some acquire/release scenarios
including case when SE refuses lock acquisition.
*/
TEST_F(MDLHtonNotifyTest, NotifyAcquireRelease) {
MDL_request request1, request2;
// Straightforward acquire/release cycle.
MDL_REQUEST_INIT(&request1, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request1, long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request1.key));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE));
m_mdl_context.release_transactional_locks();
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
EXPECT_TRUE(post_release_key().is_equal(&request1.key));
reset_counts_and_keys();
// Case when we try to acquire lock several times.
MDL_REQUEST_INIT(&request1, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
MDL_REQUEST_INIT(&request2, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request1, long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request1.key));
// The second acquisition should not cause notification.
EXPECT_FALSE(m_mdl_context.acquire_lock(&request2, long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
// On release there is only one notification call.
m_mdl_context.release_transactional_locks();
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
EXPECT_TRUE(post_release_key().is_equal(&request1.key));
reset_counts_and_keys();
// Case when lock acquisition is refused by SE.
MDL_REQUEST_INIT(&request1, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
expected_error = ER_LOCK_REFUSED_BY_ENGINE;
set_refuse_acquire();
EXPECT_TRUE(m_mdl_context.acquire_lock(&request1, long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE));
// Nothing to release and nothing to notify about.
m_mdl_context.release_transactional_locks();
EXPECT_EQ(0U, post_release_count());
}
/**
Test for SE notification in some scenarios when we successfully perform
SE notification but then fail to acquire lock for some reason.
*/
TEST_F(MDLHtonNotifyTest, NotifyAcquireFail) {
Notification lock_grabbed, release_lock;
MDL_request request;
// Acquire S lock on the table in another thread.
MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_lock,
NULL, NULL);
mdl_thread.start();
lock_grabbed.wait_for_notification();
/*
Try to acquire X lock on the table using try_acquire_lock().
*/
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request));
EXPECT_EQ(m_null_ticket, request.ticket);
/*
We treat failure to acquire X lock after successful pre-acquire
notification in the same way as lock release.
*/
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request.key));
EXPECT_TRUE(post_release_key().is_equal(&request.key));
reset_counts_and_keys();
/*
Now do the same thing using acquire_lock() and zero timeout,
*/
expected_error = ER_LOCK_WAIT_TIMEOUT;
EXPECT_TRUE(m_mdl_context.acquire_lock(&request, zero_timeout));
/*
Again we treat failure to acquire X lock after successful pre-acquire
notification in the same way as lock release.
*/
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request.key));
EXPECT_TRUE(post_release_key().is_equal(&request.key));
release_lock.notify();
mdl_thread.join();
}
/**
Test for SE notification in lock upgrade scenarios.
*/
TEST_F(MDLHtonNotifyTest, NotifyUpgrade) {
MDL_request request;
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_UPGRADABLE, MDL_TRANSACTION);
// Check that we notify SE about upgrade.
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
EXPECT_FALSE(m_mdl_context.upgrade_shared_lock(request.ticket, MDL_EXCLUSIVE,
long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request.key));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE));
// Second attempt to upgrade should not cause additional notification.
EXPECT_FALSE(m_mdl_context.upgrade_shared_lock(request.ticket, MDL_EXCLUSIVE,
long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
m_mdl_context.release_transactional_locks();
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
EXPECT_TRUE(post_release_key().is_equal(&request.key));
reset_counts_and_keys();
// Case when lock upgrade is refused by SE.
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_UPGRADABLE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
expected_error = ER_LOCK_REFUSED_BY_ENGINE;
set_refuse_acquire();
EXPECT_TRUE(m_mdl_context.upgrade_shared_lock(request.ticket, MDL_EXCLUSIVE,
long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_FALSE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE));
// Nothing to notify about during release.
m_mdl_context.release_transactional_locks();
EXPECT_EQ(0U, post_release_count());
reset_counts_and_keys();
/*
Now case when notification is successful but we fail to upgrade for some
other reason.
*/
// Acquire S lock on the table in another thread.
Notification lock_grabbed, release_lock;
MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_lock,
NULL, NULL);
mdl_thread.start();
lock_grabbed.wait_for_notification();
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_name1,
MDL_SHARED_UPGRADABLE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
expected_error = ER_LOCK_WAIT_TIMEOUT;
EXPECT_TRUE(m_mdl_context.upgrade_shared_lock(request.ticket, MDL_EXCLUSIVE,
zero_timeout));
/*
Failure to upgrade lock after successful notification is treated as release.
*/
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request.key));
EXPECT_TRUE(post_release_key().is_equal(&request.key));
m_mdl_context.release_transactional_locks();
// No additional notifications!
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
release_lock.notify();
mdl_thread.join();
}
/**
Test for SE notification in lock downgrade scenarios.
*/
TEST_F(MDLHtonNotifyTest, NotifyDowngrade) {
MDL_request request;
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
// Acquire X lock.
EXPECT_FALSE(m_mdl_context.acquire_lock(&request, long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request.key));
// First try no-op downgrade. No notification should be done.
request.ticket->downgrade_lock(MDL_EXCLUSIVE);
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
// Now downgrade to SNRW lock.
request.ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
// We should notify SE as if doing lock release.
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
EXPECT_TRUE(post_release_key().is_equal(&request.key));
m_mdl_context.release_transactional_locks();
// No additional notifications!
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(1U, post_release_count());
}
/**
Test for SE notification in scenarios involving clone operation.
*/
TEST_F(MDLHtonNotifyTest, NotifyClone) {
MDL_request request1, request2, request3, request4;
// Acquire X lock.
MDL_REQUEST_INIT(&request1, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_TRANSACTION);
EXPECT_FALSE(m_mdl_context.acquire_lock(&request1, long_timeout));
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request1.key));
EXPECT_TRUE(m_mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE));
// Now try to clone it to S lock.
MDL_REQUEST_INIT(&request2, MDL_key::TABLE, db_name, table_name1, MDL_SHARED,
MDL_EXPLICIT);
request2.ticket = request1.ticket;
EXPECT_FALSE(m_mdl_context.clone_ticket(&request2));
// There should be no additional notification in this case
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
reset_counts_and_keys();
// Now try to clone it to another X lock instance.
MDL_REQUEST_INIT(&request3, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_EXPLICIT);
request3.ticket = request1.ticket;
EXPECT_FALSE(m_mdl_context.clone_ticket(&request3));
// This should cause additional SE notification
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request1.key));
reset_counts_and_keys();
// Finally try to clone it X lock and see what happens when SE refuses.
MDL_REQUEST_INIT(&request4, MDL_key::TABLE, db_name, table_name1,
MDL_EXCLUSIVE, MDL_EXPLICIT);
request4.ticket = request1.ticket;
expected_error = ER_LOCK_REFUSED_BY_ENGINE;
set_refuse_acquire();
EXPECT_TRUE(m_mdl_context.clone_ticket(&request4));
// This should cause additional SE notification (which fails).
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(0U, post_release_count());
EXPECT_TRUE(pre_acquire_key().is_equal(&request1.key));
// Release all locks and see how much SE notifications it will cause.
m_mdl_context.release_transactional_locks();
m_mdl_context.release_lock(request2.ticket);
m_mdl_context.release_lock(request3.ticket);
// All locks are released.
EXPECT_FALSE(m_mdl_context.has_locks());
// Two instances of X locks were released so 2 post-release calls.
EXPECT_EQ(1U, pre_acquire_count());
EXPECT_EQ(2U, post_release_count());
EXPECT_TRUE(post_release_key().is_equal(&request1.key));
}
/** Test class for MDL_key class testing. Doesn't require MDL initialization. */
class MDLKeyTest : public ::testing::Test {
protected:
MDLKeyTest() {}
private:
GTEST_DISALLOW_COPY_AND_ASSIGN_(MDLKeyTest);
};
// Google Test recommends DeathTest suffix for classes use in death tests.
typedef MDLKeyTest MDLKeyDeathTest;
/*
Verifies that debug build dies with a DBUG_ASSERT if we try to construct
MDL_key with too long database or object names.
*/
#if GTEST_HAS_DEATH_TEST && !defined(DBUG_OFF)
TEST_F(MDLKeyDeathTest, DieWhenNamesAreTooLong) {
::testing::FLAGS_gtest_death_test_style = "threadsafe";
/* We need a name which is longer than NAME_LEN = 64*3 = 192.*/
const char *too_long_name =
"0123456789012345678901234567890123456789012345678901234567890123"
"0123456789012345678901234567890123456789012345678901234567890123"
"0123456789012345678901234567890123456789012345678901234567890123"
"0123456789";
EXPECT_DEATH(MDL_key key0(MDL_key::TABLE, too_long_name, ""),
".*Assertion.*strlen.*");
EXPECT_DEATH(MDL_key key1(MDL_key::TABLE, "", too_long_name),
".*Assertion.*strlen.*");
MDL_key key2;
EXPECT_DEATH(key2.mdl_key_init(MDL_key::TABLE, too_long_name, ""),
".*Assertion.*strlen.*");
EXPECT_DEATH(key2.mdl_key_init(MDL_key::TABLE, "", too_long_name),
".*Assertion.*strlen.*");
}
#endif // GTEST_HAS_DEATH_TEST && !defined(DBUG_OFF)
/*
Verifies that for production build we allow construction of
MDL_key with too long database or object names, but they are
truncated.
*/
#if defined(DBUG_OFF)
TEST_F(MDLKeyTest, TruncateTooLongNames) {
/* We need a name which is longer than NAME_LEN = 64*3 = 192.*/
const char *too_long_name =
"0123456789012345678901234567890123456789012345678901234567890123"
"0123456789012345678901234567890123456789012345678901234567890123"
"0123456789012345678901234567890123456789012345678901234567890123"
"0123456789";
MDL_key key(MDL_key::TABLE, too_long_name, too_long_name);
const char *db_name = key.db_name();
const char *name = key.name();
EXPECT_LE(strlen(db_name), (uint)NAME_LEN);
EXPECT_TRUE(strncmp(db_name, too_long_name, NAME_LEN) == 0);
EXPECT_LE(strlen(name), (uint)NAME_LEN);
EXPECT_TRUE(strncmp(name, too_long_name, NAME_LEN) == 0);
}
struct Mock_MDL_context_owner : public Test_MDL_context_owner {
void notify_shared_lock(MDL_context_owner *in_use,
bool needs_thr_lock_abort) override final {
in_use->notify_shared_lock(NULL, needs_thr_lock_abort);
}
};
using Name_vec = std::vector<std::string>;
/*
Helper function for benchmark.
*/
inline size_t read_from_env(const char *var, size_t def) {
const char *val = getenv(var);
return val ? static_cast<size_t>(atoi(val)) : def;
}
/*
Helper function for benchmark.
*/
static Name_vec make_name_vec(size_t t) {
std::vector<std::string> names;
names.reserve(t);
static std::stringstream str;
for (size_t i = 0; i < t; ++i) {
str << "t_" << i;
names.push_back(str.str());
str.str("");
}
return names;
}
/*
Helper function for benchmark.
*/
static void lock_bench(MDL_context &ctx, const Name_vec &names) {
for (auto &name : names) {
MDL_request request;
MDL_REQUEST_INIT(&request, MDL_key::TABLE, "S", name.c_str(),
MDL_INTENTION_EXCLUSIVE, MDL_TRANSACTION);
ctx.acquire_lock(&request, 2);
}
ctx.release_transactional_locks();
}
/**
Microbenchmark which tests the performance of acquire_lock (find_ticket)
*/
static void BM_FindTicket(size_t num_iterations) {
StopBenchmarkTiming();
system_charset_info = &my_charset_utf8_bin;
mdl_init();
MDL_context ctx;
Mock_MDL_context_owner owner;
ctx.init(&owner);
size_t ntickets = read_from_env("NTICKETS", 512);
std::cout << "Tickets: " << ntickets << "\n";
Name_vec names = make_name_vec(ntickets);
StartBenchmarkTiming();
for (size_t i = 0; i < num_iterations; ++i) {
lock_bench(ctx, names);
}
StopBenchmarkTiming();
mdl_destroy();
}
BENCHMARK(BM_FindTicket)
#endif // defined(DBUG_OFF)
} // namespace mdl_unittest