212 lines
6.9 KiB
C++
212 lines
6.9 KiB
C++
/* Copyright (c) 2016, 2019, 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 */
|
|
|
|
// First include (the generated) my_config.h, to get correct platform defines.
|
|
#include "my_config.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <array> /* std::array */
|
|
#include <new> /* std::bad_alloc */
|
|
#include <thread>
|
|
|
|
#include "storage/temptable/include/temptable/allocator.h" /* temptable::Allocator */
|
|
#include "storage/temptable/include/temptable/constants.h" /* temptable::ALLOCATOR_MAX_BLOCK_BYTES */
|
|
|
|
namespace temptable_allocator_unittest {
|
|
|
|
// A "probe" which gains us read-only access to temptable::MemoryMonitor.
|
|
// Necessary for implementing certain unit-tests.
|
|
struct MemoryMonitorReadOnlyProbe : private temptable::MemoryMonitor {
|
|
static size_t ram_consumption() {
|
|
return temptable::MemoryMonitor::ram_consumption();
|
|
}
|
|
};
|
|
|
|
TEST(temptable_allocator, basic) {
|
|
{
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
std::thread t([]() {
|
|
temptable::Allocator<uint8_t> allocator;
|
|
|
|
constexpr size_t n_allocate = 128;
|
|
std::array<uint8_t *, n_allocate> a;
|
|
constexpr size_t n_elements = 16;
|
|
|
|
for (size_t i = 0; i < n_allocate; ++i) {
|
|
a[i] = allocator.allocate(n_elements);
|
|
|
|
for (size_t j = 0; j < n_elements; ++j) {
|
|
a[i][j] = 0xB;
|
|
}
|
|
}
|
|
|
|
EXPECT_FALSE(temptable::shared_block.is_empty());
|
|
|
|
for (size_t i = 0; i < n_allocate; ++i) {
|
|
allocator.deallocate(a[i], n_elements);
|
|
}
|
|
});
|
|
t.join();
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
}
|
|
}
|
|
|
|
TEST(temptable_allocator, shared_block_is_kept_after_last_deallocation) {
|
|
{
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
std::thread t([]() {
|
|
temptable::Allocator<uint8_t> allocator;
|
|
|
|
uint8_t *ptr = allocator.allocate(16);
|
|
EXPECT_FALSE(temptable::shared_block.is_empty());
|
|
|
|
allocator.deallocate(ptr, 16);
|
|
EXPECT_FALSE(temptable::shared_block.is_empty());
|
|
});
|
|
t.join();
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
}
|
|
}
|
|
|
|
TEST(temptable_allocator, ram_consumption_is_not_monitored_for_shared_blocks) {
|
|
{
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
std::thread t([]() {
|
|
temptable::Allocator<uint8_t> allocator;
|
|
|
|
// RAM consumption is 0 at the start
|
|
EXPECT_EQ(MemoryMonitorReadOnlyProbe::ram_consumption(), 0);
|
|
|
|
// First allocation is fed from shared-block
|
|
size_t shared_block_n_elements = 1024 * 1024;
|
|
uint8_t *shared_block = allocator.allocate(shared_block_n_elements);
|
|
EXPECT_FALSE(temptable::shared_block.is_empty());
|
|
|
|
// RAM consumption is still 0
|
|
EXPECT_EQ(MemoryMonitorReadOnlyProbe::ram_consumption(), 0);
|
|
|
|
// Deallocate the shared-block
|
|
allocator.deallocate(shared_block, shared_block_n_elements);
|
|
EXPECT_FALSE(temptable::shared_block.is_empty());
|
|
});
|
|
t.join();
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
}
|
|
}
|
|
|
|
TEST(temptable_allocator,
|
|
ram_consumption_drops_to_zero_when_non_shared_block_is_destroyed) {
|
|
{
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
std::thread t([]() {
|
|
temptable::Allocator<uint8_t> allocator;
|
|
|
|
// RAM consumption is 0 at the start
|
|
EXPECT_EQ(MemoryMonitorReadOnlyProbe::ram_consumption(), 0);
|
|
|
|
// Make sure we fill up the shared_block first
|
|
// nr of elements must be >= 1MiB in size
|
|
size_t shared_block_n_elements = 1024 * 1024 + 256;
|
|
uint8_t *shared_block = allocator.allocate(shared_block_n_elements);
|
|
EXPECT_FALSE(temptable::shared_block.is_empty());
|
|
|
|
// Not even 1-byte should be able to fit anymore
|
|
EXPECT_FALSE(temptable::shared_block.can_accommodate(1));
|
|
|
|
// Now our next allocation should result in new block allocation ...
|
|
size_t non_shared_block_n_elements = 2 * 1024;
|
|
uint8_t *non_shared_block =
|
|
allocator.allocate(non_shared_block_n_elements);
|
|
|
|
// Make sure that pointers (Chunk's) are from different blocks
|
|
EXPECT_NE(temptable::Block(temptable::Chunk(non_shared_block)),
|
|
temptable::Block(temptable::Chunk(shared_block)));
|
|
|
|
// RAM consumption should be greater or equal than
|
|
// non_shared_block_n_elements bytes at this point
|
|
EXPECT_GE(MemoryMonitorReadOnlyProbe::ram_consumption(),
|
|
non_shared_block_n_elements);
|
|
|
|
// Deallocate the non-shared block
|
|
allocator.deallocate(non_shared_block, non_shared_block_n_elements);
|
|
|
|
// RAM consumption must drop to 0
|
|
EXPECT_EQ(MemoryMonitorReadOnlyProbe::ram_consumption(), 0);
|
|
|
|
// Deallocate the shared-block
|
|
allocator.deallocate(shared_block, shared_block_n_elements);
|
|
EXPECT_FALSE(temptable::shared_block.is_empty());
|
|
});
|
|
t.join();
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
}
|
|
}
|
|
|
|
TEST(temptable_allocator, edge) {
|
|
temptable::Allocator<uint8_t> allocator;
|
|
|
|
using namespace temptable;
|
|
|
|
EXPECT_EQ(nullptr, allocator.allocate(0));
|
|
|
|
#ifndef DBUG_OFF
|
|
DBUG_SET("+d,temptable_allocator_oom");
|
|
bool thrown = false;
|
|
try {
|
|
allocator.allocate(8_MiB);
|
|
} catch (...) {
|
|
thrown = true;
|
|
}
|
|
EXPECT_EQ(true, thrown);
|
|
DBUG_SET("-d,temptable_allocator_oom");
|
|
#endif /* DBUG_OFF */
|
|
}
|
|
|
|
TEST(temptable_allocator, block_size_cap) {
|
|
{
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
std::thread t([]() {
|
|
temptable::Allocator<uint8_t> allocator;
|
|
|
|
using namespace temptable;
|
|
|
|
constexpr size_t alloc_size = 1_MiB;
|
|
constexpr size_t n_allocate = ALLOCATOR_MAX_BLOCK_BYTES / alloc_size + 10;
|
|
std::array<uint8_t *, n_allocate> a;
|
|
|
|
for (size_t i = 0; i < n_allocate; ++i) {
|
|
a[i] = allocator.allocate(alloc_size);
|
|
}
|
|
|
|
EXPECT_FALSE(temptable::shared_block.is_empty());
|
|
|
|
for (size_t i = 0; i < n_allocate; ++i) {
|
|
allocator.deallocate(a[i], alloc_size);
|
|
}
|
|
});
|
|
t.join();
|
|
EXPECT_TRUE(temptable::shared_block.is_empty());
|
|
}
|
|
}
|
|
|
|
} /* namespace temptable_allocator_unittest */
|