Отмена boost asio таймер крайнего срока безопасно

Я пытаюсь отменить boost::asio::basic_waitable_timer<std::chrono::steady_clock> безопасно.

по этому ответ этот код должен делать эту работу:

timer.get_io_service().post([&]{timer.cancel();})

боюсь, это не сработает для меня.
Я делаю что-то не так?
Вот мой код:

#include <iostream>
#include "boost/asio.hpp"
#include <chrono>
#include <thread>
#include <random>

boost::asio::io_service io_service;
boost::asio::basic_waitable_timer<std::chrono::steady_clock> timer(io_service);
std::atomic<bool> started;

void handle_timeout(const boost::system::error_code& ec)
{
    if (!ec) {
        started = true;
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout\n";
        timer.expires_from_now(std::chrono::milliseconds(10));
        timer.async_wait(&handle_timeout);
    } else if (ec == boost::asio::error::operation_aborted) {
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout aborted\n";
    } else {
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout another error\n";
    }
}

int main() {

    std::cout << "tid: " << std::this_thread::get_id() << ", Hello, World!" << std::endl;
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(1, 100);

    for (auto i = 0; i < 1000; i++) {

        started = false;
        std::thread t([&](){

            timer.expires_from_now(std::chrono::milliseconds(0));
            timer.async_wait(&handle_timeout);

            io_service.run();
        });

        while (!started) {};
        auto sleep = dis(gen);
        std::cout << "tid: " << std::this_thread::get_id() << ", i: " << i << ", sleeps for " << sleep << " [ms]" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
        timer.get_io_service().post([](){
            std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
            timer.cancel();
        });
//      timer.cancel();
        std::cout << "tid: " << std::this_thread::get_id() << ", i: " << i << ", waiting for thread to join()" << std::endl;
        t.join();
        io_service.reset();
    }

    return 0;
}

это выход:

...
tid: 140737335076608, handle_timeout
tid: 140737335076608, handle_timeout
tid: 140737353967488, i: 2, ожидание thread to join ()
tid: 140737335076608, отмена в сообщении
tid: 140737335076608, handle_timeout прервано
tid: 140737353967488, i: 3, спит 21 [МС]
tid: 140737335076608, handle_timeout
tid: 140737353967488, i: 3, ожидание соединения потока ()
tid: 140737335076608, handle_timeout
tid: 140737335076608, отмена в сообщении
tid: 140737335076608, handle_timeout
tid: 140737335076608, handle_timeout
tid: 140737335076608, handle_timeout
tid: 140737335076608, handle_timeout
tid: 140737335076608, handle_timeout
...
продолжаться вечно...

Как видите,timer.cancel() вызывается из соответствующего потока:

tid: 140737335076608, отмена в сообщении

, но нет

tid: 140737335076608, handle_timeout прервано

далее.

Main ждет вечно.

1 ответов


отмена is безопасная.

это просто не надежные. Вы не учли случай, когда таймер не был отложен. Вы отменяете его один раз, но он просто запустит новое асинхронное ожидание после вызова обработчика завершения.

ниже приведены мои подробные шаги о том, как я проследил проблему.

резюме TL; DR

отмена времени отменяет только асинхронные операции в полете.

если вы хотите завершить асинхронную цепочку вызовов, вам придется использовать дополнительную логику для этого. Пример приведен ниже.

Отслеживание Обработчик

использование

#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1

это производит выход, который можно визуализировать с boost/libs/asio/tools/handlerviz.pl:

удачный след

enter image description here

Как видите,async_wait находится в полете, когда отмена происходит.

"плохой" след

(усеченный, потому что он будет работать бесконечно)

enter image description here

обратите внимание, как обработчик завершения видит cc=system:0, а не cc=system:125 (для operation_aborted). Это симптом того, что размещенная отмена фактически не "взяла". Единственное логическое объяснение (не видимое на диаграмме) заключается в том, что таймер уже истек до вызова отмены.

давайте сравним сырые traces1

enter image description here

1 удаление шумной разницы

Поиском

Итак, у нас есть зацепка. Мы можем его обнаружить?

    timer.get_io_service().post([](){
        std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
        if (timer.expires_from_now() >= std::chrono::steady_clock::duration(0)) {
            timer.cancel();
        } else {
            std::cout << "PANIC\n";
            timer.cancel();
        }
    });

принты:

tid: 140113177143232, i: 0, waiting for thread to join()
tid: 140113177143232, i: 1, waiting for thread to join()
tid: 140113177143232, i: 2, waiting for thread to join()
tid: 140113177143232, i: 3, waiting for thread to join()
tid: 140113177143232, i: 4, waiting for thread to join()
tid: 140113177143232, i: 5, waiting for thread to join()
tid: 140113177143232, i: 6, waiting for thread to join()
tid: 140113177143232, i: 7, waiting for thread to join()
tid: 140113177143232, i: 8, waiting for thread to join()
tid: 140113177143232, i: 9, waiting for thread to join()
tid: 140113177143232, i: 10, waiting for thread to join()
tid: 140113177143232, i: 11, waiting for thread to join()
tid: 140113177143232, i: 12, waiting for thread to join()
tid: 140113177143232, i: 13, waiting for thread to join()
tid: 140113177143232, i: 14, waiting for thread to join()
tid: 140113177143232, i: 15, waiting for thread to join()
tid: 140113177143232, i: 16, waiting for thread to join()
tid: 140113177143232, i: 17, waiting for thread to join()
tid: 140113177143232, i: 18, waiting for thread to join()
tid: 140113177143232, i: 19, waiting for thread to join()
tid: 140113177143232, i: 20, waiting for thread to join()
tid: 140113177143232, i: 21, waiting for thread to join()
tid: 140113177143232, i: 22, waiting for thread to join()
tid: 140113177143232, i: 23, waiting for thread to join()
tid: 140113177143232, i: 24, waiting for thread to join()
tid: 140113177143232, i: 25, waiting for thread to join()
tid: 140113177143232, i: 26, waiting for thread to join()
PANIC

могли бы мы сообщить о "супер-отмене" другим, более ясным способом? У нас есть... просто timer объект для работы, конечно:

Сигнализация Отключения

на