Requests: tiempo de espera total

Creado en 16 abr. 2016  ·  38Comentarios  ·  Fuente: psf/requests

Ya hacemos un gran uso del parámetro de tiempo de espera que permite establecer tiempos de espera por transacción TCP. ¡Esto es muy útil! Sin embargo, también necesitamos admitir un tiempo de espera general en toda la conexión. Al leer los documentos sobre tiempos de espera , veo que esto no es compatible actualmente, y al buscar en los problemas al menos un poco atrás, no vi otra solicitud para esta función; disculpe si la hay.

Me doy cuenta de que podemos configurar temporizadores en nuestra biblioteca para lograr esto, pero me preocupa la sobrecarga adicional (uno por subproceso, y es posible que tengamos muchos), así como los efectos adversos en la agrupación de conexiones si terminamos necesitando cancelar un solicitud. ¿Hay una buena manera de abortar una solicitud en primer lugar? No vi nada obvio en los documentos.

Entonces: a largo plazo, sería genial si pudiéramos agregar un tiempo de espera general a la biblioteca de solicitudes. A corto plazo, ¿hay alguna forma recomendada de implementar esto por mi parte?

Propose Close

Comentario más útil

@jribbens Hay algunos problemas con esto.

La parte 1 es que la complejidad de dicho parche es muy alta. Para que se comporte correctamente, debe cambiar repetidamente los tiempos de espera en el nivel de socket. Esto significa que el parche debe pasar de forma generalizada a través de httplib, que ya hemos parcheado más de lo que nos gustaría. Esencialmente, necesitaríamos acceder a httplib y volver a implementar alrededor del 50 % de sus métodos más complejos para lograr este cambio funcional.

La parte 2 es que el mantenimiento de dicho parche es relativamente oneroso. Es probable que necesitemos comenzar a mantener lo que equivale a una bifurcación paralela de httplib (más correctamente http.client en este momento) para poder hacerlo con éxito. Alternativamente, tendríamos que asumir la carga de mantenimiento de una pila HTTP diferente que sea más receptiva a este tipo de cambio. Esta parte, sospecho, comúnmente la pasan por alto aquellos que desean tener una función de este tipo: el costo de implementarla es alto, pero eso es _nada_ en comparación con los costos continuos de mantenimiento de admitir dicha función en todas las plataformas.

La parte 3 es que la ventaja de dicho parche no está clara. Ha sido mi experiencia que la mayoría de las personas que quieren un parche de tiempo de espera total no están pensando con claridad en lo que quieren. En la mayoría de los casos, los parámetros de tiempo de espera total acaban eliminando solicitudes perfectamente válidas sin ningún motivo.

Por ejemplo, suponga que ha diseñado un fragmento de código que descarga archivos y le gustaría manejar los bloqueos. Si bien inicialmente es tentador querer establecer un tiempo de espera total plano ("¡ninguna solicitud puede demorar más de 30 segundos!"), ese tiempo de espera no tiene sentido. Por ejemplo, si un archivo cambia de un tamaño de 30 MB a 30 GB, dicho archivo _nunca_ puede descargarse en ese tipo de intervalo de tiempo, aunque la descarga sea completamente saludable.

Dicho de otra manera, los tiempos de espera totales son una molestia atractiva: parecen resolver un problema, pero no lo hacen de manera efectiva. Un enfoque más útil, en mi opinión, es aprovechar el tiempo de espera de acción por socket, combinado con stream=True y iter_content , y asignarse tiempos de espera para fragmentos de datos. De la forma en que funciona iter_content , el flujo de control volverá a su código en un intervalo algo regular. Eso significa que puede establecer tiempos de espera de nivel de socket (por ejemplo, 5 s) y luego iter_content en fragmentos bastante pequeños (por ejemplo, 1 KB de datos) y estar relativamente seguro de que, a menos que esté siendo atacado activamente, no habrá denegación de servicio. es posible aquí. Si está realmente preocupado por la denegación de servicio, configure el tiempo de espera de nivel de socket mucho más bajo y el tamaño de su fragmento más pequeño (0,5 s y 512 bytes) para asegurarse de que se le devuelva el control del flujo con regularidad.

El resultado de todo esto es que creo que los tiempos de espera totales son una característica incorrecta en una biblioteca como esta. El mejor tipo de tiempo de espera es el que está ajustado para permitir que las respuestas grandes tengan suficiente tiempo para descargarse en paz, y dicho tiempo de espera se atiende mejor con tiempos de espera de nivel de socket y iter_content .

