| /* |
| * 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; |
| |
| 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()); |
| } |
| } |
| }); |
| } |