Boost :: asio :: deadline_timer does not wake up (stress scenario)

I am using deadline_timer as an asynchronous event and I am running into a situation where after a while the thread waiting for the event never wakes up (despite more calls cancel()

). I was able to reproduce this using some example code I pasted below; this is not entirely consistent, but I have seen what I think is the same problem I am facing.

boost::asio::io_service io_service;
boost::asio::deadline_timer timer(io_service);
timer.expires_at(boost::posix_time::pos_infin);

int num_events = 0;
auto waiter = [&timer, &num_events](boost::asio::yield_context context) {
  while (true) {
    std::cout << "waiting on event" << std::endl;
    boost::system::error_code e;
    timer.async_wait(context[e]);
    std::cout << "got event (" << e << ")" << std::endl;
    ++num_events;
  }
};

boost::asio::spawn(io_service, std::move(waiter));
boost::thread thread(boost::bind(&boost::asio::io_service::run, &io_service));

for (auto i = 0; i < 500000; ++i) {
  timer.cancel();
  std::cout << i << std::endl;
}

      

Am I doing something here that unsupported and inadvertently hitting some race conditions? The error code from wait()

never looks awkward (not even the last time it woke up before it never appears again). EDIT: I also noticed the original error on three different platforms (Windows, Mac and Linux), but the above test I used to reproduce was on Windows.

+3


source to share


1 answer


The deadline_timer object is not thread safe.

You are canceling it from a thread other than the one that async_wait is posting. This means that challenges can race.

I'm not sure how this can block the callback completely in your example. It seems to me that the program should / just / exit due to the hard loop to 500000 ending quickly (doing a lot of redundant undo that is never handled because the coroutine, for example, hasn't even posted a new async_wait).

So maybe you mean, "Why don't I get 500,000 events."


UPDATE

Following the comment, here's a trivial transformation that shows how you are going to normally call the timer participants from within the actor. Note: this is critically dependent on the idea of ​​being io_service

executed from only one thread!



#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/make_shared.hpp>
#include <boost/thread.hpp>
#include <iostream>

using boost::thread;
using boost::asio::io_service;

int main() {
    boost::asio::io_service     io_service;

    boost::asio::deadline_timer timer(io_service);
    timer.expires_at(boost::posix_time::pos_infin);

    boost::atomic_bool shutdown(false);

    int num_events = 0;
    auto waiter = [&timer, &num_events, &shutdown](boost::asio::yield_context context) {
        while (!shutdown) {
            std::cout << "waiting on event" << std::endl;

            boost::system::error_code e;
            timer.async_wait(context[e]);

            std::cout << "got event (" << e.message() << ")" << std::endl;
            ++num_events;
        }
    };

    boost::asio::spawn(io_service, std::move(waiter));
    boost::thread thread(boost::bind(&boost::asio::io_service::run, &io_service));

    for (auto i = 0; i < 5000; ++i) {
        io_service.post([&timer, i]{ 
                std::cout << i << std::endl;
                timer.cancel(); 
            });
    }

    io_service.post([&]{ 
            shutdown = true;
            timer.cancel();
        });

    thread.join();

    std::cout << "Check: " << num_events << " events counted\n";
}

      


Also, it looks like you just wanted to signal a background job. As stated, you can simplify the program, for example:

Watch Live On Coliru

#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/make_shared.hpp>
#include <iostream>

using boost::thread;
using boost::asio::io_service;

int main() {
    io_service svc;
    int num_events = 0;

    auto work = boost::make_shared<io_service::work>(svc); // keep svc running
    boost::thread thread(boost::bind(&io_service::run, &svc));

    for (auto i = 0; i < 500000; ++i) {
        svc.post([&num_events,i]{
                std::cout << "got event (" << i << ")" << std::endl;
                ++num_events;
                });
    }

    work.reset();
    thread.join();

    std::cout << "Check: " << num_events << " events counted\n";
}

      

This prints all 500,000 events:

got event (0)
got event (1)
got event (3)
...
got event (499998)
got event (499999)
Check: 500000 events counted

      

+1


source







All Articles