Todos 38 comentarios

Hola @emgerner-msft,

Como referencia, las siguientes son todas las variaciones de este tema, si no esta solicitud de función exacta:

También hemos discutido esto en https://github.com/sigmavirus24/requests-toolbelt/issues/51

Notará que el último enlace analiza este paquete que debería manejar esto por usted sin agregarlo a las solicitudes. La realidad es que no hay necesidad de solicitudes para hacer esto cuando otro paquete ya lo hace muy bien.

El paquete al que hace referencia lo hace bifurcando un proceso separado para ejecutar la solicitud web. Esa es una forma muy pesada de lograr el objetivo simple de un tiempo de espera y, en mi opinión, no es un sustituto de las solicitudes en sí mismas que tienen una función de tiempo de espera nativo.

@jribbens Si puede encontrar una forma que no use hilos ni procesos, sería increíble. Hasta entonces, si desea un tiempo de espera de reloj de pared, su mejor opción es ese paquete, ya que es la forma más confiable de lograrlo en este momento.

No creo que @jribbens diga que no hay hilos ni procesos. Solo que un proceso _por_ solicitud web es excesivo. Muchos idiomas tienen una forma de que varios temporizadores compartan un único hilo o proceso adicional. Simplemente no sé cómo hacerlo mejor en Python.

Parece que #1928 tiene la mayor discusión sobre alternativas, pero la mayoría viene con muchas advertencias (esto no funcionará para su caso de uso, etc.). Estoy bien con tener un código personalizado en mi biblioteca y escribir mi propia solución personalizada si esto realmente no pertenece a las solicitudes, pero creo que necesito un poco más de información sobre cómo se vería. La razón principal por la que usamos solicitudes es para alejarnos de la lógica de agrupación de conexiones TCP de bajo nivel, pero parece que al leer ese hilo para escribir este código personalizado necesito saber esa lógica, y eso es con lo que tengo algunos problemas. .

@emgerner-msft es correcto. Estoy un poco confundido por el comentario de @ sigmavirus24 , tener un "tiempo de espera total" sin usar subprocesos o procesos parece bastante pedestre y nada "sorprendente". Simplemente calcule la fecha límite al comienzo de todo el proceso (por ejemplo deadline = time.time() + total_timeout ) y luego, en cualquier operación individual, configure el tiempo de espera en deadline - time.time() .

tener un "tiempo de espera total" sin usar subprocesos o procesos parece bastante pedestre y nada "sorprendente".

Y su solución es bastante primitiva. La razón por la que _la mayoría_ de la gente quiere un tiempo de espera total (o de reloj de pared) es para evitar que una lectura se "cuelgue", en otras palabras, un caso como el siguiente:

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

Donde cada lectura toma mucho tiempo en medio de iter_content pero es menor que el tiempo de espera de lectura (supongo que aplicamos eso cuando se transmite, pero aún puede ser el caso que no lo hagamos) especificaron . Ciertamente, parecería que esto debería ser manejado simplemente por su solución @jribbens hasta que recuerde cómo funcionan los relojes y el horario de verano y los time.time() son lamentablemente insuficientes.

Finalmente, es importante tener en cuenta que la API de Solicitudes está congelada. No existe una API buena o coherente para especificar un tiempo de espera total. Y si implementáramos un tiempo de espera como usted sugiere, tendríamos innumerables errores que especificaron un tiempo de espera total de un minuto, pero tomó más tiempo porque la última vez que verificamos estábamos en menos de un minuto, pero su tiempo de espera de lectura configurado fue lo suficientemente largo como para que su tiempo de espera El error se planteó alrededor de un minuto y medio. Ese es un tiempo de espera de pared _muy_ tosco que sería un poco mejor para las personas que buscan esto, pero no diferente de la persona que lo implementa.

Disculpe si no estaba claro @sigmavirus24 , parece que ha criticado mi ilustración de principio de pseudocódigo como si pensara que era un parche literal. Sin embargo, debo señalar que time.time() no funciona de la manera que aparentemente piensa: el horario de verano no es relevante, y tampoco lo es el sesgo del reloj en las escalas de tiempo de las que estamos hablando aquí. También ha entendido mal la sugerencia si cree que ocurriría el error que describe. Finalmente, no estoy seguro de lo que quiere decir con que la API de solicitudes está "congelada", ya que la API se cambió recientemente en la versión 2.9.0, por lo que claramente lo que quiera decir no es lo que normalmente entendería por la palabra.

