Improve async mode exceptions handling

This commit is contained in:
Roman Mogylatov 2021-02-16 11:18:11 -05:00
parent 131373227c
commit 77b8d55203
4 changed files with 826 additions and 481 deletions

View File

@ -9,8 +9,8 @@ follows `Semantic versioning`_
Development version Development version
------------------- -------------------
- Improve async mode exceptions handling to prevent infinite hanging when exception occurs. - Improve async mode exceptions handling.
- Fix double printing of exception when resource initialization causes an error. - Fix double printing of exception when resource initialization causes an error in async mode.
4.23.1 4.23.1
------ ------

File diff suppressed because it is too large Load Diff

View File

@ -457,11 +457,12 @@ cdef inline void __async_prepare_args_kwargs_callback(
): ):
try: try:
awaited = future.result() awaited = future.result()
for value, (key, _) in zip(awaited, awaitables):
args[key] = value
except Exception as exception: except Exception as exception:
future_result.set_exception(exception) future_result.set_exception(exception)
else: else:
for value, (key, _) in zip(awaited, awaitables):
args[key] = value
future_result.set_result(args) future_result.set_result(args)
@ -502,9 +503,15 @@ cdef inline object __async_inject_attributes(future_instance, future_attributes)
cdef inline void __async_inject_attributes_callback(object future_result, object future): cdef inline void __async_inject_attributes_callback(object future_result, object future):
instance, attributes = future.result() try:
__inject_attributes(instance, attributes) instance, attributes = future.result()
future_result.set_result(instance)
for name, value in attributes.items():
setattr(instance, name, value)
except Exception as exception:
future_result.set_exception(exception)
else:
future_result.set_result(instance)
cdef inline void __inject_attributes(object instance, dict attributes): cdef inline void __inject_attributes(object instance, dict attributes):
@ -566,16 +573,14 @@ cdef inline object __call(
cdef inline void __async_call_callback(object future_result, object call, object future): cdef inline void __async_call_callback(object future_result, object call, object future):
try: try:
args, kwargs = future.result() args, kwargs = future.result()
result = call(*args, **kwargs)
except Exception as exception: except Exception as exception:
future_result.set_exception(exception) future_result.set_exception(exception)
else: else:
result = call(*args, **kwargs)
if __isawaitable(result): if __isawaitable(result):
result = asyncio.ensure_future(result) result = asyncio.ensure_future(result)
result.add_done_callback(functools.partial(__async_result_callback, future_result)) result.add_done_callback(functools.partial(__async_result_callback, future_result))
return return
future_result.set_result(result) future_result.set_result(result)

View File

@ -190,7 +190,7 @@ class FactoryTests(AsyncTestCase):
self.assertIsNot(service1.client, service2.client) self.assertIsNot(service1.client, service2.client)
def test_kwargs_error_injection(self): def test_injection_error(self):
async def init_resource(): async def init_resource():
raise Exception('Something went wrong') raise Exception('Something went wrong')
@ -209,6 +209,47 @@ class FactoryTests(AsyncTestCase):
self._run(container.client()) self._run(container.client())
self.assertEqual(str(context.exception), 'Something went wrong') self.assertEqual(str(context.exception), 'Something went wrong')
def test_injection_runtime_error_async_provides(self):
async def create_client(*args, **kwargs):
raise Exception('Something went wrong')
class Container(containers.DeclarativeContainer):
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
client = providers.Factory(
create_client,
resource1=resource,
resource2=None,
)
container = Container()
with self.assertRaises(Exception) as context:
self._run(container.client())
self.assertEqual(str(context.exception), 'Something went wrong')
def test_injection_call_error_async_provides(self):
async def create_client(): # <-- no args defined
...
class Container(containers.DeclarativeContainer):
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
client = providers.Factory(
create_client,
resource1=resource,
resource2=None,
)
container = Container()
with self.assertRaises(TypeError) as context:
self._run(container.client())
self.assertEqual(
str(context.exception),
"create_client() got an unexpected keyword argument 'resource1'",
)
def test_attributes_injection(self): def test_attributes_injection(self):
class ContainerWithAttributes(containers.DeclarativeContainer): class ContainerWithAttributes(containers.DeclarativeContainer):
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1)) resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
@ -255,6 +296,53 @@ class FactoryTests(AsyncTestCase):
self.assertIsNot(service1.client, service2.client) self.assertIsNot(service1.client, service2.client)
def test_attributes_injection_attribute_error(self):
class ClientWithException(Client):
@property
def attribute_set_error(self):
return None
@attribute_set_error.setter
def attribute_set_error(self, value):
raise Exception('Something went wrong')
class Container(containers.DeclarativeContainer):
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
client = providers.Factory(
ClientWithException,
resource1=resource,
resource2=resource,
)
client.add_attributes(attribute_set_error=123)
container = Container()
with self.assertRaises(Exception) as context:
self._run(container.client())
self.assertEqual(str(context.exception), 'Something went wrong')
def test_attributes_injection_runtime_error(self):
async def init_resource():
raise Exception('Something went wrong')
class Container(containers.DeclarativeContainer):
resource = providers.Resource(init_resource)
client = providers.Factory(
Client,
resource1=None,
resource2=None,
)
client.add_attributes(resource1=resource)
client.add_attributes(resource2=resource)
container = Container()
with self.assertRaises(Exception) as context:
self._run(container.client())
self.assertEqual(str(context.exception), 'Something went wrong')
def test_async_instance_and_sync_attributes_injection(self): def test_async_instance_and_sync_attributes_injection(self):
class ContainerWithAttributes(containers.DeclarativeContainer): class ContainerWithAttributes(containers.DeclarativeContainer):
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1)) resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))