PDGGK commented on code in PR #37342:
URL: https://github.com/apache/beam/pull/37342#discussion_r3754444412
##########
sdks/java/io/rrio/src/main/java/org/apache/beam/io/requestresponse/Call.java:
##########
@@ -596,13 +613,10 @@ private void executeAsync(Callable<Void> callable) throws
UserCodeExecutionExcep
private static <T> void parseAndThrow(Future<T> future, ExecutionException e)
throws UserCodeExecutionException {
future.cancel(true);
- if (e.getCause() == null) {
- throw new UserCodeExecutionException(e);
- }
- Throwable cause = checkStateNotNull(e.getCause());
- if (cause instanceof UserCodeQuotaException) {
- throw new UserCodeQuotaException(cause);
+ Throwable cause = e.getCause();
+ if (cause instanceof UserCodeExecutionException) {
+ throw (UserCodeExecutionException) cause;
}
- throw new UserCodeExecutionException(cause);
+ throw new UserCodeExecutionException(cause == null ? e : cause);
Review Comment:
Good question — and worth being precise, because the unwrap is not something
this PR introduces. The code being replaced already threw `cause`, never `e`:
```java
Throwable cause = checkStateNotNull(e.getCause());
if (cause instanceof UserCodeQuotaException) {
throw new UserCodeQuotaException(cause);
}
throw new UserCodeExecutionException(cause);
```
So the `ExecutionException` wrapper that `Future.get()` adds was already
being dropped on both paths. What changes here is only *which object carries
the type*: the old code always constructed a fresh exception around `cause`,
this one rethrows `cause` itself when it already is a
`UserCodeExecutionException`.
I ran your exact scenario — user code catching and rewrapping — to check
what actually happens to the chain:
```java
try { throw new IOException("socket reset"); }
catch (IOException io) { throw new UserCodeTimeoutException(io); }
```
| | cause chain | top stack frame | `shouldRepeat()` |
| --- | --- | --- | --- |
| before | `UserCodeExecutionException → UserCodeTimeoutException →
IOException` | `parseAndThrow` | **false** |
| after | `UserCodeTimeoutException → IOException` | where the user threw |
**true** |
Nothing from user code is lost. `UserCodeTimeoutException` and the
`IOException` it wrapped are both still there, and the surviving exception is
the object the user actually constructed, so its stack trace points at their
throw site rather than at `parseAndThrow`. The only frame that disappears is
the wrapper this method used to add itself — which is also exactly what
suppressed retry, since `UserCodeExecutionException.shouldRepeat()` returns
`false` while all three subclasses return `true`.
The honest caveat: the `ExecutionException` is discarded either way, and its
stack trace is the consumer thread parked in `Future.get()`. If you would
rather keep that context, `initCause` / `addSuppressed` on the rethrown
exception would do it, but that is a pre-existing gap rather than something
this change causes — happy to add it here if you think it is worth having.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]