Solo para separar mi discusión: en realidad no estoy argumentando que esto sea fácil. Si fuera totalmente simple, simplemente lo escribiría y dejaría de molestarte. :)

Mis problemas son:
1) Todo en los hilos que enumeraste eran parches de mono. Eso está bien, pero estoy usando esto en una biblioteca de calidad de producción y no puedo aceptar la advertencia de que los cambios internos lo rompen todo.
2) El decorador de tiempo de espera en el enlace que proporcionó es excelente, pero no tengo claro cómo afecta eso a la conexión. Incluso si aceptamos que la única buena manera de hacer tiempos de espera es con un montón de subprocesos, ¿cómo esta biblioteca hace cumplir que el socket se apague, la conexión se interrumpa, etc. Estamos haciendo muchas conexiones y esto parece potencialmente bastante propenso a fugas. Las solicitudes no tienen un método de 'abortar' que pueda encontrar (corríjame si me equivoco), entonces, ¿cómo está ocurriendo el cierre de la conexión?

Todo lo que busco es una versión clara y 'bendecida' de cómo resolver este problema por mi cuenta, o si no hay una solución perfecta, un par de soluciones con las advertencias discutidas. ¿Tiene sentido?

@ emgerner-msft Suponiendo que está utilizando CPython, el cierre de la conexión se producirá cuando la solicitud ya no continúe. En ese momento, se perderán todas las referencias a la conexión subyacente y el socket se cerrará y se desechará.

@Lukasa Bien, gracias! ¿Cómo determina la biblioteca que la solicitud ya no continúa? Por ejemplo, si utilicé la ruta del decorador de tiempo de espera y corté en medio de la descarga, ¿cuándo se detendría realmente la descarga? ¿Necesito hacer algo especial con las opciones de transmisión?

Si usa el decorador de tiempo de espera, la descarga se detendrá cuando se dispare el tiempo de espera. Esto se debe a que las señales interrumpen las llamadas al sistema, lo que significa que no habrá más llamadas al socket. Una vez que la solicitud ya no está dentro del alcance (por ejemplo, la pila se ha desenrollado fuera de su función requests.* ), eso es: CPython limpiará el objeto de conexión y desarmará la conexión. No se requieren opciones especiales de transmisión allí.

Perfecto. Estoy bien para cerrar el hilo entonces, a menos que otros tengan más que decir.

En realidad, lo siento, una preocupación más. Estaba mirando el código del decorador de tiempo de espera más de cerca ya que dijiste que usa señales era relevante, a diferencia de algo como Python Timers (presumiblemente). Parece que llama a Signal con SIGALRM , que está documentado en Python Signal para no funcionar en Windows. Necesito que esto funcione en entornos Unix y Windows, así como en Python 2.7 y 3.3+ (al igual que las solicitudes en sí). Investigaré un poco más y veré si esto realmente funcionará dado eso.

@ emgerner-msft Eso es frustrante. =(

@Lukasa Sí, probé el fragmento de código de uso básico y no funciona en Windows. Leí un poco más del código/ejemplos y jugueteé y parece que si no usamos señales, el paquete podría funcionar, pero todo tiene que ser seleccionable, lo cual no es el caso de mi aplicación. Entonces, por lo que puedo decir, el decorador de tiempo de espera no resolverá mi problema. ¿Alguna otra idea?

@emgerner-msft ¿Está seguro de que ninguna de las señales específicas de Windows es adecuada?

@Lukasa Para ser franco, simplemente no lo sé. No he usado señales antes, y al igual que no me di cuenta hasta que me dijiste que interrumpirían la solicitud, no estoy seguro de qué es apropiado. Tampoco estoy tratando de hacer que esto funcione solo en Windows. Necesito soporte completo para plataformas cruzadas (Windows y Unix) y soporte para Python 2 y Python 3. Gran parte de las señales parece específica de la plataforma que me arroja. Timer era una de las soluciones que estaba buscando que parecía de nivel menos bajo y, por lo tanto, podría solucionar mis limitaciones, pero no estoy seguro de cómo podría cerrar la conexión. Puedo leer más, pero es por eso que esperaba obtener orientación adicional de ustedes. :)

Así que este es un lugar realmente complicado para estar.

