Requests: tempo limite geral

Criado em 16 abr. 2016  ·  38Comentários  ·  Fonte: psf/requests

Já fazemos grande uso do parâmetro timeout, que permite configurar por tempo limite de transação TCP. Isso é muito útil! No entanto, também precisamos oferecer suporte a um tempo limite geral na conexão. Lendo os documentos sobre tempos limite , vejo que isso não é suportado no momento e, pesquisando os problemas pelo menos um pouco atrás, não vi outra solicitação para esse recurso - desculpe-me se houver.

Percebo que podemos definir temporizadores em nossa biblioteca para fazer isso, mas estou preocupado com a sobrecarga adicional (uma por encadeamento, e podemos ter muitos), bem como quaisquer efeitos adversos ao pool de conexões se acabarmos precisando abortar um solicitar. Existe uma boa maneira de abortar uma solicitação em primeiro lugar? Não vi nada óbvio nos documentos.

Então: A longo prazo, seria ótimo se pudéssemos adicionar o tempo limite geral à biblioteca de solicitações. A curto prazo, existe uma maneira recomendada de implementar isso do meu lado?

Propose Close

Comentários muito úteis

@jribbens Existem alguns problemas com isso.

A parte 1 é que a complexidade de tal patch é muito alta. Para que ele se comporte corretamente, você precisa alterar repetidamente os tempos limite no nível do soquete. Isso significa que o patch precisa ser passado de forma generalizada através do httplib, que já corrigimos mais do que gostaríamos. Essencialmente, precisaríamos acessar o httplib e reimplementar cerca de 50% de seus métodos mais complexos para obter essa mudança funcional.

A parte 2 é que a manutenção de tal patch é relativamente onerosa. Provavelmente precisaríamos começar a manter o que equivale a um fork paralelo de httplib (mais propriamente http.client neste momento) para fazer isso com sucesso. Alternativamente, precisaríamos assumir a carga de manutenção de uma pilha HTTP diferente que é mais receptiva a esse tipo de alteração. Esta parte é, eu suspeito, comumente perdida por aqueles que desejam ter tal recurso: o custo de implementação é alto, mas isso é _nada_ comparado aos custos de manutenção contínuos de suporte a tal recurso em todas as plataformas.

A parte 3 é que a vantagem de tal patch não é clara. Tem sido minha experiência que a maioria das pessoas que deseja um patch de tempo limite total não está pensando com clareza sobre o que deseja. Na maioria dos casos, os parâmetros de tempo limite total acabam tendo o efeito de eliminar solicitações perfeitamente boas sem motivo.

Por exemplo, suponha que você criou um código que baixa arquivos e gostaria de lidar com travamentos. Embora seja inicialmente tentador definir um tempo limite total simples ("nenhuma solicitação pode levar mais de 30 segundos!"), esse tempo limite perde o ponto. Por exemplo, se um arquivo mudar de 30 MB para 30 GB de tamanho, esse arquivo _nunca_ pode ser baixado nesse tipo de intervalo de tempo, mesmo que o download possa ser totalmente íntegro.

Dito de outra forma, os tempos de espera totais são um incômodo atraente: eles parecem resolver um problema, mas não o fazem de forma eficaz. Uma abordagem mais útil, na minha opinião, é aproveitar o tempo limite por ação de soquete, combinado com stream=True e iter_content e atribuir tempos limite para blocos de dados. Da maneira que iter_content funciona, o fluxo de controle será retornado ao seu código em um intervalo um tanto regular. Isso significa que você pode definir tempos limite no nível do soquete (por exemplo, 5s) e, em seguida, iter_content em pedaços bastante pequenos (por exemplo, 1 KB de dados) e estar relativamente confiante de que, a menos que você esteja sendo atacado ativamente, não há negação de serviço é possível aqui. Se você está realmente preocupado com a negação de serviço, defina o tempo limite no nível do soquete muito menor e o tamanho do bloco menor (0,5s e 512 bytes) para garantir que você receba regularmente o fluxo de controle.

O resultado de tudo isso é que acredito que o tempo limite total é um recurso incorreto em uma biblioteca como esta. O melhor tipo de tempo limite é aquele que é ajustado para permitir que respostas grandes tenham tempo suficiente para download em paz, e esse tempo limite é melhor atendido por tempos limite no nível do soquete e iter_content .

