We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Keepalive connections aren't released when closing the ConnectionPool
Hello. I am having an issue where it looks like connections aren't being closed correctly, and after i reach a number of requests equivalent to "hard_limit" of pool_limits, i get a PoolTimeout exception.

I tried upgrading to httpx==0.10.1, with no success.

Minimal example:
```
import httpx, asyncio, logging
from httpx import PoolLimits
from random import randint

queue = asyncio.Queue()

clients = [
	httpx.AsyncClient(
		http2=True,
		pool_limits=PoolLimits(soft_limit=2, hard_limit=10),
		cookies={'a': '123456789', 'b': '987654321'},
	)
]

async def worker_loop(cid, client, queue):
	while 1:
		sub_id = await queue.get()

		async with client as c:
			r = await c.get(f'https://mywebsite.dummy/submission.php?id={sub_id}')

		if r.status_code != 200:
			print(cid, f'Got status code {r.status_code} while parsing {sub_id}')
			return

async def main():
	for i in range(2500):
		await queue.put(randint(1, 80000000))

	for k, v in enumerate(clients):
		asyncio.create_task(worker_loop(k, v, queue))

	while 1:
		if queue.qsize() == 0:
			await queue.put(randint(1, 80000000))
		await asyncio.sleep(2)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.stop()
```


