Faraday: 区分打开和读取超时的 TimeoutErrors

创建于 2017-08-09  ·  32评论  ·  资料来源: lostisland/faraday

在 faraday/adapter/rack.rb 中,打开和读取超时都会引发 TimeoutError:

timeout  = env[:request][:timeout] || env[:request][:open_timeout]
response = if timeout
  Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
else ... end

根据https://stackoverflow.com/questions/10322283/what-is-timeout-and-open-timeout-in-faraday , open_timeout 用于 tcp 连接, timeout 用于响应读取。

为这些超时设置单独的异常类型会很好。 然后我们可以确定是否重试请求。 添加像 Faraday::Error::OpenTimeoutError 和 Faraday::Error::ResponseTimeoutError 这样的东西并在这里使用它们有意义吗?

feature help wanted

最有用的评论

@coberlin我相信这可能是一个不错的补充,我只是担心向后兼容性。
然而,一个可能的解决方案可能是让OpenTimeoutErrorResponseTimeoutError继承自TimeoutError ,这样现有的rescue将继续按预期工作。
绝对值得一些测试😃

所有32条评论

@coberlin我相信这可能是一个不错的补充,我只是担心向后兼容性。
然而,一个可能的解决方案可能是让OpenTimeoutErrorResponseTimeoutError继承自TimeoutError ,这样现有的rescue将继续按预期工作。
绝对值得一些测试😃

rack_adapter 可能是此功能的错误位置。 我认为机架应用程序不一定区分打开和读取超时。 也许这个特性可以在 HTTPClient 适配器或其他适配器中工作? 来自适配器/httpclient.rb:

    @app.call env
  rescue ::HTTPClient::TimeoutError, Errno::ETIMEDOUT
    raise Faraday::Error::TimeoutError, $!
  rescue ::HTTPClient::BadResponseError => err
    if err.message.include?('status 407')
      raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
    else
      raise Faraday::Error::ClientError, $!
    end
  rescue Errno::ECONNREFUSED, IOError, SocketError
    raise Faraday::Error::ConnectionFailed, $!
  rescue => err
    if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
      raise Faraday::SSLError, err
    else
      raise
    end

::HTTPClient::TimeoutError 有 3 个子类 ConnectTimeoutError、ReceiveTimeoutError、SendTimeoutError,参见例如http://www.rubydoc.info/gems/httpclient/2.1.5.2/HTTPClient/TimeoutError

Faraday 已经有 Faraday::Error::ConnectionFailed。 这是否适用于 ConnectTimeoutError? Faraday::Error::TimeoutError 可以细分为 Faraday::Error::ReceiveTimeoutError 和 Faraday::Error::SendTimeoutError。

Faraday 已经有 Faraday::Error::ConnectionFailed。 这是否适用于 ConnectTimeoutError?

这是有道理的,但它不会向后兼容。 我们必须记住,人们已经在他们的应用程序中捕获了Faraday::Error::TimeoutError ,因此切换到ConnectionFailed将阻止这些情况。
相反,我们想要做的是为Faraday::Error::TimeoutError定义 2 个子类,它们的名称应该尽可能通用:

  • Faraday::Error::OpenTimeoutError
  • Faraday::Error::ReadTimeoutError

下一步是进入每个适配器并相应地映射适配器异常。 例如对于 HTTPClient:

  • HTTPClient::ConnectTimeoutError ==> Faraday::Error::OpenTimeoutError
  • HTTPClient::ReceiveTimeoutError ==> Faraday::Error::ReadTimeoutError
  • HTTPClient::TimeoutError ==> Faraday::Error::TimeoutError(这也会捕获SendTimeoutError,我不确定在法拉第或特定设置中有相应的映射)

最后,应尽可能添加测试:)

大家好。