Todos 38 comentários

Olá @emgerner-msft,

Para referência, a seguir estão todas as variações desse tema, se não essa solicitação de recurso exata:

Também discutimos isso em https://github.com/sigmavirus24/requests-toolbelt/issues/51

Você notará que o último link discute este pacote que deve lidar com isso para você sem adicioná-lo às solicitações. A realidade é que não há necessidade de requisições para fazer isso quando outro pacote já faz isso muito bem.

O pacote que você faz referência faz isso bifurcando um processo separado para executar a solicitação da web. Essa é uma maneira muito pesada de atingir o objetivo simples de um tempo limite e, na minha opinião, não é de forma alguma um substituto para as próprias solicitações com um recurso de tempo limite nativo.

@jribbens Se você puder encontrar uma maneira que não use threads nem processos, isso seria incrível. Até então, se você quiser um tempo limite do relógio de parede, sua melhor aposta é esse pacote, pois é a maneira mais confiável de conseguir isso no momento.

Eu não acho que @jribbens está dizendo que não há threads nem processos. Apenas que um processo _per_ solicitação da web é excessivo. Muitos idiomas têm uma maneira de vários temporizadores compartilharem um único thread ou processo adicional. Só não estou ciente de como fazer isso melhor em Python.

Parece que #1928 tem a maior discussão de alternativas, mas a maioria vem com muitas ressalvas (isso não funcionará para o seu caso de uso, etc). Estou bem em ter algum código personalizado em minha biblioteca e escrever minha própria solução personalizada se isso realmente não pertencer às solicitações, mas acho que preciso de um pouco mais de informações sobre como isso seria. A razão pela qual usamos solicitações é fugir da lógica de pool de conexão TCP de baixo nível, mas parece que lendo esse thread, para escrever esse código personalizado, preciso conhecer essa lógica, e é com isso que estou tendo alguns problemas .

@emgerner-msft está correto. Estou um pouco confuso com o comentário do @sigmavirus24 , ter um "tempo limite total" sem usar threads ou processos parece bastante pedestre e nada "incrível". Basta calcular o prazo no início de todo o processo (por exemplo deadline = time.time() + total_timeout ) e, em seguida, em qualquer operação individual, defina o tempo limite como deadline - time.time() .

ter um "tempo limite total" sem usar threads ou processos parece bastante pedestre e nada "incrível".

E sua solução é bastante primitiva. A razão pela qual a maioria das pessoas deseja um tempo limite total (ou relógio de parede) é impedir que uma leitura "trave", em outras palavras, um caso como o seguinte:

r = requests.get(url, stream=True)
for chunk in r.iter_content(chunksize):
    process_data(chunk)

Onde cada leitura leva muito tempo no meio de iter_content , mas é menor que o tempo limite de leitura (suponho que aplicamos isso ao transmitir, mas ainda pode ser o caso de não fazermos) eles especificaram . Certamente, parece que isso deve ser simplesmente tratado por sua solução @jribbens até que você se lembre de como os relógios se desviam e o horário de verão funciona e os time.time() são lamentavelmente insuficientes.

Por fim, é importante ter em mente que a API de Requests está congelada. Não existe uma API boa ou consistente para especificar um tempo limite total. E se implementássemos um tempo limite como você sugere, teríamos inúmeros bugs que eles especificaram um tempo limite total de um minuto, mas demorou mais porque na última vez que verificamos estávamos com menos de um minuto, mas o tempo limite de leitura configurado era longo o suficiente para que o tempo limite erro foi gerado em torno de um minuto e meio. Esse é um tempo limite de parede _muito_ difícil que seria um pouco melhor para as pessoas que procuram isso, mas não é diferente da pessoa que está implementando isso.

Desculpe se não fui claro @sigmavirus24 , você parece ter criticado minha ilustração de princípio de pseudocódigo como se pensasse que era um patch literal. Devo salientar, porém, que time.time() não funciona do jeito que você aparentemente pensa - o horário de verão não é relevante, e nem a distorção do relógio nas escalas de tempo sobre as quais estamos falando aqui. Além disso, você não entendeu a sugestão se acha que o bug que você descreve ocorreria. Finalmente, não tenho certeza do que você quer dizer com a API de solicitações sendo "congelada", pois a API foi alterada recentemente na versão 2.9.0, então claramente o que você quer dizer não é o que eu normalmente entenderia pela palavra.