La realidad es que no hay más o menos una forma multiplataforma de matar un hilo, excepto interrumpiéndolo, que es básicamente lo que es una señal. Eso significa, creo, que las señales son la única ruta que realmente tienes para hacer que esto funcione en todas las plataformas. Me inclino a tratar de hacer ping a un experto en Windowsy Pythony: @brettcannon , ¿tiene alguna buena sugerencia aquí?

Por interés, ¿hay alguna razón para no implementar el "tiempo de espera total" en las solicitudes que no sea implementar y probar que requiere trabajo? Quiero decir, si hoy apareciera un parche para implementarlo mágicamente, ¿en teoría sería rechazado o aceptado? Aprecio y estoy de acuerdo con el punto de vista de "eliminar la complejidad innecesaria", pero "puede hacerlo bifurcando un proceso separado" no hace que esta característica sea innecesaria en mi opinión.

@jribbens Hay algunos problemas con esto.

La parte 1 es que la complejidad de dicho parche es muy alta. Para que se comporte correctamente, debe cambiar repetidamente los tiempos de espera en el nivel de socket. Esto significa que el parche debe pasar de forma generalizada a través de httplib, que ya hemos parcheado más de lo que nos gustaría. Esencialmente, necesitaríamos acceder a httplib y volver a implementar alrededor del 50 % de sus métodos más complejos para lograr este cambio funcional.

La parte 2 es que el mantenimiento de dicho parche es relativamente oneroso. Es probable que necesitemos comenzar a mantener lo que equivale a una bifurcación paralela de httplib (más correctamente http.client en este momento) para poder hacerlo con éxito. Alternativamente, tendríamos que asumir la carga de mantenimiento de una pila HTTP diferente que sea más receptiva a este tipo de cambio. Esta parte, sospecho, comúnmente la pasan por alto aquellos que desean tener una función de este tipo: el costo de implementarla es alto, pero eso es _nada_ en comparación con los costos continuos de mantenimiento de admitir dicha función en todas las plataformas.

La parte 3 es que la ventaja de dicho parche no está clara. Ha sido mi experiencia que la mayoría de las personas que quieren un parche de tiempo de espera total no están pensando con claridad en lo que quieren. En la mayoría de los casos, los parámetros de tiempo de espera total acaban eliminando solicitudes perfectamente válidas sin ningún motivo.

Por ejemplo, suponga que ha diseñado un fragmento de código que descarga archivos y le gustaría manejar los bloqueos. Si bien inicialmente es tentador querer establecer un tiempo de espera total plano ("¡ninguna solicitud puede demorar más de 30 segundos!"), ese tiempo de espera no tiene sentido. Por ejemplo, si un archivo cambia de un tamaño de 30 MB a 30 GB, dicho archivo _nunca_ puede descargarse en ese tipo de intervalo de tiempo, aunque la descarga sea completamente saludable.

Dicho de otra manera, los tiempos de espera totales son una molestia atractiva: parecen resolver un problema, pero no lo hacen de manera efectiva. Un enfoque más útil, en mi opinión, es aprovechar el tiempo de espera de acción por socket, combinado con stream=True y iter_content , y asignarse tiempos de espera para fragmentos de datos. De la forma en que funciona iter_content , el flujo de control volverá a su código en un intervalo algo regular. Eso significa que puede establecer tiempos de espera de nivel de socket (por ejemplo, 5 s) y luego iter_content en fragmentos bastante pequeños (por ejemplo, 1 KB de datos) y estar relativamente seguro de que, a menos que esté siendo atacado activamente, no habrá denegación de servicio. es posible aquí. Si está realmente preocupado por la denegación de servicio, configure el tiempo de espera de nivel de socket mucho más bajo y el tamaño de su fragmento más pequeño (0,5 s y 512 bytes) para asegurarse de que se le devuelva el control del flujo con regularidad.

El resultado de todo esto es que creo que los tiempos de espera totales son una característica incorrecta en una biblioteca como esta. El mejor tipo de tiempo de espera es el que está ajustado para permitir que las respuestas grandes tengan suficiente tiempo para descargarse en paz, y dicho tiempo de espera se atiende mejor con tiempos de espera de nivel de socket y iter_content .

Tal vez @zooba tenga una idea, ya que realmente sabe cómo funciona Windows. :)

(Sin relación, una de mis actividades favoritas es configurar una cadena de expertos en un problema de GitHub).

Jaja, ya conozco a @zooba y @brettcannon. Puedo discutir con ellos aquí o internamente, ya que una solución a esto probablemente también los ayudaría.

