Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
35a147a
Introduce new exception type for un-pickleable exceptions
Aug 25, 2025
1140ad4
fix lint
Aug 25, 2025
bc48190
Merge branch 'master' into unserializable-exception
sampan-s-nayak Aug 26, 2025
de3e555
address comments
Aug 26, 2025
848eac5
Merge branch 'master' into unserializable-exception
sampan-s-nayak Aug 26, 2025
9b42253
Add UnpickleableException to exceptions documentation
Aug 26, 2025
a7ad824
address comment
Aug 26, 2025
ef10264
address comment
Aug 28, 2025
702d96b
fix lint
Aug 28, 2025
5605deb
fix description
Aug 28, 2025
dae74d3
fix doc string indentation
Aug 29, 2025
5cac24d
custom exception serializer POC + test
Sep 2, 2025
03a440e
fix lint
Sep 2, 2025
3fd9c9a
clean up comments
Sep 2, 2025
132de6b
update comments and error message
Sep 2, 2025
6d457d2
fix lint
Sep 3, 2025
7abb43e
Merge branch 'master' into unserializable-exception
sampan-s-nayak Sep 3, 2025
e6f0443
minor change
Sep 3, 2025
ce53ff3
Merge remote-tracking branch 'origin/unserializable-exception' into u…
Sep 3, 2025
1e1582e
clean up code
Sep 3, 2025
da39a16
fix lint error
Sep 3, 2025
cff7b8e
Merge branch 'master' into unserializable-exception2
sampan-s-nayak Sep 4, 2025
61b285b
tweak error message
Sep 4, 2025
b469def
improve docs for unserializable exceptions
Sep 8, 2025
ef12e32
fix lint
Sep 8, 2025
fb5cea8
improve docs
Sep 8, 2025
6c3442c
Merge branch 'master' into unserializable-exception2
sampan-s-nayak Sep 8, 2025
5f70d5b
fix docs lint issue
Sep 8, 2025
51f316d
Merge remote-tracking branch 'origin/unserializable-exception2' into …
Sep 8, 2025
278c54e
address comment
Sep 9, 2025
a3ce312
address comments
Sep 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions doc/source/ray-core/objects/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,63 @@ There are at least 3 ways to define your custom serialization process:
except TypeError:
pass

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a link here by doing

.. _custom-exception-serializer

so that later on you can refer in the docstring.

.. _custom-exception-serializer:

Custom Serializers for Exceptions
----------------------------------

When Ray tasks raise exceptions that cannot be serialized with the default pickle mechanism, you can register custom serializers to handle them (Note: the serializer must be registered in the driver and all workers).

.. testcode::

import ray
import threading

class CustomError(Exception):
def __init__(self, message, data):
self.message = message
self.data = data
self.lock = threading.Lock() # Cannot be serialized

def custom_serializer(exc):
return {"message": exc.message, "data": str(exc.data)}

def custom_deserializer(state):
return CustomError(state["message"], state["data"])

# Register in the driver
ray.util.register_serializer(
CustomError,
serializer=custom_serializer,
deserializer=custom_deserializer
)

@ray.remote
def task_that_registers_serializer_and_raises():
# Register the custom serializer in the worker
ray.util.register_serializer(
CustomError,
serializer=custom_serializer,
deserializer=custom_deserializer
)

# Now raise the custom exception
raise CustomError("Something went wrong", {"complex": "data"})

# The custom exception will be properly serialized across worker boundaries
try:
ray.get(task_that_registers_serializer_and_raises.remote())
except ray.exceptions.RayTaskError as e:
print(f"Caught exception: {e.cause}") # This will be our CustomError

When a custom exception is raised in a remote task, Ray will:

1. Serialize the exception using your custom serializer
2. Wrap it in a :class:`RayTaskError <ray.exceptions.RayTaskError>`
3. The deserialized exception will be available as ``ray_task_error.cause``

Whenever serialization fails, Ray throws an :class:`UnserializableException <ray.exceptions.UnserializableException>` containing the string representation of the original stack trace.


Troubleshooting
---------------
Expand Down
4 changes: 2 additions & 2 deletions python/ray/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,7 @@ class UnserializableException(RayError):
the original exception along with its stack trace that was captured at the
time of serialization.

reference for more details: https://docs.ray.io/en/latest/ray-core/objects/serialization.html
For more details and how to handle this with custom serializers, :ref:`configuring custom exeception serializers <custom-exception-serializer>`

Args:
original_stack_trace: The string representation and stack trace of the
Expand All @@ -928,7 +928,7 @@ def __init__(self, original_stack_trace: str):

def __str__(self):
return (
"Failed to deserialize exception. Refer to https://docs.ray.io/en/latest/ray-core/objects/serialization.html#troubleshooting to troubleshoot.\n"
"Failed to deserialize exception. Refer to https://docs.ray.io/en/latest/ray-core/objects/serialization.html#custom-serializers-for-exceptions for more information.\n"
"Original exception:\n"
f"{self._original_stack_trace}"
)
Expand Down
39 changes: 38 additions & 1 deletion python/ray/tests/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def __repr__(self):


def test_unpickleable_stacktrace(shutdown_only):
expected_output = """Failed to deserialize exception. Refer to https://docs.ray.io/en/latest/ray-core/objects/serialization.html#troubleshooting to troubleshoot.
expected_output = """Failed to deserialize exception. Refer to https://docs.ray.io/en/latest/ray-core/objects/serialization.html#custom-serializers-for-exceptions for more information.
Original exception:
ray.exceptions.RayTaskError: ray::f() (pid=XXX, ip=YYY)
File "FILE", line ZZ, in f
Expand Down Expand Up @@ -330,6 +330,43 @@ def f():
assert clean_noqa(expected_output) == scrub_traceback(str(excinfo.value))


def test_exception_with_registered_serializer(shutdown_only):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file can probably be converted to using ray_start_regular_shared -- can you try? (separate PR if you prefer)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ill raise a separate pr for this

class NoPickleError(OSError):
def __init__(self, msg):
self.msg = msg

def __str__(self):
return f"message: {self.msg}"

def _serializer(e: NoPickleError):
return {"msg": e.msg}

def _deserializer(state):
return NoPickleError(state["msg"] + " deserialized")

@ray.remote
def raise_custom_exception():
ray.util.register_serializer(
NoPickleError, serializer=_serializer, deserializer=_deserializer
)
raise NoPickleError("message")

try:
with pytest.raises(NoPickleError) as exc_info:
ray.get(raise_custom_exception.remote())

# Ensure dual-typed exception and message propagation
assert isinstance(exc_info.value, RayTaskError)
# if custom serializer was not registered, this would be an instance of UnserializableException()
assert isinstance(exc_info.value, NoPickleError)
assert "message" in str(exc_info.value)
# modified message should not be in the exception string, only in the cause
assert "deserialized" not in str(exc_info.value)
assert "message deserialized" in str(exc_info.value.cause)
finally:
ray.util.deregister_serializer(NoPickleError)


def test_serialization_error_message(shutdown_only):
expected_output_ray_put = """Could not serialize the put value <unlocked _thread.lock object at ADDRESS>:\nINSPECT_SERIALIZABILITY""" # noqa
expected_output_task = """Could not serialize the argument <unlocked _thread.lock object at ADDRESS> for a task or actor test_traceback.test_serialization_error_message.<locals>.task_with_unserializable_arg:\nINSPECT_SERIALIZABILITY""" # noqa
Expand Down