Só para separar minha discussão: na verdade não estou argumentando que isso é fácil. Se fosse totalmente simples, eu apenas escreveria e pararia de incomodar você. :)

Meus problemas são:
1) Tudo nos tópicos que você listou eram patches de macaco. Tudo bem, mas estou usando isso em uma biblioteca de qualidade de produção e não posso aceitar a ressalva de que mudanças internas quebram tudo.
2) O decorador de tempo limite no link que você deu é ótimo, mas não tenho certeza de como isso afeta a conexão. Mesmo se aceitarmos que a única boa maneira de fazer timeouts é com um monte de threads, como essa biblioteca impõe que o soquete seja desligado, a conexão caia, etc. Estamos fazendo muitas conexões e isso parece potencialmente bastante propenso a vazamentos. requests não tem um método 'abort' que eu possa encontrar (me corrija se eu estiver errado) então como está acontecendo o desligamento da conexão?

Tudo o que estou procurando é uma versão clara e 'abençoada' de como resolver esse problema sozinho ou, se não houver uma solução perfeita, algumas soluções com as advertências discutidas. Isso faz sentido?

@emgerner-msft Supondo que você esteja usando o CPython, o desligamento da conexão acontecerá quando a solicitação não continuar mais. Nesse ponto, todas as referências à conexão subjacente serão perdidas e o soquete será fechado e descartado.

@Lukasa Ok, obrigado! Como a biblioteca determina que a solicitação não está mais em andamento? Por exemplo, se eu usasse a rota do decorador de tempo limite e cortasse no meio do download, quando o download realmente pararia? Preciso fazer algo especial com as opções de streaming?

Se você usar o decorador de tempo limite, o download será interrompido quando o tempo limite for disparado. Isso ocorre porque os sinais interrompem syscalls, o que significa que não haverá mais chamadas no soquete. Uma vez que a solicitação não está mais no escopo (por exemplo, a pilha foi desenrolada para fora de sua função requests.* ), isso está em: CPython limpará o objeto de conexão e interromperá a conexão. Não são necessárias opções especiais de streaming.

Perfeito. Estou bem para fechar o tópico então, a menos que outros tenham mais a dizer.

Na verdade, desculpe, mais uma preocupação. Estava analisando o código do decorador de tempo limite mais de perto, já que você disse que ele usa sinais era relevante, em oposição a algo como Python Timers (presumivelmente). Parece que ele chama o sinal com SIGALRM que está documentado no Python Signal para não funcionar no Windows. Eu preciso que isso funcione em ambientes Unix e Windows, bem como em Python 2.7 e 3.3+ (muito parecido com os próprios pedidos). Vou pesquisar um pouco mais e ver se isso realmente funcionará, dado isso.

@emgerner-msft Isso é frustrante. =(

@Lukasa Sim, tentei o trecho de uso básico e não funciona no Windows. Eu li um pouco mais do código/exemplos e mexi e parece que se não usarmos sinais, o pacote pode funcionar, mas tudo tem que ser selecionável, o que não é o caso do meu aplicativo. Então, tanto quanto eu posso dizer, o decorador de tempo limite não resolverá meu problema. Alguma outra ideia?

@emgerner-msft Você tem certeza de que nenhum dos sinais específicos do Windows é adequado?

@Lukasa Para ser franco, eu simplesmente não sei. Eu não usei sinais antes, e assim como eu não percebi até você me dizer que eles interromperiam o pedido, não tenho certeza do que é apropriado. Também não estou tentando fazer isso apenas para funcionar no Windows. Eu preciso de suporte completo para crossplat (Windows e Unix) e suporte para Python 2 e Python 3. Tantos sinais parecem específicos da plataforma que está me jogando. O timer era uma das soluções que eu estava olhando que parecia menos de baixo nível e, portanto, poderia cuidar das minhas restrições, mas não tenho certeza de como poderia fechar a conexão. Eu posso ler mais, mas é por isso que eu esperava obter orientação adicional de vocês. :)

Portanto, este é um lugar realmente complicado de se estar.