I checked with netstat, and only one actual connection is opened to the IP address, so pooling seems to work fine.
I really cannot understand why. I even tried using the "aclose()" syntax, without the "async with" block, but no difference at all.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `httpx/dispatch/connection_pool.py`
Content:
```
1 import typing
2 
3 from ..backends.base import BaseSemaphore, ConcurrencyBackend, lookup_backend
4 from ..config import (
5     DEFAULT_POOL_LIMITS,
6     CertTypes,
7     PoolLimits,
8     SSLConfig,
9     Timeout,
10     VerifyTypes,
11 )
12 from ..exceptions import PoolTimeout
13 from ..models import Origin, Request, Response
14 from ..utils import get_logger
15 from .base import Dispatcher
16 from .connection import HTTPConnection
17 
18 CONNECTIONS_DICT = typing.Dict[Origin, typing.List[HTTPConnection]]
19 
20 
21 logger = get_logger(__name__)
22 
23 
24 class NullSemaphore(BaseSemaphore):
25     async def acquire(self, timeout: float = None) -> None:
26         return
27 
28     def release(self) -> None:
29         return
30 
31 
32 class ConnectionStore:
33     """
34     We need to maintain collections of connections in a way that allows us to:
35 
36     * Lookup connections by origin.
37     * Iterate over connections by insertion time.
38     * Return the total number of connections.
39     """
40 
41     def __init__(self) -> None:
42         self.all: typing.Dict[HTTPConnection, float] = {}
43         self.by_origin: typing.Dict[Origin, typing.Dict[HTTPConnection, float]] = {}
44 
45     def pop_by_origin(
46         self, origin: Origin, http2_only: bool = False
47     ) -> typing.Optional[HTTPConnection]:
48         try:
49             connections = self.by_origin[origin]
50         except KeyError:
51             return None
52 
53         connection = next(reversed(list(connections.keys())))
54         if http2_only and not connection.is_http2:
55             return None
56 
57         del connections[connection]
58         if not connections:
59             del self.by_origin[origin]
60         del self.all[connection]
61 
62         return connection
63 
64     def add(self, connection: HTTPConnection) -> None:
65         self.all[connection] = 0.0
66         try:
67             self.by_origin[connection.origin][connection] = 0.0
68         except KeyError:
69             self.by_origin[connection.origin] = {connection: 0.0}
70 
71     def remove(self, connection: HTTPConnection) -> None:
72         del self.all[connection]
73         del self.by_origin[connection.origin][connection]
74         if not self.by_origin[connection.origin]:
75             del self.by_origin[connection.origin]
76 
77     def clear(self) -> None:
78         self.all.clear()
79         self.by_origin.clear()
80 
81     def __iter__(self) -> typing.Iterator[HTTPConnection]:
82         return iter(self.all.keys())
83 
84     def __len__(self) -> int:
85         return len(self.all)
86 
87 
88 class ConnectionPool(Dispatcher):
89     KEEP_ALIVE_EXPIRY = 5.0
90 
91     def __init__(
92         self,
93         *,
94         verify: VerifyTypes = True,
95         cert: CertTypes = None,
96         trust_env: bool = None,
97         pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,
98         http2: bool = False,
99         backend: typing.Union[str, ConcurrencyBackend] = "auto",
100         uds: typing.Optional[str] = None,
101     ):
102         self.ssl = SSLConfig(verify=verify, cert=cert, trust_env=trust_env, http2=http2)
103         self.pool_limits = pool_limits
104         self.is_closed = False
105         self.uds = uds
106 
107         self.keepalive_connections = ConnectionStore()
108         self.active_connections = ConnectionStore()
109 
110         self.backend = lookup_backend(backend)
111         self.next_keepalive_check = 0.0
112 
113     @property
114     def max_connections(self) -> BaseSemaphore:
115         # We do this lazily, to make sure backend autodetection always
116         # runs within an async context.
117         if not hasattr(self, "_max_connections"):
118             limit = self.pool_limits.hard_limit
119             if limit:
120                 self._max_connections = self.backend.create_semaphore(
121                     limit, exc_class=PoolTimeout
122                 )
123             else:
124                 self._max_connections = NullSemaphore()
125 
126         return self._max_connections
127 
128     @property
129     def num_connections(self) -> int:
130         return len(self.keepalive_connections) + len(self.active_connections)
131 
132     async def check_keepalive_expiry(self) -> None:
133         now = self.backend.time()
134         if now < self.next_keepalive_check:
135             return
136         self.next_keepalive_check = now + 1.0
137 
138         # Iterate through all the keep alive connections.
139         # We create a list here to avoid any 'changed during iteration' errors.
140         keepalives = list(self.keepalive_connections.all.keys())
141         for connection in keepalives:
142             if connection.expires_at is not None and now > connection.expires_at:
143                 self.keepalive_connections.remove(connection)
144                 self.max_connections.release()
145                 await connection.close()
146 
147     async def send(self, request: Request, timeout: Timeout = None) -> Response:
148         await self.check_keepalive_expiry()
149         connection = await self.acquire_connection(
150             origin=request.url.origin, timeout=timeout
151         )
152         try:
153             response = await connection.send(request, timeout=timeout)
154         except BaseException as exc:
155             self.active_connections.remove(connection)
156             self.max_connections.release()
157             raise exc
158 
159         return response
160 
161     async def acquire_connection(
162         self, origin: Origin, timeout: Timeout = None
163     ) -> HTTPConnection:
164         logger.trace(f"acquire_connection origin={origin!r}")
165         connection = self.pop_connection(origin)
166 
167         if connection is None:
168             pool_timeout = None if timeout is None else timeout.pool_timeout
169 
170             await self.max_connections.acquire(timeout=pool_timeout)
171             connection = HTTPConnection(
172                 origin,
173                 ssl=self.ssl,
174                 backend=self.backend,
175                 release_func=self.release_connection,
176                 uds=self.uds,
177             )
178             logger.trace(f"new_connection connection={connection!r}")
179         else:
180             logger.trace(f"reuse_connection connection={connection!r}")
181 
182         self.active_connections.add(connection)
183 
184         return connection
185 
186     async def release_connection(self, connection: HTTPConnection) -> None:
187         logger.trace(f"release_connection connection={connection!r}")
188         if connection.is_closed:
189             self.active_connections.remove(connection)
190             self.max_connections.release()
191         elif (
192             self.pool_limits.soft_limit is not None
193             and self.num_connections > self.pool_limits.soft_limit
194         ):
195             self.active_connections.remove(connection)
196             self.max_connections.release()
197             await connection.close()
198         else:
199             now = self.backend.time()
200             connection.expires_at = now + self.KEEP_ALIVE_EXPIRY
201             self.active_connections.remove(connection)
202             self.keepalive_connections.add(connection)
203 
204     async def close(self) -> None:
205         self.is_closed = True
206         connections = list(self.keepalive_connections)
207         self.keepalive_connections.clear()
208         for connection in connections:
209             await connection.close()
210 
211     def pop_connection(self, origin: Origin) -> typing.Optional[HTTPConnection]:
212         connection = self.active_connections.pop_by_origin(origin, http2_only=True)
213         if connection is None:
214             connection = self.keepalive_connections.pop_by_origin(origin)
215 
216         if connection is not None and connection.is_connection_dropped():
217             self.max_connections.release()
218             connection = None
219 
220         return connection
221
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/httpx/dispatch/connection_pool.py b/httpx/dispatch/connection_pool.py
--- a/httpx/dispatch/connection_pool.py
+++ b/httpx/dispatch/connection_pool.py
@@ -206,6 +206,7 @@
         connections = list(self.keepalive_connections)
         self.keepalive_connections.clear()
         for connection in connections:
+            self.max_connections.release()
             await connection.close()
 
     def pop_connection(self, origin: Origin) -> typing.Optional[HTTPConnection]:

{"golden_diff": "diff --git a/httpx/dispatch/connection_pool.py b/httpx/dispatch/connection_pool.py\n--- a/httpx/dispatch/connection_pool.py\n+++ b/httpx/dispatch/connection_pool.py\n@@ -206,6 +206,7 @@\n         connections = list(self.keepalive_connections)\n         self.keepalive_connections.clear()\n         for connection in connections:\n+            self.max_connections.release()\n             await connection.close()\n \n     def pop_connection(self, origin: Origin) -> typing.Optional[HTTPConnection]:\n", "issue": "Keepalive connections aren't released when closing the ConnectionPool\nHello. I am having an issue where it looks like connections aren't being closed correctly, and after i reach a number of requests equivalent to \"hard_limit\" of pool_limits, i get a PoolTimeout exception.\r\n\r\nI tried upgrading to httpx==0.10.1, with no success.\r\n\r\nMinimal example:\r\n```\r\nimport httpx, asyncio, logging\r\nfrom httpx import PoolLimits\r\nfrom random import randint\r\n\r\nqueue = asyncio.Queue()\r\n\r\nclients = [\r\n\thttpx.AsyncClient(\r\n\t\thttp2=True,\r\n\t\tpool_limits=PoolLimits(soft_limit=2, hard_limit=10),\r\n\t\tcookies={'a': '123456789', 'b': '987654321'},\r\n\t)\r\n]\r\n\r\nasync def worker_loop(cid, client, queue):\r\n\twhile 1:\r\n\t\tsub_id = await queue.get()\r\n\r\n\t\tasync with client as c:\r\n\t\t\tr = await c.get(f'https://mywebsite.dummy/submission.php?id={sub_id}')\r\n\r\n\t\tif r.status_code != 200:\r\n\t\t\tprint(cid, f'Got status code {r.status_code} while parsing {sub_id}')\r\n\t\t\treturn\r\n\r\nasync def main():\r\n\tfor i in range(2500):\r\n\t\tawait queue.put(randint(1, 80000000))\r\n\r\n\tfor k, v in enumerate(clients):\r\n\t\tasyncio.create_task(worker_loop(k, v, queue))\r\n\r\n\twhile 1:\r\n\t\tif queue.qsize() == 0:\r\n\t\t\tawait queue.put(randint(1, 80000000))\r\n\t\tawait asyncio.sleep(2)\r\n\r\nloop = asyncio.get_event_loop()\r\nloop.run_until_complete(main())\r\nloop.stop()\r\n```\r\n\r\n\r\nI checked with netstat, and only one actual connection is opened to the IP address, so pooling seems to work fine.\r\nI really cannot understand why. I even tried using the \"aclose()\" syntax, without the \"async with\" block, but no difference at all.\n", "before_files": [{"content": "import typing\n\nfrom ..backends.base import BaseSemaphore, ConcurrencyBackend, lookup_backend\nfrom ..config import (\n    DEFAULT_POOL_LIMITS,\n    CertTypes,\n    PoolLimits,\n    SSLConfig,\n    Timeout,\n    VerifyTypes,\n)\nfrom ..exceptions import PoolTimeout\nfrom ..models import Origin, Request, Response\nfrom ..utils import get_logger\nfrom .base import Dispatcher\nfrom .connection import HTTPConnection\n\nCONNECTIONS_DICT = typing.Dict[Origin, typing.List[HTTPConnection]]\n\n\nlogger = get_logger(__name__)\n\n\nclass NullSemaphore(BaseSemaphore):\n    async def acquire(self, timeout: float = None) -> None:\n        return\n\n    def release(self) -> None:\n        return\n\n\nclass ConnectionStore:\n    \"\"\"\n    We need to maintain collections of connections in a way that allows us to:\n\n    * Lookup connections by origin.\n    * Iterate over connections by insertion time.\n    * Return the total number of connections.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.all: typing.Dict[HTTPConnection, float] = {}\n        self.by_origin: typing.Dict[Origin, typing.Dict[HTTPConnection, float]] = {}\n\n    def pop_by_origin(\n        self, origin: Origin, http2_only: bool = False\n    ) -> typing.Optional[HTTPConnection]:\n        try:\n            connections = self.by_origin[origin]\n        except KeyError:\n            return None\n\n        connection = next(reversed(list(connections.keys())))\n        if http2_only and not connection.is_http2:\n            return None\n\n        del connections[connection]\n        if not connections:\n            del self.by_origin[origin]\n        del self.all[connection]\n\n        return connection\n\n    def add(self, connection: HTTPConnection) -> None:\n        self.all[connection] = 0.0\n        try:\n            self.by_origin[connection.origin][connection] = 0.0\n        except KeyError:\n            self.by_origin[connection.origin] = {connection: 0.0}\n\n    def remove(self, connection: HTTPConnection) -> None:\n        del self.all[connection]\n        del self.by_origin[connection.origin][connection]\n        if not self.by_origin[connection.origin]:\n            del self.by_origin[connection.origin]\n\n    def clear(self) -> None:\n        self.all.clear()\n        self.by_origin.clear()\n\n    def __iter__(self) -> typing.Iterator[HTTPConnection]:\n        return iter(self.all.keys())\n\n    def __len__(self) -> int:\n        return len(self.all)\n\n\nclass ConnectionPool(Dispatcher):\n    KEEP_ALIVE_EXPIRY = 5.0\n\n    def __init__(\n        self,\n        *,\n        verify: VerifyTypes = True,\n        cert: CertTypes = None,\n        trust_env: bool = None,\n        pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,\n        http2: bool = False,\n        backend: typing.Union[str, ConcurrencyBackend] = \"auto\",\n        uds: typing.Optional[str] = None,\n    ):\n        self.ssl = SSLConfig(verify=verify, cert=cert, trust_env=trust_env, http2=http2)\n        self.pool_limits = pool_limits\n        self.is_closed = False\n        self.uds = uds\n\n        self.keepalive_connections = ConnectionStore()\n        self.active_connections = ConnectionStore()\n\n        self.backend = lookup_backend(backend)\n        self.next_keepalive_check = 0.0\n\n    @property\n    def max_connections(self) -> BaseSemaphore:\n        # We do this lazily, to make sure backend autodetection always\n        # runs within an async context.\n        if not hasattr(self, \"_max_connections\"):\n            limit = self.pool_limits.hard_limit\n            if limit:\n                self._max_connections = self.backend.create_semaphore(\n                    limit, exc_class=PoolTimeout\n                )\n            else:\n                self._max_connections = NullSemaphore()\n\n        return self._max_connections\n\n    @property\n    def num_connections(self) -> int:\n        return len(self.keepalive_connections) + len(self.active_connections)\n\n    async def check_keepalive_expiry(self) -> None:\n        now = self.backend.time()\n        if now < self.next_keepalive_check:\n            return\n        self.next_keepalive_check = now + 1.0\n\n        # Iterate through all the keep alive connections.\n        # We create a list here to avoid any 'changed during iteration' errors.\n        keepalives = list(self.keepalive_connections.all.keys())\n        for connection in keepalives:\n            if connection.expires_at is not None and now > connection.expires_at:\n                self.keepalive_connections.remove(connection)\n                self.max_connections.release()\n                await connection.close()\n\n    async def send(self, request: Request, timeout: Timeout = None) -> Response:\n        await self.check_keepalive_expiry()\n        connection = await self.acquire_connection(\n            origin=request.url.origin, timeout=timeout\n        )\n        try:\n            response = await connection.send(request, timeout=timeout)\n        except BaseException as exc:\n            self.active_connections.remove(connection)\n            self.max_connections.release()\n            raise exc\n\n        return response\n\n    async def acquire_connection(\n        self, origin: Origin, timeout: Timeout = None\n    ) -> HTTPConnection:\n        logger.trace(f\"acquire_connection origin={origin!r}\")\n        connection = self.pop_connection(origin)\n\n        if connection is None:\n            pool_timeout = None if timeout is None else timeout.pool_timeout\n\n            await self.max_connections.acquire(timeout=pool_timeout)\n            connection = HTTPConnection(\n                origin,\n                ssl=self.ssl,\n                backend=self.backend,\n                release_func=self.release_connection,\n                uds=self.uds,\n            )\n            logger.trace(f\"new_connection connection={connection!r}\")\n        else:\n            logger.trace(f\"reuse_connection connection={connection!r}\")\n\n        self.active_connections.add(connection)\n\n        return connection\n\n    async def release_connection(self, connection: HTTPConnection) -> None:\n        logger.trace(f\"release_connection connection={connection!r}\")\n        if connection.is_closed:\n            self.active_connections.remove(connection)\n            self.max_connections.release()\n        elif (\n            self.pool_limits.soft_limit is not None\n            and self.num_connections > self.pool_limits.soft_limit\n        ):\n            self.active_connections.remove(connection)\n            self.max_connections.release()\n            await connection.close()\n        else:\n            now = self.backend.time()\n            connection.expires_at = now + self.KEEP_ALIVE_EXPIRY\n            self.active_connections.remove(connection)\n            self.keepalive_connections.add(connection)\n\n    async def close(self) -> None:\n        self.is_closed = True\n        connections = list(self.keepalive_connections)\n        self.keepalive_connections.clear()\n        for connection in connections:\n            await connection.close()\n\n    def pop_connection(self, origin: Origin) -> typing.Optional[HTTPConnection]:\n        connection = self.active_connections.pop_by_origin(origin, http2_only=True)\n        if connection is None:\n            connection = self.keepalive_connections.pop_by_origin(origin)\n\n        if connection is not None and connection.is_connection_dropped():\n            self.max_connections.release()\n            connection = None\n\n        return connection\n", "path": "httpx/dispatch/connection_pool.py"}], "after_files": [{"content": "import typing\n\nfrom ..backends.base import BaseSemaphore, ConcurrencyBackend, lookup_backend\nfrom ..config import (\n    DEFAULT_POOL_LIMITS,\n    CertTypes,\n    PoolLimits,\n    SSLConfig,\n    Timeout,\n    VerifyTypes,\n)\nfrom ..exceptions import PoolTimeout\nfrom ..models import Origin, Request, Response\nfrom ..utils import get_logger\nfrom .base import Dispatcher\nfrom .connection import HTTPConnection\n\nCONNECTIONS_DICT = typing.Dict[Origin, typing.List[HTTPConnection]]\n\n\nlogger = get_logger(__name__)\n\n\nclass NullSemaphore(BaseSemaphore):\n    async def acquire(self, timeout: float = None) -> None:\n        return\n\n    def release(self) -> None:\n        return\n\n\nclass ConnectionStore:\n    \"\"\"\n    We need to maintain collections of connections in a way that allows us to:\n\n    * Lookup connections by origin.\n    * Iterate over connections by insertion time.\n    * Return the total number of connections.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.all: typing.Dict[HTTPConnection, float] = {}\n        self.by_origin: typing.Dict[Origin, typing.Dict[HTTPConnection, float]] = {}\n\n    def pop_by_origin(\n        self, origin: Origin, http2_only: bool = False\n    ) -> typing.Optional[HTTPConnection]:\n        try:\n            connections = self.by_origin[origin]\n        except KeyError:\n            return None\n\n        connection = next(reversed(list(connections.keys())))\n        if http2_only and not connection.is_http2:\n            return None\n\n        del connections[connection]\n        if not connections:\n            del self.by_origin[origin]\n        del self.all[connection]\n\n        return connection\n\n    def add(self, connection: HTTPConnection) -> None:\n        self.all[connection] = 0.0\n        try:\n            self.by_origin[connection.origin][connection] = 0.0\n        except KeyError:\n            self.by_origin[connection.origin] = {connection: 0.0}\n\n    def remove(self, connection: HTTPConnection) -> None:\n        del self.all[connection]\n        del self.by_origin[connection.origin][connection]\n        if not self.by_origin[connection.origin]:\n            del self.by_origin[connection.origin]\n\n    def clear(self) -> None:\n        self.all.clear()\n        self.by_origin.clear()\n\n    def __iter__(self) -> typing.Iterator[HTTPConnection]:\n        return iter(self.all.keys())\n\n    def __len__(self) -> int:\n        return len(self.all)\n\n\nclass ConnectionPool(Dispatcher):\n    KEEP_ALIVE_EXPIRY = 5.0\n\n    def __init__(\n        self,\n        *,\n        verify: VerifyTypes = True,\n        cert: CertTypes = None,\n        trust_env: bool = None,\n        pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,\n        http2: bool = False,\n        backend: typing.Union[str, ConcurrencyBackend] = \"auto\",\n        uds: typing.Optional[str] = None,\n    ):\n        self.ssl = SSLConfig(verify=verify, cert=cert, trust_env=trust_env, http2=http2)\n        self.pool_limits = pool_limits\n        self.is_closed = False\n        self.uds = uds\n\n        self.keepalive_connections = ConnectionStore()\n        self.active_connections = ConnectionStore()\n\n        self.backend = lookup_backend(backend)\n        self.next_keepalive_check = 0.0\n\n    @property\n    def max_connections(self) -> BaseSemaphore:\n        # We do this lazily, to make sure backend autodetection always\n        # runs within an async context.\n        if not hasattr(self, \"_max_connections\"):\n            limit = self.pool_limits.hard_limit\n            if limit:\n                self._max_connections = self.backend.create_semaphore(\n                    limit, exc_class=PoolTimeout\n                )\n            else:\n                self._max_connections = NullSemaphore()\n\n        return self._max_connections\n\n    @property\n    def num_connections(self) -> int:\n        return len(self.keepalive_connections) + len(self.active_connections)\n\n    async def check_keepalive_expiry(self) -> None:\n        now = self.backend.time()\n        if now < self.next_keepalive_check:\n            return\n        self.next_keepalive_check = now + 1.0\n\n        # Iterate through all the keep alive connections.\n        # We create a list here to avoid any 'changed during iteration' errors.\n        keepalives = list(self.keepalive_connections.all.keys())\n        for connection in keepalives:\n            if connection.expires_at is not None and now > connection.expires_at:\n                self.keepalive_connections.remove(connection)\n                self.max_connections.release()\n                await connection.close()\n\n    async def send(self, request: Request, timeout: Timeout = None) -> Response:\n        await self.check_keepalive_expiry()\n        connection = await self.acquire_connection(\n            origin=request.url.origin, timeout=timeout\n        )\n        try:\n            response = await connection.send(request, timeout=timeout)\n        except BaseException as exc:\n            self.active_connections.remove(connection)\n            self.max_connections.release()\n            raise exc\n\n        return response\n\n    async def acquire_connection(\n        self, origin: Origin, timeout: Timeout = None\n    ) -> HTTPConnection:\n        logger.trace(f\"acquire_connection origin={origin!r}\")\n        connection = self.pop_connection(origin)\n\n        if connection is None:\n            pool_timeout = None if timeout is None else timeout.pool_timeout\n\n            await self.max_connections.acquire(timeout=pool_timeout)\n            connection = HTTPConnection(\n                origin,\n                ssl=self.ssl,\n                backend=self.backend,\n                release_func=self.release_connection,\n                uds=self.uds,\n            )\n            logger.trace(f\"new_connection connection={connection!r}\")\n        else:\n            logger.trace(f\"reuse_connection connection={connection!r}\")\n\n        self.active_connections.add(connection)\n\n        return connection\n\n    async def release_connection(self, connection: HTTPConnection) -> None:\n        logger.trace(f\"release_connection connection={connection!r}\")\n        if connection.is_closed:\n            self.active_connections.remove(connection)\n            self.max_connections.release()\n        elif (\n            self.pool_limits.soft_limit is not None\n            and self.num_connections > self.pool_limits.soft_limit\n        ):\n            self.active_connections.remove(connection)\n            self.max_connections.release()\n            await connection.close()\n        else:\n            now = self.backend.time()\n            connection.expires_at = now + self.KEEP_ALIVE_EXPIRY\n            self.active_connections.remove(connection)\n            self.keepalive_connections.add(connection)\n\n    async def close(self) -> None:\n        self.is_closed = True\n        connections = list(self.keepalive_connections)\n        self.keepalive_connections.clear()\n        for connection in connections:\n            self.max_connections.release()\n            await connection.close()\n\n    def pop_connection(self, origin: Origin) -> typing.Optional[HTTPConnection]:\n        connection = self.active_connections.pop_by_origin(origin, http2_only=True)\n        if connection is None:\n            connection = self.keepalive_connections.pop_by_origin(origin)\n\n        if connection is not None and connection.is_connection_dropped():\n            self.max_connections.release()\n            connection = None\n\n        return connection\n", "path": "httpx/dispatch/connection_pool.py"}]}