android_system_core/libmemunreachable/tests/ThreadCapture_test.cpp
Colin Cross bcb4ed3eaa imprecise mark and sweep native memory leak detector
libmemunreachable uses an imprecise mark and sweep pass over all memory
allocated by jemalloc in order to find unreachable allocations.

Change-Id: Ia70bbf31f5b40ff71dab28cfd6cd06c5ef01a2d4
2016-02-08 17:08:49 -08:00

351 lines
8.4 KiB
C++

/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed 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 "ThreadCapture.h"
#include <fcntl.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <thread>
#include <gtest/gtest.h>
#include <android-base/unique_fd.h>
#include "Allocator.h"
#include "ScopedDisableMalloc.h"
#include "ScopedPipe.h"
using namespace std::chrono_literals;
class ThreadListTest : public ::testing::TestWithParam<int> {
public:
ThreadListTest() : stop_(false) {}
~ThreadListTest() {
// pthread_join may return before the entry in /proc/pid/task/ is gone,
// loop until ListThreads only finds the main thread so the next test
// doesn't fail.
WaitForThreads();
}
virtual void TearDown() {
ASSERT_TRUE(heap.empty());
}
protected:
template<class Function>
void StartThreads(unsigned int threads, Function&& func) {
threads_.reserve(threads);
tids_.reserve(threads);
for (unsigned int i = 0; i < threads; i++) {
threads_.emplace_back([&, i, threads, this]() {
{
std::lock_guard<std::mutex> lk(m_);
tids_.push_back(gettid());
if (tids_.size() == threads) {
cv_start_.notify_one();
}
}
func();
{
std::unique_lock<std::mutex> lk(m_);
cv_stop_.wait(lk, [&] {return stop_;});
}
});
}
{
std::unique_lock<std::mutex> lk(m_);
cv_start_.wait(lk, [&]{ return tids_.size() == threads; });
}
}
void StopThreads() {
{
std::lock_guard<std::mutex> lk(m_);
stop_ = true;
}
cv_stop_.notify_all();
for (auto i = threads_.begin(); i != threads_.end(); i++) {
i->join();
}
threads_.clear();
tids_.clear();
}
std::vector<pid_t>& tids() {
return tids_;
}
Heap heap;
private:
void WaitForThreads() {
auto tids = TidList{heap};
ThreadCapture thread_capture{getpid(), heap};
for (unsigned int i = 0; i < 100; i++) {
EXPECT_TRUE(thread_capture.ListThreads(tids));
if (tids.size() == 1) {
break;
}
std::this_thread::sleep_for(10ms);
}
EXPECT_EQ(1U, tids.size());
}
std::mutex m_;
std::condition_variable cv_start_;
std::condition_variable cv_stop_;
bool stop_;
std::vector<pid_t> tids_;
std::vector<std::thread> threads_;
};
TEST_F(ThreadListTest, list_one) {
ScopedDisableMallocTimeout disable_malloc;
ThreadCapture thread_capture(getpid(), heap);
auto expected_tids = allocator::vector<pid_t>(1, getpid(), heap);
auto list_tids = allocator::vector<pid_t>(heap);
ASSERT_TRUE(thread_capture.ListThreads(list_tids));
ASSERT_EQ(expected_tids, list_tids);
if (!HasFailure()) {
ASSERT_FALSE(disable_malloc.timed_out());
}
}
TEST_P(ThreadListTest, list_some) {
const unsigned int threads = GetParam() - 1;
StartThreads(threads, [](){});
std::vector<pid_t> expected_tids = tids();
expected_tids.push_back(getpid());
auto list_tids = allocator::vector<pid_t>(heap);
{
ScopedDisableMallocTimeout disable_malloc;
ThreadCapture thread_capture(getpid(), heap);
ASSERT_TRUE(thread_capture.ListThreads(list_tids));
if (!HasFailure()) {
ASSERT_FALSE(disable_malloc.timed_out());
}
}
StopThreads();
std::sort(list_tids.begin(), list_tids.end());
std::sort(expected_tids.begin(), expected_tids.end());
ASSERT_EQ(expected_tids.size(), list_tids.size());
EXPECT_TRUE(std::equal(expected_tids.begin(), expected_tids.end(), list_tids.begin()));
}
INSTANTIATE_TEST_CASE_P(ThreadListTest, ThreadListTest, ::testing::Values(1, 2, 10, 1024));
class ThreadCaptureTest : public ThreadListTest {
public:
ThreadCaptureTest() {}
~ThreadCaptureTest() {}
void Fork(std::function<void()>&& child_init,
std::function<void()>&& child_cleanup,
std::function<void(pid_t)>&& parent) {
ScopedPipe start_pipe;
ScopedPipe stop_pipe;
int pid = fork();
if (pid == 0) {
// child
child_init();
EXPECT_EQ(1, TEMP_FAILURE_RETRY(write(start_pipe.Sender(), "+", 1))) << strerror(errno);
char buf;
EXPECT_EQ(1, TEMP_FAILURE_RETRY(read(stop_pipe.Receiver(), &buf, 1))) << strerror(errno);
child_cleanup();
_exit(0);
} else {
// parent
ASSERT_GT(pid, 0);
char buf;
ASSERT_EQ(1, TEMP_FAILURE_RETRY(read(start_pipe.Receiver(), &buf, 1))) << strerror(errno);
parent(pid);
ASSERT_EQ(1, TEMP_FAILURE_RETRY(write(stop_pipe.Sender(), "+", 1))) << strerror(errno);
siginfo_t info{};
ASSERT_EQ(0, TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED))) << strerror(errno);
}
}
};
TEST_P(ThreadCaptureTest, capture_some) {
const unsigned int threads = GetParam();
Fork([&](){
// child init
StartThreads(threads - 1, [](){});
},
[&](){
// child cleanup
StopThreads();
},
[&](pid_t child){
// parent
ASSERT_GT(child, 0);
{
ScopedDisableMallocTimeout disable_malloc;
ThreadCapture thread_capture(child, heap);
auto list_tids = allocator::vector<pid_t>(heap);
ASSERT_TRUE(thread_capture.ListThreads(list_tids));
ASSERT_EQ(threads, list_tids.size());
ASSERT_TRUE(thread_capture.CaptureThreads());
auto thread_info = allocator::vector<ThreadInfo>(heap);
ASSERT_TRUE(thread_capture.CapturedThreadInfo(thread_info));
ASSERT_EQ(threads, thread_info.size());
ASSERT_TRUE(thread_capture.ReleaseThreads());
if (!HasFailure()) {
ASSERT_FALSE(disable_malloc.timed_out());
}
}
});
}
INSTANTIATE_TEST_CASE_P(ThreadCaptureTest, ThreadCaptureTest, ::testing::Values(1, 2, 10, 1024));
TEST_F(ThreadCaptureTest, capture_kill) {
int ret = fork();
if (ret == 0) {
// child
sleep(10);
} else {
// parent
ASSERT_GT(ret, 0);
{
ScopedDisableMallocTimeout disable_malloc;
ThreadCapture thread_capture(ret, heap);
thread_capture.InjectTestFunc([&](pid_t tid){
syscall(SYS_tgkill, ret, tid, SIGKILL);
usleep(10000);
});
auto list_tids = allocator::vector<pid_t>(heap);
ASSERT_TRUE(thread_capture.ListThreads(list_tids));
ASSERT_EQ(1U, list_tids.size());
ASSERT_FALSE(thread_capture.CaptureThreads());
if (!HasFailure()) {
ASSERT_FALSE(disable_malloc.timed_out());
}
}
}
}
TEST_F(ThreadCaptureTest, capture_signal) {
const int sig = SIGUSR1;
ScopedPipe pipe;
// For signal handler
static ScopedPipe* g_pipe;
Fork([&](){
// child init
pipe.CloseReceiver();
g_pipe = &pipe;
struct sigaction act{};
act.sa_handler = [](int){
char buf = '+';
write(g_pipe->Sender(), &buf, 1);
g_pipe->CloseSender();
};
sigaction(sig, &act, NULL);
sigset_t set;
sigemptyset(&set);
sigaddset(&set, sig);
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
},
[&](){
// child cleanup
g_pipe = nullptr;
pipe.Close();
},
[&](pid_t child){
// parent
ASSERT_GT(child, 0);
pipe.CloseSender();
{
ScopedDisableMallocTimeout disable_malloc;
ThreadCapture thread_capture(child, heap);
thread_capture.InjectTestFunc([&](pid_t tid){
syscall(SYS_tgkill, child, tid, sig);
usleep(10000);
});
auto list_tids = allocator::vector<pid_t>(heap);
ASSERT_TRUE(thread_capture.ListThreads(list_tids));
ASSERT_EQ(1U, list_tids.size());
ASSERT_TRUE(thread_capture.CaptureThreads());
auto thread_info = allocator::vector<ThreadInfo>(heap);
ASSERT_TRUE(thread_capture.CapturedThreadInfo(thread_info));
ASSERT_EQ(1U, thread_info.size());
ASSERT_TRUE(thread_capture.ReleaseThreads());
usleep(100000);
char buf;
ASSERT_EQ(1, TEMP_FAILURE_RETRY(read(pipe.Receiver(), &buf, 1)));
ASSERT_EQ(buf, '+');
if (!HasFailure()) {
ASSERT_FALSE(disable_malloc.timed_out());
}
}
});
}