/* 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
