mysql5/mysql-5.7.27/unittest/gunit/locking_service-t.cc

573 lines
21 KiB
C++

/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
// First include (the generated) my_config.h, to get correct platform defines.
#include "my_config.h"
#include <gtest/gtest.h>
#include "locking_service.h"
#include "mdl.h"
#include "mysqld_error.h"
#include "test_utils.h"
#include "thread_utils.h"
/*
Putting everything in a namespace prevents any (unintentional)
name clashes with the code under test.
*/
namespace locking_service {
using thread::Thread;
using thread::Notification;
using my_testing::Server_initializer;
using my_testing::Mock_error_handler;
const char namespace1[]= "namespace1";
const char namespace2[]= "namespace2";
const char lock_name1[]= "lock1";
const char lock_name2[]= "lock2";
const char lock_name3[]= "lock3";
const char lock_name4[]= "lock4";
class LockingServiceTest : public ::testing::Test
{
protected:
LockingServiceTest() {}
static void SetUpTestCase()
{
m_old_error_handler_hook= error_handler_hook;
// Make sure my_error() ends up calling my_message_sql so that
// Mock_error_handler is actually triggered.
error_handler_hook= my_message_sql;
}
static void TearDownTestCase()
{
error_handler_hook= m_old_error_handler_hook;
}
virtual void SetUp()
{
mdl_init();
m_initializer.SetUp();
m_thd= m_initializer.thd();
// Slight hack: Makes THD::is_connected() return true.
// This prevents MDL_context::acquire_lock() from thinking
// the connection has died and the wait should be aborted.
m_thd->system_thread= SYSTEM_THREAD_SLAVE_IO;
}
virtual void TearDown()
{
m_initializer.TearDown();
mdl_destroy();
}
Server_initializer m_initializer;
THD *m_thd;
static void (*m_old_error_handler_hook)(uint, const char *, myf);
};
void (*LockingServiceTest::m_old_error_handler_hook)(uint, const char *, myf);
/**
Test acquire and release of several read or write locks.
*/
TEST_F(LockingServiceTest, AcquireAndRelease)
{
// Take two read locks
const char *names1[]= { lock_name1, lock_name2 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 2,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name2, MDL_SHARED));
// Release the locks
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name2, MDL_SHARED));
// Take one write lock
const char *names2[]= { lock_name3 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names2, 1,
LOCKING_SERVICE_WRITE, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name3, MDL_EXCLUSIVE));
// Take another write lock
const char *names3[]= { lock_name4 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names3, 1,
LOCKING_SERVICE_WRITE, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name3, MDL_EXCLUSIVE));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name4, MDL_EXCLUSIVE));
// Take the read locks again
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 2,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name2, MDL_SHARED));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name3, MDL_EXCLUSIVE));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name4, MDL_EXCLUSIVE));
// Release all locks
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name2, MDL_SHARED));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name3, MDL_SHARED));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name4, MDL_SHARED));
}
/**
Test that names are case sensitive
*/
TEST_F(LockingServiceTest, CaseSensitive)
{
const char *lower[]= { "test" };
const char *upper[]= { "TEST" };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, lower, 1,
LOCKING_SERVICE_WRITE, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, "test", MDL_EXCLUSIVE));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, "TEST", MDL_EXCLUSIVE));
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
EXPECT_FALSE(acquire_locking_service_locks(m_thd, "test", upper, 1,
LOCKING_SERVICE_WRITE, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, "test", "TEST", MDL_EXCLUSIVE));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, "TEST", "TEST", MDL_EXCLUSIVE));
EXPECT_FALSE(release_locking_service_locks(m_thd, "test"));
}
/**
Test verfication of name lengths.
*/
TEST_F(LockingServiceTest, ValidNames)
{
const char *ok_name[]= { "test" };
{
const char *null= NULL;
const char *names1[]= { null };
Mock_error_handler error_handler(m_thd, ER_LOCKING_SERVICE_WRONG_NAME);
EXPECT_TRUE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(acquire_locking_service_locks(m_thd, null, ok_name, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(release_locking_service_locks(m_thd, null));
EXPECT_EQ(3, error_handler.handle_called());
}
{
const char *empty= "";
const char *names2[]= { empty };
Mock_error_handler error_handler(m_thd, ER_LOCKING_SERVICE_WRONG_NAME);
EXPECT_TRUE(acquire_locking_service_locks(m_thd, namespace1, names2, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(acquire_locking_service_locks(m_thd, empty, ok_name, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(release_locking_service_locks(m_thd, empty));
EXPECT_EQ(3, error_handler.handle_called());
}
{
const char *long65= "12345678901234567890123456789012345678901234567890123456789012345";
const char *names3[]= { long65 };
Mock_error_handler error_handler(m_thd, ER_LOCKING_SERVICE_WRONG_NAME);
EXPECT_TRUE(acquire_locking_service_locks(m_thd, namespace1, names3, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(acquire_locking_service_locks(m_thd, long65, ok_name, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(release_locking_service_locks(m_thd, long65));
EXPECT_EQ(3, error_handler.handle_called());
}
}
/**
Test interaction (or lack of it) with transactional locks.
*/
TEST_F(LockingServiceTest, TransactionInteraction)
{
// Releasing transactional locks should not affect lock service locks.
const char *names1[]= { lock_name1 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_READ, 3600));
m_thd->mdl_context.release_transactional_locks();
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_WRITE, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_EXCLUSIVE));
m_thd->mdl_context.release_transactional_locks();
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_EXCLUSIVE));
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
// Releasing lock service locks should not affect transactional locks.
MDL_request fake_request;
MDL_REQUEST_INIT(&fake_request,
MDL_key::SCHEMA, "db", "table",
MDL_EXCLUSIVE,
MDL_TRANSACTION);
EXPECT_FALSE(m_thd->mdl_context.acquire_lock(&fake_request, 3600));
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_READ, 3600));
m_thd->mdl_context.release_transactional_locks();
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
}
/**
Utility thread for acquiring lock service locks with notification of
acquire and release.
*/
class LockServiceThread : public Thread
{
public:
LockServiceThread(const char **names, size_t num,
enum_locking_service_lock_type lock_type,
Notification *grabbed_arg, Notification *release_arg)
: m_names(names), m_num(num), m_lock_type(lock_type),
m_lock_grabbed(grabbed_arg), m_lock_release(release_arg)
{}
virtual void run()
{
Server_initializer m_initializer;
m_initializer.SetUp();
THD *m_thd= m_initializer.thd();
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, m_names,
m_num, m_lock_type, 3600));
if (m_lock_grabbed)
m_lock_grabbed->notify();
if (m_lock_release)
m_lock_release->wait_for_notification();
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
m_initializer.TearDown();
}
private:
const char **m_names;
size_t m_num;
enum_locking_service_lock_type m_lock_type;
Notification *m_lock_grabbed;
Notification *m_lock_release;
};
/**
Test that read locks are compatible with read locks but
incompatible with write locks.
*/
TEST_F(LockingServiceTest, ReadCompatibility)
{
const char *names1[]= { lock_name1 };
Notification lock_grabbed, lock_release;
LockServiceThread thread(names1, 1, LOCKING_SERVICE_READ,
&lock_grabbed, &lock_release);
thread.start();
lock_grabbed.wait_for_notification();
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
{
Mock_error_handler error_handler(m_thd, ER_LOCKING_SERVICE_TIMEOUT);
EXPECT_TRUE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_WRITE, 2));
// Wait 2 seconds here so that we hit the "abs_timeout is far away"
// code path in MDL_context::acquire_lock() on all platforms.
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_EQ(1, error_handler.handle_called());
}
lock_release.notify();
thread.join();
}
/**
Test that write locks are incompatible with write locks.
*/
TEST_F(LockingServiceTest, WriteCompatibility)
{
const char *names1[]= { lock_name1 };
Notification lock_grabbed, lock_release;
LockServiceThread thread(names1, 1, LOCKING_SERVICE_WRITE,
&lock_grabbed, &lock_release);
thread.start();
lock_grabbed.wait_for_notification();
{
Mock_error_handler error_handler(m_thd, ER_LOCKING_SERVICE_TIMEOUT);
EXPECT_TRUE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_WRITE, 1));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_EQ(1, error_handler.handle_called());
}
lock_release.notify();
thread.join();
}
/**
Test that if acquisition of multiple locks fails because of one
lock conflicts, no locks are acquired.
*/
TEST_F(LockingServiceTest, AtomicAcquire)
{
const char *names1[]= { lock_name1 };
Notification lock_grabbed, lock_release;
LockServiceThread thread(names1, 1, LOCKING_SERVICE_READ,
&lock_grabbed, &lock_release);
thread.start();
lock_grabbed.wait_for_notification();
{
// Conflict on lock_name1, lock_name2 should not be acquired.
const char *names2[]= { lock_name1, lock_name2 };
Mock_error_handler error_handler(m_thd, ER_LOCKING_SERVICE_TIMEOUT);
EXPECT_TRUE(acquire_locking_service_locks(m_thd, namespace1, names2, 2,
LOCKING_SERVICE_WRITE, 1));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name2, MDL_SHARED));
EXPECT_EQ(1, error_handler.handle_called());
}
{
// Reverse order of lock names - should give same result.
const char *names2[]= { lock_name2, lock_name1 };
Mock_error_handler error_handler(m_thd, ER_LOCKING_SERVICE_TIMEOUT);
EXPECT_TRUE(acquire_locking_service_locks(m_thd, namespace1, names2, 2,
LOCKING_SERVICE_WRITE, 1));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name2, MDL_SHARED));
EXPECT_EQ(1, error_handler.handle_called());
}
lock_release.notify();
thread.join();
}
/**
Test that namespaces are independent.
*/
TEST_F(LockingServiceTest, Namespaces)
{
const char *names1[]= { lock_name1 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace2, lock_name1, MDL_SHARED));
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace2));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_SHARED));
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
// Take write lock with namespace1 in a separate thread.
Notification lock_grabbed, lock_release;
LockServiceThread thread(names1, 1, LOCKING_SERVICE_WRITE,
&lock_grabbed, &lock_release);
thread.start();
lock_grabbed.wait_for_notification();
// We should be able to take a write lock in namespace2.
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace2, names1, 1,
LOCKING_SERVICE_WRITE, 3600));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace2, lock_name1, MDL_EXCLUSIVE));
EXPECT_FALSE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_EXCLUSIVE));
lock_release.notify();
thread.join();
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace2));
}
/**
Thread which acquires a write lock on lock_name1 and then disconnects.
*/
class LockServiceDisconnectThread : public Thread
{
public:
LockServiceDisconnectThread() {}
virtual void run()
{
Server_initializer m_initializer;
m_initializer.SetUp();
THD *m_thd= m_initializer.thd();
const char *names1[]= { lock_name1 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_WRITE, 3600));
m_initializer.TearDown();
}
};
/**
Test that locks are released automatically on disconnect.
*/
TEST_F(LockingServiceTest, Disconnect)
{
LockServiceDisconnectThread thread;
thread.start();
thread.join();
// Check that we now can acquire a write lock on name1.
const char *names1[]= { lock_name1 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_WRITE, 3600));
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
}
/**
Utility thread for deadlock tests. Acquires two locks in order.
*/
class LockServiceDeadlockThread : public Thread
{
public:
LockServiceDeadlockThread(Notification *grabbed1_arg,
Notification *wait_arg)
: m_lock_grabbed1(grabbed1_arg), m_wait(wait_arg)
{}
virtual void run()
{
Server_initializer m_initializer;
m_initializer.SetUp();
THD *m_thd= m_initializer.thd();
const char *names1[]= { lock_name1 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_WRITE, 3600));
m_lock_grabbed1->notify();
m_wait->wait_for_notification();
{
// The deadlock should be resolved by aborting the wait for the read lock
// We should therefore fail.
const char *names2[]= { lock_name2 };
Mock_error_handler error_handler(m_thd, ER_LOCKING_SERVICE_DEADLOCK);
EXPECT_TRUE(acquire_locking_service_locks(m_thd, namespace1, names2, 1,
LOCKING_SERVICE_READ, 3600));
EXPECT_EQ(1, error_handler.handle_called());
}
// We still have the first lock
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_EXCLUSIVE));
// Release locks so that the other thread can continue.
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
m_initializer.TearDown();
}
private:
Notification *m_lock_grabbed1;
Notification *m_wait;
};
/**
Test that deadlock is detected and lock acquisition fails.
The wait for a read lock should be aborted in preference for
aborting the wait for a write lock.
*/
TEST_F(LockingServiceTest, DeadlockRead)
{
// Start a thread which acquires a write lock on name1
Notification lock_grabbed1, wait;
LockServiceDeadlockThread thread(&lock_grabbed1, &wait);
thread.start();
lock_grabbed1.wait_for_notification();
// Acquire write lock on name 2
const char *names2[]= { lock_name2 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names2, 1,
LOCKING_SERVICE_WRITE, 3600));
// Signal the other thread to continue, taking write lock on name1
wait.notify();
// The other thread will be aborted so that we will acquire the lock.
const char *names1[]= { lock_name1 };
EXPECT_FALSE(acquire_locking_service_locks(m_thd, namespace1, names1, 1,
LOCKING_SERVICE_WRITE, 3600));
// Both locks should now be held
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name1, MDL_EXCLUSIVE));
EXPECT_TRUE(m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::LOCKING_SERVICE, namespace1, lock_name2, MDL_EXCLUSIVE));
thread.join();
EXPECT_FALSE(release_locking_service_locks(m_thd, namespace1));
}
} // namespace