@emgerner-msft Pensé que podrías, pero no quería suponer: ¡MSFT es una gran organización!

@Lukasa Solo leyendo a través de la pared de texto que acaba de escribir arriba, ¡interesante! Sobre la discusión de stream=True e iter_content para programar descargas, ¿cuál es la forma equivalente de manejar cargas más grandes?

_PS_: El párrafo anterior que comienza con 'Expuesto de otra manera...' es el tipo de guía que busqué en los documentos. Dada la cantidad de solicitudes que recibe para el tiempo de espera máximo (y sus razones válidas para no hacerlo), tal vez lo mejor que puede hacer es agregar parte de esa información en los documentos de tiempo de espera.

lol @lukasa Tomo tu punto sobre el mantenimiento, que ya estaba en mi mente, pero en "característica vs falla" me temo que soy completamente opuesto a ti. Creo que cualquiera que _no_ quiere un tiempo de espera total no está pensando claramente en lo que quiere, y tengo dificultades para imaginar una situación en la que lo que describe como un error "la descarga de 30 MB cambia a 30 GB y, por lo tanto, falla" no lo es de hecho, una característica beneficiosa!

Puede, como dice, hacer algo un poco similar (pero sospecho que sin la mayoría de los beneficios de un tiempo de espera total) usando stream=True pero pensé que el punto de las solicitudes era que manejaba las cosas por usted...

Pensé que el objetivo de las solicitudes era que manejaba las cosas por ti.

Maneja HTTP por usted. Los hechos de que ya manejamos los tiempos de espera de conexión y lectura y que hemos tenido un par de exenciones a nuestra congelación de funciones de varios años son tangenciales a la discusión de utilidad, conveniencia, consistencia (en múltiples plataformas) y mantenibilidad. Agradecemos sus comentarios y su opinión. Si tiene nueva información para presentar, se lo agradeceríamos.

También puede ser revelador que las solicitudes no manejen todo, por la cantidad de solicitudes de características rechazadas en este proyecto y el hecho de que hay un proyecto separado que implementa patrones de uso comunes para los usuarios (el cinturón de herramientas de solicitudes). Si un tiempo de espera total pertenece a algún lugar, estaría allí, pero nuevamente, tendría que funcionar en Windows, BSD, Linux y OSX con una excelente cobertura de prueba y sin que sea una pesadilla mantenerlo.

Sobre la discusión de stream=True e iter_content para programar descargas, ¿cuál es la forma equivalente de manejar cargas más grandes?

Defina un generador para su carga y páselo a data . O, si la codificación fragmentada no es una opción ganadora para usted, defina un objeto similar a un archivo con un método mágico read y pase _eso_ a data .

Permítanme elaborar un poco. Si pasa un generador a data , las solicitudes iterarán sobre él y enviarán cada fragmento a su vez. Esto significa que para enviar datos, necesariamente tendremos que entregar el flujo de control a su código para cada fragmento. Esto le permite hacer lo que quiera en ese momento, incluso lanzar excepciones para abortar la solicitud por completo.

Si por alguna razón no puede usar la codificación de transferencia fragmentada para sus cargas (poco probable, pero posible si el servidor en cuestión es realmente malo), puede hacer lo mismo creando un objeto similar a un archivo que tenga una longitud y luego haciendo su magic en la llamada read , que se llamará repetidamente para fragmentos de 8192 bytes. Nuevamente, esto asegura que el flujo de control pase por su código de manera intermitente, lo que le permite usar su propia lógica.

PD: El párrafo anterior que comienza con 'Put another way,...' es el tipo de orientación que busqué en los documentos. Dada la cantidad de solicitudes que recibe para el tiempo de espera máximo (y sus razones válidas para no hacerlo), tal vez lo mejor que puede hacer es agregar parte de esa información en los documentos de tiempo de espera.

Supongo_. En términos generales, sin embargo, siempre estoy nervioso por poner un texto algo defensivo en la documentación. Supongo que podría incluirse en una sección de preguntas frecuentes, pero el texto que explica por qué _no_ tenemos algo rara vez es útil en la documentación. El espacio en los documentos estaría mejor servido, sospecho, por una receta para hacer algo.

Creo que cualquiera que no quiera un tiempo de espera total no está pensando claramente en lo que quiere, y tengo dificultades para imaginar una situación en la que lo que describe como un error "La descarga de 30 MB cambia a 30 GB y, por lo tanto, falla" no lo es. de hecho, una característica beneficiosa!

