Увеличьте asio одновременную проблему отмены таймера со стренгой - PullRequest
0 голосов
/ 26 января 2020

Я пишу программу со следующей функцией:

  1. Сервер запускается и запускается trans2C, trans2C устанавливает candidate_timer_ и вызывает себя каждые 300 мс. Скажем, после вызова первого trans2C сервера term равно 0, затем обработчик вызывает второе trans2C, term равно 1 и т. Д.
  2. Во время каждого term сервер запустит RV, а RV установит retry_timer_ для повторного вызова каждые 10 мс. При установке retry_timer_, RV проверит номер обработчика retry_timer_, если он не равен 0, сгенерирует исключение.
  3. Я использовал boost::asio::strand и существует только один поток.
  4. Каждый раз, когда trans2C запускается, он отменяет candidate_timer_ и retry_timer_.

Скажем, в момент времени в term 8, RV set retry_timer_ позвонить себе через 10мс. Но через 5 мсек candidate_timer_ истекает и вызывает trans2C для ввода term 9 и отменяет оба таймера. В обычных ситуациях retry_timer_ будет отменен, поэтому при каждом вызове RV обработчик на retry_timer_ не будет обработан, и программа будет l oop навсегда. Однако это не так.

Что не так с моей программой?

Воспроизводимый код:

#define BOOST_ASIO_ENABLE_HANDLER_TRACKING

#include <iostream>
#include <vector>
#include <string>
#include <boost/asio.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>


namespace logging = boost::log;
namespace keywords = boost::log::keywords;
namespace attrs = boost::log::attributes;

using namespace boost::asio;
using namespace std;
using boost::asio::ip::tcp;


std::tuple<string, int> another_server("127.0.0.1", 7777);

class instance {
public:
    instance(io_service &loop, const string &_ip, int _port) :
            server_(_ip, _port),
            ioContext(loop),
            candidate_timer_(loop),
            strand_(boost::asio::make_strand(loop)),
            retry_timer_(loop) {
    }

    void run() {
        trans2C();
        ioContext.run();
    }


private:
    void trans2C() {
        BOOST_LOG_TRIVIAL(error) << "trans to candidate";
        cancel_all_timers();
        int waiting_count = candidate_timer_.expires_from_now(boost::posix_time::milliseconds(300));
        if (waiting_count == 0) {
        } else {
            throw std::logic_error("trans2C只能是timer_candidate_expire自然到期触发,所以不可能有waiting_count不为0的情况,否则就是未考虑的情况");
        }
        candidate_timer_.async_wait(boost::asio::bind_executor(strand_, [this](const boost::system::error_code &error) {
            if (error == boost::asio::error::operation_aborted) {
            } else {
                trans2C();
            }
        }));
        RV(another_server);
    }


    void RV(const tuple<string, int> &server) {
        BOOST_LOG_TRIVIAL(trace) << " send rpc_rv to server ";

        int waiting_counts = retry_timer_.expires_from_now(boost::posix_time::milliseconds(10));
        if (waiting_counts != 0) {
            std::ostringstream oss;
            oss << "rv retry timer is set and hooks is not zero, it should be ";
            string s = oss.str();
            throw logic_error(s);
        } else {
            retry_timer_.async_wait(boost::asio::bind_executor(strand_, [this, server](const boost::system::error_code &error) {
                BOOST_LOG_TRIVIAL(trace) << "rv retry_timers expires";
                if (error == boost::asio::error::operation_aborted) {
                    BOOST_LOG_TRIVIAL(error) << "rv retry callback error: " << error.message();
                } else {
                    RV(server);
                }
            }));
        }
    }

private:
    void cancel_all_timers() {
        BOOST_LOG_TRIVIAL(trace) << " all timer canceled";
        candidate_timer_.cancel();
        int retry_handlers_canceled_number = retry_timer_.cancel();
        BOOST_LOG_TRIVIAL(trace) << retry_handlers_canceled_number << " retry_timer is canceled by cancell_all_timers";

    }

    io_context &ioContext;
    std::tuple<string, int> server_;
    deadline_timer candidate_timer_;
    deadline_timer retry_timer_;
    boost::asio::strand<boost::asio::io_context::executor_type> strand_;
};

int main() {
    try {
        boost::asio::io_service io;
        int _port = 8888;
        instance raft_instance(io, "127.0.0.1", _port);
        raft_instance.run();
    } catch (std::exception &exception) {
        BOOST_LOG_TRIVIAL(error) << " exception: " << exception.what();
    }
    return 0;
}

CmakeList:

cmake_minimum_required(VERSION 3.14)
project(raft)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS " -DBOOST_LOG_DYN_LINK")


set(BOOST_ROOT /Users/ynx/boost/build)
#include(FindProtobuf)
#find_package(protobuf REQUIRED)

#if (protobuf_VERBOSE)
#    message(STATUS "Using Protocol Buffers ${Protobuf_VERSION}")
#endif ()


find_package(Boost COMPONENTS log log_setup serialization REQUIED)
include_directories(${Boost_INCLUDE_DIRS})


add_executable(test_boost_timer_bug tests/boost_timer_bug.cpp
        )
target_link_libraries(test_boost_timer_bug
        ${Boost_LOG_LIBRARY}
        ${Boost_LOG_SETUP_LIBRARY}
        ${Boost_SERIALIZATION_LIBRARY}
        ${Boost_WSERIALIZATION_LIBRARY}
        ${Boost_FILE_SYSTEM_LIBRARY}
        ${Boost_THREAD_LIBRARY}
        ${Boost_SYSTEM_LIBRARY})

Журнал отслеживания обработчика может показать что-то странное (я не могу вставить это здесь, поскольку это превысит ограничение текста в 30000 StackOverflow. Но это легко воспроизвести, просто запустите код), последняя отмена перед cra sh показывает, что он отменяет 0 обработчиков, в то время как «нормальные» отмены возврата 1.

1 Ответ

0 голосов
/ 26 января 2020

В соответствии с этим Как избежать запуска уже уничтоженного boost :: asio :: deadline_timer и ссылки на таймер повышения. Таймер истек не может быть отменен. Я думаю, что мне нужно изменить дизайн моей программы, чтобы она соответствовала поведению asio :: timer. И вот решение: Безопасное отключение форсированного таймера крайнего срока asio

...