blob: 5a025bf9f27a37fc63bb6bb3175ba3b87a05aeac [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <cstdlib>
#include <random>
#include "common/object-pool.h"
#include "runtime/bufferpool/free-list.h"
#include "runtime/bufferpool/system-allocator.h"
#include "runtime/test-env.h"
#include "service/fe-support.h"
#include "testutil/gtest-util.h"
#include "testutil/rand-util.h"
#include "common/names.h"
namespace impala {
class FreeListTest : public ::testing::Test {
protected:
virtual void SetUp() override {
test_env_.reset(new TestEnv);
ASSERT_OK(test_env_->Init());
allocator_ = obj_pool_.Add(new SystemAllocator(MIN_BUFFER_LEN));
RandTestUtil::SeedRng("FREE_LIST_TEST_SEED", &rng_);
}
virtual void TearDown() override {
allocator_ = nullptr;
obj_pool_.Clear();
}
void AllocateBuffers(
int num_buffers, int64_t buffer_len, vector<BufferHandle>* buffers) {
for (int i = 0; i < num_buffers; ++i) {
BufferHandle buffer;
ASSERT_OK(allocator_->Allocate(buffer_len, &buffer));
buffers->push_back(move(buffer));
}
}
vector<const void*> GetSortedAddrs(const vector<BufferHandle>& buffers) {
vector<const void*> addrs;
for (const BufferHandle& buffer : buffers) addrs.push_back(buffer.data());
std::sort(addrs.begin(), addrs.end());
return addrs;
}
void AddFreeBuffers(FreeList* list, vector<BufferHandle>* buffers) {
for (BufferHandle& buffer : *buffers) {
list->AddFreeBuffer(move(buffer));
}
buffers->clear();
}
void FreeBuffers(vector<BufferHandle>&& buffers) {
for (BufferHandle& buffer : buffers) {
allocator_->Free(move(buffer));
}
}
const static int MIN_BUFFER_LEN = 1024;
boost::scoped_ptr<TestEnv> test_env_;
/// Per-test random number generator. Seeded before every test.
std::mt19937 rng_;
/// Pool for objects with per-test lifetime. Cleared after every test.
ObjectPool obj_pool_;
/// The buffer allocator, owned by 'obj_pool_'.
SystemAllocator* allocator_;
};
const int FreeListTest::MIN_BUFFER_LEN;
// Functional test for a small free list.
TEST_F(FreeListTest, SmallList) {
const int LIST_SIZE = 2;
FreeList small_list;
// PopFreeBuffer() on empty list returns false.
BufferHandle buffer;
ASSERT_FALSE(small_list.PopFreeBuffer(&buffer));
ASSERT_FALSE(small_list.PopFreeBuffer(&buffer));
// Add various numbers of buffers to the free list and check that they're
// either freed or returned in the order expected.
for (int num_buffers = 0; num_buffers <= LIST_SIZE + 2; ++num_buffers) {
for (int attempt = 0; attempt < 10; ++attempt) {
LOG(INFO) << "num_buffers " << num_buffers << " attempt " << attempt;
vector<BufferHandle> buffers;
AllocateBuffers(num_buffers, MIN_BUFFER_LEN, &buffers);
// Keep track of the addresses so we can validate the buffer order.
const vector<const void*> addrs = GetSortedAddrs(buffers);
// Try shuffling to make sure we don't always just add in ascending order.
std::shuffle(buffers.begin(), buffers.end(), rng_);
AddFreeBuffers(&small_list, &buffers);
// Shrink list down to LIST_SIZE.
FreeBuffers(
small_list.GetBuffersToFree(max<int64_t>(0, small_list.Size() - LIST_SIZE)));
// The LIST_SIZE buffers with the lowest address should be retained, and the
// remaining buffers should have been freed.
for (int i = 0; i < min(num_buffers, LIST_SIZE); ++i) {
ASSERT_TRUE(small_list.PopFreeBuffer(&buffer)) << i;
ASSERT_EQ(addrs[i], buffer.data()) << i;
buffers.push_back(move(buffer));
}
ASSERT_FALSE(small_list.PopFreeBuffer(&buffer));
FreeBuffers(move(buffers));
}
}
}
// Functional test that makes sure the free lists return buffers in ascending order
TEST_F(FreeListTest, ReturnOrder) {
const int LIST_SIZE = 100;
FreeList list;
for (int num_buffers = 50; num_buffers <= 400; num_buffers *= 2) {
for (int attempt = 0; attempt < 5; ++attempt) {
LOG(INFO) << "num_buffers " << num_buffers << " attempt " << attempt;
vector<BufferHandle> buffers;
AllocateBuffers(num_buffers, MIN_BUFFER_LEN, &buffers);
// Keep track of the addresses so we can validate the buffer order.
const vector<const void*> addrs = GetSortedAddrs(buffers);
// Try shuffling to make sure we don't always just add in ascending order.
std::shuffle(buffers.begin(), buffers.end(), rng_);
AddFreeBuffers(&list, &buffers);
// Free buffers. Only the buffers with the high addresses should be freed.
FreeBuffers(list.GetBuffersToFree(max<int64_t>(0, list.Size() - LIST_SIZE)));
// Validate that the buffers with lowest addresses are returned in ascending order.
BufferHandle buffer;
for (int i = 0; i < min(LIST_SIZE, num_buffers); ++i) {
ASSERT_TRUE(list.PopFreeBuffer(&buffer));
ASSERT_EQ(addrs[i], buffer.data()) << i;
allocator_->Free(move(buffer));
}
ASSERT_FALSE(list.PopFreeBuffer(&buffer));
}
}
}
}