chriskohlhoff / asio

Asio C++ Library
http://think-async.com/Asio
4.73k stars 1.2k forks source link

Boost ASIO multithread application crashes when job are posted between the threads #1352

Open rohitpai opened 10 months ago

rohitpai commented 10 months ago

I am trying to scale a boost single threaded application to a multi threaded application. The code is based on this article . The approach I am taking is to have dedicated IO_Context for each thread. In my application I need to post ASIO JOB from other thread to the other thread's IO_Context. As per boost article this should work but my application is crashing with segmentation fault after few iterations.

#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>

namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::system::error_code;

static std::atomic_int tid_gen = 0;
thread_local int const tid     = [] { return ++tid_gen; }();
static constexpr auto  now = std::chrono::steady_clock::now;
static auto const      start   = now();
static std::mutex console_mx;

void trace(auto const&... msg) {
    std::lock_guard lk(console_mx);
    std::cerr << "at " << std::setw(8) << (now() - start)/1ms << "ms - tid:" << tid << " ";
    (std::cerr << ... << msg) << std::endl;
}

void worker(asio::io_context& ioContext) {
    trace("Worker thread enter");
    ioContext.run(); // Run the io_context to handle asynchronous operations
    trace("Worker thread exit");
}

int main() {
    try {
        asio::io_context ioContext1;
        asio::io_context ioContext2;
        std::function<void(const boost::system::error_code& error)> handler1;
        std::function<void(const boost::system::error_code& error)> handler2;

        asio::steady_timer task1(ioContext1, 100ms);
        asio::steady_timer task2(ioContext2, 200ms);

        handler1 = [&task1, &handler1, &ioContext2](error_code ec) {
            //trace("Start Task1: ", ec.message());
            if (!ec)
                usleep(5000);
            asio::post(ioContext2, []{
                trace("Task1 posted job on Task2");
            });
            task1.async_wait(handler1);
        };
        task1.async_wait(handler1);

        handler2 = [&task2, &handler2, &ioContext1](error_code ec) {
            //trace("Start Task2: ", ec.message());
            if (!ec)
                usleep(10000);
            asio::post(ioContext1, []{
                trace("Task2 posted job on Task1");
            });  
            task2.async_wait(handler2);
        };
        task2.async_wait(handler2);

        // Create a work object to prevent ioContext.run() from returning immediately
        auto work1 = make_work_guard(ioContext1);
        auto work2 = make_work_guard(ioContext2);

        // Create multiple worker threads
        std::vector<std::thread> threads;
        threads.emplace_back(worker, std::ref(ioContext1));
        threads.emplace_back(worker, std::ref(ioContext2));

        trace("App started :", std::thread::hardware_concurrency());

        work1.reset();
        work2.reset();

        // Join the worker threads
        for (auto& thread : threads) {
            thread.join();
        }

        trace("All worker threads joined.");
    } catch (std::exception const& e) {
        trace("Exception: ", std::quoted(e.what()));
    }
}

Here is the back trace

Thread 3 "application" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x763de380 (LWP 29973)]
boost::asio::detail::scheduler::do_run_one (this=this@entry=0x42d188, lock=..., this_thread=..., ec=...) at /usr/include/boost/asio/detail/impl/scheduler.ipp:458

Is this approach should work or expected to fail? I have tried adding separate locks before posting the jobs but that also did not help. My application is running on linux with boost library version 1.79