274 lines
6.8 KiB
C++
274 lines
6.8 KiB
C++
/* Copyright (c) 2013, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "my_config.h"
|
|
#include <gtest/gtest.h>
|
|
#include "tc_log.h"
|
|
#include "sql_class.h"
|
|
#include "test_utils.h"
|
|
#include "thread_utils.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <process.h> // getpid
|
|
#endif
|
|
|
|
using my_testing::Server_initializer;
|
|
|
|
/**
|
|
Override msync/fsync, saves a *lot* of time during unit testing.
|
|
*/
|
|
|
|
class TC_LOG_MMAP_no_msync : public TC_LOG_MMAP
|
|
{
|
|
protected:
|
|
virtual int do_msync_and_fsync(int fd, void *addr, size_t len, int flags)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
This class is a friend of TC_LOG_MMAP, so it needs to be outside the unittest
|
|
namespace.
|
|
*/
|
|
|
|
class TCLogMMapTest : public ::testing::Test
|
|
{
|
|
public:
|
|
TCLogMMapTest()
|
|
: tc_log_mmap(NULL)
|
|
{
|
|
}
|
|
|
|
|
|
virtual void SetUp()
|
|
{
|
|
initializer.SetUp();
|
|
total_ha_2pc= 2;
|
|
tc_heuristic_recover= TC_HEURISTIC_NOT_USED;
|
|
/*
|
|
Assign a transaction coordinator object to am
|
|
instance of TCLogMMapTest. This transaction coordinator
|
|
is shared among all threads are run.
|
|
*/
|
|
|
|
tc_log_mmap= new TC_LOG_MMAP_no_msync();
|
|
// Make a slightly randomized name for the file,
|
|
// to avoid recovery from other runs.
|
|
char namebuff[FN_REFLEN];
|
|
my_snprintf(namebuff, FN_REFLEN,
|
|
"tc_log_mmap_test_%d", static_cast<int>(getpid()));
|
|
ASSERT_EQ(0, tc_log_mmap->open(namebuff));
|
|
}
|
|
|
|
|
|
virtual void TearDown()
|
|
{
|
|
tc_log_mmap->close();
|
|
delete tc_log_mmap;
|
|
initializer.TearDown();
|
|
}
|
|
|
|
|
|
THD *thd()
|
|
{
|
|
return initializer.thd();
|
|
}
|
|
|
|
|
|
/**
|
|
Run test case in single threaded environment.
|
|
This method uses THD value hold by the initializer data member.
|
|
*/
|
|
|
|
void testCommit(ulonglong xid)
|
|
{
|
|
testCommit(xid, thd());
|
|
}
|
|
|
|
|
|
/**
|
|
Run a test case with the THD supplied outside. This method is used
|
|
when there are several threads running the same test case. In this case
|
|
we need a dedicated THD object for every thread in order not to hit
|
|
upon the wrong condition when two threads free a memroot (indirectly by
|
|
calling thd->get_transaction()->cleanup()) for the same THD object.
|
|
*/
|
|
|
|
void testCommit(ulonglong xid, THD *thd_val)
|
|
{
|
|
XID_STATE *xid_state= thd_val->get_transaction()->xid_state();
|
|
xid_state->set_query_id(xid);
|
|
EXPECT_EQ(TC_LOG_MMAP::RESULT_SUCCESS, tc_log_mmap->commit(thd_val, true));
|
|
thd_val->get_transaction()->cleanup();
|
|
}
|
|
|
|
ulong testLog(ulonglong xid)
|
|
{
|
|
return tc_log_mmap->log_xid(xid);
|
|
}
|
|
|
|
void testUnlog(ulong cookie, ulonglong xid)
|
|
{
|
|
tc_log_mmap->unlog(cookie, xid);
|
|
}
|
|
|
|
protected:
|
|
TC_LOG_MMAP_no_msync* tc_log_mmap;
|
|
Server_initializer initializer;
|
|
};
|
|
|
|
namespace tc_log_mmap_unittest {
|
|
|
|
TEST_F(TCLogMMapTest, TClogCommit)
|
|
{
|
|
// test calling of log/unlog for xid=1
|
|
testCommit(1);
|
|
}
|
|
|
|
class TC_Log_MMap_thread : public thread::Thread
|
|
{
|
|
public:
|
|
TC_Log_MMap_thread()
|
|
: m_start_xid(0), m_end_xid(0),
|
|
m_tc_log_mmap(NULL), initializer(NULL)
|
|
{
|
|
}
|
|
|
|
~TC_Log_MMap_thread()
|
|
{
|
|
initializer->TearDown();
|
|
delete initializer;
|
|
}
|
|
|
|
void init (ulonglong start_value, ulonglong end_value,
|
|
TCLogMMapTest* tc_log_mmap, Server_initializer* initializer_value)
|
|
{
|
|
m_start_xid= start_value;
|
|
m_end_xid= end_value;
|
|
m_tc_log_mmap= tc_log_mmap;
|
|
initializer= initializer_value;
|
|
initializer->SetUp();
|
|
}
|
|
|
|
virtual void run()
|
|
{
|
|
ulonglong xid= m_start_xid;
|
|
while (xid < m_end_xid)
|
|
{
|
|
m_tc_log_mmap->testCommit(xid++, initializer->thd());
|
|
}
|
|
}
|
|
|
|
protected:
|
|
ulonglong m_start_xid, m_end_xid;
|
|
TCLogMMapTest* m_tc_log_mmap;
|
|
Server_initializer* initializer;
|
|
};
|
|
|
|
TEST_F(TCLogMMapTest, ConcurrentAccess)
|
|
{
|
|
static const unsigned MAX_WORKER_THREADS= 10;
|
|
static const unsigned VALUE_INTERVAL= 100;
|
|
|
|
TC_Log_MMap_thread tclog_threads[MAX_WORKER_THREADS];
|
|
|
|
ulonglong start_value= 0;
|
|
for (unsigned i=0; i < MAX_WORKER_THREADS; ++i)
|
|
{
|
|
/*
|
|
Each thread gets a dedicated instance of class Server_initializer and
|
|
hence it also gets a separate THD object.
|
|
*/
|
|
tclog_threads[i].init(start_value, start_value + VALUE_INTERVAL, this,
|
|
new Server_initializer());
|
|
tclog_threads[i].start();
|
|
start_value+= VALUE_INTERVAL;
|
|
}
|
|
|
|
for (unsigned i=0; i < MAX_WORKER_THREADS; ++i)
|
|
tclog_threads[i].join();
|
|
}
|
|
|
|
|
|
TEST_F(TCLogMMapTest, FillAllPagesAndReuse)
|
|
{
|
|
/* Get maximum number of XIDs which can be stored in TC log. */
|
|
const uint MAX_XIDS= tc_log_mmap->size();
|
|
ulong cookie;
|
|
/* Fill TC log. */
|
|
for(my_xid xid= 1; xid < MAX_XIDS; ++xid)
|
|
(void)testLog(xid);
|
|
cookie= testLog(MAX_XIDS);
|
|
/*
|
|
Now free one slot and try to reuse it.
|
|
This should work and not crash on assert.
|
|
*/
|
|
testUnlog(cookie, MAX_XIDS);
|
|
testLog(MAX_XIDS + 1);
|
|
}
|
|
|
|
|
|
TEST_F(TCLogMMapTest, ConcurrentOverflow)
|
|
{
|
|
const uint WORKER_THREADS= 10;
|
|
const uint XIDS_TO_REUSE= 100;
|
|
|
|
/*
|
|
Get maximum number of XIDs which can be stored in TC log.
|
|
*/
|
|
const uint MAX_XIDS= tc_log_mmap->size();
|
|
ulong cookies[XIDS_TO_REUSE];
|
|
|
|
/*
|
|
Fill TC log. Remember cookies for last XIDS_TO_REUSE xids.
|
|
*/
|
|
for(my_xid xid= 1; xid <= MAX_XIDS - XIDS_TO_REUSE; ++xid)
|
|
testLog(xid);
|
|
for (uint i= 0; i < XIDS_TO_REUSE; ++i)
|
|
cookies[i]= testLog(MAX_XIDS - XIDS_TO_REUSE + 1 + i);
|
|
|
|
/*
|
|
Now create several threads which will try to do commit.
|
|
Since log is full they will have to wait until we free some slots.
|
|
*/
|
|
TC_Log_MMap_thread threads[WORKER_THREADS];
|
|
for (uint i= 0; i < WORKER_THREADS; ++i)
|
|
{
|
|
/*
|
|
Each thread gets a dedicated instance of class Server_initializer and hence
|
|
it also gets a separate THD object.
|
|
*/
|
|
threads[i].init(MAX_XIDS + i * (XIDS_TO_REUSE/WORKER_THREADS),
|
|
MAX_XIDS + (i + 1) * (XIDS_TO_REUSE/WORKER_THREADS), this,
|
|
new Server_initializer());
|
|
threads[i].start();
|
|
}
|
|
|
|
/*
|
|
Once started all threads should block since we are out of free slots
|
|
in the log, Resume threads by freeing necessary slots. Resumed thread
|
|
should not hang or assert.
|
|
*/
|
|
for (uint i= 0; i < XIDS_TO_REUSE; ++i)
|
|
testUnlog(cookies[i], MAX_XIDS - XIDS_TO_REUSE + 1 + i);
|
|
|
|
/* Wait till all threads are done. */
|
|
for (uint i=0; i < WORKER_THREADS; ++i)
|
|
threads[i].join();
|
|
}
|
|
|
|
}
|