Je, no soy:

  • administrador de paquetes (por ejemplo, pip, que usa solicitudes), donde los paquetes pueden variar enormemente en el tamaño de los datos
  • raspador web, que puede ejecutarse en varios sitios que varían enormemente en tamaño
  • un agregador de registro que descarga archivos de registro de hosts que tienen niveles muy diferentes de nosotros (y, por lo tanto, tamaños de archivo de registro)
  • descargador de videos (los videos pueden variar enormemente en tamaño)

En realidad, creo que el caso en que el desarrollador sabe dentro de un orden de magnitud con qué tamaño de archivo se enfrentará es el caso poco común. En la mayoría de los casos, los desarrolladores no tienen idea. Y, en general, diría que hacer suposiciones sobre esos tamaños es imprudente. Si tiene limitaciones en cuanto al tamaño de la descarga, su código debería codificar deliberadamente esas suposiciones (por ejemplo, en forma de comprobaciones de la longitud del contenido), en lugar de codificarlas implícitamente y mezclarlas con el ancho de banda de la red del usuario para que otras personas lean el contenido. el código puede verlos claramente.

pero pensé que el punto de las solicitudes era que manejaba las cosas por ti...

Las solicitudes muy deliberadamente no manejan todo para los usuarios. Tratar de hacer todo es una tarea imposible, y es imposible crear una buena biblioteca que haga eso. Regularmente les decimos a los usuarios que se desplacen a urllib3 para lograr algo.

Solo ponemos código en las solicitudes si podemos hacerlo mejor o más limpio que la mayoría de los usuarios. Si no, no hay valor. Todavía no estoy convencido de que el tiempo de espera total sea una de esas cosas, especialmente dado lo que percibo como una utilidad relativamente marginal cuando se agrega a nuestra base de usuarios.

Dicho esto, estoy abierto a que me convenzan de que estoy equivocado: simplemente no he visto un argumento convincente para ello todavía (y, para evitarlo, "¡Lo necesito!" no es un argumento convincente: tengo que dar algunas razones!).

@sigmavirus24

Si un tiempo de espera total pertenece a algún lugar, estaría allí, pero nuevamente, tendría que funcionar en Windows, BSD, Linux y OSX con una excelente cobertura de prueba y sin que sea una pesadilla mantenerlo.

¡Acordado!

@lukasa Supongo que mi pensamiento es que no solo lo quiero, de hecho, casi todos los usuarios lo querrían si lo pensaran (o no se dan cuenta de que aún no está allí). La mitad de los escenarios de uso anteriores donde dice que debe evitarse, diría que es vital (raspador web y agregador de registros); los otros dos son menos necesarios ya que es probable que haya un usuario esperando el resultado que puede cancelar la descarga manualmente si ellos quieren. ¡Cualquier cosa que se ejecute en segundo plano sin una interfaz de usuario y no use un tiempo de espera general tiene errores en mi opinión!

Supongo que mi pensamiento es que no solo lo quiero, de hecho, casi todos los usuarios lo querrían si pensaran en ello (o no se dan cuenta de que aún no está allí).

@jribbens tenemos varios años (más de una década si combina las experiencias de los tres) de hablar y comprender las necesidades de nuestros usuarios. Lo que ha sido necesario para casi todos (al menos el 98%) de los usuarios ha sido tiempos de espera de conexión y lectura. Entendemos que una minoría muy vocal de nuestros usuarios quiere un tiempo de espera general. Teniendo en cuenta lo que podemos extrapolar para que sea el tamaño del grupo de usuarios potenciales para esa función frente al tamaño potencial de los usuarios que no necesitan esa función y la complejidad del mantenimiento y desarrollo de la función, en realidad no es algo que vayamos a hacer. hacer.

Si tiene algo _nuevo_ para compartir, nos gustaría escucharlo, pero todo lo que ha dicho hasta ahora es que, en su opinión, cualquier cosa que use solicitudes sin un tiempo de espera general tiene errores y me imagino que hay muchos usuarios que se ofendería por su afirmación de que sus decisiones de diseño tienen errores. Por lo tanto, absténgase de insultar la inteligencia de nuestros usuarios.

@sigmavirus24 A lo largo de este hilo ha sido innecesariamente condescendiente, incendiario y grosero, y le pido cortésmente que deje de hacerlo.

