diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 7104ba267f57..1720405ab867 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -34,6 +34,7 @@ TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_REST, TRANSPORT_TYPES, + ErrorCode, ) from flwr.common.exit_handlers import register_exit_handlers from flwr.common.logger import log, warn_deprecated_feature @@ -483,7 +484,7 @@ def _load_client_app() -> ClientApp: # Create an error reply message that will never be used to prevent # the used-before-assignment linting error reply_message = message.create_error_reply( - error=Error(code=0, reason="Unknown") + error=Error(code=ErrorCode.UNKNOWN, reason="Unknown") ) # Handle app loading and task message @@ -491,27 +492,41 @@ def _load_client_app() -> ClientApp: # Load ClientApp instance client_app: ClientApp = load_client_app_fn() + # Execute ClientApp reply_message = client_app(message=message, context=context) - # Update node state - node_state.update_context( - run_id=message.metadata.run_id, - context=context, - ) except Exception as ex: # pylint: disable=broad-exception-caught - log(ERROR, "ClientApp raised an exception", exc_info=ex) # Legacy grpc-bidi if transport in ["grpc-bidi", None]: + log(ERROR, "Client raised an exception.", exc_info=ex) # Raise exception, crash process raise ex # Don't update/change NodeState - # Create error message + e_code = ErrorCode.CLIENT_APP_RAISED_EXCEPTION # Reason example: ":<'division by zero'>" reason = str(type(ex)) + ":<'" + str(ex) + "'>" + exc_entity = "ClientApp" + if isinstance(ex, LoadClientAppError): + reason = ( + "An exception was raised when attempting to load " + "`ClientApp`" + ) + e_code = ErrorCode.LOAD_CLIENT_APP_EXCEPTION + exc_entity = "SuperNode" + + log(ERROR, "%s raised an exception", exc_entity, exc_info=ex) + + # Create error message reply_message = message.create_error_reply( - error=Error(code=0, reason=reason) + error=Error(code=e_code, reason=reason) + ) + else: + # No exception, update node state + node_state.update_context( + run_id=message.metadata.run_id, + context=context, ) # Send diff --git a/src/py/flwr/client/client_app.py b/src/py/flwr/client/client_app.py index 79e7720cbb8e..c9d337700147 100644 --- a/src/py/flwr/client/client_app.py +++ b/src/py/flwr/client/client_app.py @@ -28,6 +28,15 @@ from .typing import ClientAppCallable +class ClientAppException(Exception): + """Exception raised when an exception is raised while executing a ClientApp.""" + + def __init__(self, message: str): + ex_name = self.__class__.__name__ + self.message = f"\nException {ex_name} occurred. Message: " + message + super().__init__(self.message) + + class ClientApp: """Flower ClientApp. diff --git a/src/py/flwr/common/constant.py b/src/py/flwr/common/constant.py index dd100ba25d25..6a4061a72505 100644 --- a/src/py/flwr/common/constant.py +++ b/src/py/flwr/common/constant.py @@ -81,7 +81,8 @@ class ErrorCode: """Error codes for Message's Error.""" UNKNOWN = 0 - CLIENT_APP_RAISED_EXCEPTION = 1 + LOAD_CLIENT_APP_EXCEPTION = 1 + CLIENT_APP_RAISED_EXCEPTION = 2 def __new__(cls) -> ErrorCode: """Prevent instantiation.""" diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index ea74bf492ab9..9c27fca79c12 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -22,9 +22,9 @@ from logging import DEBUG, ERROR, INFO, WARN from typing import Callable, Dict, List, Optional -from flwr.client.client_app import ClientApp, LoadClientAppError +from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError from flwr.client.node_state import NodeState -from flwr.common.constant import PING_MAX_INTERVAL +from flwr.common.constant import PING_MAX_INTERVAL, ErrorCode from flwr.common.logger import log from flwr.common.message import Error from flwr.common.object_ref import load_app @@ -94,9 +94,18 @@ async def worker( except Exception as ex: # pylint: disable=broad-exception-caught log(ERROR, ex) log(ERROR, traceback.format_exc()) + + if isinstance(ex, ClientAppException): + e_code = ErrorCode.CLIENT_APP_RAISED_EXCEPTION + elif isinstance(ex, LoadClientAppError): + e_code = ErrorCode.LOAD_CLIENT_APP_EXCEPTION + else: + e_code = ErrorCode.UNKNOWN + reason = str(type(ex)) + ":<'" + str(ex) + "'>" - error = Error(code=0, reason=reason) - out_mssg = message.create_error_reply(error=error) + out_mssg = message.create_error_reply( + error=Error(code=e_code, reason=reason) + ) finally: if out_mssg: diff --git a/src/py/flwr/simulation/ray_transport/ray_actor.py b/src/py/flwr/simulation/ray_transport/ray_actor.py index 9773203628ab..9caf0fc3e6c0 100644 --- a/src/py/flwr/simulation/ray_transport/ray_actor.py +++ b/src/py/flwr/simulation/ray_transport/ray_actor.py @@ -16,7 +16,6 @@ import asyncio import threading -import traceback from abc import ABC from logging import DEBUG, ERROR, WARNING from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union @@ -25,22 +24,13 @@ from ray import ObjectRef from ray.util.actor_pool import ActorPool -from flwr.client.client_app import ClientApp, LoadClientAppError +from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError from flwr.common import Context, Message from flwr.common.logger import log ClientAppFn = Callable[[], ClientApp] -class ClientException(Exception): - """Raised when client side logic crashes with an exception.""" - - def __init__(self, message: str): - div = ">" * 7 - self.message = "\n" + div + "A ClientException occurred." + message - super().__init__(self.message) - - class VirtualClientEngineActor(ABC): """Abstract base class for VirtualClientEngine Actors.""" @@ -71,17 +61,7 @@ def run( raise load_ex except Exception as ex: - client_trace = traceback.format_exc() - mssg = ( - "\n\tSomething went wrong when running your client run." - "\n\tClient " - + cid - + " crashed when the " - + self.__class__.__name__ - + " was running its run." - "\n\tException triggered on the client side: " + client_trace, - ) - raise ClientException(str(mssg)) from ex + raise ClientAppException(str(ex)) from ex return cid, out_message, context