From: Tom Christie Date: Wed, 24 Apr 2019 10:42:45 +0000 (+0100) Subject: Close outstanding connections on pool.close() X-Git-Tag: 0.2.1^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=799df23dde030ed3f638fc593d6be9a665e37918;p=thirdparty%2Fhttpx.git Close outstanding connections on pool.close() --- diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..55549909 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +sudo: required +dist: xenial +language: python + +cache: pip + +python: + - "3.6" + - "3.7" + +install: + - pip install -U -r requirements.txt + +script: + - scripts/test + +after_script: + - pip install codecov + - codecov diff --git a/httpcore/__init__.py b/httpcore/__init__.py index c6851003..9824e854 100644 --- a/httpcore/__init__.py +++ b/httpcore/__init__.py @@ -2,9 +2,9 @@ from .config import PoolLimits, SSLConfig, TimeoutConfig from .connectionpool import ConnectionPool from .datastructures import URL, Origin, Request, Response from .exceptions import ( - BadResponse, ConnectTimeout, PoolTimeout, + ProtocolError, ReadTimeout, ResponseClosed, StreamConsumed, @@ -13,4 +13,4 @@ from .exceptions import ( from .http11 import HTTP11Connection from .sync import SyncClient, SyncConnectionPool -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/httpcore/compat.py b/httpcore/compat.py deleted file mode 100644 index 35c31d64..00000000 --- a/httpcore/compat.py +++ /dev/null @@ -1,52 +0,0 @@ -import asyncio - -if hasattr(asyncio, "run"): - asyncio_run = asyncio.run - -else: # pragma: nocover - - def asyncio_run(main, *, debug=False): # type: ignore - if asyncio._get_running_loop() is not None: - raise RuntimeError( - "asyncio.run() cannot be called from a running event loop" - ) - - if not asyncio.iscoroutine(main): - raise ValueError("a coroutine was expected, got {!r}".format(main)) - - loop = asyncio.new_event_loop() - try: - asyncio.set_event_loop(loop) - loop.set_debug(debug) - return loop.run_until_complete(main) - finally: - try: - _cancel_all_tasks(loop) - loop.run_until_complete(loop.shutdown_asyncgens()) - finally: - asyncio.set_event_loop(None) - loop.close() - - def _cancel_all_tasks(loop): # type: ignore - to_cancel = asyncio.all_tasks(loop) - if not to_cancel: - return - - for task in to_cancel: - task.cancel() - - loop.run_until_complete( - tasks.gather(*to_cancel, loop=loop, return_exceptions=True) - ) - - for task in to_cancel: - if task.cancelled(): - continue - if task.exception() is not None: - loop.call_exception_handler( - { - "message": "unhandled exception during asyncio.run() shutdown", - "exception": task.exception(), - "task": task, - } - ) diff --git a/httpcore/connectionpool.py b/httpcore/connectionpool.py index d815919b..a2040dbc 100644 --- a/httpcore/connectionpool.py +++ b/httpcore/connectionpool.py @@ -10,9 +10,9 @@ from .config import ( SSLConfig, TimeoutConfig, ) -from .http11 import HTTP11Connection from .datastructures import Client, Origin, Request, Response from .exceptions import PoolTimeout +from .http11 import HTTP11Connection class ConnectionPool(Client): @@ -102,6 +102,12 @@ class ConnectionPool(Client): async def close(self) -> None: self.is_closed = True + all_connections = [] + for connections in self._keepalive_connections.values(): + all_connections.extend(list(connections)) + self._keepalive_connections.clear() + for connection in all_connections: + await connection.close() class ConnectionSemaphore: diff --git a/httpcore/exceptions.py b/httpcore/exceptions.py index 69892afe..30814332 100644 --- a/httpcore/exceptions.py +++ b/httpcore/exceptions.py @@ -22,9 +22,9 @@ class PoolTimeout(Timeout): """ -class BadResponse(Exception): +class ProtocolError(Exception): """ - A malformed HTTP response. + Malformed HTTP. """ diff --git a/httpcore/http11.py b/httpcore/http11.py index 23cc27ce..7c38c603 100644 --- a/httpcore/http11.py +++ b/httpcore/http11.py @@ -161,3 +161,4 @@ class HTTP11Connection(Client): if self._writer is not None: self._writer.close() + await self._writer.wait_closed() diff --git a/httpcore/sync.py b/httpcore/sync.py index 8ca8b189..1d560faf 100644 --- a/httpcore/sync.py +++ b/httpcore/sync.py @@ -1,15 +1,16 @@ +import asyncio import typing from types import TracebackType -from .compat import asyncio_run from .config import SSLConfig, TimeoutConfig from .connectionpool import ConnectionPool from .datastructures import URL, Client, Response class SyncResponse: - def __init__(self, response: Response): + def __init__(self, response: Response, loop: asyncio.AbstractEventLoop): self._response = response + self._loop = loop @property def status_code(self) -> int: @@ -28,23 +29,24 @@ class SyncResponse: return self._response.body def read(self) -> bytes: - return asyncio_run(self._response.read()) + return self._loop.run_until_complete(self._response.read()) def stream(self) -> typing.Iterator[bytes]: inner = self._response.stream() while True: try: - yield asyncio_run(inner.__anext__()) + yield self._loop.run_until_complete(inner.__anext__()) except StopAsyncIteration as exc: break def close(self) -> None: - return asyncio_run(self._response.close()) + return self._loop.run_until_complete(self._response.close()) class SyncClient: def __init__(self, client: Client): self._client = client + self._loop = asyncio.new_event_loop() def request( self, @@ -57,7 +59,7 @@ class SyncClient: timeout: typing.Optional[TimeoutConfig] = None, stream: bool = False, ) -> SyncResponse: - response = asyncio_run( + response = self._loop.run_until_complete( self._client.request( method, url, @@ -68,10 +70,10 @@ class SyncClient: stream=stream, ) ) - return SyncResponse(response) + return SyncResponse(response, self._loop) def close(self) -> None: - asyncio_run(self._client.close()) + self._loop.run_until_complete(self._client.close()) def __enter__(self) -> "SyncClient": return self diff --git a/requirements.txt b/requirements.txt index 563558e9..5108a8d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,6 @@ brotlipy # Testing autoflake black -codecov isort mypy pytest