@Lukasa Revisé en detalle sus sugerencias sobre cómo cargar y descargar transmisiones y leí los documentos sobre estos temas. Si pudiera validar mis suposiciones/preguntas, sería genial.

  1. Para descargas de transmisión, si uso algo como un tiempo de espera de lectura '(por ejemplo, 5 segundos) y luego iter_content en fragmentos bastante pequeños (por ejemplo, 1 KB de datos)', eso significa que la biblioteca de solicitudes aplicará el tiempo de espera de 5 segundos para cada lectura de 1 KB y el tiempo de espera si tarda más de 5s. ¿Correcto?
  2. Para las cargas de transmisión, si utilizo un generador o un objeto similar a un archivo que devuelve fragmentos de datos y configuro el tiempo de espera de lectura en 5 s, la biblioteca de solicitudes aplicará el tiempo de espera de 5 s para cada fragmento que devuelva y el tiempo de espera si lleva más tiempo. ¿Correcto?
  3. Si no uso un generador para cargar y simplemente paso bytes directamente, ¿cómo decide la biblioteca de solicitudes aplicar el tiempo de espera de lectura que configuré? Por ejemplo, si paso un fragmento de 4 MB y un tiempo de espera de lectura de 5 s, ¿cuándo se aplica exactamente ese tiempo de espera de lectura?
  4. Si no uso iter_content y simplemente hago que las solicitudes descarguen todo el contenido directamente en la solicitud con un tiempo de espera de lectura de 5 segundos, ¿cuándo se aplica exactamente ese tiempo de espera de lectura?

Tengo una comprensión general de los sockets/protocolo TCP/etc, pero no exactamente cómo funciona urllib con estos conceptos en un nivel inferior o si las solicitudes hacen algo especial además de pasar los valores. Quiero entender exactamente cómo se aplican los tiempos de espera, ya que simplemente recuperar el flujo de control y aplicar mi propio esquema de tiempo de espera no funciona debido a los problemas cruzados al terminar el hilo. Si hay material de lectura adicional para responder a mis preguntas, ¡no dude en recomendarme! En cualquier caso, espero que esta sea mi última serie de preguntas. :)

Gracias por su ayuda hasta ahora.

@emgerner-msft Vale:

  1. No. Lamentablemente, es más complejo que eso. Como se discutió, cada tiempo de espera se aplica _por llamada de socket_, pero no podemos garantizar cuántas llamadas de socket hay en un fragmento determinado. La razón bastante compleja de esto es que la biblioteca estándar envuelve el zócalo de respaldo en un objeto de búfer (generalmente algo como io.BufferedReader ). Eso hará tantas llamadas recv_into como sea necesario hasta que haya proporcionado suficientes datos. Eso puede ser tan pequeño como cero (si ya hay suficientes datos en el búfer) o tanto como exactamente la cantidad de bytes que ha recibido si el par remoto le está alimentando por goteo un byte a la vez. Realmente hay muy poco que podamos hacer al respecto: debido a la naturaleza de una llamada de read() contra un objeto almacenado en búfer, ni siquiera recuperamos el flujo de control entre cada llamada recv_into .

Eso significa que la _única_ forma de garantizar que no obtenga más de una espera de n segundos es hacer iter_content con un tamaño de porción de 1 . Esa es una forma absurdamente ineficiente de descargar un archivo (pasa demasiado tiempo en el código de Python), pero es la única forma de obtener la garantía que desea.

  1. También creo que la respuesta a eso es no. Actualmente no tenemos noción de un tiempo de espera de _send_. La forma de obtener uno es usar socket.setdefaulttimeout .
  2. Los tiempos de espera de lectura se aplican solo a las lecturas, por lo que no importa cómo pase el cuerpo.
  3. Ese tiempo de espera de lectura sufre las mismas preocupaciones que el caso iter_content : si tiene solicitudes para descargar todo, terminaremos emitiendo tantas llamadas recv_into como sea necesario para descargar el cuerpo, y se aplica el tiempo de espera a cada uno por turno.

Te estás topando con el problema central aquí: las solicitudes simplemente no se acercan lo suficiente al socket para lograr exactamente lo que estás buscando. _podríamos_ agregar un tiempo de espera de envío: es un trabajo de solicitud de función que se está considerando, y no sufre los mismos problemas que el tiempo de espera de lectura, pero para todo lo demás estamos atascados porque httplib insiste (correctamente) en intercambiar a una representación de socket con búfer, y luego el resto de httplib usa esa representación con búfer.

@Lukasa

Ah, qué lío, jaja. Pensé que podría ser el caso, pero realmente esperaba estar equivocado.

