Search code examples
c++deadlockgoogletest

Can I test deadlock in googletest?


I have a small class that creates multiple threads. When I call stop() method, it sets a flag for stopping threads and joins on the thread until they are stopped. Here is the code of stop() method:

void stop() {
    running_flag_ = false;
    for (auto& th : threads_) {
        if (th.joinable())
            th.join();
    }
}

I wonder if there is a way to test if this method works as expected, meaning this method should always return. I don't think dead test is really suitable here. Any idea if there is a way to test this?


Solution

  • When you have a deadlock some threads are waiting on locks until they are released. There is no way to recover process form such state, without adding extra code.

    The only way to overcome this problem is to detect timeout and crash unit test application when timeout accrues. Some times ago I used this code to detect issue so build machine do not hang to long:

    class TerminateOnDeadlockGuard final
    {
    public:
        using Clock = std::chrono::system_clock;
        using Duration = Clock::duration;
    
        explicit TerminateOnDeadlockGuard(Duration timeout = std::chrono::seconds{30});
    
        ~TerminateOnDeadlockGuard();
    
    private:
        void waitForCompletion();
    
    private:
        const Duration m_timeout;
        std::mutex m_mutex;
        std::condition_variable m_wasCompleted;
        std::condition_variable m_guardStarted;
        std::thread m_guardThread;
    };
    //---------
    TerminateOnDeadlockGuard::TerminateOnDeadlockGuard(Duration timeout) : m_timeout{timeout}
    {
        m_guardThread = std::thread(&TerminateOnDeadlockGuard::waitForCompletion, this);
        std::unique_lock lock{m_mutex};
        m_guardStarted.wait(lock);
    }
    
    TerminateOnDeadlockGuard::~TerminateOnDeadlockGuard()
    {
        m_wasCompleted.notify_one();
        m_guardThread.join();
    }
    
    void TerminateOnDeadlockGuard::waitForCompletion()
    {
        std::unique_lock lock{m_mutex};
        m_guardStarted.notify_one();
        if (std::cv_status::timeout == m_wasCompleted.wait_for(lock, m_timeout))
        {
            std::cerr << "Test timeout!!!" << std::endl;
            std::exit(-1); // you can use `abort` here to create crash log.
        }
    }
    

    Usage:

    TEST(SomeTestSuite, myTest)
    {
        TerminateOnDeadlockGuard guardDeadLock;
    
        // test code here
    }
    

    Now there is google death test which could come to the rescue. This forks process at let child process to terminate.

    using namespace std::chrono_literals;
    
    class NoDeadLockTest : public ::testing::Test
    {
    public:
        void someTest()
        {
            TerminateOnDeadlockGuard guardDeadLock{5s};
            actualTest();
            std::exit(::testing::Test::HasFailure() ? 2 : 0);
        }
    
        void actualTest()
        {
    #if VERSION == 0
            ASSERT_TRUE(true);
    #elif VERSION == 1
            ASSERT_TRUE(false);
    #elif VERSION == 2
            std::this_thread::sleep_for(10s);
    #endif
        }
    };
    
    TEST_F(NoDeadLockTest, myTest)
    {
        EXPECT_EXIT(someTest(), testing::ExitedWithCode(0), "");
    }
    

    I've checked this on godbolt and it is far from perfect: https://godbolt.org/z/nasros577 but shows there is hope if some extra effort is done.

    I relay recommend to use some tool to detect the dead locks and fix code. I used thread sanitizer - I've tried it and it is very effective.