我们在“一点”前进行了这个讨论(https://github.com/lostisland/faraday/pull/324)。

我正在再试一次(https://github.com/mistersourcerer/faraday/tree/718_mrsrcr_timeout-wrapping-2nd-chance),一旦我有一些进展,我将尝试打开一个新的 PR。

@mistersourcerer ,感谢您的推动,我完全不知道发生了讨论。
当我看到 PR 关闭时,我有点困惑,但是代码中的更改,很可能知道您最终以某种方式合并了更改 😄
在这种情况下,您的帮助将不胜感激,因为我认为您已经对之前工作中的超时测试感到满意(即使我们谈论的是 3 年前!)。
我希望我对OpenTimeoutErrorReadTimeoutError的解释清楚,但如果情况并非如此,请告诉我。
花点时间完成后打开拉取请求👍

嘿@iMacTia。

如果我没记错的话,我们当时并没有设法解决这个问题。 但我不确定到底是为什么。
主要问题是编写一个在所有适配器中始终失败的测试。 所以,我认为我的代码当时根本没有合并。
不管怎样,哈哈几年后我对此有了一个想法,让我们看看它是怎么回事。

现在,_EMSynchrony_ 的测试在 Travis 上失败了,但在本地没有。 试图弄清楚。 我什至在考虑开放一个“早期”公关,所以也许我们可以讨论这个。

你的解释非常清楚,似乎是完美的方式。

感谢您在这方面所做的出色工作,伙计。

谢谢@mistersourcerer!

RE您的更改:我不太确定发生了什么,但我看到@mislav终于在这里合并了您的更改: https :

所以庆幸,在大多数(如果不是全部)适配器上, Errno::ETIMEDOUT已经被包裹在Faraday::Error::TimeoutError之下

感谢您为这个@mistersourcerer 工作!

这里查看您的提交,我想知道是否为了向后兼容,我们需要 2 个新子类:用于 Net::HTTP 的OpenConnectionError < ConnectionError和用于 HttpClient 的OpenTimeoutError < TimeoutError

这个问题似乎有些混乱。
原因是 #438 决定将“打开超时”错误处理为ConnectionFailed 。 这可以说是最好的决定,但现实是有人决定走这条路。
现在,这不仅会影响 Rack 适配器,还会影响所有其他适配器,它们的行为甚至可能不一致。
我打算用 v1.0 将它们标准化为相同的行为,我会保留这个问题作为参考。

跟进我之前的评论。

基本上,我们目前在打开超时的情况下引发Faraday::ConnectionFailed错误,而在读取超时时引发Faraday::TimeoutError 。 尽管不同的适配器当前的行为方式不同,但这似乎是最常见的行为。
这是大约 3 年前决定的,但在这里我们正在讨论为前一种情况使用Faraday::TimeoutError (使用适当的子类来区分打开和关闭)。

一方面,我理解这会更接近现实,但如果我从实施的角度分析问题,我发现很难证明这种改变是合理的。
如果我调用一个服务并得到一个ConnectionFailed ,我知道我的电话不可能被处理。 我可能没有连接到服务器,或者无法解析主机名,或者发生了其他事情。
如果我得到TimeoutError ,那么我的请求可能已被处理或部分处理,并且我可能错过了响应。 这是一个完全不同的情况,需要与我正在调用的服务器仔细检查发生了什么。

使打开超时成为TimeoutError的子类别意味着在更复杂的域下采取简单的情况(请求未处理),并且肯定需要额外的检查来决定要做什么:是打开超时还是读取暂停?

我们需要:

  1. 决定如何冒泡打开超时
  2. 将所有适配器标准化为相同的行为

@coberlin @erik-escobedo @mislav @mistersourcerer想听听您在考虑上述问题后的想法😄

使用ConnectionFailed处理打开超时错误对我来说很有意义,并且可以通过将打开超时错误与其他超时错误区分开来提供我希望得到的结果。 例如,对于适配器一致性,这意味着Net::HTTP原样可以,但HTTPClient会发生变化,ConnectTimeoutErrors 映射到 ConnectionFailed 而不是 TimeoutError。

没关系,一旦我们决定将所有适配器标准化为相同的行为(显然在 v1.0 中,因为这将向后不兼容)

将用例扔进戒指:

在工作中,由于 Nginx + Kubernetes 无法路由到挂起的 pod(或其他东西),我们一直在遭受一些开放超时。 无论如何,NetHTTP 过去常常抛出 OpenTimeout 和 ReadTimeout 错误,这对我们调试哪个是哪个非常方便。

现在我们已经切换到 Typhoeus我们很遗憾地将所有超时都混在一起了,我们很难判断我们在 nginx + kuber 问题上的工作是否得到了改善,或者我们是否刚刚成功地向日益挣扎的提出了更多请求系统。 无论哪种方式,超时次数都大致相同,如果没有将它们分开,我们就有点陷入猜测。

我不认为仅仅添加Faraday::OpenTimeoutError就足够了,我们应该有Faraday::OpenTimeoutErrorFaraday::ReadTimeoutError从 IMO 扩展的Faraday::TimeoutError

@philsturgeon以及其他提议的解决方案怎么样,这也有帮助吗?

打开超时 -> Faraday::ConnectionFailed
读取超时 -> Faraday::TimeoutError

这应该是所有适配器的行为,但不幸的是有些行为不符合预期(即 Typhoeus)

我觉得这些是不同的东西。

ConnectionFailed 似乎是“我不知道如何与此服务器通信”,例如无效的 DNS/IP 等。

OpenTimeout 是“我知道这个服务器在哪里,我只是在等待它做某事”

OpenTimeout 是“我知道这个服务器在哪里,我只是在等待它做某事”

我不同意,我宁愿说:

打开超时:我正在尝试联系服务器,但我无法访问它(注意:连接尚未建立或“打开”)。
读取超时:我已经与服务器建立了连接,但我正在等待它做某事(读取输出)。

有故障的防火墙/代理/负载平衡器只是您可能如何获得开放超时的简单示例,但在所有这些情况下,与服务器的连接尚未启动。 这对我来说是最重要的一点。 “ConnectionFailed”对我来说只是意味着:我无法连接到服务器。 它非常适合这些情况。

如果您仍然认为应该存在特定的 Faraday::OpenTimeoutError,那么我建议从ConnectionFailed而不是TimeoutError继承,但我同意这会有点混乱并且不知道如何这将有助于实践。
请参阅我之前的https://github.com/lostisland/faraday/issues/718#issuecomment -343957963,了解这实际上如何有助于管理错误。

是否有意义? 我想找到适合所有人的解决方案

我接受你对开放超时更准确的定义,但我得出了不同的结论。

您认为打开超时被视为连接失败,因为您愿意等待该连接的时间被视为连接的一部分。 如果你这样解释的话,“5 秒内无法建立联系”当然是有道理的,但这不是很多人的想法。

对于许多人来说,开超时只是意味着它没有发生。 这使得它不像大多数连接失败那样是一个明确的声明,即“服务器已关闭”或“这个 DNS 是垃圾”。

我想这没什么关系,因为连接失败和打开超时都应该重试,因为读取超时可能被认为是退避的理由?

我同意,我们可以就可以应用的阅读进行尽可能多的争论,但我的观点的实用性也是你所说的:如果你得到一个开放超时,这意味着你可以重试请求,如果你得到读取超时意味着您必须非常小心,因为您的请求可能已被处理(全部或部分)。 巧合的是,开放超时的实际含义与失败连接的含义相匹配,因此我将使其从那里继承。

今天人们正在捕捉ConnectionFailedTimeoutError异常,其背后的逻辑很可能反映了我们之前所说的。 如果我们将新异常作为ConnectionFailed的子类引入,那么大多数(如果不是全部)应用程序将不需要任何更改的可能性很高。

我从语义的角度理解(并同意),尽管 OpenTimeout 只是另一种类型的超时。

但是,嘿,如果我们称它为ConnectionTimedOut呢?

open_timeout: X是属性的名称,它表示要等待多长时间才能抛出ConnectionTimedOut这会引起一些混淆。

好点😞

称它为ConnectionOpenTimeout ? 它清楚地表明它是一个连接问题,并清楚地表明它是一个打开超时。 我认为这个名称与“X 秒内无法建立连接”的含义一致,尽管有些人可能仍然想知道为什么超时不是超时。 😅

我觉得不错👍!

@iMacTia嘿,如果你能给我一些关于从哪里开始的指示,我可以尝试这样做。

谢谢@philsturgeon ,那就太好了! 请允许我回顾一下围绕这一点的要点:

  1. 所有更改都需要针对 v1.0 分支完成(因为它们向后不兼容)。
  2. 跨适配器的超时管理行为不一致,因此我们需要对其进行标准化。
  3. 约定的行为如下:
  4. 在 OPEN 超时的情况下,我们将引发ConnectionOpenTimeout错误,该错误将从ConnectionFailed继承。
  5. 在 READ 超时的情况下,我们将引发TimeoutError

我错过了什么吗?

ConnectionOpenTimeout会被添加到默认的 Faraday::Request::Retry 处理异常中吗?

@mjhoy这是一个很好的观点,但目前 Retry 中间件不会在出现连接问题时重试。 如果连接成功但超时,它只会重试请求。 事实上,如果您调用的服务根本无法访问,我不确定重试请求是否有意义,在这种情况下,您可能更愿意返回异常并执行其他操作。

但是, Faraday::Request::Retry是可配置的,因此没有什么可以阻止您将ConnectionOpenTimeout甚至ConnectionFailed到您希望它处理的异常列表中。

在将这些添加到默认例外列表之前,我想查看更多“社区意见”

我们偶尔会遇到需要重试的 API 端点的OpenTimeout错误; 从上面Errno::ETIMEDOUT, Timeout::Error, Error::TimeoutErrorNet::OpenTimeoutTimeout::Error的子类; 法拉第对他们的区别对待并不是特别清楚。 那么也许应该更新文档? 无论如何,是的,我们配置了中间件; 我只是想知道默认值是否有意义。

@mjhoy您说Timeout::Error包括Net::OpenTimeout是对的,因此对于当前的实现,似乎也应该重试打开超时。 此外, Timeout::Error在正常情况下被适配器拯救并重新引发,因此它在异常列表中的存在可能是不必要的,或者只是为了额外的安全。

一旦我们完成了异常重构, Net::OpenTimeout将作为一个新异常被引发,正如我在我的评论中所说,改变当前的默认行为。

我仍然认为这不应该成为默认值的一部分,但在进行工作时绝对需要考虑。

谢谢你提出这个😄

嘿,抱歉,这在我的团队积压工作中搁置了一年,现在我们的优先事项发生了很大变化。 我不会在这个问题上做任何工作,但祝你好运!

@iMacTia有人致力于此更改吗? 我不介意将其用于 v2.0。

@ragav0102 ,感谢支持!
目前还没有人在做这方面的工作,因为我们仍在努力推出 v1.0。

我们非常感谢您的帮助,但我们还没有 v2.0 的计划,所以我不知道它什么时候发布,所以您的更改可能需要等待几个月才能使用。

如果您在其中一个项目中需要它,那么这可能是不可行的。
如果你刚刚通过并想贡献,我建议你选择 v1.0 预定的东西,因为它会更快发布 😄

知道了!

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

iMacTia picture iMacTia  ·  3评论

ioquatix picture ioquatix  ·  4评论

Lewiscowles1986 picture Lewiscowles1986  ·  4评论

aleksb86 picture aleksb86  ·  3评论

luizkowalski picture luizkowalski  ·  3评论