Primero, necesitamos desesperadamente un tiempo de espera de envío. Simplemente no puedo decirles a mis usuarios que sus cargas pueden bloquearse infinitamente y que no tenemos un plan para solucionar el problema. :/

Parece que estoy en una situación imposible en este momento. No hay soporte de biblioteca para el tiempo de espera total (que entiendo). No hay garantías sobre cómo funciona exactamente el tiempo de espera existente con varios tamaños de fragmento; si lo hubiera, podría resumir el tiempo: tiempo de espera de conexión + tiempo de espera de lectura * tamaño de fragmento. Poder interrumpir el flujo con el modo de transmisión y los generadores es bueno, pero como no tengo una solución para abortar los subprocesos de manera multiplataforma, esto tampoco ayuda. ¿Ves otras opciones para seguir adelante? ¿Qué están haciendo otros usuarios para resolver estos problemas?

Primero, necesitamos desesperadamente un tiempo de espera de envío. Simplemente no puedo decirles a mis usuarios que sus cargas pueden bloquearse infinitamente y que no tenemos un plan para solucionar el problema. :/

Por lo tanto, la lógica de tiempo de espera utilizada en las solicitudes es fundamentalmente la de urllib3, por lo que debería ser suficiente para realizar el cambio allí: siéntase libre de abrir una solicitud de función y podemos ayudarlo con el cambio. Y a corto plazo, siéntete libre de investigar usando setdefaulttimeout .

¿Ves otras opciones para seguir adelante? ¿Qué están haciendo otros usuarios para resolver estos problemas?

Las opciones que tiene aquí dependen de sus limitaciones específicas.

Si _debe_ tener un tiempo de espera determinista (es decir, si debe poder garantizar que una solicitud no demorará más de _n_ segundos), entonces no puede hacerlo fácilmente con la biblioteca estándar de Python tal como existe hoy. En Python 2.7 necesitaría parchear socket._fileobject para permitirle ejecutar un tiempo de espera secuencial para cada llamada a recv , pero en Python 3 es aún más difícil porque necesita parchear una clase cuya implementación está en C ( io.BufferedReader ), que va a ser una pesadilla.

De lo contrario, la única forma de obtenerlo es desactivar el almacenamiento en búfer en la biblioteca estándar. Eso romperá httplib y todos nuestros parches encima, lo que supone que podemos hacer una llamada read(x) que no se comportará como la llamada al sistema read en un socket, sino como read syscall en un archivo (es decir, devuelve una longitud determinista).

Dicho de otra manera: si _necesita_ un tiempo de espera determinista, encontrará que una gran cantidad de bibliotecas simplemente no pueden proporcionarlo. Básicamente, si usan httplib o socket.makefile entonces no tendrá suerte: simplemente no hay una forma clara de garantizar que el control regrese a usted en un tiempo definido, excepto por la emisión repetida de longitud. -1 lee. Puedes hacer eso, pero perjudicará tu desempeño.

Entonces, aquí tiene una compensación: si desea un tiempo de espera determinista, la forma en que se implementa el almacenamiento en búfer en la biblioteca estándar de Python (y, por lo tanto, en las solicitudes) simplemente no lo pondrá a su disposición. Puede recuperar eso deshabilitando el almacenamiento en búfer y reescribiendo el código, pero eso perjudica potencialmente su rendimiento a menos que vuelva a implementar el almacenamiento en búfer de una manera que reconozca los tiempos de espera.

Podría intentar implementar el código requerido en la biblioteca estándar de Python en la clase BufferedReader : definitivamente puede preguntar a la gente de Python si están interesados. Pero no aguantaría la respiración.

Por lo tanto, la lógica de tiempo de espera utilizada en las solicitudes es fundamentalmente la de urllib3, por lo que debería ser suficiente para realizar el cambio allí: siéntase libre de abrir una solicitud de función y podemos ayudarlo con el cambio. Y a corto plazo, siéntase libre de investigar usando setdefaulttimeout.

¿Solicitud de función en urllib3 o aquí? Abrirá uno (o ambos) lo antes posible.

Solicitud de función en urllib3: no necesitamos exponer nada nuevo en las solicitudes.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

justlurking picture justlurking  ·  3Comentarios

eromoe picture eromoe  ·  3Comentarios

iLaus picture iLaus  ·  3Comentarios

jake491 picture jake491  ·  3Comentarios

ReimarBauer picture ReimarBauer  ·  4Comentarios