We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
RemoteExceptionWrapper should not serialise unnecessarily.
**Is your feature request related to a problem? Please describe.**

Exceptions from tasks are wrapped in parsl's `RemoteExceptionWrapper` which is intended to be a helper that can ensure exceptions can be represented as values and moved between python runtimes.

As part of this, it always serializes exception information.

Serialization is not always necessary, but always happens: at least in the case of the local thread provider, the exception object never moves between python runtimes.

In some cases, exceptions cannot be serialised/deserialised properly: for example, the globus SDK does strange things with exception initializers (#785, #1997) which interact with the strange things RemoteExceptionWrapper is doing, but usually globus calls are made on the submit side (because the globus file staging providers run tasks locally).

**Describe the solution you'd like**
I would like RemoteExceptionWrapper to only perform the serialization/deserialization loop when necessary: when the RemoteExceptionWrapper itself is serialized/deserialized, rather than always.

**Describe alternatives you've considered**
Persuade Globus to do their exceptions differently.

**Additional context**
The only practical case that I have seen this affect things is the error handling path for globus file staging, although it is a broader theoretical problem.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `parsl/app/errors.py`
Content:
```
1 """Exceptions raised by Apps."""
2 from functools import wraps
3 from typing import Callable, List, Union, Any, TypeVar, Optional
4 from types import TracebackType
5 
6 import dill
7 import logging
8 from tblib import Traceback
9 
10 from six import reraise
11 
12 from parsl.data_provider.files import File
13 
14 logger = logging.getLogger(__name__)
15 
16 
17 class ParslError(Exception):
18     """Base class for all exceptions.
19 
20     Only to be invoked when a more specific error is not available.
21     """
22 
23 
24 class NotFutureError(ParslError):
25     """A non future item was passed to a function that expected a future.
26 
27     This is basically a type error.
28     """
29 
30 
31 class AppException(ParslError):
32     """An error raised during execution of an app.
33 
34     What this exception contains depends entirely on context
35     """
36 
37 
38 class AppBadFormatting(ParslError):
39     """An error raised during formatting of a bash function.
40     """
41 
42 
43 class BashExitFailure(AppException):
44     """A non-zero exit code returned from a @bash_app
45 
46     Contains:
47     app name (str)
48     exitcode (int)
49     """
50 
51     def __init__(self, app_name: str, exitcode: int) -> None:
52         self.app_name = app_name
53         self.exitcode = exitcode
54 
55     def __str__(self) -> str:
56         return f"bash_app {self.app_name} failed with unix exit code {self.exitcode}"
57 
58 
59 class AppTimeout(AppException):
60     """An error raised during execution of an app when it exceeds its allotted walltime.
61     """
62 
63 
64 class BashAppNoReturn(AppException):
65     """Bash app returned no string.
66 
67     Contains:
68     reason(string)
69     """
70 
71     def __init__(self, reason: str) -> None:
72         super().__init__(reason)
73         self.reason = reason
74 
75 
76 class MissingOutputs(ParslError):
77     """Error raised at the end of app execution due to missing output files.
78 
79     Contains:
80     reason(string)
81     outputs(List of strings/files..)
82     """
83 
84     def __init__(self, reason: str, outputs: List[Union[str, File]]) -> None:
85         super().__init__(reason, outputs)
86         self.reason = reason
87         self.outputs = outputs
88 
89     def __repr__(self) -> str:
90         return "Missing Outputs: {0}, Reason:{1}".format(self.outputs, self.reason)
91 
92 
93 class BadStdStreamFile(ParslError):
94     """Error raised due to bad filepaths specified for STDOUT/ STDERR.
95 
96     Contains:
97        reason(string)
98        exception object
99     """
100 
101     def __init__(self, reason: str, exception: Exception) -> None:
102         super().__init__(reason, exception)
103         self._reason = reason
104         self._exception = exception
105 
106     def __repr__(self) -> str:
107         return "Bad Stream File: {} Exception: {}".format(self._reason, self._exception)
108 
109     def __str__(self) -> str:
110         return self.__repr__()
111 
112 
113 class RemoteExceptionWrapper:
114     def __init__(self, e_type: type, e_value: BaseException, traceback: Optional[TracebackType]) -> None:
115 
116         self.e_type = dill.dumps(e_type)
117         self.e_value = dill.dumps(e_value)
118         self.e_traceback = None if traceback is None else Traceback(traceback)
119         if e_value.__cause__ is None:
120             self.cause = None
121         else:
122             cause = e_value.__cause__
123             self.cause = self.__class__(type(cause), cause, cause.__traceback__)
124 
125     def reraise(self) -> None:
126 
127         t = dill.loads(self.e_type)
128 
129         # the type is logged here before deserialising v and tb
130         # because occasionally there are problems deserialising the
131         # value (see #785, #548) and the fix is related to the
132         # specific exception type.
133         logger.debug("Reraising exception of type {}".format(t))
134 
135         v = self.get_exception()
136 
137         reraise(t, v, v.__traceback__)
138 
139     def get_exception(self) -> Exception:
140         v = dill.loads(self.e_value)
141         if self.cause is not None:
142             v.__cause__ = self.cause.get_exception()
143         if self.e_traceback is not None:
144             tb = self.e_traceback.as_traceback()
145             return v.with_traceback(tb)
146         else:
147             return v
148 
149 
150 R = TypeVar('R')
151 
152 # There appears to be no solution to typing this without a mypy plugin.
153 # The reason is because wrap_error maps a Callable[[X...], R] to a Callable[[X...], Union[R, R2]].
154 # However, there is no provision in Python typing for pattern matching all possible types of
155 # callable arguments. This is because Callable[] is, in the infinite wisdom of the typing module,
156 # only used for callbacks: "There is no syntax to indicate optional or keyword arguments; such
157 # function types are rarely used as callback types.".
158 # The alternative supported by the typing module, of saying Callable[..., R] ->
159 #   Callable[..., Union[R, R2]] results in no pattern matching between the first and second
160 # ellipsis.
161 # Yet another bogus solution that was here previously would simply define wrap_error as
162 #   wrap_error(T) -> T, where T was a custom TypeVar. This obviously missed the fact that
163 # the returned function had its return signature modified.
164 # Ultimately, the best choice appears to be Callable[..., R] -> Callable[..., Union[R, ?Exception]],
165 #  since it results in the correct type specification for the return value(s) while treating the
166 #  arguments as Any.
167 
168 
169 def wrap_error(func: Callable[..., R]) -> Callable[..., Union[R, RemoteExceptionWrapper]]:
170     @wraps(func)  # type: ignore
171     def wrapper(*args: object, **kwargs: object) -> Any:
172         import sys
173         from parsl.app.errors import RemoteExceptionWrapper
174         try:
175             return func(*args, **kwargs)  # type: ignore
176         except Exception:
177             return RemoteExceptionWrapper(*sys.exc_info())
178     return wrapper  # type: ignore
179
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/parsl/app/errors.py b/parsl/app/errors.py
--- a/parsl/app/errors.py
+++ b/parsl/app/errors.py
@@ -2,8 +2,6 @@
 from functools import wraps
 from typing import Callable, List, Union, Any, TypeVar, Optional
 from types import TracebackType
-
-import dill
 import logging
 from tblib import Traceback
 
@@ -113,8 +111,8 @@
 class RemoteExceptionWrapper:
     def __init__(self, e_type: type, e_value: BaseException, traceback: Optional[TracebackType]) -> None:
 
-        self.e_type = dill.dumps(e_type)
-        self.e_value = dill.dumps(e_value)
+        self.e_type = e_type
+        self.e_value = e_value
         self.e_traceback = None if traceback is None else Traceback(traceback)
         if e_value.__cause__ is None:
             self.cause = None
@@ -124,20 +122,20 @@
 
     def reraise(self) -> None:
 
-        t = dill.loads(self.e_type)
+        t = self.e_type
 
         # the type is logged here before deserialising v and tb
         # because occasionally there are problems deserialising the
         # value (see #785, #548) and the fix is related to the
         # specific exception type.
-        logger.debug("Reraising exception of type {}".format(t))
+        logger.debug("Reraising exception of type {}".format(self.e_type))
 
         v = self.get_exception()
 
         reraise(t, v, v.__traceback__)
 
-    def get_exception(self) -> Exception:
-        v = dill.loads(self.e_value)
+    def get_exception(self) -> BaseException:
+        v = self.e_value
         if self.cause is not None:
             v.__cause__ = self.cause.get_exception()
         if self.e_traceback is not None:

{"golden_diff": "diff --git a/parsl/app/errors.py b/parsl/app/errors.py\n--- a/parsl/app/errors.py\n+++ b/parsl/app/errors.py\n@@ -2,8 +2,6 @@\n from functools import wraps\n from typing import Callable, List, Union, Any, TypeVar, Optional\n from types import TracebackType\n-\n-import dill\n import logging\n from tblib import Traceback\n \n@@ -113,8 +111,8 @@\n class RemoteExceptionWrapper:\n     def __init__(self, e_type: type, e_value: BaseException, traceback: Optional[TracebackType]) -> None:\n \n-        self.e_type = dill.dumps(e_type)\n-        self.e_value = dill.dumps(e_value)\n+        self.e_type = e_type\n+        self.e_value = e_value\n         self.e_traceback = None if traceback is None else Traceback(traceback)\n         if e_value.__cause__ is None:\n             self.cause = None\n@@ -124,20 +122,20 @@\n \n     def reraise(self) -> None:\n \n-        t = dill.loads(self.e_type)\n+        t = self.e_type\n \n         # the type is logged here before deserialising v and tb\n         # because occasionally there are problems deserialising the\n         # value (see #785, #548) and the fix is related to the\n         # specific exception type.\n-        logger.debug(\"Reraising exception of type {}\".format(t))\n+        logger.debug(\"Reraising exception of type {}\".format(self.e_type))\n \n         v = self.get_exception()\n \n         reraise(t, v, v.__traceback__)\n \n-    def get_exception(self) -> Exception:\n-        v = dill.loads(self.e_value)\n+    def get_exception(self) -> BaseException:\n+        v = self.e_value\n         if self.cause is not None:\n             v.__cause__ = self.cause.get_exception()\n         if self.e_traceback is not None:\n", "issue": "RemoteExceptionWrapper should not serialise unnecessarily.\n**Is your feature request related to a problem? Please describe.**\r\n\r\nExceptions from tasks are wrapped in parsl's `RemoteExceptionWrapper` which is intended to be a helper that can ensure exceptions can be represented as values and moved between python runtimes.\r\n\r\nAs part of this, it always serializes exception information.\r\n\r\nSerialization is not always necessary, but always happens: at least in the case of the local thread provider, the exception object never moves between python runtimes.\r\n\r\nIn some cases, exceptions cannot be serialised/deserialised properly: for example, the globus SDK does strange things with exception initializers (#785, #1997) which interact with the strange things RemoteExceptionWrapper is doing, but usually globus calls are made on the submit side (because the globus file staging providers run tasks locally).\r\n\r\n**Describe the solution you'd like**\r\nI would like RemoteExceptionWrapper to only perform the serialization/deserialization loop when necessary: when the RemoteExceptionWrapper itself is serialized/deserialized, rather than always.\r\n\r\n**Describe alternatives you've considered**\r\nPersuade Globus to do their exceptions differently.\r\n\r\n**Additional context**\r\nThe only practical case that I have seen this affect things is the error handling path for globus file staging, although it is a broader theoretical problem.\n", "before_files": [{"content": "\"\"\"Exceptions raised by Apps.\"\"\"\nfrom functools import wraps\nfrom typing import Callable, List, Union, Any, TypeVar, Optional\nfrom types import TracebackType\n\nimport dill\nimport logging\nfrom tblib import Traceback\n\nfrom six import reraise\n\nfrom parsl.data_provider.files import File\n\nlogger = logging.getLogger(__name__)\n\n\nclass ParslError(Exception):\n    \"\"\"Base class for all exceptions.\n\n    Only to be invoked when a more specific error is not available.\n    \"\"\"\n\n\nclass NotFutureError(ParslError):\n    \"\"\"A non future item was passed to a function that expected a future.\n\n    This is basically a type error.\n    \"\"\"\n\n\nclass AppException(ParslError):\n    \"\"\"An error raised during execution of an app.\n\n    What this exception contains depends entirely on context\n    \"\"\"\n\n\nclass AppBadFormatting(ParslError):\n    \"\"\"An error raised during formatting of a bash function.\n    \"\"\"\n\n\nclass BashExitFailure(AppException):\n    \"\"\"A non-zero exit code returned from a @bash_app\n\n    Contains:\n    app name (str)\n    exitcode (int)\n    \"\"\"\n\n    def __init__(self, app_name: str, exitcode: int) -> None:\n        self.app_name = app_name\n        self.exitcode = exitcode\n\n    def __str__(self) -> str:\n        return f\"bash_app {self.app_name} failed with unix exit code {self.exitcode}\"\n\n\nclass AppTimeout(AppException):\n    \"\"\"An error raised during execution of an app when it exceeds its allotted walltime.\n    \"\"\"\n\n\nclass BashAppNoReturn(AppException):\n    \"\"\"Bash app returned no string.\n\n    Contains:\n    reason(string)\n    \"\"\"\n\n    def __init__(self, reason: str) -> None:\n        super().__init__(reason)\n        self.reason = reason\n\n\nclass MissingOutputs(ParslError):\n    \"\"\"Error raised at the end of app execution due to missing output files.\n\n    Contains:\n    reason(string)\n    outputs(List of strings/files..)\n    \"\"\"\n\n    def __init__(self, reason: str, outputs: List[Union[str, File]]) -> None:\n        super().__init__(reason, outputs)\n        self.reason = reason\n        self.outputs = outputs\n\n    def __repr__(self) -> str:\n        return \"Missing Outputs: {0}, Reason:{1}\".format(self.outputs, self.reason)\n\n\nclass BadStdStreamFile(ParslError):\n    \"\"\"Error raised due to bad filepaths specified for STDOUT/ STDERR.\n\n    Contains:\n       reason(string)\n       exception object\n    \"\"\"\n\n    def __init__(self, reason: str, exception: Exception) -> None:\n        super().__init__(reason, exception)\n        self._reason = reason\n        self._exception = exception\n\n    def __repr__(self) -> str:\n        return \"Bad Stream File: {} Exception: {}\".format(self._reason, self._exception)\n\n    def __str__(self) -> str:\n        return self.__repr__()\n\n\nclass RemoteExceptionWrapper:\n    def __init__(self, e_type: type, e_value: BaseException, traceback: Optional[TracebackType]) -> None:\n\n        self.e_type = dill.dumps(e_type)\n        self.e_value = dill.dumps(e_value)\n        self.e_traceback = None if traceback is None else Traceback(traceback)\n        if e_value.__cause__ is None:\n            self.cause = None\n        else:\n            cause = e_value.__cause__\n            self.cause = self.__class__(type(cause), cause, cause.__traceback__)\n\n    def reraise(self) -> None:\n\n        t = dill.loads(self.e_type)\n\n        # the type is logged here before deserialising v and tb\n        # because occasionally there are problems deserialising the\n        # value (see #785, #548) and the fix is related to the\n        # specific exception type.\n        logger.debug(\"Reraising exception of type {}\".format(t))\n\n        v = self.get_exception()\n\n        reraise(t, v, v.__traceback__)\n\n    def get_exception(self) -> Exception:\n        v = dill.loads(self.e_value)\n        if self.cause is not None:\n            v.__cause__ = self.cause.get_exception()\n        if self.e_traceback is not None:\n            tb = self.e_traceback.as_traceback()\n            return v.with_traceback(tb)\n        else:\n            return v\n\n\nR = TypeVar('R')\n\n# There appears to be no solution to typing this without a mypy plugin.\n# The reason is because wrap_error maps a Callable[[X...], R] to a Callable[[X...], Union[R, R2]].\n# However, there is no provision in Python typing for pattern matching all possible types of\n# callable arguments. This is because Callable[] is, in the infinite wisdom of the typing module,\n# only used for callbacks: \"There is no syntax to indicate optional or keyword arguments; such\n# function types are rarely used as callback types.\".\n# The alternative supported by the typing module, of saying Callable[..., R] ->\n#   Callable[..., Union[R, R2]] results in no pattern matching between the first and second\n# ellipsis.\n# Yet another bogus solution that was here previously would simply define wrap_error as\n#   wrap_error(T) -> T, where T was a custom TypeVar. This obviously missed the fact that\n# the returned function had its return signature modified.\n# Ultimately, the best choice appears to be Callable[..., R] -> Callable[..., Union[R, ?Exception]],\n#  since it results in the correct type specification for the return value(s) while treating the\n#  arguments as Any.\n\n\ndef wrap_error(func: Callable[..., R]) -> Callable[..., Union[R, RemoteExceptionWrapper]]:\n    @wraps(func)  # type: ignore\n    def wrapper(*args: object, **kwargs: object) -> Any:\n        import sys\n        from parsl.app.errors import RemoteExceptionWrapper\n        try:\n            return func(*args, **kwargs)  # type: ignore\n        except Exception:\n            return RemoteExceptionWrapper(*sys.exc_info())\n    return wrapper  # type: ignore\n", "path": "parsl/app/errors.py"}], "after_files": [{"content": "\"\"\"Exceptions raised by Apps.\"\"\"\nfrom functools import wraps\nfrom typing import Callable, List, Union, Any, TypeVar, Optional\nfrom types import TracebackType\nimport logging\nfrom tblib import Traceback\n\nfrom six import reraise\n\nfrom parsl.data_provider.files import File\n\nlogger = logging.getLogger(__name__)\n\n\nclass ParslError(Exception):\n    \"\"\"Base class for all exceptions.\n\n    Only to be invoked when a more specific error is not available.\n    \"\"\"\n\n\nclass NotFutureError(ParslError):\n    \"\"\"A non future item was passed to a function that expected a future.\n\n    This is basically a type error.\n    \"\"\"\n\n\nclass AppException(ParslError):\n    \"\"\"An error raised during execution of an app.\n\n    What this exception contains depends entirely on context\n    \"\"\"\n\n\nclass AppBadFormatting(ParslError):\n    \"\"\"An error raised during formatting of a bash function.\n    \"\"\"\n\n\nclass BashExitFailure(AppException):\n    \"\"\"A non-zero exit code returned from a @bash_app\n\n    Contains:\n    app name (str)\n    exitcode (int)\n    \"\"\"\n\n    def __init__(self, app_name: str, exitcode: int) -> None:\n        self.app_name = app_name\n        self.exitcode = exitcode\n\n    def __str__(self) -> str:\n        return f\"bash_app {self.app_name} failed with unix exit code {self.exitcode}\"\n\n\nclass AppTimeout(AppException):\n    \"\"\"An error raised during execution of an app when it exceeds its allotted walltime.\n    \"\"\"\n\n\nclass BashAppNoReturn(AppException):\n    \"\"\"Bash app returned no string.\n\n    Contains:\n    reason(string)\n    \"\"\"\n\n    def __init__(self, reason: str) -> None:\n        super().__init__(reason)\n        self.reason = reason\n\n\nclass MissingOutputs(ParslError):\n    \"\"\"Error raised at the end of app execution due to missing output files.\n\n    Contains:\n    reason(string)\n    outputs(List of strings/files..)\n    \"\"\"\n\n    def __init__(self, reason: str, outputs: List[Union[str, File]]) -> None:\n        super().__init__(reason, outputs)\n        self.reason = reason\n        self.outputs = outputs\n\n    def __repr__(self) -> str:\n        return \"Missing Outputs: {0}, Reason:{1}\".format(self.outputs, self.reason)\n\n\nclass BadStdStreamFile(ParslError):\n    \"\"\"Error raised due to bad filepaths specified for STDOUT/ STDERR.\n\n    Contains:\n       reason(string)\n       exception object\n    \"\"\"\n\n    def __init__(self, reason: str, exception: Exception) -> None:\n        super().__init__(reason, exception)\n        self._reason = reason\n        self._exception = exception\n\n    def __repr__(self) -> str:\n        return \"Bad Stream File: {} Exception: {}\".format(self._reason, self._exception)\n\n    def __str__(self) -> str:\n        return self.__repr__()\n\n\nclass RemoteExceptionWrapper:\n    def __init__(self, e_type: type, e_value: BaseException, traceback: Optional[TracebackType]) -> None:\n\n        self.e_type = e_type\n        self.e_value = e_value\n        self.e_traceback = None if traceback is None else Traceback(traceback)\n        if e_value.__cause__ is None:\n            self.cause = None\n        else:\n            cause = e_value.__cause__\n            self.cause = self.__class__(type(cause), cause, cause.__traceback__)\n\n    def reraise(self) -> None:\n\n        t = self.e_type\n\n        # the type is logged here before deserialising v and tb\n        # because occasionally there are problems deserialising the\n        # value (see #785, #548) and the fix is related to the\n        # specific exception type.\n        logger.debug(\"Reraising exception of type {}\".format(self.e_type))\n\n        v = self.get_exception()\n\n        reraise(t, v, v.__traceback__)\n\n    def get_exception(self) -> BaseException:\n        v = self.e_value\n        if self.cause is not None:\n            v.__cause__ = self.cause.get_exception()\n        if self.e_traceback is not None:\n            tb = self.e_traceback.as_traceback()\n            return v.with_traceback(tb)\n        else:\n            return v\n\n\nR = TypeVar('R')\n\n# There appears to be no solution to typing this without a mypy plugin.\n# The reason is because wrap_error maps a Callable[[X...], R] to a Callable[[X...], Union[R, R2]].\n# However, there is no provision in Python typing for pattern matching all possible types of\n# callable arguments. This is because Callable[] is, in the infinite wisdom of the typing module,\n# only used for callbacks: \"There is no syntax to indicate optional or keyword arguments; such\n# function types are rarely used as callback types.\".\n# The alternative supported by the typing module, of saying Callable[..., R] ->\n#   Callable[..., Union[R, R2]] results in no pattern matching between the first and second\n# ellipsis.\n# Yet another bogus solution that was here previously would simply define wrap_error as\n#   wrap_error(T) -> T, where T was a custom TypeVar. This obviously missed the fact that\n# the returned function had its return signature modified.\n# Ultimately, the best choice appears to be Callable[..., R] -> Callable[..., Union[R, ?Exception]],\n#  since it results in the correct type specification for the return value(s) while treating the\n#  arguments as Any.\n\n\ndef wrap_error(func: Callable[..., R]) -> Callable[..., Union[R, RemoteExceptionWrapper]]:\n    @wraps(func)  # type: ignore\n    def wrapper(*args: object, **kwargs: object) -> Any:\n        import sys\n        from parsl.app.errors import RemoteExceptionWrapper\n        try:\n            return func(*args, **kwargs)  # type: ignore\n        except Exception:\n            return RemoteExceptionWrapper(*sys.exc_info())\n    return wrapper  # type: ignore\n", "path": "parsl/app/errors.py"}]}