A realidade é que não há mais ou menos nenhuma maneira multiplataforma de matar um thread, exceto interrompendo-o, que é basicamente o que é um sinal. Isso significa, eu acho, que os sinais são a única rota que você realmente tem para fazer isso funcionar nas plataformas. Estou inclinado a tentar fazer ping em um especialista em Windowsy Pythony: @brettcannon , você tem uma boa sugestão aqui?

Por curiosidade, há um motivo para não implementar o "tempo limite total" em solicitações que não seja a implementação e o teste que exigem trabalho? Quero dizer, se um patch para implementá-lo magicamente aparecesse hoje, em teoria, seria rejeitado ou aceito? Eu aprecio e concordo com o ponto de vista "eliminar complexidade desnecessária", mas "você pode fazer isso bifurcando um processo separado" não torna esse recurso desnecessário na minha opinião.

@jribbens Existem alguns problemas com isso.

A parte 1 é que a complexidade de tal patch é muito alta. Para que ele se comporte corretamente, você precisa alterar repetidamente os tempos limite no nível do soquete. Isso significa que o patch precisa ser passado de forma generalizada através do httplib, que já corrigimos mais do que gostaríamos. Essencialmente, precisaríamos acessar o httplib e reimplementar cerca de 50% de seus métodos mais complexos para obter essa mudança funcional.

A parte 2 é que a manutenção de tal patch é relativamente onerosa. Provavelmente precisaríamos começar a manter o que equivale a um fork paralelo de httplib (mais propriamente http.client neste momento) para fazer isso com sucesso. Alternativamente, precisaríamos assumir a carga de manutenção de uma pilha HTTP diferente que é mais receptiva a esse tipo de alteração. Esta parte é, eu suspeito, comumente perdida por aqueles que desejam ter tal recurso: o custo de implementação é alto, mas isso é _nada_ comparado aos custos de manutenção contínuos de suporte a tal recurso em todas as plataformas.

A parte 3 é que a vantagem de tal patch não é clara. Tem sido minha experiência que a maioria das pessoas que deseja um patch de tempo limite total não está pensando com clareza sobre o que deseja. Na maioria dos casos, os parâmetros de tempo limite total acabam tendo o efeito de eliminar solicitações perfeitamente boas sem motivo.

Por exemplo, suponha que você criou um código que baixa arquivos e gostaria de lidar com travamentos. Embora seja inicialmente tentador definir um tempo limite total simples ("nenhuma solicitação pode levar mais de 30 segundos!"), esse tempo limite perde o ponto. Por exemplo, se um arquivo mudar de 30 MB para 30 GB de tamanho, esse arquivo _nunca_ pode ser baixado nesse tipo de intervalo de tempo, mesmo que o download possa ser totalmente íntegro.

Dito de outra forma, os tempos de espera totais são um incômodo atraente: eles parecem resolver um problema, mas não o fazem de forma eficaz. Uma abordagem mais útil, na minha opinião, é aproveitar o tempo limite por ação de soquete, combinado com stream=True e iter_content e atribuir tempos limite para blocos de dados. Da maneira que iter_content funciona, o fluxo de controle será retornado ao seu código em um intervalo um tanto regular. Isso significa que você pode definir tempos limite no nível do soquete (por exemplo, 5s) e, em seguida, iter_content em pedaços bastante pequenos (por exemplo, 1 KB de dados) e estar relativamente confiante de que, a menos que você esteja sendo atacado ativamente, não há negação de serviço é possível aqui. Se você está realmente preocupado com a negação de serviço, defina o tempo limite no nível do soquete muito menor e o tamanho do bloco menor (0,5s e 512 bytes) para garantir que você receba regularmente o fluxo de controle.

O resultado de tudo isso é que acredito que o tempo limite total é um recurso incorreto em uma biblioteca como esta. O melhor tipo de tempo limite é aquele que é ajustado para permitir que respostas grandes tenham tempo suficiente para download em paz, e esse tempo limite é melhor atendido por tempos limite no nível do soquete e iter_content .

Talvez @zooba tenha uma ideia, pois ele realmente sabe como o Windows funciona. :)

(Não relacionado, uma das minhas coisas favoritas a fazer é configurar uma cadeia de especialistas em um problema do GitHub.)

Haha, eu já conheço @zooba e @brettcannon. Posso discutir com eles aqui ou internamente, pois uma solução para isso provavelmente os ajudaria também.

