|
1 | 1 |
|
2 | | -import time |
3 | 2 | import asyncio |
4 | | -import logging |
| 3 | +import threading |
5 | 4 | from . import errors |
6 | 5 | from ..base import BaseClient |
7 | 6 | from ..common import Serializers |
@@ -50,10 +49,11 @@ def delete(self, path, *, on_response=None, **kwargs): |
50 | 49 |
|
51 | 50 | def _request(self, request, *, on_response=None): |
52 | 51 | on_response = on_response if on_response else SyncResponse.new() |
53 | | - return execute_request(self._async_session, self.join_headers(request), on_response=on_response) |
| 52 | + response = execute(self._session.await_promise, self.join_headers(request), on_response=on_response) |
| 53 | + return errors.accept(response) |
54 | 54 |
|
55 | 55 | def close(self): # pylint: disable=invalid-overridden-method |
56 | | - return asyncio.get_event_loop().run_until_complete(super().close()) |
| 56 | + return execute(super().close) |
57 | 57 |
|
58 | 58 |
|
59 | 59 | class Folders(Client): |
@@ -190,52 +190,73 @@ def login(self): |
190 | 190 | return response.json() |
191 | 191 |
|
192 | 192 |
|
193 | | -def execute_request(async_session, request, *, on_response, max_retries=3, backoff_factor=2): |
194 | | - retries = 0 |
195 | | - while retries < max_retries: |
196 | | - try: |
197 | | - response = asyncio.get_event_loop().run_until_complete(async_session.await_promise(request, on_response=on_response)) |
198 | | - return errors.accept(response) |
199 | | - except (ConnectionError, TimeoutError): |
200 | | - retries += 1 |
201 | | - if retries < max_retries: |
202 | | - delay = backoff_factor ** retries |
203 | | - logging.getLogger('cterasdk.http').warning("Retrying in %s seconds.", delay) |
204 | | - time.sleep(delay) |
205 | | - else: |
206 | | - logging.getLogger('cterasdk.http').error("Max retries reached. Request failed.") |
207 | | - raise |
208 | | - return None |
| 193 | +event_loop = asyncio.new_event_loop() |
| 194 | + |
| 195 | + |
| 196 | +def execute(target, *args, **kwargs): |
| 197 | + loop = asyncio.get_event_loop() |
| 198 | + |
| 199 | + if not loop.is_running(): |
| 200 | + return loop.run_until_complete(target(*args, **kwargs)) |
| 201 | + |
| 202 | + return run_threadsafe(event_loop, target, *args, **kwargs) |
209 | 203 |
|
210 | 204 |
|
211 | 205 | class SyncResponse(AsyncResponse): |
212 | 206 | """Synchronous Response Object""" |
213 | 207 |
|
214 | | - def __init__(self, response): |
215 | | - super().__init__(response) |
216 | | - self._executor = asyncio.get_event_loop() |
217 | | - |
218 | 208 | def iter_content(self, chunk_size=None): |
219 | 209 | while True: |
220 | 210 | try: |
221 | | - yield self._executor.run_until_complete(super().async_iter_content(chunk_size).__anext__()) |
| 211 | + yield execute(super().async_iter_content(chunk_size).__anext__) |
222 | 212 | except StopAsyncIteration: |
223 | 213 | break |
224 | 214 |
|
225 | 215 | def text(self): # pylint: disable=invalid-overridden-method |
226 | | - return self._consume_response(super().text) |
| 216 | + return execute(super().text) |
227 | 217 |
|
228 | 218 | def json(self): # pylint: disable=invalid-overridden-method |
229 | | - return self._consume_response(super().json) |
| 219 | + return execute(super().json) |
230 | 220 |
|
231 | 221 | def xml(self): # pylint: disable=invalid-overridden-method |
232 | | - return self._consume_response(super().xml) |
233 | | - |
234 | | - def _consume_response(self, consumer): |
235 | | - return self._executor.run_until_complete(consumer()) |
| 222 | + return execute(super().xml) |
236 | 223 |
|
237 | 224 | @staticmethod |
238 | 225 | def new(): |
239 | 226 | async def new_response(response): |
240 | 227 | return SyncResponse(response) |
241 | 228 | return new_response |
| 229 | + |
| 230 | + |
| 231 | +def run_threadsafe(loop, target, *args, **kwargs): |
| 232 | + event = threading.Event() |
| 233 | + |
| 234 | + t = Task(loop, event, target, *args, **kwargs) |
| 235 | + t.start() |
| 236 | + |
| 237 | + event.wait() |
| 238 | + |
| 239 | + if t.exception: |
| 240 | + raise t.exception |
| 241 | + return t.response |
| 242 | + |
| 243 | + |
| 244 | +class Task(threading.Thread): |
| 245 | + |
| 246 | + def __init__(self, loop, event, target, *args, **kwargs): |
| 247 | + super().__init__(name='Thread-safe Executor') |
| 248 | + self.loop = loop |
| 249 | + self.event = event |
| 250 | + self.target = target |
| 251 | + self.args = args |
| 252 | + self.kwargs = kwargs |
| 253 | + self.exception = None |
| 254 | + self.response = None |
| 255 | + |
| 256 | + def run(self): |
| 257 | + try: |
| 258 | + self.response = self.loop.run_until_complete(self.target(*self.args, **self.kwargs)) |
| 259 | + except Exception as e: # pylint: disable=broad-exception-caught |
| 260 | + self.exception = e |
| 261 | + finally: |
| 262 | + self.event.set() |
0 commit comments