我有一个非常简单的Boost.Asio案例:受async_read
保护的deadline_timer
。我也有一个std::atomic_bool DEBUG[2]
。 async_read
处理程序设置DEBUG[0]
; deadline_timer
设置DEBUG[1]
。即使错误代码为error::operation_aborted
,也会无条件地发生这种情况。
现在,当我致电io_service::run_one()
时,我通常会看到其中一个DEBUG
指标设置。但是,在至少10%的情况下,run_one
返回1
但两个指标都没有设置,即两个处理程序都没有被调用。 (还缺少处理程序的其他副作用)。
现在run_one
应该返回执行的处理程序的数量,所以当它返回1时它必须执行一个处理程序 - 但是哪个处理程序,如果不是我的?
我问的原因是因为即使在.reset()之后,io_service
对象也会被破坏。
相关代码 - 相当冗长以解决问题:
boost::asio::deadline_timer deadline(thread_io_service);
deadline.expires_from_now(boost::posix_time::seconds(timeoutSeconds));
read_counter += 2; // Initialized to 1 in ctor, so always odd.
// C++11: Cannot capture expressions such as this->read_counter.
unsigned read_counter_copy = read_counter;
read_timeout.store(0, std::memory_order_release); // 0 = no timeout.
deadline.async_wait([&, read_counter_copy](boost::system::error_code const&)
{
// read_counter_copy is very intentionally captured by value - this timeout applies only to the next read.
read_timeout.store(read_counter_copy, std::memory_order_release);
DEBUG[0] = true;
}
);
// Start reading "asynchronously", wait for completion or timeout:
std::atomic<boost::system::error_code> ec(boost::asio::error::would_block);
size_t len = 0;
boost::asio::async_read(socket, boost::asio::buffer(buffer + byteShift), boost::asio::transfer_exactly(nrBytes),
[&](boost::system::error_code const& err, size_t bytesTransferred)
{
len = bytesTransferred;
ec.store(err, std::memory_order_release);
DEBUG[1] = true;
}
);
// We only have 5 states to deal with
enum { pending, timeout, read, read_then_timeout, timeout_then_read } state = pending;
for (;;)
{
if (state == read_then_timeout) assert(false); // unreachable - breaks directly
else if (state == timeout_then_read) assert(false); // unreachable - breaks directly
// [pending, read, timeout] i.e. only one handler has run yet.
thread_io_service.run_one(); // Don't trust this - check the actual handlers and update state accordingly.
if (state == pending && read_timeout.load(std::memory_order_acquire) == read_counter)
{
state = timeout;
socket.cancel(); // This will cause the read handler to be called with ec=aborted
continue;
}
if (state == read && read_timeout.load(std::memory_order_acquire) == read_counter)
{
state = read_then_timeout;
break; //
}
if (state == pending && ec.load(std::memory_order_acquire) != boost::asio::error::would_block)
{
state = read;
deadline.cancel();
continue;
}
if (state == timeout && ec.load(std::memory_order_acquire) != boost::asio::error::would_block)
{
state = timeout_then_read; // Might still be a succesfull read (race condition)
break;
}
// This is the actual problem: neither read nor timeout.
// DEBUG == {false,false} when this happens.
L_NET(warning) << "Boost.Asio spurious return";
}
assert(state == timeout_then_read || state == read_then_timeout);
thread_io_service.reset();
答案 0 :(得分:2)
如果您正在使用async_read
来读取TCP流,那么会在async_read_some
内部设置一个内部处理程序,当它返回时会检查到目前为止的数据和/或收到的数据量,并在完成或错误时调用您的处理程序,或再次调用async_read_some
。
然而,我对io_service损坏感到惊讶,但这可能取决于你调用reset的位置。实际上,如果在仍然存在处理程序的情况下调用reset()
,并且在lambdas中捕获的引用超出范围之后,则可以调用UB。
答案 1 :(得分:2)
正在调用的处理程序是作为async_read
组合操作的一部分创建的中间完成处理程序。组合的操作在另一个操作的多个项中实现,并且这些中间操作中的每一个都具有它们自己的完成处理程序。此外,run_one()
不会以不同方式处理这些中间完成处理程序。
如果async_timeout
和async_read
完成处理程序都没有被调用,但run_one()
返回表示处理程序已运行,则async_read
操作由...组成至少两次async_read_some
次操作。如果在套接字有一些可用数据时启动async_read()
,但第一次读取不满足完成条件,则会发生这种情况。例如,套接字可能具有一些数据,但不是所有所需数据(例如0 <socket.available()
<缓冲区大小)。
可以让Handler Tracking有时更好地了解调用哪些处理程序。定义BOOST_ASIO_ENABLE_HANDLER_TRACKING
时,Asio会将处理程序调试输出写入标准错误,包括处理程序标识符。下面是demonstrates处理程序跟踪的示例,其中async_read
组合操作将由至少两个中间操作组成,并且调用io_service.run_one()
调用中间完成处理程序:
#include <functional> // std::bind
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <boost/asio.hpp>
const auto noop = std::bind([]{});
int main()
{
using boost::asio::ip::tcp;
// Create all I/O objects.
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket socket1(io_service);
tcp::socket socket2(io_service);
// Connect the sockets.
socket1.async_connect(acceptor.local_endpoint(), noop);
acceptor.accept(socket2);
io_service.run();
io_service.reset();
// Write data from socket1 to socket2.
const std::string data = "example";
boost::asio::write(socket1, boost::asio::buffer(data));
// Initiate a composed async_read operation that attempts to
// read more data than is immediately available.
assert(socket2.available());
std::vector<char> buffer(socket2.available() + 1);
boost::asio::async_read(socket2, boost::asio::buffer(buffer), noop);
// Invoke completion handler for intermediate async_read_some
// operatoin.
assert(1 == io_service.run_one());
// Write more data to the socket, allowing the async_read composed
// operation to complete.
boost::asio::write(socket1, boost::asio::buffer(data));
assert(1 == io_service.run());
}
运行时,它提供类似于以下的输出:
@asio|1477939244.378393|0*1|socket@0x7fff9987bd20.async_connect // 1
@asio|1477939244.378925|>1|ec=system:0 // 2
@asio|1477939244.379056|<1| // 3
@asio|1477939244.379207|0*2|socket@0x7fff9987bd40.async_receive // 4
@asio|1477939244.379402|>2|ec=system:0,bytes_transferred=7 // 5
@asio|1477939244.379572|2*3|socket@0x7fff9987bd40.async_receive // 6
@asio|1477939244.379749|<2| // 7
@asio|1477939244.379874|>3|ec=system:0,bytes_transferred=1 // 8
@asio|1477939244.380063|<3| // 9
@asio|1477939244.380249|0|socket@0x7fff9987bd40.close // 10
@asio|1477939244.380456|0|socket@0x7fff9987bd20.close // 11
@asio|1477939244.380643|0|socket@0x7fff9987bd00.close // 12
它可以逐行读取:
socket1.async_connect()
,创建处理程序1 socket2.async_receive()
,创建处理程序2 7
字节socket2.async_receive()
,创建处理程序3。1
字节socket2
socket1
acceptor