@emgerner-msft Achei que sim, mas não quis presumir: a MSFT é uma grande organização!

@Lukasa Apenas lendo a parede de texto que você acabou de escrever acima - interessante! Na discussão de stream=True e iter_content para tempo de downloads, qual é a maneira equivalente de lidar com uploads maiores?

_PS_: O parágrafo acima começando com 'Coloque de outra forma,..' é o tipo de orientação que procurei nos documentos. Dado o número de solicitações que você recebe para o tempo limite máximo (e seus motivos válidos para não fazê-lo), talvez a melhor coisa a fazer seja adicionar algumas dessas informações nos documentos de tempo limite ?

lol @lukasa eu aceito seu ponto sobre manutenção, que já estava na minha mente, mas em "feature vs misfeature" eu tenho medo de ser completamente o oposto de você. Acho que quem _não_ quer um tempo limite total não está pensando claramente sobre o que quer, e estou tendo dificuldade em imaginar uma situação em que o que você descreve como um bug "o download de 30 MB muda para 30 GB e, portanto, falha" não é de fato, um recurso benéfico!

Você pode, como diz, fazer algo um pouco semelhante (mas suspeito que sem a maioria dos benefícios de um tempo limite total) usando stream=True mas pensei que o objetivo das solicitações era que ele lidasse com as coisas para você ...

Eu pensei que o objetivo dos pedidos era que ele cuidasse das coisas para você

Ele lida com HTTP para você. Os fatos de que já lidamos com tempo limite de conexão e leitura e que tivemos algumas isenções ao nosso congelamento de recursos de vários anos são tangenciais à discussão de utilidade, conveniência, consistência (em várias plataformas) e capacidade de manutenção. Agradecemos seu feedback e sua opinião. Se você tiver novas informações para apresentar, agradecemos.

Também pode estar dizendo que as solicitações não tratam de tudo, pelo número de solicitações de recursos rejeitadas neste projeto e pelo fato de haver um projeto separado implementando padrões de uso comum para os usuários (o conjunto de ferramentas de solicitações). Se um tempo limite total pertencer a qualquer lugar, ele estaria lá, mas, novamente, teria que funcionar no Windows, BSD, Linux e OSX com excelente cobertura de teste e sem ser um pesadelo para manter.

Na discussão de stream=True e iter_content para tempo de downloads, qual é a maneira equivalente de lidar com uploads maiores?

Defina um gerador para seu upload e passe-o para data . Ou, se a codificação em partes não for um vencedor para você, defina um objeto semelhante a um arquivo com um método mágico read e passe _that_ para data .

Deixe-me elaborar um pouco. Se você passar um gerador para data , as solicitações irão iterar sobre ele e enviarão cada pedaço por vez. Isso significa que, para enviar dados, necessariamente teremos que entregar o fluxo de controle ao seu código para cada pedaço. Isso permite que você faça o que quiser nesse tempo, incluindo lançar exceções para abortar completamente a solicitação.

Se por algum motivo você não puder usar a codificação de transferência em partes para seus uploads (improvável, mas possível se o servidor em questão for muito ruim), você pode fazer o mesmo criando um objeto semelhante a um arquivo que tenha um comprimento e, em seguida, faça o seu magic na chamada read , que será chamada repetidamente para pedaços de 8192 bytes. Novamente, isso garante que o fluxo de controle passe pelo seu código de forma intermitente, o que permite que você use sua própria lógica.

PS: O parágrafo acima começando com 'Coloque de outra forma, ..' é o tipo de orientação que procurei nos documentos. Dado o número de solicitações que você recebe para o tempo limite máximo (e seus motivos válidos para não fazê-lo), talvez a melhor coisa a fazer seja adicionar algumas dessas informações nos documentos de tempo limite?

Eu suponho_. De um modo geral, porém, estou sempre nervoso em colocar um texto um tanto defensivo na documentação. Poderia entrar em um FAQ, eu acho, mas o texto que explica por que _não_ temos algo raramente é útil na documentação. O espaço nos documentos seria mais bem servido, desconfio, por uma receita para fazer alguma coisa.

