Faraday: Различать TimeoutErrors для таймаутов открытия и чтения

Созданный на 9 авг. 2017  ·  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-соединения, а таймаут - для чтения ответа.

Было бы неплохо иметь отдельные типы исключений для этих таймаутов. Тогда мы могли определить, следует ли повторять запрос. Имеет ли смысл добавлять что-то вроде Faraday :: Error :: OpenTimeoutError и Faraday :: Error :: ResponseTimeoutError и использовать их здесь?

feature help wanted

Самый полезный комментарий

Привет @coberlin Я считаю, что это может быть хорошим дополнением, я просто боюсь обратной совместимости.
Однако возможное решение для этого может заключаться в том, чтобы унаследовать OpenTimeoutError и ResponseTimeoutError от TimeoutError , чтобы существующие rescue s продолжали работать должным образом.
Определенно стоит попробовать 😃

Все 32 Комментарий

Привет @coberlin Я считаю, что это может быть хорошим дополнением, я просто боюсь обратной совместимости.
Однако возможное решение для этого может заключаться в том, чтобы унаследовать OpenTimeoutError и ResponseTimeoutError от TimeoutError , чтобы существующие rescue s продолжали работать должным образом.
Определенно стоит попробовать 😃

Rack_adapter может быть неподходящим местом для этой функции. Я думаю, что Rack-приложения не обязательно различают тайм-ауты открытия и чтения. Возможно, эта функция будет работать в адаптере 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 :: Error :: ConnectionFailed. Подходит ли это для ConnectTimeoutError? Faraday :: Error :: TimeoutError можно подразделить на Faraday :: Error :: ReceiveTimeoutError и Faraday :: Error :: SendTimeoutError.

У Фарадея уже есть Faraday :: Error :: ConnectionFailed. Подходит ли это для ConnectTimeoutError?

Это имеет смысл, но не будет иметь обратной совместимости. Мы должны помнить, что люди уже ловят Faraday::Error::TimeoutError в своем приложении, поэтому переход на ConnectionFailed остановит эти случаи.
Вместо этого мы хотим определить 2 подкласса для Faraday::Error::TimeoutError , имена которых должны быть как можно более общими:

  • Фарадей :: Ошибка :: OpenTimeoutError
  • Фарадей :: Ошибка :: ReadTimeoutError

Следующий шаг - перейти к каждому адаптеру и соответствующим образом сопоставить исключения адаптера. Например, для HTTPClient:

  • HTTPClient :: ConnectTimeoutError ==> Faraday :: Error :: OpenTimeoutError
  • HTTPClient :: ReceiveTimeoutError ==> Faraday :: Error :: ReadTimeoutError
  • HTTPClient :: TimeoutError ==> Faraday :: Error :: TimeoutError (это также перехватит SendTimeoutError, у которого, я не уверен, есть соответствующее сопоставление в Faraday или конкретный параметр)

Напоследок надо добавить тесты там, где это возможно :)

Эй, ребята.

