We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Async request session caching incompatible with Flask
Flask is not natively async, so it creates a new thread loop for every async request.

When async web3py does an aiohttp call, it caches the session. So the next time you make a request it pulls the stale session out of the cache and throws an "Event loop is closed" exception.

To fix the issue we made the following code changes in request.py as a hack.

```
async def get_async_session(endpoint_uri: URI) -> ClientSession:
    cache_key = generate_cache_key(endpoint_uri)
    ########### TEMP CODE ###########
    if True:# cache_key not in _async_session_cache: # no caching for you!
    ########### TEMP CODE ###########
        await cache_async_session(endpoint_uri, ClientSession(raise_for_status=True))
    return _async_session_cache.get_cache_entry(cache_key)


async def async_get_response_from_get_request(
    endpoint_uri: URI, *args: Any, **kwargs: Any
) -> ClientResponse:
    kwargs.setdefault("timeout", ClientTimeout(10))
    session = await get_async_session(endpoint_uri)
    response = await session.get(endpoint_uri, *args, **kwargs)
    ########### TEMP CODE ###########
    await session.close()
    ########### TEMP CODE ###########
    return response


async def async_get_response_from_post_request(
    endpoint_uri: URI, *args: Any, **kwargs: Any
) -> ClientResponse:
    kwargs.setdefault("timeout", ClientTimeout(10))
    session = await get_async_session(endpoint_uri)
    response = await session.post(endpoint_uri, *args, **kwargs)
    ########### TEMP CODE ###########
    await session.close()
    ########### TEMP CODE ###########
    return response
```

Would be nice to have a setting to disable session caching.