Acho que quem não quer um tempo limite total não está pensando claramente sobre o que quer, e estou tendo dificuldade em imaginar uma situação em que o que você descreve como um bug "o download de 30 MB muda para 30 GB e, portanto, falha" não é de fato, um recurso benéfico!

Ei, eu não sou:

  • gerenciador de pacotes (por exemplo, pip, que usa solicitações), onde os pacotes podem variar muito no tamanho dos dados
  • web scraper, que pode ser executado em vários sites que variam muito em tamanho
  • um agregador de log que baixa arquivos de log de hosts que têm níveis muito variados de nós (e, portanto, tamanhos de arquivo de log)
  • downloader de vídeo (os vídeos podem variar muito em tamanho)

Na verdade, acho que o caso em que o desenvolvedor sabe em uma ordem de magnitude com quais tamanhos de arquivo eles estarão lidando é o caso incomum. Na maioria dos casos, os desenvolvedores não têm ideia. E geralmente eu diria que fazer suposições sobre esses tamanhos é imprudente. Se você tiver restrições no tamanho do download, seu código deve codificar deliberadamente essas suposições (por exemplo, na forma de verificações no comprimento do conteúdo), em vez de codificá-las implicitamente e misturá-las com a largura de banda da rede do usuário para que outras pessoas lendo o código pode vê-los claramente.

mas eu pensei que o objetivo dos pedidos era que ele cuidasse das coisas para você...

Solicitações muito deliberadamente não lidam com tudo para os usuários. Tentar fazer tudo é uma tarefa impossível, e é impossível construir uma boa biblioteca que faça isso. Nós regularmente dizemos aos usuários para descer para urllib3 para conseguir algo.

Só colocamos código em solicitações se pudermos fazê-lo melhor ou mais limpo do que a maioria dos usuários será capaz de fazer. Se não, não há valor. Ainda não estou convencido de que o tempo limite total é uma dessas coisas, especialmente considerando o que percebo ser a utilidade relativamente marginal quando agregada em nossa base de usuários.

Dito isso, estou aberto a ser convencido de que estou errado: só não vi um argumento convincente para isso ainda (e, para despistá-lo, "eu preciso disso!" não é um argumento convincente: tem que dar algumas razões!).

@sigmavirus24

Se um tempo limite total pertencer a qualquer lugar, ele estaria lá, mas, novamente, teria que funcionar no Windows, BSD, Linux e OSX com excelente cobertura de teste e sem ser um pesadelo para manter.

Acordado!

@lukasa Suponho que meu pensamento é que não apenas eu quero, na verdade, quase todos os usuários gostariam se pensassem sobre isso (ou não percebem que ainda não está lá). Metade dos seus cenários de uso acima de onde você diz que deve ser evitado, eu diria que é vital (raspador da web e agregador de logs) - os outros dois são menos necessários, pois é provável que haja um usuário aguardando o resultado que pode cancelar o download manualmente se eles querem. Qualquer coisa que seja executada em segundo plano sem uma interface do usuário e não use um tempo limite geral é bugada na minha opinião!

Suponho que meu pensamento é que não apenas eu quero, na verdade, quase todos os usuários gostariam se pensassem sobre isso (ou não percebem que ainda não está lá).

@jribbens , temos vários anos (mais de uma década se você combinar as experiências de nós três) conversando e entendendo as necessidades de nossos usuários. O que foi necessário para quase todos (pelo menos 98%) usuários foi conexão e tempo limite de leitura. Entendemos que uma minoria muito expressiva de nossos usuários deseja um tempo limite geral. Dado o que podemos extrapolar para o tamanho do grupo de usuários em potencial para esse recurso versus qual é o tamanho potencial de usuários que não precisam desse recurso e a complexidade da manutenção e desenvolvimento do recurso, não é realmente algo que vamos façam.

Se você tiver algo _novo_ para compartilhar, gostaríamos de ouvir isso, mas tudo o que você disse até agora é que, na sua opinião, qualquer coisa que use solicitações sem um tempo limite geral é bugada e posso imaginar que há muitos usuários que se ofenderia com sua afirmação de que suas decisões de design são problemáticas. Portanto, evite insultar a inteligência de nossos usuários.

@sigmavirus24 Ao longo deste tópico, você foi desnecessariamente condescendente, inflamatório e rude, e estou pedindo educadamente, por favor, pare.

