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]

Reply via email to