mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-10-11 18:08:02 +00:00
Fix P100 errors on multi-requests (#930)
Fixes an issue reported by @bdraco with the P100 not working in the latest branch: `[Errno None] Can not write request body for HOST_REDACTED, ClientOSError(None, 'Can not write request body for URL_REDACTED'))` Issue caused by the number of multi requests going above the default batch of 5 and the P100 not being able to handle the second multi request happening immediately as it closes the connection after each query (See https://github.com/python-kasa/python-kasa/pull/690 for similar issue). This introduces a small wait time on concurrent requests once the device has raised a ClientOSError.
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
|
||||
import aiohttp
|
||||
@@ -28,12 +29,20 @@ def get_cookie_jar() -> aiohttp.CookieJar:
|
||||
class HttpClient:
|
||||
"""HttpClient Class."""
|
||||
|
||||
# Some devices (only P100 so far) close the http connection after each request
|
||||
# and aiohttp doesn't seem to handle it. If a Client OS error is received the
|
||||
# http client will start ensuring that sequential requests have a wait delay.
|
||||
WAIT_BETWEEN_REQUESTS_ON_OSERROR = 0.25
|
||||
|
||||
def __init__(self, config: DeviceConfig) -> None:
|
||||
self._config = config
|
||||
self._client_session: aiohttp.ClientSession = None
|
||||
self._jar = aiohttp.CookieJar(unsafe=True, quote_cookie=False)
|
||||
self._last_url = URL(f"http://{self._config.host}/")
|
||||
|
||||
self._wait_between_requests = 0.0
|
||||
self._last_request_time = 0.0
|
||||
|
||||
@property
|
||||
def client(self) -> aiohttp.ClientSession:
|
||||
"""Return the underlying http client."""
|
||||
@@ -60,6 +69,14 @@ class HttpClient:
|
||||
|
||||
If the request is provided via the json parameter json will be returned.
|
||||
"""
|
||||
# Once we know a device needs a wait between sequential queries always wait
|
||||
# first rather than keep erroring then waiting.
|
||||
if self._wait_between_requests:
|
||||
now = time.time()
|
||||
gap = now - self._last_request_time
|
||||
if gap < self._wait_between_requests:
|
||||
await asyncio.sleep(self._wait_between_requests - gap)
|
||||
|
||||
_LOGGER.debug("Posting to %s", url)
|
||||
response_data = None
|
||||
self._last_url = url
|
||||
@@ -89,6 +106,9 @@ class HttpClient:
|
||||
response_data = json_loads(response_data.decode())
|
||||
|
||||
except (aiohttp.ServerDisconnectedError, aiohttp.ClientOSError) as ex:
|
||||
if isinstance(ex, aiohttp.ClientOSError):
|
||||
self._wait_between_requests = self.WAIT_BETWEEN_REQUESTS_ON_OSERROR
|
||||
self._last_request_time = time.time()
|
||||
raise _ConnectionError(
|
||||
f"Device connection error: {self._config.host}: {ex}", ex
|
||||
) from ex
|
||||
@@ -103,6 +123,10 @@ class HttpClient:
|
||||
f"Unable to query the device: {self._config.host}: {ex}", ex
|
||||
) from ex
|
||||
|
||||
# For performance only request system time if waiting is enabled
|
||||
if self._wait_between_requests:
|
||||
self._last_request_time = time.time()
|
||||
|
||||
return resp.status, response_data
|
||||
|
||||
def get_cookie(self, cookie_name: str) -> str | None:
|
||||
|
Reference in New Issue
Block a user