boost::asio::io_context::stop gtest 安装和拆卸中的 segfalt

boost::asio::io_context::stop segfalt in gtest setup and teardown

使用 C++17。我正在尝试设置一个 gtest 装置,它将为每个测试用例创建一个新的 io_context 到 运行 计时器。我的测试 segfault 大约有 90% 的时间。如果我调试和步进非常慢,我可以一直到 运行。

我不确定这里发生了什么。我已经每隔 运行 创建了一个新线程和新 ioservice,只是为了确保没有从以前的测试中遗留。然后我完全删除了测试内容以缩小范围。它会引发停止调用。

#include "gtest/gtest.h"
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iostream>


class TrafficLightTestSuite : public testing::Test
{
public:

protected:
    boost::asio::io_context * m_ioContext;
    boost::thread           * m_ioThread;

    void SetUp() override
    {
        std::cout << "Setup" << std::endl;

        m_ioThread = new boost::thread([&]()
            {
                m_ioContext = new boost::asio::io_context();
                std::cout << "IO Service created" << std::endl;

                // Keep the io service alive until we are done
                boost::asio::io_service::work work(*m_ioContext);
                m_ioContext->run();
                std::cout << "IO Context run exited" << std::endl;

                delete m_ioContext;
                m_ioContext = nullptr;
                std::cout << "IO Context deleted" << std::endl;
            });
    }

    void TearDown() override
    {
        std::cout << "Tear down stopping IO Context" << std::endl;

        m_ioContext->stop();
        m_ioThread->join();

        std::cout << "Thread exit" << std::endl;
        delete m_ioThread;
    }
};

TEST_F(TrafficLightTestSuite, testTimeLapse)
{
    std::cout << "Performing test" << std::endl;
}

运行宁时的输出:

Testing started at 1:49 PM ...
Setup
Performing test
Tear down stopping IO Context
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

使用调试器单步执行时的输出:

Testing started at 1:55 PM ...
Setup
IO Service created
Performing test
Tear down stopping IO Context
IO Context run exited
IO Context deleted
Thread exit

Process finished with exit code 0

io_context 必须是线程安全的,不然你怎么告诉它停止? 有人发现问题了吗?

m_ioContext->stop(); 发生在 m_ioContext = new boost::asio::io_context(); 之前。只需将其创建移动到 SetUp 并将删除移动到 TearDown。你甚至不需要指点。

#include "gtest/gtest.h"
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iostream>


class TrafficLightTestSuite : public testing::Test
{
public:

protected:
    boost::asio::io_context m_ioContext{};
    boost::thread           m_ioThread{};

    void SetUp() override
    {
        std::cout << "Setup" << std::endl;
        std::cout << "IO Service created" << std::endl;

        m_ioThread = boost::thread([&]()
            {

                // Keep the io service alive until we are done
                boost::asio::io_service::work work(m_ioContext);
                m_ioContext.run();
                std::cout << "IO Context run exited" << std::endl;
            });
    }

    void TearDown() override
    {
        std::cout << "Tear down stopping IO Context" << std::endl;

        m_ioContext.stop();
        m_ioThread.join();

        std::cout << "Thread exit" << std::endl;
        std::cout << "IO Context deleted" << std::endl;
    }
};

TEST_F(TrafficLightTestSuite, testTimeLapse)
{
    std::cout << "Performing test" << std::endl;
}

问题是无法保证在主线程恢复执行之前线程上的任何内容都会执行。这导致了在执行测试用例之前未创建 ioservice 和 运行 的问题,假设 ioservice 将是 运行.

这可以通过使用可以等待的信号量来修复,并在调用 ioservice::run 之前在该信号量上排队 post。这样,一旦 ioservice 运行,信号量 posts 和测试用例就无法执行,直到发生这种情况。

class TrafficLightTestSuite : public testing::Test
{
public:
protected:
    boost::asio::io_context       m_ioContext {};
    boost::asio::io_service::work m_work {m_ioContext};  // Keeps the ioservice running when nothing is posted
    boost::thread                 m_ioThread {};
    boost::interprocess::interprocess_semaphore m_semaphore {0};

    void SetUp() override
    {
        // When the ioservice starts up, the semaphore will notify anyone that was waiting on it to run
        m_ioContext.post([&]() { m_semaphore.post(); });

        // Run the io service on its own thread, where completion handlers will be called
        m_ioThread = boost::thread(boost::bind(&boost::asio::io_service::run, &m_ioContext));

        // Test cases should wait on the semaphore to ensure the io service is running before they execute.
        // In production environment, you'd probably init the ioservice in some manner of application setup and do a
        // similar notification when everything was initialized.
        m_semaphore.wait();
    }

    void TearDown() override
    {
        m_ioContext.stop();
        m_ioThread.join();
    }
};

TEST_F(TrafficLightTestSuite, testInitialColor)
{
    // This is guaranteed not to execute until the ioservice is running
}

您还可以使用条件变量创建自己的信号量。如果使用 C++20,可以使用标准信号量,