You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
8.7 KiB
349 lines
8.7 KiB
/* |
|
* 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 "Allocator.h" |
|
#include "ScopedDisableMalloc.h" |
|
#include "ScopedPipe.h" |
|
|
|
using namespace std::chrono_literals; |
|
|
|
namespace android { |
|
|
|
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([&, 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()); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
} // namespace android
|
|
|