什么处理程序确实提升.Asio在幕后执行?

时间:2016-10-27 11:42:25

标签: c++ c++11 boost-asio

我有一个非常简单的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();

2 个答案:

答案 0 :(得分:2)

如果您正在使用async_read来读取TCP流,那么会在async_read_some内部设置一个内部处理程序,当它返回时会检查到目前为止的数据和/或收到的数据量,并在完成或错误时调用您的处理程序,或再次调用async_read_some

然而,我对io_service损坏感到惊讶,但这可能取决于你调用reset的位置。实际上,如果在仍然存在处理程序的情况下调用reset(),并且在lambdas中捕获的引用超出范围之后,则可以调用UB。

答案 1 :(得分:2)

正在调用的处理程序是作为async_read组合操作的一部分创建的中间完成处理程序。组合的操作在另一个操作的多个项中实现,并且这些中间操作中的每一个都具有它们自己的完成处理程序。此外,run_one()不会以不同方式处理这些中间完成处理程序。

如果async_timeoutasync_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

它可以逐行读取:

  1. 从处理程序(0)外部调用socket1.async_connect(),创建处理程序1
  2. 成功输入处理程序
  3. 退出处理程序1
  4. 从处理程序(0)外部调用socket2.async_receive(),创建处理程序2
  5. 成功输入处理程序2并读取7字节
  6. 从处理程序2内部,调用socket2.async_receive(),创建处理程序3。
  7. 退出处理程序2.
  8. 成功输入处理程序3并阅读1字节
  9. 退出处理程序3
  10. 关闭socket2
  11. 关闭socket1
  12. 关闭acceptor