|
27 | 27 | ) |
28 | 28 | from web3.exceptions import ( |
29 | 29 | ProviderConnectionError, |
| 30 | + TimeExhausted, |
30 | 31 | Web3ValidationError, |
31 | 32 | ) |
32 | 33 | from web3.providers.persistent import ( |
| 34 | + DEFAULT_PERSISTENT_CONNECTION_TIMEOUT, |
33 | 35 | PersistentConnectionProvider, |
34 | 36 | ) |
35 | 37 | from web3.types import ( |
@@ -64,7 +66,7 @@ def __init__( |
64 | 66 | self, |
65 | 67 | endpoint_uri: Optional[Union[URI, str]] = None, |
66 | 68 | websocket_kwargs: Optional[Dict[str, Any]] = None, |
67 | | - call_timeout: Optional[int] = None, |
| 69 | + call_timeout: Optional[float] = DEFAULT_PERSISTENT_CONNECTION_TIMEOUT, |
68 | 70 | ) -> None: |
69 | 71 | self.endpoint_uri = URI(endpoint_uri) |
70 | 72 | if self.endpoint_uri is None: |
@@ -167,21 +169,40 @@ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: |
167 | 169 | return response |
168 | 170 |
|
169 | 171 | async def _get_response_for_request_id(self, request_id: RPCId) -> RPCResponse: |
170 | | - response_id = None |
171 | | - response = None |
172 | | - while response_id != request_id: |
173 | | - response = await self._ws_recv() |
174 | | - response_id = response.get("id") |
| 172 | + async def _match_response_id_to_request_id() -> RPCResponse: |
| 173 | + response_id = None |
| 174 | + response = None |
| 175 | + while response_id != request_id: |
| 176 | + response = await self._ws_recv() |
| 177 | + response_id = response.get("id") |
| 178 | + |
| 179 | + if response_id == request_id: |
| 180 | + break |
| 181 | + else: |
| 182 | + # cache all responses that are not the desired response |
| 183 | + await self._request_processor.cache_raw_response( |
| 184 | + response, |
| 185 | + ) |
| 186 | + await asyncio.sleep(0.1) |
| 187 | + |
| 188 | + return response |
175 | 189 |
|
176 | | - if response_id == request_id: |
177 | | - break |
178 | | - else: |
179 | | - # cache all responses that are not the desired response |
180 | | - await self._request_processor.cache_raw_response( |
181 | | - response, |
182 | | - ) |
183 | | - |
184 | | - return response |
| 190 | + try: |
| 191 | + # Enters a while loop, looking for a response id match to the request id. |
| 192 | + # If the provider does not give responses with matching ids, this will |
| 193 | + # hang forever. The JSON-RPC spec requires that providers respond with |
| 194 | + # the same id that was sent in the request, but we need to handle these |
| 195 | + # "bad" cases somewhat gracefully. |
| 196 | + return await asyncio.wait_for( |
| 197 | + _match_response_id_to_request_id(), self.call_timeout |
| 198 | + ) |
| 199 | + except asyncio.TimeoutError: |
| 200 | + raise TimeExhausted( |
| 201 | + f"Timed out waiting for response to request id `{request_id}` after " |
| 202 | + f"{self.call_timeout} seconds. This is likely due to the provider not " |
| 203 | + "issuing a response with the same id that was sent in the request, " |
| 204 | + "which is required by the JSON-RPC spec." |
| 205 | + ) |
185 | 206 |
|
186 | 207 | async def _ws_recv(self) -> RPCResponse: |
187 | 208 | return json.loads( |
|
0 commit comments