Я пишу программу со следующей функцией:
- Сервер запускается и запускается
trans2C
, trans2C
устанавливает candidate_timer_
и вызывает себя каждые 300 мс. Скажем, после вызова первого trans2C
сервера term
равно 0, затем обработчик вызывает второе trans2C
, term
равно 1 и т. Д. - Во время каждого
term
сервер запустит RV
, а RV
установит retry_timer_
для повторного вызова каждые 10 мс. При установке retry_timer_
, RV
проверит номер обработчика retry_timer_
, если он не равен 0, сгенерирует исключение. - Я использовал
boost::asio::strand
и существует только один поток. - Каждый раз, когда
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.