@Lukasa Analisei detalhadamente suas sugestões sobre como fazer upload e download de streaming e ler os documentos sobre esses tópicos. Se você pudesse validar minhas suposições / perguntas, seria ótimo.

  1. Para downloads de streaming, se eu usar algo como um tempo limite de leitura '(por exemplo, 5s) e, em seguida, iter_content em pedaços bastante pequenos (por exemplo, 1 KB de dados)', isso significa que a biblioteca de solicitações aplicará o tempo limite de 5s para cada leitura de 1 KB e o tempo limite se leva mais de 5s. Correto?
  2. Para uploads de streaming, se eu usar um gerador ou objeto semelhante a um arquivo que retorna blocos de dados e definir o tempo limite de leitura para 5s, a biblioteca de solicitação aplicará o tempo limite de 5s para cada bloco que eu retornar e o tempo limite se demorar mais. Correto?
  3. Se eu não usar um gerador para upload e simplesmente passar bytes diretamente, como a biblioteca de solicitações decide aplicar o tempo limite de leitura que eu defini? Por exemplo, se eu passar um pedaço de tamanho 4 MB e um tempo limite de leitura de 5 segundos, quando exatamente esse tempo limite de leitura é aplicado?
  4. Se eu não usar iter_content e simplesmente fizer com que as solicitações baixem todo o conteúdo diretamente na solicitação com um tempo limite de leitura de 5s, quando exatamente esse tempo limite de leitura é aplicado?

Eu tenho uma compreensão geral de soquetes/protocolo TCP/etc, mas não exatamente como urllib funciona com esses conceitos em um nível mais baixo ou se as solicitações fazem algo especial além de passar os valores. Eu quero entender exatamente como os tempos limite são aplicados, pois simplesmente obter o fluxo de controle de volta e aplicar meu próprio esquema de tempo limite não funciona, devido aos problemas de plataforma cruzada com o término do encadeamento. Se houver material de leitura adicional para responder às minhas perguntas, sinta-se à vontade para me indicar! De qualquer forma, espero que este seja meu último conjunto de perguntas. :)

Obrigado pela vossa ajuda até agora.

@emgerner-msft Ok:

  1. Não. É mais complexo do que isso, infelizmente. Conforme discutido, cada tempo limite se aplica _per socket call_, mas não podemos garantir quantas chamadas de socket estão em um determinado pedaço. A razão bastante complexa para isso é que a biblioteca padrão envolve o soquete de apoio em um objeto de buffer (geralmente algo como io.BufferedReader ). Isso fará quantas chamadas recv_into forem necessárias até fornecer dados suficientes. Isso pode ser tão pouco quanto zero (se já houver dados suficientes no buffer) ou exatamente o número de bytes que você recebeu se o peer remoto estiver alimentando você um byte por vez. Há realmente muito pouco que podemos fazer sobre isso: devido à natureza de uma chamada read() contra um objeto em buffer, nem mesmo temos o fluxo de controle de volta entre cada chamada recv_into .

Isso significa que a maneira _only_ de garantir que você não terá mais do que uma espera de n segundos é fazer iter_content com um tamanho de bloco de 1 . Essa é uma maneira absurdamente ineficiente de baixar um arquivo (gasta muito tempo no código Python), mas é a única maneira de obter a garantia desejada.

  1. Eu também acredito que a resposta para isso é não. Atualmente, não temos noção de um tempo limite de _send_. A maneira de obter um é usar socket.setdefaulttimeout .
  2. Os tempos limite de leitura são aplicados apenas a leituras, portanto, não importa como você passa o corpo.
  3. Esse tempo limite de leitura sofre as mesmas preocupações que o caso iter_content : se você tiver solicitações para baixar tudo, acabaremos emitindo quantas chamadas recv_into forem necessárias para baixar o corpo, e o tempo limite se aplica a cada um por sua vez.

Você está se deparando com o problema central aqui: as solicitações simplesmente não chegam perto o suficiente do soquete para alcançar exatamente o que você está procurando. Nós _poderíamos_ adicionar um tempo limite de envio: esse é um trabalho de solicitação de recurso considerado, e não sofre os mesmos problemas que o tempo limite de leitura, mas para todo o resto estamos presos porque httplib insiste (com razão) em trocar para uma representação de soquete em buffer e, em seguida, o restante de httplib usa essa representação em buffer.

