Em faraday / adapter / rack.rb, TimeoutError é gerado para tempos limites de abertura e leitura:
timeout = env[:request][:timeout] || env[:request][:open_timeout]
response = if timeout
Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
else ... end
De acordo com https://stackoverflow.com/questions/10322283/what-is-timeout-and-open-timeout-in-faraday , open_timeout é para a conexão tcp e o tempo limite é para a resposta lida.
Seria bom ter tipos de exceção separados para esses tempos limite. Então, poderíamos determinar se devemos ou não repetir a solicitação. Adicionar algo como Faraday :: Error :: OpenTimeoutError e Faraday :: Error :: ResponseTimeoutError e usá-los aqui faz sentido?
Olá @coberlin , acredito que esta seja uma boa adição, só estou com medo da compatibilidade com versões anteriores.
No entanto, uma possível solução para isso pode ser ter OpenTimeoutError
e ResponseTimeoutError
para herdar de TimeoutError
, de modo que rescue
s existentes continuem funcionando conforme o esperado.
Definitivamente vale a pena alguns testes 😃
O rack_adapter pode ser o local errado para este recurso. Acho que os aplicativos Rack não fazem necessariamente distinção entre tempos limite de abertura e leitura. Talvez esse recurso funcione no adaptador HTTPClient ou em outros adaptadores? Do adaptador / 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 tem 3 subclasses ConnectTimeoutError, ReceiveTimeoutError, SendTimeoutError, consulte, por exemplo, http://www.rubydoc.info/gems/httpclient/2.1.5.2/HTTPClient/TimeoutError
Faraday já tem Faraday :: Error :: ConnectionFailed. Isso é apropriado para ConnectTimeoutError? Faraday :: Error :: TimeoutError pode ser subclassificado em Faraday :: Error :: ReceiveTimeoutError e Faraday :: Error :: SendTimeoutError.
Faraday já tem Faraday :: Error :: ConnectionFailed. Isso é apropriado para ConnectTimeoutError?
Isso faz sentido, mas não seria compatível com versões anteriores. Temos que ter em mente que as pessoas já estão identificando Faraday::Error::TimeoutError
em seus aplicativos, portanto, mudar para ConnectionFailed
interromperá esses casos.
O que queremos fazer, em vez disso, é definir 2 subclasses para Faraday::Error::TimeoutError
cujos nomes devem ser o mais genéricos possível:
A próxima etapa é acessar cada adaptador e mapear as exceções do adaptador de acordo. Por exemplo, para o HTTPClient:
Finalmente, os testes devem ser adicionados sempre que possível :)
Ei pessoal.
Conversamos "há pouco" tempo atrás (https://github.com/lostisland/faraday/pull/324).
Estou tentando novamente (https://github.com/mistersourcerer/faraday/tree/718_mrsrcr_timeout-wrapping-2nd-chance), tentarei abrir um novo PR assim que tiver algum progresso.
Olá @mistersourcerer , obrigado pela cutucada, eu não sabia que a discussão estava acontecendo.
Estou um pouco confuso quando vejo o PR fechado, mas a mudança no código, talvez saiba que você mesclou sua mudança de alguma forma no final 😄
Agradecemos sua ajuda neste caso, pois acho que você já está confortável com o teste de Timeout de seu trabalho anterior (embora estejamos falando de cerca de 3 anos atrás!).
Espero que minha explicação sobre OpenTimeoutError
e ReadTimeoutError
seja clara, mas se não for o caso, por favor me avise.
Não tenha pressa e abra uma solicitação de pull assim que terminar 👍
Olá @iMacTia.
Se bem me lembro, não conseguimos resolver a situação naquela época. Mas não sei exatamente por quê.
O principal problema era escrever um teste que falhou consistentemente entre todos os adaptadores. Então, eu não acho que meu código foi mesclado na época.
Enfim, eu tive uma ideia para isso alguns anos depois, haha, vamos ver no que dá.
No momento, os testes de _EMSynchrony_ estão falhando no Travis, mas não localmente. tentando descobrir isso. Estou pensando até em abrir um PR "inicial", então talvez possamos discutir isso.
E sua explicação é cristalina, parece a maneira perfeita de ir com ela.
Obrigado pelo ótimo trabalho nisso, cara.
Obrigado @mistersourcerer!
RE suas alterações: não tenho certeza do que aconteceu, mas vejo que @mislav finalmente mesclou suas alterações aqui: https://github.com/lostisland/faraday/commit/f73d13ee09814fa68b37efa7bddafa47331948c2
Então, alegre-se, Errno::ETIMEDOUT
já está embrulhado em Faraday::Error::TimeoutError
na maioria (senão em todos) dos adaptadores 😄
Obrigado por trabalhar neste @mistersourcerer!
Olhando para o seu commit aqui , eu me pergunto se para compatibilidade com versões anteriores, precisamos de 2 novas subclasses: OpenConnectionError < ConnectionError
para Net :: HTTP e OpenTimeoutError < TimeoutError
para HttpClient?
Parece haver alguma confusão em torno desse problema.
A razão é que uma decisão foi tomada em # 438 para tratar os erros de "tempo limite de abertura" como ConnectionFailed
. Essa é sem dúvida a melhor decisão, mas a realidade é que alguém decidiu seguir esse caminho.
Agora, isso não afeta apenas o adaptador de Rack, mas também todos os outros adaptadores, e seu comportamento provavelmente nem mesmo é consistente.
Estou planejando padronizá-los com o mesmo comportamento da v1.0 e manterei esse problema como referência.
Acompanhamento em meu comentário anterior.
Basicamente, estamos gerando um erro Faraday::ConnectionFailed
no caso de um tempo limite de abertura, enquanto levantamos um Faraday::TimeoutError
para um tempo limite de leitura. Embora adaptadores diferentes estejam se comportando de maneiras diferentes, esse parece ser o comportamento mais comum.
Isso foi decidido cerca de 3 anos atrás, mas aqui estamos discutindo sobre ter um Faraday::TimeoutError
para o primeiro caso também (com subclasses adequadas para distinguir entre abrir e fechar).
Por um lado entendo que seria mais próximo da realidade, mas se eu analisar a questão do ponto de vista da implementação, acho difícil justificar essa mudança.
Se eu ligar para um serviço e receber de volta ConnectionFailed
, sei que minha chamada não pode ter sido processada. Provavelmente não alcancei o servidor ou não consegui resolver o nome do host ou algo mais aconteceu.
Se eu receber TimeoutError
volta, então minha solicitação pode ter sido processada, ou parcialmente processada, e eu posso ter perdido a resposta. Esse é um caso completamente diferente e exige que verifique com o servidor que eu estava chamando o que aconteceu.
Tornar o tempo limite de abertura uma subcategoria de TimeoutError
significa pegar uma situação simples (solicitação não processada) em um domínio mais complexo e certamente requer verificações adicionais para decidir o que fazer: foi um tempo limite de abertura ou uma leitura tempo esgotado?
Nós precisamos:
@coberlin @ erik-escobedo @mislav @mistersourcerer gostaria de ouvir sua opinião após considerar o acima 😄
Usar ConnectionFailed
para erros de tempo limite de abertura faz sentido para mim e forneceria o que eu esperava conseguir ao distinguir os erros de tempo limite de abertura dos outros erros de tempo limite. Para consistência do adaptador, isso significaria, por exemplo, que Net::HTTP
está ok como está, mas HTTPClient
mudaria, com ConnectTimeoutErrors mapeando para ConnectionFailed em vez de TimeoutError.
Tudo bem, uma vez que decidimos padronizar todos os adaptadores para o mesmo comportamento (na v1.0 obviamente, pois isso será incompatível com versões anteriores)
Jogando um caso de uso no ringue:
No trabalho, estamos sofrendo com alguns tempos limite de abertura devido à falha do Nginx + Kubernetes ao rotear para pods suspensos (ou algo assim). De qualquer forma, o NetHTTP costumava lançar erros OpenTimeout e ReadTimeout, e isso era muito útil para nós depurar qual era qual.
Agora que mudamos para Typhoeus , infelizmente temos todos os tempos limites combinados e é difícil para nós dizer se nosso trabalho nos problemas do nginx + kuber foi melhorado ou se agora estamos fazendo mais solicitações para um sistema. De qualquer maneira, o número de tempos limite é quase o mesmo e, sem separá-los, ficamos presos em adivinhações.
Não acho que apenas adicionar Faraday::OpenTimeoutError
seja suficiente, devemos ter Faraday::OpenTimeoutError
e Faraday::ReadTimeoutError
estendendo-se de Faraday::TimeoutError
IMO.
@philsturgeon e quanto à outra solução proposta, isso também ajudaria?
Tempo limite de abertura -> Faraday::ConnectionFailed
Tempo limite de leitura -> Faraday::TimeoutError
Esse deve ser o comportamento em todos os adaptadores, mas infelizmente alguns não estão se comportando como esperado (por exemplo, Typhoeus)
Eu sinto que essas são coisas diferentes.
ConnectionFailed parece "Não tenho ideia de como falar com este servidor", como um DNS / IP inválido etc.
OpenTimeout é "Eu sei onde este servidor está, estou apenas esperando que ele faça alguma coisa"
OpenTimeout é "Eu sei onde este servidor está, estou apenas esperando que ele faça alguma coisa"
Discordo disso, prefiro dizer:
Tempo limite de abertura: Estou tentando entrar em contato com o servidor, mas não consigo alcançá-lo (Observação: conexão não estabelecida ou "aberta" ainda).
Tempo limite de leitura: estabeleci uma conexão com o servidor, mas estou esperando que ele faça alguma coisa (lendo a saída).
Um firewall / proxy / load_balancer com defeito são apenas exemplos simples de como você pode obter um tempo limite de abertura, mas em todos esses casos a conexão com o servidor ainda não foi iniciada. Essa é a parte mais importante para mim. "ConnectionFailed" para mim significa simplesmente: não consegui me conectar ao servidor. E se encaixa perfeitamente nesses casos.
Se você ainda acha que um Faraday :: OpenTimeoutError específico deve existir, eu sugiro que herde de ConnectionFailed
vez de TimeoutError
mas concordo que seria um pouco confuso e não tenho certeza de como ajudaria na prática.
Consulte meu https://github.com/lostisland/faraday/issues/718#issuecomment -343957963 anterior sobre como isso pode realmente ajudar a gerenciar o erro.
Isso faz sentido? Eu gostaria de encontrar uma solução que se adapte a todos
Aceito suas definições mais precisas para tempo limite aberto, mas chego a uma conclusão diferente.
Você considera o tempo limite de abertura como uma falha de conexão, já que a quantidade de tempo que você deseja esperar por essa conexão é considerada parte da conexão. "Falha ao fazer uma conexão em 5s" certamente faz sentido se você explicar assim, mas não é assim que muitas pessoas pensam.
Para muitos, tempo limite aberto apenas significa que ainda não aconteceu. Isso a torna menos definitiva do que a maioria das falhas de conexão, que é "O servidor está fora do ar" ou "Este DNS é lixo".
Suponho que não importa muito, já que falhas de conexão e tempos limite de abertura devem ser tentados novamente, aqui, como um tempo limite de leitura pode ser considerado motivo para recuar?
Eu concordo, podemos argumentar o quanto quisermos sobre a leitura que se possa aplicar a ela, mas a praticidade do meu ponto é o que você disse também: Se você obtiver um tempo limite aberto, significa que pode repetir a solicitação, se receber um Tempo limite de leitura significa que você deve ter MUITO cuidado, pois sua solicitação pode ter sido processada (total ou parcialmente). Coincidentemente, o significado prático de um tempo limite de abertura corresponde ao de uma conexão com falha, portanto, eu o faria herdar de lá.
Hoje as pessoas estão captando ConnectionFailed
e TimeoutError
exceções e a lógica por trás muito provavelmente está refletindo o que dissemos antes. Se introduzirmos a nova exceção como uma subclasse de ConnectionFailed
então as chances são altas de que a maioria (senão todos) dos aplicativos não precise de nenhuma mudança.
Eu entendo (e concordo) de um ponto de vista semântico que um OpenTimeout é apenas outro tipo de Timeout.
Mas ei, e se chamarmos de ConnectionTimedOut
vez disso?
Haveria alguma confusão em torno de open_timeout: X
sendo o nome da propriedade que diz quanto tempo esperar até lançar ConnectionTimedOut
.
Bom ponto 😞
Chamá-lo de ConnectionOpenTimeout
? Ele deixa claro que é um problema de conexão e que é um tempo limite de abertura. Acho que esse nome continua sendo entendido de acordo com o significado de "Falha ao fazer uma conexão em X segundos", embora algumas pessoas ainda possam se perguntar por que o tempo limite não é um tempo limite. 😅
Parece bom para mim 👍!
@iMacTia ei, se você pudesse me dar algumas dicas de por onde começar, eu poderia tentar fazer isso.
Obrigado @philsturgeon , isso seria ótimo! Permitam-me recapitular os principais pontos em torno disso:
ConnectionOpenTimeout
que herdará de ConnectionFailed
.TimeoutError
.Eu perdi alguma coisa?
ConnectionOpenTimeout
será adicionado às exceções padrão Faraday :: Request :: Repetir manipuladas?
@mjhoy esse é um bom ponto, mas no momento o middleware Retry não tenta novamente em caso de problemas de conexão. Ele tenta novamente a solicitação apenas se a conexão for bem-sucedida, mas houver um tempo limite. Na verdade, não tenho certeza se faz sentido repetir uma solicitação se o serviço que você está chamando não estiver acessível. Você pode preferir obter a exceção de volta e fazer outra coisa nesse caso.
No entanto, Faraday::Request::Retry
é configurável, então nada impede você de adicionar ConnectionOpenTimeout
ou mesmo ConnectionFailed
à lista de exceções que você deseja que ele controle.
Eu gostaria de ver mais algumas "opiniões da comunidade" antes de adicioná-las à lista de exceções padrão
Ocasionalmente, estamos encontrando OpenTimeout
erros com um endpoint de API que precisa ser repetido; parece, pela sua lógica acima, que os tempos limite de abertura são seguros para tentar novamente (mais seguro do que um tempo limite de leitura). Também presumimos que, com o middleware de nova tentativa, os tempos limite de abertura e leitura seriam repetidos; a documentação diz: "Por padrão, ele tenta novamente 2 vezes e trata apenas as exceções de tempo limite." As exceções padrão tratadas são Errno::ETIMEDOUT, Timeout::Error,
Error::TimeoutError
, e Net::OpenTimeout
é uma subclasse de Timeout::Error
; não estava muito claro se Faraday os estava tratando de maneira diferente. Então, talvez a documentação deva ser atualizada? Em qualquer caso, sim, configuramos o middleware; Só estou me perguntando se o padrão faz sentido.
@mjhoy Você está certo ao dizer que Timeout::Error
inclui Net::OpenTimeout
também, portanto, com a implementação atual, parece que o tempo limite de abertura também deve ser tentado novamente. Além disso, Timeout::Error
é resgatado e ressuscitado pelo adaptador em circunstâncias normais, portanto, sua presença na lista de exceção pode ser desnecessária ou apenas para segurança extra.
Assim que terminarmos de refatorar as exceções, o Net::OpenTimeout
será gerado como uma nova exceção e como eu disse em meu comentário, mudando o comportamento padrão atual.
Ainda acredito que isso não deveria fazer parte dos padrões, mas é definitivamente algo a se considerar ao fazer o trabalho.
Obrigado por levantar esta questão 😄
Ei, desculpe, isso definhou no acúmulo de minhas equipes por um ano e agora nossas prioridades mudaram bastante. Não estarei trabalhando neste assunto, mas boa sorte!
@iMacTia Alguém está trabalhando nessa mudança? Eu não me importo de pegar isso para a v2.0.
Olá @ ragav0102 , obrigado pelo apoio!
Ninguém está trabalhando nisso ainda, pois ainda estamos nos esforçando para lançar a v1.0.
Nós definitivamente apreciaríamos a ajuda, mas não temos um plano ainda para a v2.0, então não posso dizer quando ela será lançada, então suas alterações podem precisar esperar meses antes de serem usadas.
Se você precisar disso em um de seus projetos, provavelmente não é viável.
Se você acabou de ser aprovado e gostaria de contribuir, sugiro que escolha algo agendado para a v1.0, pois será lançado muito antes 😄
Entendi!
Comentários muito úteis
Olá @coberlin , acredito que esta seja uma boa adição, só estou com medo da compatibilidade com versões anteriores.
No entanto, uma possível solução para isso pode ser ter
OpenTimeoutError
eResponseTimeoutError
para herdar deTimeoutError
, de modo querescue
s existentes continuem funcionando conforme o esperado.Definitivamente vale a pena alguns testes 😃