Skip to content

Commit 3b9c0f0

Browse files
authored
Add correct kwargs to ClientSession init to allow graceful AsyncHTTPProvider shutdown (#3557)
1 parent 0893752 commit 3b9c0f0

File tree

5 files changed

+63
-3
lines changed

5 files changed

+63
-3
lines changed

docs/providers.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ AsyncHTTPProvider
214214
>>> # If you want to pass in your own session:
215215
>>> custom_session = ClientSession()
216216
>>> await w3.provider.cache_async_session(custom_session) # This method is an async method so it needs to be handled accordingly
217+
>>> # when you're finished, disconnect:
218+
>>> w3.provider.disconnect()
217219
218220
Under the hood, the ``AsyncHTTPProvider`` uses the python
219221
`aiohttp <https://docs.aiohttp.org/en/stable/>`_ library for making requests.

newsfragments/3557.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a ``disconnect`` method to the AsyncHTTPProvider that closes all sessions and clears the cache

tests/integration/go_ethereum/test_goethereum_http.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,45 @@ class TestGoEthereumAsyncNetModuleTest(GoEthereumAsyncNetModuleTest):
159159

160160

161161
class TestGoEthereumAsyncEthModuleTest(GoEthereumAsyncEthModuleTest):
162-
pass
162+
@pytest.mark.asyncio
163+
async def test_async_http_provider_disconnects_gracefully(
164+
self, async_w3, endpoint_uri
165+
) -> None:
166+
w3_1 = async_w3
167+
168+
w3_2 = AsyncWeb3(AsyncHTTPProvider(endpoint_uri))
169+
assert w3_1 != w3_2
170+
171+
await w3_1.eth.get_block("latest")
172+
await w3_2.eth.get_block("latest")
173+
174+
w3_1_session_cache = w3_1.provider._request_session_manager.session_cache
175+
w3_2_session_cache = w3_2.provider._request_session_manager.session_cache
176+
177+
for _, session in w3_1_session_cache.items():
178+
assert not session.closed
179+
for _, session in w3_2_session_cache.items():
180+
assert not session.closed
181+
assert w3_1_session_cache != w3_2_session_cache
182+
183+
await w3_1.provider.disconnect()
184+
await w3_2.provider.disconnect()
185+
186+
assert len(w3_1_session_cache) == 0
187+
assert len(w3_2_session_cache) == 0
188+
189+
@pytest.mark.asyncio
190+
async def test_async_http_provider_reuses_cached_session(self, async_w3) -> None:
191+
await async_w3.eth.get_block("latest")
192+
session_cache = async_w3.provider._request_session_manager.session_cache
193+
assert len(session_cache) == 1
194+
session = list(session_cache._data.values())[0]
195+
196+
await async_w3.eth.get_block("latest")
197+
assert len(session_cache) == 1
198+
assert session == list(session_cache._data.values())[0]
199+
await async_w3.provider.disconnect()
200+
assert len(session_cache) == 0
163201

164202

165203
class TestGoEthereumAsyncTxPoolModuleTest(GoEthereumAsyncTxPoolModuleTest):

web3/_utils/http_session_manager.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ClientResponse,
1919
ClientSession,
2020
ClientTimeout,
21+
TCPConnector,
2122
)
2223
from eth_typing import (
2324
URI,
@@ -174,7 +175,12 @@ async def async_cache_and_return_session(
174175
async with async_lock(self.session_pool, self._lock):
175176
if cache_key not in self.session_cache:
176177
if session is None:
177-
session = ClientSession(raise_for_status=True)
178+
session = ClientSession(
179+
raise_for_status=True,
180+
connector=TCPConnector(
181+
force_close=True, enable_cleanup_closed=True
182+
),
183+
)
178184

179185
cached_session, evicted_items = self.session_cache.cache(
180186
cache_key, session
@@ -213,7 +219,12 @@ async def async_cache_and_return_session(
213219
)
214220

215221
# replace stale session with a new session at the cache key
216-
_session = ClientSession(raise_for_status=True)
222+
_session = ClientSession(
223+
raise_for_status=True,
224+
connector=TCPConnector(
225+
force_close=True, enable_cleanup_closed=True
226+
),
227+
)
217228
cached_session, evicted_items = self.session_cache.cache(
218229
cache_key, _session
219230
)

web3/providers/rpc/async_rpc.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,11 @@ async def make_batch_request(
177177
self.logger.debug("Received batch response HTTP.")
178178
responses_list = cast(List[RPCResponse], self.decode_rpc_response(raw_response))
179179
return sort_batch_response_by_response_ids(responses_list)
180+
181+
async def disconnect(self) -> None:
182+
cache = self._request_session_manager.session_cache
183+
for _, session in cache.items():
184+
await session.close()
185+
cache.clear()
186+
187+
self.logger.info(f"Successfully disconnected from: {self.endpoint_uri}")

0 commit comments

Comments
 (0)