@Lukasa

Ah, que confusão, haha. Eu pensei que poderia ser o caso, mas eu estava realmente esperando que eu estivesse errado.

Primeiro, precisamos desesperadamente de um tempo limite de envio. Simplesmente não posso dizer aos meus usuários que seus uploads podem travar infinitamente e não temos um plano para corrigir o problema. :/

Parece que estou em uma situação impossível neste momento. Não há suporte de biblioteca para tempo limite total (o que eu entendo). Não há garantias sobre exatamente como o tempo limite existente funciona com vários tamanhos de bloco - se houvesse, eu poderia resumir o tempo: tempo limite de conexão + tempo limite de leitura * tamanho do bloco. Ser capaz de interromper o fluxo com o modo de fluxo e geradores é bom, mas como não tenho uma solução para realmente abortar os threads de maneira multiplataforma, isso também não ajuda. Você vê outras opções para seguir em frente? O que outros usuários estão fazendo para resolver esses problemas?

Primeiro, precisamos desesperadamente de um tempo limite de envio. Simplesmente não posso dizer aos meus usuários que seus uploads podem travar infinitamente e não temos um plano para corrigir o problema. :/

Portanto, a lógica de tempo limite usada nas solicitações é fundamentalmente a de urllib3, portanto, deve ser suficiente para fazer a alteração lá: fique à vontade para abrir uma solicitação de recurso e podemos ajudá-lo na alteração. E no curto prazo, sinta-se à vontade para investigar usando setdefaulttimeout .

Você vê outras opções para seguir em frente? O que outros usuários estão fazendo para resolver esses problemas?

As opções que você tem aqui dependem de suas restrições específicas.

Se você _deve_ ter um tempo limite determinístico (ou seja, se for possível garantir que uma solicitação não demore mais de _n_ segundos), então você não pode fazer isso facilmente com a biblioteca padrão do Python como ela existe hoje. No Python 2.7, você precisaria corrigir socket._fileobject para permitir que você executasse um tempo limite sequencial para cada chamada recv , mas no Python 3 é ainda mais difícil porque você precisa fazer o patch em uma classe cuja implementação está em C ( io.BufferedReader ), que vai ser um pesadelo.

Caso contrário, a única maneira de obtê-lo é _off_ buffering na biblioteca padrão. Isso quebrará o httplib e todos os nossos patches em cima dele, que assumem que podemos fazer uma chamada read(x) que se comportará não como o syscall read em um soquete, mas como o read syscall em um arquivo (ou seja, retorna um comprimento determinístico).

Dito de outra forma: se você _precisar_ de um tempo limite determinístico, você descobrirá que um grande número de bibliotecas simplesmente não consegue fornecê-lo para você. Basicamente, se eles usarem httplib ou socket.makefile , você ficará sem sorte: simplesmente não há uma maneira limpa de garantir que o controle retorne a você em um tempo definido, exceto pela emissão repetida de comprimento -1 lê. Você _pode_ fazer isso, mas vai prejudicar seu desempenho.

Portanto, você tem uma compensação aqui: se você deseja um tempo limite determinístico, a maneira como o buffer é implementado na biblioteca padrão do Python (e, portanto, nas solicitações) simplesmente não disponibilizará isso para você. Você pode recuperar isso desabilitando o buffer e reescrevendo o código, mas isso prejudica seu desempenho potencialmente muito, a menos que você reimplemente o buffer de uma maneira que reconheça os tempos limite.

Você pode tentar implementar o código necessário na biblioteca padrão do Python na classe BufferedReader : você pode definitivamente perguntar ao pessoal do Python se eles estão interessados. Mas eu não prenderia minha respiração.

Portanto, a lógica de tempo limite usada nas solicitações é fundamentalmente a de urllib3, portanto, deve ser suficiente para fazer a alteração lá: fique à vontade para abrir uma solicitação de recurso e podemos ajudá-lo na alteração. E no curto prazo, sinta-se à vontade para investigar usando setdefaulttimeout.

Solicitação de recurso em urllib3 ou aqui? Abrirá um (ou ambos) o mais rápido possível.

Solicitação de recurso em urllib3: não precisamos expor nada de novo nas solicitações.

Esta página foi útil?
0 / 5 - 0 avaliações