blob: e5a53489152dad5f97f5c479572a5e7761911ab0 [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 <string>
#include "runtime/free-pool.h"
#include "runtime/mem-pool.h"
#include "runtime/mem-tracker.h"
#include "testutil/gtest-util.h"
#include "common/names.h"
namespace impala {
TEST(FreePoolTest, Basic) {
MemTracker tracker;
MemPool mem_pool(&tracker);
FreePool pool(&mem_pool);
// Start off with some corner cases.
uint8_t* p1 = pool.Allocate(0);
ASSERT_TRUE(p1 != NULL);
pool.Free(p1);
pool.Free(NULL);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 0);
p1 = pool.Allocate(1);
*p1 = 111; // Scribble something to make sure it doesn't mess up the list.
ASSERT_TRUE(p1 != NULL);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 16);
pool.Free(p1);
// Allocating this should return p again.
for (int i = 0; i < 10; ++i) {
uint8_t* p2 = pool.Allocate(1);
*p2 = 111;
EXPECT_EQ(p1, p2);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 16);
pool.Free(p2);
}
uint8_t* p2 = pool.Allocate(1);
*p2 = 111;
// p3 will cause a new allocation.
uint8_t* p3 = pool.Allocate(1);
*p3 = 111;
EXPECT_TRUE(p1 == p2);
EXPECT_TRUE(p1 != p3);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 32);
pool.Free(p2);
pool.Free(p3);
// We know have 2 1 byte allocations, which were rounded up to 8 bytes. Make an 8
// byte allocation, which can reuse one of the existing ones.
uint8_t* p4 = pool.Allocate(2);
memset(p4, 123, 2);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 32);
EXPECT_TRUE(p4 == p1 || p4 == p2 || p4 == p3);
pool.Free(p4);
// Make a 9 byte allocation, which requires a new allocation.
uint8_t* p5 = pool.Allocate(9);
memset(p5, 123, 9);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 56);
pool.Free(p5);
EXPECT_TRUE(p5 != p1);
EXPECT_TRUE(p5 != p2);
EXPECT_TRUE(p5 != p3);
// Everything's freed. Try grabbing the ones that have been allocated.
p1 = pool.Allocate(1);
*p1 = 123;
p2 = pool.Allocate(1);
*p2 = 123;
p5 = pool.Allocate(9);
memset(p5, 123, 9);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 56);
// Make another 1 byte allocation.
p4 = pool.Allocate(1);
*p4 = 1;
EXPECT_EQ(mem_pool.total_allocated_bytes(), 72);
mem_pool.FreeAll();
// Try making allocations larger than 1GB.
uint8_t* p6 = pool.Allocate(1LL << 32);
EXPECT_TRUE(p6 != NULL);
for (int64_t i = 0; i < (1LL << 32); i += (1 << 29)) {
*(p6 + i) = i;
}
EXPECT_EQ(mem_pool.total_allocated_bytes(), (1LL << 32) + 8);
// Test zero-byte allocation.
p6 = pool.Allocate(0);
EXPECT_TRUE(p6 != NULL);
EXPECT_EQ(mem_pool.total_allocated_bytes(), (1LL << 32) + 8);
pool.Free(p6);
mem_pool.FreeAll();
}
#ifdef ADDRESS_SANITIZER
// The following tests confirm that ASAN catches invalid memory accesses thanks to the
// FreePool manually (un)poisoning memory.
TEST(FreePoolTest, OutOfBoundsAccess) {
MemTracker tracker;
MemPool mem_pool(&tracker);
FreePool pool(&mem_pool);
uint8_t* ptr = pool.Allocate(5);
ptr[4] = 'O';
ASSERT_DEATH({ptr[5] = 'X';}, "use-after-poison");
mem_pool.FreeAll();
}
TEST(FreePoolTest, UseAfterFree) {
MemTracker tracker;
MemPool mem_pool(&tracker);
FreePool pool(&mem_pool);
uint8_t* ptr = pool.Allocate(5);
ptr[4] = 'O';
pool.Free(ptr);
ASSERT_DEATH({ptr[0] = 'X';}, "use-after-poison");
mem_pool.FreeAll();
}
TEST(FreePoolTest, ReallocPoison) {
MemTracker tracker;
MemPool mem_pool(&tracker);
FreePool pool(&mem_pool);
uint8_t* ptr = pool.Allocate(32);
ptr[30] = 'O';
ptr = pool.Reallocate(ptr, 16);
ASSERT_DEATH({ptr[30] = 'X';}, "use-after-poison");
mem_pool.FreeAll();
}
#endif
// In this test we make two allocations at increasing sizes and then we
// free both to prime the pool. Then, for a few iterations, we make the same allocations
// as we did to prime the pool in a random order. We validate that the returned
// allocation is one of the two original and the pool did not increase in size.
TEST(FreePoolTest, Loop) {
MemTracker tracker;
MemPool mem_pool(&tracker);
FreePool pool(&mem_pool);
map<int64_t, pair<uint8_t*, uint8_t*>> primed_allocations;
vector<int64_t> allocation_sizes;
int64_t expected_pool_size = 0;
// Pick a non-power of 2 to exercise more code.
for (int64_t size = 5; size < 6LL * 1024 * 1024 * 1024; size *= 5) {
uint8_t* p1 = pool.Allocate(size);
uint8_t* p2 = pool.Allocate(size);
EXPECT_TRUE(p1 != NULL);
EXPECT_TRUE(p2 != NULL);
EXPECT_TRUE(p1 != p2);
// Scribble the expected value (used below).
*p1 = 1;
*p2 = 1;
primed_allocations[size] = make_pair(p1, p2);
pool.Free(p1);
pool.Free(p2);
allocation_sizes.push_back(size);
}
expected_pool_size = mem_pool.total_allocated_bytes();
for (int iters = 1; iters <= 5; ++iters) {
std::random_shuffle(allocation_sizes.begin(), allocation_sizes.end());
for (int i = 0; i < allocation_sizes.size(); ++i) {
uint8_t* p1 = pool.Allocate(allocation_sizes[i]);
uint8_t* p2 = pool.Allocate(allocation_sizes[i]);
EXPECT_TRUE(p1 != p2);
EXPECT_TRUE(p1 == primed_allocations[allocation_sizes[i]].first ||
p1 == primed_allocations[allocation_sizes[i]].second);
EXPECT_TRUE(p2 == primed_allocations[allocation_sizes[i]].first ||
p2 == primed_allocations[allocation_sizes[i]].second);
EXPECT_EQ(*p1, iters);
EXPECT_EQ(*p2, iters);
++(*p1);
++(*p2);
pool.Free(p1);
pool.Free(p2);
}
EXPECT_EQ(mem_pool.total_allocated_bytes(), expected_pool_size);
}
mem_pool.FreeAll();
}
TEST(FreePoolTest, ReAlloc) {
MemTracker tracker;
MemPool mem_pool(&tracker);
FreePool pool(&mem_pool);
uint8_t* ptr = pool.Reallocate(NULL, 0);
ptr = pool.Reallocate(ptr, 0);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 0);
ptr = pool.Reallocate(ptr, 600);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 1024 + 8);
uint8_t* ptr2 = pool.Reallocate(ptr, 200);
EXPECT_TRUE(ptr == ptr2);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 1024 + 8);
uint8_t* ptr3 = pool.Reallocate(ptr, 2000);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 1024 + 8 + 2048 + 8);
EXPECT_TRUE(ptr2 != ptr3);
// The original 600 allocation should be there.
ptr = pool.Allocate(600);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 1024 + 8 + 2048 + 8);
// Try allocation larger than 1GB.
uint8_t* ptr4 = pool.Reallocate(ptr3, 1LL << 32);
EXPECT_TRUE(ptr3 != ptr4);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 1024 + 8 + 2048 + 8 + (1LL << 32) + 8);
// Shrink the allocation.
uint8_t* ptr5 = pool.Reallocate(ptr4, 1024);
EXPECT_TRUE(ptr4 == ptr5);
EXPECT_EQ(mem_pool.total_allocated_bytes(), 1024 + 8 + 2048 + 8 + (1LL << 32) + 8);
pool.Free(ptr5);
mem_pool.FreeAll();
}
}