---
**Note:** We prefer to use issues to track our work. If you think you've encountered a bug in web3py or 
have a feature request, you're in the right place. If you have implementation or usage questions, 
please refer to our [documentation](https://web3py.readthedocs.io/en/latest/) and/or join the conversation 
on [discord](https://discord.gg/GHryRvPB84).


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `web3/_utils/request.py`
Content:
```
1 import asyncio
2 from concurrent.futures import (
3     ThreadPoolExecutor,
4 )
5 import logging
6 import os
7 import threading
8 from typing import (
9     Any,
10     Dict,
11     List,
12     Optional,
13     Union,
14 )
15 
16 from aiohttp import (
17     ClientResponse,
18     ClientSession,
19     ClientTimeout,
20 )
21 from eth_typing import (
22     URI,
23 )
24 import requests
25 
26 from web3._utils.async_caching import (
27     async_lock,
28 )
29 from web3._utils.caching import (
30     generate_cache_key,
31 )
32 from web3.utils.caching import (
33     SimpleCache,
34 )
35 
36 logger = logging.getLogger(__name__)
37 
38 DEFAULT_TIMEOUT = 10
39 
40 
41 def get_default_http_endpoint() -> URI:
42     return URI(os.environ.get("WEB3_HTTP_PROVIDER_URI", "http://localhost:8545"))
43 
44 
45 _session_cache = SimpleCache()
46 _session_cache_lock = threading.Lock()
47 
48 
49 def cache_and_return_session(
50     endpoint_uri: URI, session: requests.Session = None
51 ) -> requests.Session:
52     # cache key should have a unique thread identifier
53     cache_key = generate_cache_key(f"{threading.get_ident()}:{endpoint_uri}")
54 
55     evicted_items = None
56     with _session_cache_lock:
57         if cache_key not in _session_cache:
58             if session is None:
59                 session = requests.Session()
60 
61             evicted_items = _session_cache.cache(cache_key, session)
62             logger.debug(f"Session cached: {endpoint_uri}, {session}")
63 
64         cached_session = _session_cache.get_cache_entry(cache_key)
65 
66     if evicted_items is not None:
67         evicted_sessions = evicted_items.values()
68         for evicted_session in evicted_sessions:
69             logger.debug(
70                 f"Session cache full. Session evicted from cache: {evicted_session}",
71             )
72         threading.Timer(
73             DEFAULT_TIMEOUT + 0.1,
74             _close_evicted_sessions,
75             args=[evicted_sessions],
76         ).start()
77 
78     return cached_session
79 
80 
81 def get_response_from_get_request(
82     endpoint_uri: URI, *args: Any, **kwargs: Any
83 ) -> requests.Response:
84     kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
85     session = cache_and_return_session(endpoint_uri)
86     response = session.get(endpoint_uri, *args, **kwargs)
87     return response
88 
89 
90 def json_make_get_request(
91     endpoint_uri: URI, *args: Any, **kwargs: Any
92 ) -> Dict[str, Any]:
93     response = get_response_from_get_request(endpoint_uri, *args, **kwargs)
94     response.raise_for_status()
95     return response.json()
96 
97 
98 def get_response_from_post_request(
99     endpoint_uri: URI, *args: Any, **kwargs: Any
100 ) -> requests.Response:
101     kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
102     session = cache_and_return_session(endpoint_uri)
103     response = session.post(endpoint_uri, *args, **kwargs)
104     return response
105 
106 
107 def make_post_request(
108     endpoint_uri: URI, data: Union[bytes, Dict[str, Any]], *args: Any, **kwargs: Any
109 ) -> bytes:
110     response = get_response_from_post_request(endpoint_uri, data=data, *args, **kwargs)
111     response.raise_for_status()
112     return response.content
113 
114 
115 def _close_evicted_sessions(evicted_sessions: List[requests.Session]) -> None:
116     for evicted_session in evicted_sessions:
117         evicted_session.close()
118         logger.debug(f"Closed evicted session: {evicted_session}")
119 
120 
121 # --- async --- #
122 
123 
124 _async_session_cache = SimpleCache()
125 _async_session_cache_lock = threading.Lock()
126 _async_session_pool = ThreadPoolExecutor(max_workers=1)
127 
128 
129 async def cache_and_return_async_session(
130     endpoint_uri: URI,
131     session: Optional[ClientSession] = None,
132 ) -> ClientSession:
133     # cache key should have a unique thread identifier
134     cache_key = generate_cache_key(f"{threading.get_ident()}:{endpoint_uri}")
135 
136     evicted_items = None
137     async with async_lock(_async_session_pool, _async_session_cache_lock):
138         if cache_key not in _async_session_cache:
139             if session is None:
140                 session = ClientSession(raise_for_status=True)
141 
142             evicted_items = _async_session_cache.cache(cache_key, session)
143             logger.debug(f"Async session cached: {endpoint_uri}, {session}")
144 
145         cached_session = _async_session_cache.get_cache_entry(cache_key)
146 
147     if evicted_items is not None:
148         # At this point the evicted sessions are already popped out of the cache and
149         # just stored in the `evicted_sessions` dict. So we can kick off a future task
150         # to close them and it should be safe to pop out of the lock here.
151         evicted_sessions = evicted_items.values()
152         for evicted_session in evicted_sessions:
153             logger.debug(
154                 "Async session cache full. Session evicted from cache: "
155                 f"{evicted_session}",
156             )
157         # Kick off a future task, in a separate thread, to close the evicted
158         # sessions. In the case that the cache filled very quickly and some
159         # sessions have been evicted before their original request has been made,
160         # we set the timer to a bit more than the `DEFAULT_TIMEOUT` for a call. This
161         # should make it so that any call from an evicted session can still be made
162         # before the session is closed.
163         threading.Timer(
164             DEFAULT_TIMEOUT + 0.1,
165             _async_close_evicted_sessions,
166             args=[evicted_sessions],
167         ).start()
168 
169     return cached_session
170 
171 
172 async def async_get_response_from_get_request(
173     endpoint_uri: URI, *args: Any, **kwargs: Any
174 ) -> ClientResponse:
175     kwargs.setdefault("timeout", ClientTimeout(DEFAULT_TIMEOUT))
176     session = await cache_and_return_async_session(endpoint_uri)
177     response = await session.get(endpoint_uri, *args, **kwargs)
178     return response
179 
180 
181 async def async_json_make_get_request(
182     endpoint_uri: URI, *args: Any, **kwargs: Any
183 ) -> Dict[str, Any]:
184     response = await async_get_response_from_get_request(endpoint_uri, *args, **kwargs)
185     response.raise_for_status()
186     return await response.json()
187 
188 
189 async def async_get_response_from_post_request(
190     endpoint_uri: URI, *args: Any, **kwargs: Any
191 ) -> ClientResponse:
192     kwargs.setdefault("timeout", ClientTimeout(DEFAULT_TIMEOUT))
193     session = await cache_and_return_async_session(endpoint_uri)
194     response = await session.post(endpoint_uri, *args, **kwargs)
195     return response
196 
197 
198 async def async_make_post_request(
199     endpoint_uri: URI, data: Union[bytes, Dict[str, Any]], *args: Any, **kwargs: Any
200 ) -> bytes:
201     response = await async_get_response_from_post_request(
202         endpoint_uri, data=data, *args, **kwargs
203     )
204     response.raise_for_status()
205     return await response.read()
206 
207 
208 async def async_get_json_from_client_response(
209     response: ClientResponse,
210 ) -> Dict[str, Any]:
211     return await response.json()
212 
213 
214 def _async_close_evicted_sessions(evicted_sessions: List[ClientSession]) -> None:
215     loop = asyncio.new_event_loop()
216 
217     for evicted_session in evicted_sessions:
218         loop.run_until_complete(evicted_session.close())
219         logger.debug(f"Closed evicted async session: {evicted_session}")
220 
221     if any(not evicted_session.closed for evicted_session in evicted_sessions):
222         logger.warning(
223             f"Some evicted async sessions were not properly closed: {evicted_sessions}"
224         )
225     loop.close()
226
```
--- 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/web3/_utils/request.py b/web3/_utils/request.py
--- a/web3/_utils/request.py
+++ b/web3/_utils/request.py
@@ -142,6 +142,39 @@
             evicted_items = _async_session_cache.cache(cache_key, session)
             logger.debug(f"Async session cached: {endpoint_uri}, {session}")
 
+        else:
+            # get the cached session
+            cached_session = _async_session_cache.get_cache_entry(cache_key)
+            session_is_closed = cached_session.closed
+            session_loop_is_closed = cached_session._loop.is_closed()
+
+            warning = (
+                "Async session was closed"
+                if session_is_closed
+                else "Loop was closed for async session"
+                if session_loop_is_closed
+                else None
+            )
+            if warning:
+                logger.debug(
+                    f"{warning}: {endpoint_uri}, {cached_session}. "
+                    f"Creating and caching a new async session for uri."
+                )
+
+                _async_session_cache._data.pop(cache_key)
+                if not session_is_closed:
+                    # if loop was closed but not the session, close the session
+                    await cached_session.close()
+                logger.debug(
+                    f"Async session closed and evicted from cache: {cached_session}"
+                )
+
+                # replace stale session with a new session at the cache key
+                _session = ClientSession(raise_for_status=True)
+                evicted_items = _async_session_cache.cache(cache_key, _session)
+                logger.debug(f"Async session cached: {endpoint_uri}, {_session}")
+
+        # get the cached session
         cached_session = _async_session_cache.get_cache_entry(cache_key)
 
     if evicted_items is not None:

{"golden_diff": "diff --git a/web3/_utils/request.py b/web3/_utils/request.py\n--- a/web3/_utils/request.py\n+++ b/web3/_utils/request.py\n@@ -142,6 +142,39 @@\n             evicted_items = _async_session_cache.cache(cache_key, session)\n             logger.debug(f\"Async session cached: {endpoint_uri}, {session}\")\n \n+        else:\n+            # get the cached session\n+            cached_session = _async_session_cache.get_cache_entry(cache_key)\n+            session_is_closed = cached_session.closed\n+            session_loop_is_closed = cached_session._loop.is_closed()\n+\n+            warning = (\n+                \"Async session was closed\"\n+                if session_is_closed\n+                else \"Loop was closed for async session\"\n+                if session_loop_is_closed\n+                else None\n+            )\n+            if warning:\n+                logger.debug(\n+                    f\"{warning}: {endpoint_uri}, {cached_session}. \"\n+                    f\"Creating and caching a new async session for uri.\"\n+                )\n+\n+                _async_session_cache._data.pop(cache_key)\n+                if not session_is_closed:\n+                    # if loop was closed but not the session, close the session\n+                    await cached_session.close()\n+                logger.debug(\n+                    f\"Async session closed and evicted from cache: {cached_session}\"\n+                )\n+\n+                # replace stale session with a new session at the cache key\n+                _session = ClientSession(raise_for_status=True)\n+                evicted_items = _async_session_cache.cache(cache_key, _session)\n+                logger.debug(f\"Async session cached: {endpoint_uri}, {_session}\")\n+\n+        # get the cached session\n         cached_session = _async_session_cache.get_cache_entry(cache_key)\n \n     if evicted_items is not None:\n", "issue": "Async request session caching incompatible with Flask\nFlask is not natively async, so it creates a new thread loop for every async request.\r\n\r\nWhen async web3py does an aiohttp call, it caches the session. So the next time you make a request it pulls the stale session out of the cache and throws an \"Event loop is closed\" exception.\r\n\r\nTo fix the issue we made the following code changes in request.py as a hack.\r\n\r\n```\r\nasync def get_async_session(endpoint_uri: URI) -> ClientSession:\r\n    cache_key = generate_cache_key(endpoint_uri)\r\n    ########### TEMP CODE ###########\r\n    if True:# cache_key not in _async_session_cache: # no caching for you!\r\n    ########### TEMP CODE ###########\r\n        await cache_async_session(endpoint_uri, ClientSession(raise_for_status=True))\r\n    return _async_session_cache.get_cache_entry(cache_key)\r\n\r\n\r\nasync def async_get_response_from_get_request(\r\n    endpoint_uri: URI, *args: Any, **kwargs: Any\r\n) -> ClientResponse:\r\n    kwargs.setdefault(\"timeout\", ClientTimeout(10))\r\n    session = await get_async_session(endpoint_uri)\r\n    response = await session.get(endpoint_uri, *args, **kwargs)\r\n    ########### TEMP CODE ###########\r\n    await session.close()\r\n    ########### TEMP CODE ###########\r\n    return response\r\n\r\n\r\nasync def async_get_response_from_post_request(\r\n    endpoint_uri: URI, *args: Any, **kwargs: Any\r\n) -> ClientResponse:\r\n    kwargs.setdefault(\"timeout\", ClientTimeout(10))\r\n    session = await get_async_session(endpoint_uri)\r\n    response = await session.post(endpoint_uri, *args, **kwargs)\r\n    ########### TEMP CODE ###########\r\n    await session.close()\r\n    ########### TEMP CODE ###########\r\n    return response\r\n```\r\n\r\nWould be nice to have a setting to disable session caching.\r\n\r\n---\r\n**Note:** We prefer to use issues to track our work. If you think you've encountered a bug in web3py or \r\nhave a feature request, you're in the right place. If you have implementation or usage questions, \r\nplease refer to our [documentation](https://web3py.readthedocs.io/en/latest/) and/or join the conversation \r\non [discord](https://discord.gg/GHryRvPB84).\r\n\n", "before_files": [{"content": "import asyncio\nfrom concurrent.futures import (\n    ThreadPoolExecutor,\n)\nimport logging\nimport os\nimport threading\nfrom typing import (\n    Any,\n    Dict,\n    List,\n    Optional,\n    Union,\n)\n\nfrom aiohttp import (\n    ClientResponse,\n    ClientSession,\n    ClientTimeout,\n)\nfrom eth_typing import (\n    URI,\n)\nimport requests\n\nfrom web3._utils.async_caching import (\n    async_lock,\n)\nfrom web3._utils.caching import (\n    generate_cache_key,\n)\nfrom web3.utils.caching import (\n    SimpleCache,\n)\n\nlogger = logging.getLogger(__name__)\n\nDEFAULT_TIMEOUT = 10\n\n\ndef get_default_http_endpoint() -> URI:\n    return URI(os.environ.get(\"WEB3_HTTP_PROVIDER_URI\", \"http://localhost:8545\"))\n\n\n_session_cache = SimpleCache()\n_session_cache_lock = threading.Lock()\n\n\ndef cache_and_return_session(\n    endpoint_uri: URI, session: requests.Session = None\n) -> requests.Session:\n    # cache key should have a unique thread identifier\n    cache_key = generate_cache_key(f\"{threading.get_ident()}:{endpoint_uri}\")\n\n    evicted_items = None\n    with _session_cache_lock:\n        if cache_key not in _session_cache:\n            if session is None:\n                session = requests.Session()\n\n            evicted_items = _session_cache.cache(cache_key, session)\n            logger.debug(f\"Session cached: {endpoint_uri}, {session}\")\n\n        cached_session = _session_cache.get_cache_entry(cache_key)\n\n    if evicted_items is not None:\n        evicted_sessions = evicted_items.values()\n        for evicted_session in evicted_sessions:\n            logger.debug(\n                f\"Session cache full. Session evicted from cache: {evicted_session}\",\n            )\n        threading.Timer(\n            DEFAULT_TIMEOUT + 0.1,\n            _close_evicted_sessions,\n            args=[evicted_sessions],\n        ).start()\n\n    return cached_session\n\n\ndef get_response_from_get_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> requests.Response:\n    kwargs.setdefault(\"timeout\", DEFAULT_TIMEOUT)\n    session = cache_and_return_session(endpoint_uri)\n    response = session.get(endpoint_uri, *args, **kwargs)\n    return response\n\n\ndef json_make_get_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> Dict[str, Any]:\n    response = get_response_from_get_request(endpoint_uri, *args, **kwargs)\n    response.raise_for_status()\n    return response.json()\n\n\ndef get_response_from_post_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> requests.Response:\n    kwargs.setdefault(\"timeout\", DEFAULT_TIMEOUT)\n    session = cache_and_return_session(endpoint_uri)\n    response = session.post(endpoint_uri, *args, **kwargs)\n    return response\n\n\ndef make_post_request(\n    endpoint_uri: URI, data: Union[bytes, Dict[str, Any]], *args: Any, **kwargs: Any\n) -> bytes:\n    response = get_response_from_post_request(endpoint_uri, data=data, *args, **kwargs)\n    response.raise_for_status()\n    return response.content\n\n\ndef _close_evicted_sessions(evicted_sessions: List[requests.Session]) -> None:\n    for evicted_session in evicted_sessions:\n        evicted_session.close()\n        logger.debug(f\"Closed evicted session: {evicted_session}\")\n\n\n# --- async --- #\n\n\n_async_session_cache = SimpleCache()\n_async_session_cache_lock = threading.Lock()\n_async_session_pool = ThreadPoolExecutor(max_workers=1)\n\n\nasync def cache_and_return_async_session(\n    endpoint_uri: URI,\n    session: Optional[ClientSession] = None,\n) -> ClientSession:\n    # cache key should have a unique thread identifier\n    cache_key = generate_cache_key(f\"{threading.get_ident()}:{endpoint_uri}\")\n\n    evicted_items = None\n    async with async_lock(_async_session_pool, _async_session_cache_lock):\n        if cache_key not in _async_session_cache:\n            if session is None:\n                session = ClientSession(raise_for_status=True)\n\n            evicted_items = _async_session_cache.cache(cache_key, session)\n            logger.debug(f\"Async session cached: {endpoint_uri}, {session}\")\n\n        cached_session = _async_session_cache.get_cache_entry(cache_key)\n\n    if evicted_items is not None:\n        # At this point the evicted sessions are already popped out of the cache and\n        # just stored in the `evicted_sessions` dict. So we can kick off a future task\n        # to close them and it should be safe to pop out of the lock here.\n        evicted_sessions = evicted_items.values()\n        for evicted_session in evicted_sessions:\n            logger.debug(\n                \"Async session cache full. Session evicted from cache: \"\n                f\"{evicted_session}\",\n            )\n        # Kick off a future task, in a separate thread, to close the evicted\n        # sessions. In the case that the cache filled very quickly and some\n        # sessions have been evicted before their original request has been made,\n        # we set the timer to a bit more than the `DEFAULT_TIMEOUT` for a call. This\n        # should make it so that any call from an evicted session can still be made\n        # before the session is closed.\n        threading.Timer(\n            DEFAULT_TIMEOUT + 0.1,\n            _async_close_evicted_sessions,\n            args=[evicted_sessions],\n        ).start()\n\n    return cached_session\n\n\nasync def async_get_response_from_get_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> ClientResponse:\n    kwargs.setdefault(\"timeout\", ClientTimeout(DEFAULT_TIMEOUT))\n    session = await cache_and_return_async_session(endpoint_uri)\n    response = await session.get(endpoint_uri, *args, **kwargs)\n    return response\n\n\nasync def async_json_make_get_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> Dict[str, Any]:\n    response = await async_get_response_from_get_request(endpoint_uri, *args, **kwargs)\n    response.raise_for_status()\n    return await response.json()\n\n\nasync def async_get_response_from_post_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> ClientResponse:\n    kwargs.setdefault(\"timeout\", ClientTimeout(DEFAULT_TIMEOUT))\n    session = await cache_and_return_async_session(endpoint_uri)\n    response = await session.post(endpoint_uri, *args, **kwargs)\n    return response\n\n\nasync def async_make_post_request(\n    endpoint_uri: URI, data: Union[bytes, Dict[str, Any]], *args: Any, **kwargs: Any\n) -> bytes:\n    response = await async_get_response_from_post_request(\n        endpoint_uri, data=data, *args, **kwargs\n    )\n    response.raise_for_status()\n    return await response.read()\n\n\nasync def async_get_json_from_client_response(\n    response: ClientResponse,\n) -> Dict[str, Any]:\n    return await response.json()\n\n\ndef _async_close_evicted_sessions(evicted_sessions: List[ClientSession]) -> None:\n    loop = asyncio.new_event_loop()\n\n    for evicted_session in evicted_sessions:\n        loop.run_until_complete(evicted_session.close())\n        logger.debug(f\"Closed evicted async session: {evicted_session}\")\n\n    if any(not evicted_session.closed for evicted_session in evicted_sessions):\n        logger.warning(\n            f\"Some evicted async sessions were not properly closed: {evicted_sessions}\"\n        )\n    loop.close()\n", "path": "web3/_utils/request.py"}], "after_files": [{"content": "import asyncio\nfrom concurrent.futures import (\n    ThreadPoolExecutor,\n)\nimport logging\nimport os\nimport threading\nfrom typing import (\n    Any,\n    Dict,\n    List,\n    Optional,\n    Union,\n)\n\nfrom aiohttp import (\n    ClientResponse,\n    ClientSession,\n    ClientTimeout,\n)\nfrom eth_typing import (\n    URI,\n)\nimport requests\n\nfrom web3._utils.async_caching import (\n    async_lock,\n)\nfrom web3._utils.caching import (\n    generate_cache_key,\n)\nfrom web3.utils.caching import (\n    SimpleCache,\n)\n\nlogger = logging.getLogger(__name__)\n\nDEFAULT_TIMEOUT = 10\n\n\ndef get_default_http_endpoint() -> URI:\n    return URI(os.environ.get(\"WEB3_HTTP_PROVIDER_URI\", \"http://localhost:8545\"))\n\n\n_session_cache = SimpleCache()\n_session_cache_lock = threading.Lock()\n\n\ndef cache_and_return_session(\n    endpoint_uri: URI, session: requests.Session = None\n) -> requests.Session:\n    # cache key should have a unique thread identifier\n    cache_key = generate_cache_key(f\"{threading.get_ident()}:{endpoint_uri}\")\n\n    evicted_items = None\n    with _session_cache_lock:\n        if cache_key not in _session_cache:\n            if session is None:\n                session = requests.Session()\n\n            evicted_items = _session_cache.cache(cache_key, session)\n            logger.debug(f\"Session cached: {endpoint_uri}, {session}\")\n\n        cached_session = _session_cache.get_cache_entry(cache_key)\n\n    if evicted_items is not None:\n        evicted_sessions = evicted_items.values()\n        for evicted_session in evicted_sessions:\n            logger.debug(\n                f\"Session cache full. Session evicted from cache: {evicted_session}\",\n            )\n        threading.Timer(\n            DEFAULT_TIMEOUT + 0.1,\n            _close_evicted_sessions,\n            args=[evicted_sessions],\n        ).start()\n\n    return cached_session\n\n\ndef get_response_from_get_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> requests.Response:\n    kwargs.setdefault(\"timeout\", DEFAULT_TIMEOUT)\n    session = cache_and_return_session(endpoint_uri)\n    response = session.get(endpoint_uri, *args, **kwargs)\n    return response\n\n\ndef json_make_get_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> Dict[str, Any]:\n    response = get_response_from_get_request(endpoint_uri, *args, **kwargs)\n    response.raise_for_status()\n    return response.json()\n\n\ndef get_response_from_post_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> requests.Response:\n    kwargs.setdefault(\"timeout\", DEFAULT_TIMEOUT)\n    session = cache_and_return_session(endpoint_uri)\n    response = session.post(endpoint_uri, *args, **kwargs)\n    return response\n\n\ndef make_post_request(\n    endpoint_uri: URI, data: Union[bytes, Dict[str, Any]], *args: Any, **kwargs: Any\n) -> bytes:\n    response = get_response_from_post_request(endpoint_uri, data=data, *args, **kwargs)\n    response.raise_for_status()\n    return response.content\n\n\ndef _close_evicted_sessions(evicted_sessions: List[requests.Session]) -> None:\n    for evicted_session in evicted_sessions:\n        evicted_session.close()\n        logger.debug(f\"Closed evicted session: {evicted_session}\")\n\n\n# --- async --- #\n\n\n_async_session_cache = SimpleCache()\n_async_session_cache_lock = threading.Lock()\n_async_session_pool = ThreadPoolExecutor(max_workers=1)\n\n\nasync def cache_and_return_async_session(\n    endpoint_uri: URI,\n    session: Optional[ClientSession] = None,\n) -> ClientSession:\n    # cache key should have a unique thread identifier\n    cache_key = generate_cache_key(f\"{threading.get_ident()}:{endpoint_uri}\")\n\n    evicted_items = None\n    async with async_lock(_async_session_pool, _async_session_cache_lock):\n        if cache_key not in _async_session_cache:\n            if session is None:\n                session = ClientSession(raise_for_status=True)\n\n            evicted_items = _async_session_cache.cache(cache_key, session)\n            logger.debug(f\"Async session cached: {endpoint_uri}, {session}\")\n\n        else:\n            # get the cached session\n            cached_session = _async_session_cache.get_cache_entry(cache_key)\n            session_is_closed = cached_session.closed\n            session_loop_is_closed = cached_session._loop.is_closed()\n\n            warning = (\n                \"Async session was closed\"\n                if session_is_closed\n                else \"Loop was closed for async session\"\n                if session_loop_is_closed\n                else None\n            )\n            if warning:\n                logger.debug(\n                    f\"{warning}: {endpoint_uri}, {cached_session}. \"\n                    f\"Creating and caching a new async session for uri.\"\n                )\n\n                _async_session_cache._data.pop(cache_key)\n                if not session_is_closed:\n                    # if loop was closed but not the session, close the session\n                    await cached_session.close()\n                logger.debug(\n                    f\"Async session closed and evicted from cache: {cached_session}\"\n                )\n\n                # replace stale session with a new session at the cache key\n                _session = ClientSession(raise_for_status=True)\n                evicted_items = _async_session_cache.cache(cache_key, _session)\n                logger.debug(f\"Async session cached: {endpoint_uri}, {_session}\")\n\n        # get the cached session\n        cached_session = _async_session_cache.get_cache_entry(cache_key)\n\n    if evicted_items is not None:\n        # At this point the evicted sessions are already popped out of the cache and\n        # just stored in the `evicted_sessions` dict. So we can kick off a future task\n        # to close them and it should be safe to pop out of the lock here.\n        evicted_sessions = evicted_items.values()\n        for evicted_session in evicted_sessions:\n            logger.debug(\n                \"Async session cache full. Session evicted from cache: \"\n                f\"{evicted_session}\",\n            )\n        # Kick off a future task, in a separate thread, to close the evicted\n        # sessions. In the case that the cache filled very quickly and some\n        # sessions have been evicted before their original request has been made,\n        # we set the timer to a bit more than the `DEFAULT_TIMEOUT` for a call. This\n        # should make it so that any call from an evicted session can still be made\n        # before the session is closed.\n        threading.Timer(\n            DEFAULT_TIMEOUT + 0.1,\n            _async_close_evicted_sessions,\n            args=[evicted_sessions],\n        ).start()\n\n    return cached_session\n\n\nasync def async_get_response_from_get_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> ClientResponse:\n    kwargs.setdefault(\"timeout\", ClientTimeout(DEFAULT_TIMEOUT))\n    session = await cache_and_return_async_session(endpoint_uri)\n    response = await session.get(endpoint_uri, *args, **kwargs)\n    return response\n\n\nasync def async_json_make_get_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> Dict[str, Any]:\n    response = await async_get_response_from_get_request(endpoint_uri, *args, **kwargs)\n    response.raise_for_status()\n    return await response.json()\n\n\nasync def async_get_response_from_post_request(\n    endpoint_uri: URI, *args: Any, **kwargs: Any\n) -> ClientResponse:\n    kwargs.setdefault(\"timeout\", ClientTimeout(DEFAULT_TIMEOUT))\n    session = await cache_and_return_async_session(endpoint_uri)\n    response = await session.post(endpoint_uri, *args, **kwargs)\n    return response\n\n\nasync def async_make_post_request(\n    endpoint_uri: URI, data: Union[bytes, Dict[str, Any]], *args: Any, **kwargs: Any\n) -> bytes:\n    response = await async_get_response_from_post_request(\n        endpoint_uri, data=data, *args, **kwargs\n    )\n    response.raise_for_status()\n    return await response.read()\n\n\nasync def async_get_json_from_client_response(\n    response: ClientResponse,\n) -> Dict[str, Any]:\n    return await response.json()\n\n\ndef _async_close_evicted_sessions(evicted_sessions: List[ClientSession]) -> None:\n    loop = asyncio.new_event_loop()\n\n    for evicted_session in evicted_sessions:\n        loop.run_until_complete(evicted_session.close())\n        logger.debug(f\"Closed evicted async session: {evicted_session}\")\n\n    if any(not evicted_session.closed for evicted_session in evicted_sessions):\n        logger.warning(\n            f\"Some evicted async sessions were not properly closed: {evicted_sessions}\"\n        )\n    loop.close()\n", "path": "web3/_utils/request.py"}]}