/* * Copyright (C) 2018 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 "perfetto/base/watchdog.h" #include "gtest/gtest.h" #include "perfetto/base/logging.h" #include "perfetto/base/paged_memory.h" #include "perfetto/base/scoped_file.h" #include "perfetto/base/thread_utils.h" #include <signal.h> #include <time.h> #include <condition_variable> #include <map> #include <memory> #include <mutex> #include <thread> #include <vector> namespace perfetto { namespace base { namespace { class TestWatchdog : public Watchdog { public: explicit TestWatchdog(uint32_t polling_interval_ms) : Watchdog(polling_interval_ms) {} ~TestWatchdog() override {} }; TEST(WatchdogTest, NoTimerCrashIfNotEnabled) { // CreateFatalTimer should be a noop if the watchdog is not enabled. TestWatchdog watchdog(100); auto handle = watchdog.CreateFatalTimer(1); usleep(100 * 1000); } TEST(WatchdogTest, TimerCrash) { // Create a timer for 20 ms and don't release wihin the time. EXPECT_DEATH( { TestWatchdog watchdog(100); watchdog.Start(); auto handle = watchdog.CreateFatalTimer(20); usleep(200 * 1000); }, ""); } TEST(WatchdogTest, CrashEvenWhenMove) { std::map<int, Watchdog::Timer> timers; EXPECT_DEATH( { TestWatchdog watchdog(100); watchdog.Start(); timers.emplace(0, watchdog.CreateFatalTimer(20)); usleep(200 * 1000); }, ""); } TEST(WatchdogTest, CrashMemory) { EXPECT_DEATH( { // Allocate 8MB of data and use it to increase RSS. const size_t kSize = 8 * 1024 * 1024; auto void_ptr = PagedMemory::Allocate(kSize); volatile uint8_t* ptr = static_cast<volatile uint8_t*>(void_ptr.Get()); for (size_t i = 0; i < kSize; i += sizeof(size_t)) { *reinterpret_cast<volatile size_t*>(&ptr[i]) = i; } TestWatchdog watchdog(5); watchdog.SetMemoryLimit(8 * 1024 * 1024, 25); watchdog.Start(); // Sleep so that the watchdog has some time to pick it up. usleep(1000 * 1000); }, ""); } TEST(WatchdogTest, CrashCpu) { EXPECT_DEATH( { TestWatchdog watchdog(1); watchdog.SetCpuLimit(10, 25); watchdog.Start(); volatile int x = 0; for (;;) { x++; } }, ""); } // The test below tests that the fatal timer signal is sent to the thread that // created the timer and not a random one. int RestoreSIGABRT(const struct sigaction* act) { return sigaction(SIGABRT, act, nullptr); } PlatformThreadID g_aborted_thread = 0; void SIGABRTHandler(int) { g_aborted_thread = GetThreadId(); } TEST(WatchdogTest, TimerCrashDeliveredToCallerThread) { // Setup a signal handler so that SIGABRT doesn't cause a crash but just // records the current thread id. struct sigaction oldact; struct sigaction newact = {}; newact.sa_handler = SIGABRTHandler; ASSERT_EQ(sigaction(SIGABRT, &newact, &oldact), 0); base::ScopedResource<const struct sigaction*, RestoreSIGABRT, nullptr> auto_restore(&oldact); // Create 8 threads. All of them but one will just sleep. The selected one // will register a watchdog and fail. const size_t kKillThreadNum = 3; std::mutex mutex; std::condition_variable cv; bool quit = false; g_aborted_thread = 0; PlatformThreadID expected_tid = 0; auto thread_fn = [&mutex, &cv, &quit, &expected_tid](size_t thread_num) { if (thread_num == kKillThreadNum) { expected_tid = GetThreadId(); TestWatchdog watchdog(100); watchdog.Start(); auto handle = watchdog.CreateFatalTimer(2); usleep(200 * 1000); // This will be interrupted by the fatal timer. std::unique_lock<std::mutex> lock(mutex); quit = true; cv.notify_all(); } else { std::unique_lock<std::mutex> lock(mutex); cv.wait(lock, [&quit] { return quit; }); } }; std::vector<std::thread> threads; for (size_t i = 0; i < 8; i++) threads.emplace_back(thread_fn, i); // Join them all. for (auto& thread : threads) thread.join(); EXPECT_EQ(g_aborted_thread, expected_tid); } } // namespace } // namespace base } // namespace perfetto