调用async_resolve.cancel(),但async_resolve的回调处理程序不返回boost::asio::error::operation_aborted

问题描述 投票:0回答:3

我使用boost asio来处理http请求和应答,为了避免async_resolve不调用它的回调处理程序,我设置了一个超时,就像这样:

void resolve()
{
  resolver_.async_resolve(query,strand_.wrap(boost::bind(&connection::handle_resolve,
                          shared_from_this(),
                          boost::asio::placeholders::error,
                          boost::asio::placeholders::iterator)));
  int cancel_num = timer_.expires_from_now(boost::posix_time::seconds(resolve_timeout_));
  timer_.async_wait(strand_.wrap(boost::bind(&connection::handle_resolve_timeout,
                    shared_from_this(),
                    boost::asio::placeholders::error)));
}


void connection::handle_resolve_timeout(const boost::system::error_code& err)
{
  if (err != boost::asio::error::operation_aborted)
  {
      resolver_.cancel();
  }
}

void connection::handle_resolve(const boost::system::error_code& err,
                    boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
  timer_.cancel();
  if(!err)
  {
    boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
    socket_.async_connect(endpoint,strand_.wrap(boost::bind(&connection::handle_connect,
                  shared_from_this(),
                  boost::asio::placeholders::error,
                  ++endpoint_iterator)));
    //to distinct the type of timeout ,0:connect timeout,1:read timeout
    int flag = 0;
    int cancel_num = timer_.expires_from_now(boost::posix_time::seconds(connect_timeout_));
    timer_.async_wait(strand_.wrap(boost::bind(&connection::handle_timeout,
                      shared_from_this(),
                      boost::asio::placeholders::error,
                      flag)));
  }
  else if(err != boost::asio::error::operation_aborted)
  {
    status_ = resolve_error;
    check_.do_finish(shared_from_this());
  }
  else
  {
    FASTCHECK_INFO("resolve is canceled\n");
  }
}

当解决超时时,我发现handle_resolve_timeout被调用,但是handle_resolve没有返回boost::asio::error::operation_aborted,为什么,我很困惑,有人可以帮我解释一下吗?

boost boost-asio
3个回答
2
投票

根据 boost-users 邮件列表的讨论,

resolver::cancel()
只能取消待处理的、排队的解析请求,而不能取消当前正在执行的请求。


1
投票

作为对 @Cubbi 答案的延迟跟进,在该问题发布几个月后,在 Boost 问题跟踪器 上也提出了此错误。 Asio 解析器 API 有点令人困惑,因为它表明任何

async_resolve()
操作都可以根据需要取消。我自己也有同样的问题。事实证明,在 Asio 实现中,对
async_resolve()
的调用会在幕后对
getaddrinfo()
进行同步系统调用。我在最近的代码广播中讨论了这个有趣的行为。


0
投票

附注并进一步解释一下:

boost::asio::ip::tcp::resolver::async_resolve()
创建一个后台线程来执行实际的名称解析,这些名称解析是使用系统 C 库的
getaddrinfo()
调用一一执行的。即使您在应用程序中创建多个
resolver
对象,所有异步解析请求都会被序列化并转发到该单个线程。

该实现的优点是,对

async_resolve()
的调用不会阻塞任何其他
async()
方法,尽管
getaddrinfo()
会阻塞。但缺点是,所有对
async_resolve()
的调用都会互相阻塞!因此,如果应用程序需要解析 100 个主机名,并且 DNS 出现故障并且查找平均需要 5 秒,则应用程序启动时间将为 500 秒。此外,如果当前无法访问特定的 DNS 服务器,则该查找将挂起,直到
getaddrinfo()
超时。不,
resolver::cancel()
对当前查找没有影响,因为
getaddrinfo()
不可取消。

有几种方法可以改善这种情况:

    需要执行大量地址解析的应用程序可以使用带有专用线程池的专用
  1. io_context
    ,在其中执行
    同步地址解析。然后,至少可以有与该解析器线程池中的线程一样多的并行 getaddrinfo()
     调用。
  2. 对当前实现进行一些小的升级可以添加该功能来向阻塞的解析器线程发出信号,不再需要其结果并且应该尽快退出,然后
  3. detach()
     该线程并忘记它。下一个名称解析将创建一个新线程。这将允许应用程序立即 
    cancel()
     当前活动的查找(例如,当截止日期计时器触发时)并立即转到下一个查找(将在新线程中启动)。尽管如此,分离的“守护进程”线程仍会尝试完成其 
    getaddrinfo()
     调用,如果这样做,结果通常会缓存在系统中的某个位置。当应用程序在几秒钟后重试该特定传出连接时,缓存的结果将用于立即应答解析器请求,然后连接可以成功。
  4. 至少对于像 Linux 这样使用
  5. glibc
     的系统,
    boost::asio::ip::tcp::resolver
     可以升级为使用 GNU 扩展 
    getaddrinfo_a()
    ,它真正实现了名称解析的并行化。不幸的是,
    getaddrinfo_a()
     也存在与 
    select()
     系统调用类似的弱点,因为调用 
    gai_suspend()
    (读取后台 
    getaddrinfo_a()
     请求的一个或多个结果)的开销随着未完成的名称解析的数量呈二次方增加。更不幸的是,目前尚不清楚 
    getaddrinfo_a()
     是如何(有效)实际实施的。实现它的 RedHat 人员的白皮书表明,它使用围绕 
    getaddrinfo()
     的线程池,至少对于更复杂的情况是这样。所以实际上,使用 
    getaddrinfo_a()
     可能与上面的解决方案 1 一样好或坏。
© www.soinside.com 2019 - 2024. All rights reserved.