-
Notifications
You must be signed in to change notification settings - Fork 61
Dont use internal synchronicity UserCodeException type #3168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
raise exc.unwrap() | ||
|
||
|
||
async def _process_result_wrap_exceptions(result: api_pb2.GenericResult, data_format: int, stub, client=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option is to use a boolean flag to wrap exceptions, but that feels a bit more smelly to me
@@ -357,6 +356,35 @@ async def test_function_future_async(client, servicer): | |||
assert future.object_id not in servicer.cleared_function_calls # keep results around a bit longer for futures | |||
|
|||
|
|||
def test_function_spawn_exception(client, servicer): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kind of scary that we didn't have any tests for spawn or generator calls with exceptions...
@@ -533,7 +561,7 @@ def test_map_exceptions(client, servicer): | |||
|
|||
res = list(custom_function_modal.map(range(6), return_exceptions=True)) | |||
assert res[:4] == [0, 1, 4, 9] and res[5] == 25 | |||
assert type(res[4]) is UserCodeException and "bad" in str(res[4]) | |||
assert type(res[4]) is CustomException and "bad" in str(res[4]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking change!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although this is breaking, I'll say this is more of a bug fix.
or not ctx.sync_client_retries_enabled | ||
): | ||
return await self._get_single_output() | ||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only place where we actually "need" the exception wrapping (in order to detect retryable errors), so it's explicitly using _get_single_output_wrap_exceptions
and then unwrapping it in an outer try/except
Related thing I noticed - |
Nice! Just curious, how did you start down this rabbit hole? :) Was this causing a user issue? Yea, calling |
except _ContainerException as exc: | ||
raise exc.unwrap() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: In an effort to try to reduce the amount of indenting can the try
, except
be a decorator? Something like this:
def _unwrap_container_exception(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs)
except _ContainerException as exc:
raise exc.unwrap()
return wrapper
@_unwrap_container_exception
async def run_function(self) -> Any:
...
@@ -22,6 +22,19 @@ class Error(Exception): | |||
""" | |||
|
|||
|
|||
class _ContainerException(Exception): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer this name a little more:
class _ContainerException(Exception): | |
class _WrappedException(Exception): |
At a glance, I thought "Container" meant "in the runtime container".
@@ -533,7 +561,7 @@ def test_map_exceptions(client, servicer): | |||
|
|||
res = list(custom_function_modal.map(range(6), return_exceptions=True)) | |||
assert res[:4] == [0, 1, 4, 9] and res[5] == 25 | |||
assert type(res[4]) is UserCodeException and "bad" in str(res[4]) | |||
assert type(res[4]) is CustomException and "bad" in str(res[4]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although this is breaking, I'll say this is more of a bug fix.
Superseded by #3226 |
This turned out to be a bit of a rabbit hole.
map(return_exceptions=True)
has returned actual synchronicity.UserCodeException objects instead of the actual exception (!)This PR removes the usage of this internal synchronicity exception type in favor of an explicit modal-specific exception wrapper type,
_ContainerException
(open to other names). This means we also need to explicitly unwrap these whenever exceptions can be returned to users.Unfortunately, this is is a breaking change for
.map(return_exceptions=True)
in that it will no longer wrap the user exceptions... But I believe that has never actually been the intent of the functionality, and it's quite ugly that it has been returning a synchronicity exception type (!).Closes CLI-419
Compatibility checklist
Check these boxes or delete any item (or this section) if not relevant for this PR.
Note on protobuf: protobuf message changes in one place may have impact to
multiple entities (client, server, worker, database). See points above.
Release checklist
If you intend for this commit to trigger a full release to PyPI, please ensure that the following steps have been taken:
modal_version/__init__.py
) has been updated with the next logical versionChangelog