Skip to content

Commit a7e206d

Browse files
Add suppressed exception to original cause when calling Future.sync* (#14898)
Motivation: The `Promise.sync` and `syncUninterruptibly` methods will rethrow any exception that cause the promise to fail. This gives the stack trace of the original failure, but people loose the stack trace telling which specific sync call propagated the exception. This can make debugging confusing and more difficult that necessary. We have to keep this behavior by default for compatibility, but we can add the extra details as suppressed exception and so help with debugging. Modification: Add`CompletionException` when `sync` or `syncUninterruptibly` is called as suppressed exception to the original cause. Result: Easier to debug. --------- Co-authored-by: Norman Maurer <[email protected]>
1 parent e8b0009 commit a7e206d

File tree

2 files changed

+20
-4
lines changed

2 files changed

+20
-4
lines changed

common/src/main/java/io/netty/util/concurrent/DefaultPromise.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.netty.util.internal.logging.InternalLoggerFactory;
2525

2626
import java.util.concurrent.CancellationException;
27+
import java.util.concurrent.CompletionException;
2728
import java.util.concurrent.ExecutionException;
2829
import java.util.concurrent.TimeUnit;
2930
import java.util.concurrent.TimeoutException;
@@ -33,11 +34,22 @@
3334
import static java.util.concurrent.TimeUnit.MILLISECONDS;
3435

3536
public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
37+
/**
38+
* System property with integer type value, that determine the max reentrancy/recursion level for when
39+
* listener notifications prompt other listeners to be notified.
40+
* <p>
41+
* When the reentrancy/recursion level becomes greater than this number, a new task will instead be scheduled
42+
* on the event loop, to finish notifying any subsequent listners.
43+
* <p>
44+
* The default value is {@code 8}.
45+
*/
46+
public static final String PROPERTY_MAX_LISTENER_STACK_DEPTH = "io.netty.defaultPromise.maxListenerStackDepth";
47+
3648
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultPromise.class);
3749
private static final InternalLogger rejectedExecutionLogger =
3850
InternalLoggerFactory.getInstance(DefaultPromise.class.getName() + ".rejectedExecution");
3951
private static final int MAX_LISTENER_STACK_DEPTH = Math.min(8,
40-
SystemPropertyUtil.getInt("io.netty.defaultPromise.maxListenerStackDepth", 8));
52+
SystemPropertyUtil.getInt(PROPERTY_MAX_LISTENER_STACK_DEPTH, 8));
4153
@SuppressWarnings("rawtypes")
4254
private static final AtomicReferenceFieldUpdater<DefaultPromise, Object> RESULT_UPDATER =
4355
AtomicReferenceFieldUpdater.newUpdater(DefaultPromise.class, Object.class, "result");
@@ -49,10 +61,11 @@ public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
4961

5062
private volatile Object result;
5163
private final EventExecutor executor;
64+
5265
/**
5366
* One or more listeners. Can be a {@link GenericFutureListener} or a {@link DefaultFutureListeners}.
5467
* If {@code null}, it means either 1) no listeners were added yet or 2) all listeners were notified.
55-
*
68+
* <p>
5669
* Threading - synchronized(this). We must support adding listeners when there is no EventExecutor.
5770
*/
5871
private GenericFutureListener<? extends Future<?>> listener;
@@ -70,7 +83,7 @@ public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
7083

7184
/**
7285
* Creates a new instance.
73-
*
86+
* <p>
7487
* It is preferable to use {@link EventExecutor#newPromise()} to create a new promise
7588
*
7689
* @param executor
@@ -668,6 +681,9 @@ private void rethrowIfFailed() {
668681
return;
669682
}
670683

684+
if (!(cause instanceof CancellationException) && cause.getSuppressed().length == 0) {
685+
cause.addSuppressed(new CompletionException("Rethrowing promise failure cause", null));
686+
}
671687
PlatformDependent.throwException(cause);
672688
}
673689

common/src/main/java/io/netty/util/concurrent/Future.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public interface Future<V> extends java.util.concurrent.Future<V> {
153153

154154
/**
155155
* Return the result without blocking. If the future is not done yet this will return {@code null}.
156-
*
156+
* <p>
157157
* As it is possible that a {@code null} value is used to mark the future as successful you also need to check
158158
* if the future is really done with {@link #isDone()} and not rely on the returned {@code null} value.
159159
*/

0 commit comments

Comments
 (0)