У нас было это обсуждение «немного» назад (https://github.com/lostisland/faraday/pull/324).

Я попробую еще раз (https://github.com/mistersourcerer/faraday/tree/718_mrsrcr_timeout-wrapping-2nd-chance), попробую открыть новый PR, как только у меня будет какой-то прогресс.

Привет, @mistersourcerer , спасибо за толчок, я совершенно не знал, что обсуждение имело место.
Я немного сбит с толку, когда вижу, что PR закрыт, но изменение кода, должно быть, я знаю, что вы каким-то образом слили свое изменение в конце 😄
В этом случае мы будем признательны за вашу помощь, поскольку я думаю, что вы уже довольны тестированием Timeout из вашей предыдущей работы (хотя мы говорим о 3 года назад!).
Я надеюсь, что мои объяснения по поводу OpenTimeoutError и ReadTimeoutError ясны, но если это не так, дайте мне знать.
Не торопитесь и откройте пул-реквест, когда закончите 👍

Привет, @iMacTia.

Если я правильно помню, тогда нам не удалось разрешить ситуацию. Но я не совсем уверен, почему.
Основная проблема заключалась в том, чтобы написать тест, который неизменно давал сбой для всех адаптеров. Так что я не думаю, что в то время мой код вообще был объединен.
В любом случае, у меня есть идея по этому поводу через несколько лет, ха-ха, давайте посмотрим, как это пойдет.

Прямо сейчас тесты для _EMSynchrony_ не работают на Трэвисе, но не локально. Пытаюсь понять это. Я думаю даже об открытом "раннем" пиаре, так что, может быть, мы сможем это обсудить.

И ваше объяснение кристально ясное, кажется, идеальный способ согласиться с этим.

Спасибо за отличную работу, чувак.

Спасибо @mistersourcerer!

RE ваши изменения: я не совсем уверен в том, что произошло, но я вижу, что @mislav наконец объединил ваши изменения здесь: https://github.com/lostisland/faraday/commit/f73d13ee09814fa68b37efa7bddafa47331948c2

Так что радуйтесь, Errno::ETIMEDOUT уже обернут в Faraday::Error::TimeoutError на большинстве (если не на всех) адаптерах 😄

Спасибо за работу над этим @mistersourcerer!

Глядя на ваш коммит здесь , мне интересно, нужны ли нам для обратной совместимости 2 новых подкласса: OpenConnectionError < ConnectionError для Net :: HTTP и OpenTimeoutError < TimeoutError для HttpClient?

Похоже, в этом вопросе есть некоторая путаница.
Причина в том, что на # 438 было принято решение обрабатывать ошибки «тайм-аут открытия» как ConnectionFailed . Возможно, это лучшее решение, но на самом деле кто-то решил пойти по этому пути.
Теперь это влияет не только на адаптер стойки, но и на все остальные адаптеры, и их поведение, вероятно, даже не согласовано.
Я планирую стандартизировать их поведение на том же уровне, что и в версии 1.0, и оставлю эту проблему для справки.

Продолжение моего предыдущего комментария.

По сути, в настоящее время мы генерируем ошибку Faraday::ConnectionFailed в случае тайм-аута открытия, в то время как мы поднимаем Faraday::TimeoutError для тайм-аута чтения. Хотя разные адаптеры в настоящее время ведут себя по-разному, это, по-видимому, наиболее распространенное поведение.
Это было решено примерно 3 года назад, но здесь мы обсуждаем наличие Faraday::TimeoutError для первого случая (с соответствующими подклассами для различения между открытием и закрытием).

С одной стороны, я понимаю, что это было бы ближе к реальности, но если я проанализирую проблему с точки зрения реализации, мне будет трудно оправдать это изменение.
Если я позвоню в службу и получу в ответ ConnectionFailed , я знаю, что мой вызов не может быть обработан. Возможно, я не подключился к серверу, или не смог разрешить имя хоста, или произошло что-то еще.
Если я верну TimeoutError , мой запрос мог быть обработан или частично обработан, и я мог пропустить ответ. Это совершенно другой случай, и он требует перепроверить с сервером, на который я звонил, что произошло.

Превращение тайм-аута открытия в подкатегорию TimeoutError означает принятие простой ситуации (запрос не обработан) в более сложном домене и, безусловно, требует дополнительных проверок, чтобы решить, что делать: было ли это тайм-аут открытия или чтение тайм-аут?

Нам нужно:

  1. Решите, как увеличить время ожидания открытия
  2. Стандартизируйте все адаптеры к одинаковому поведению

@coberlin @ Erik-Эскобедо @mislav @mistersourcerer хотел бы услышать ваши мысли после рассмотрения выше 😄

Использование ConnectionFailed для ошибок тайм-аута открытия имеет для меня смысл и обеспечит то, что я надеялся получить, отличая ошибки тайм-аута открытия от других ошибок тайм-аута. Для согласованности адаптера это будет означать, например, что Net::HTTP в порядке, но HTTPClient изменится с сопоставлением ConnectTimeoutErrors с ConnectionFailed вместо TimeoutError.

Ничего страшного, как только мы решим стандартизировать все адаптеры для одного и того же поведения (очевидно, в версии 1.0, поскольку это будет обратно несовместимо)

Выбрасывая вариант использования на ринг:

На работе мы страдали от некоторых открытых таймаутов из-за того, что Nginx + Kubernetes не мог направить к зависшим модулям (или чему-то еще). Как бы то ни было, NetHTTP выдавал ошибки OpenTimeout и ReadTimeout, и это было действительно удобно для отладки, которая была какой.

Теперь, когда мы перешли на Typhoeus, у нас, к сожалению, все таймауты отключены вместе, и нам сложно сказать, улучшилась ли наша работа над проблемами nginx + kuber, или мы только сейчас успешно делаем больше запросов к все более затрудняющимся система. В любом случае количество тайм-аутов примерно одинаково, и, не разделяя их, мы как бы застреваем в догадках.

Я не думаю, что простого добавления Faraday::OpenTimeoutError достаточно, у нас должны быть Faraday::OpenTimeoutError и Faraday::ReadTimeoutError продолжающиеся от Faraday::TimeoutError IMO.

@philsturgeon, а что насчет другого предлагаемого решения, это тоже поможет?

Тайм-аут открытия -> Faraday::ConnectionFailed
Тайм-аут чтения -> Faraday::TimeoutError

Это должно быть поведение на всех адаптерах, но, к сожалению, некоторые из них работают не так, как ожидалось (например, Typhoeus).

Я чувствую, что это разные вещи.

ConnectionFailed выглядит как «Я понятия не имею, как разговаривать с этим сервером», например, неверный DNS / IP и т. Д.

OpenTimeout: «Я знаю, где находится этот сервер, и просто жду, чтобы он что-то сделал»

OpenTimeout: «Я знаю, где находится этот сервер, и просто жду, чтобы он что-то сделал»

Я не согласен с этим, скорее скажу:

Тайм-аут открытия: я пытаюсь связаться с сервером, но не могу связаться с ним (Примечание: соединение еще не установлено или «открыто»).
Тайм-аут чтения: я установил соединение с сервером, но я жду, пока он что-то сделает (чтение вывода).

Неисправный брандмауэр / прокси / load_balancer - это просто простые примеры того, как вы можете получить тайм-аут открытия, но во всех этих случаях соединение с сервером еще не началось. Для меня это самый важный момент. «ConnectionFailed» для меня просто означает: я не могу подключиться к серверу. И он идеально подходит для этих случаев.

Если вы все еще думаете, что должна существовать конкретная ошибка Faraday :: OpenTimeoutError, я бы посоветовал унаследовать ее от ConnectionFailed а не от TimeoutError но я согласен, что это немного сбивает с толку и не уверен, как это помогло бы на практике.
См. Мой предыдущий https://github.com/lostisland/faraday/issues/718#issuecomment -343957963 о том, как это может действительно помочь справиться с ошибкой.

Имеет ли это смысл? Я хотел бы найти решение, подходящее всем

Я принимаю ваши более точные определения тайм-аута открытия, но прихожу к другому выводу.

Вы считаете, что время ожидания открытия считается ошибкой соединения, поскольку время, в течение которого вы хотите дождаться этого соединения, считается частью соединения. «Не удалось установить соединение за 5 секунд», конечно, имеет смысл, если вы объясните это так, но многие люди думают иначе.

Для многих, открытая таймаут просто означает , что до сих пор не произошло. Это делает его менее однозначным заявлением, чем большинство сбоев соединения, например «Сервер не работает» или «Этот DNS - мусор».

Я полагаю, это не имеет большого значения, так как сбои подключения и тайм-ауты открытия должны быть повторены, так как тайм-аут чтения может считаться основанием для отказа?

Я согласен, мы можем сколько угодно спорить о чтении, которое можно применить к нему, но практичность моей точки зрения - это то, что вы сказали: если вы получаете тайм-аут открытия, это означает, что вы можете повторить запрос, если вы получите Тайм-аут чтения означает, что вы должны быть ОЧЕНЬ осторожны, поскольку ваш запрос мог быть обработан (полностью или частично). По совпадению практическое значение открытого тайм-аута совпадает с практическим значением неудачного соединения, поэтому я бы сделал его унаследованным оттуда.

Сегодня люди ловят исключения ConnectionFailed и TimeoutError и логика, лежащая в основе, очень вероятно, отражает то, что мы говорили ранее. Если мы введем новое исключение как подкласс 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 , это хороший

Однако Faraday::Request::Retry можно настроить, поэтому ничто не мешает вам добавить ConnectionOpenTimeout или даже ConnectionFailed в список исключений, которые вы хотите обработать.

Я хотел бы узнать больше «мнения сообщества», прежде чем добавлять их в список исключений по умолчанию.

Иногда мы сталкиваемся с ошибками OpenTimeout с конечной точкой API, которые необходимо повторить; из приведенной выше логики кажется, что время ожидания открытия безопасно для повторной попытки (более безопасно, чем тайм-аут чтения). Мы также предполагали, что с промежуточным программным обеспечением retry будут повторяться тайм-ауты открытия и чтения; в документации говорится: «По умолчанию повторяется 2 попытки и обрабатываются только исключения из-за тайм-аута». По умолчанию обрабатываются исключения Errno::ETIMEDOUT, Timeout::Error, Error::TimeoutError , а Net::OpenTimeout является подклассом Timeout::Error ; не было особенно ясно, что Фарадей относился к ним иначе. Так что, возможно, следует обновить документацию? В любом случае да, мы настроили промежуточное ПО; Мне просто интересно, имеет ли смысл значение по умолчанию.

@mjhoy Вы правы, говоря, что Timeout::Error включает Net::OpenTimeout поэтому с текущей реализацией кажется, что время ожидания открытия также следует повторить. Более того, Timeout::Error восстанавливается и повторно вызывается адаптером при нормальных обстоятельствах, поэтому его присутствие в списке исключений может быть ненужным или просто для дополнительной безопасности.

Как только мы закончим рефакторинг исключений, Net::OpenTimeout будет вызвано как новое исключение и, как я сказал в своем комментарии, изменит текущее поведение по умолчанию.

Я все еще считаю, что это не должно быть частью настроек по умолчанию, но это определенно то, что нужно учитывать при выполнении работы.

Спасибо, что подняли этот вопрос 😄

Привет, извините, это томилось в моей команде в течение года, и теперь наши приоритеты сильно изменились. Я не буду заниматься этим вопросом, но удачи!

@iMacTia Кто-нибудь работает над этим изменением? Я не против использовать это в версии 2.0.

Привет @ ragav0102 , спасибо за поддержку!
Никто еще не работает над этим, так как мы все еще настаиваем на выпуске версии 1.0.

Мы определенно будем признательны за помощь, но у нас пока нет плана для версии 2.0, поэтому я не могу сказать, когда она будет выпущена, поэтому вашим изменениям, возможно, придется подождать несколько месяцев, прежде чем их можно будет использовать.

Если вам это нужно в одном из ваших проектов, то, вероятно, это невозможно.
Если вы только что сдали экзамен и хотели бы внести свой вклад, я бы посоветовал вам выбрать что-нибудь, запланированное на v1.0, поскольку оно будет выпущено гораздо раньше 😄

Понятно!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги