问题
The most difficult debugging problem I've run across recently is deadlocks between asynchronous operations. For example, given two CompletionStage
chains, where the first chain invokes a method that depends upon the completion of the second chain, and the second chain invokes a method that depends upon the completion of the first chain. It isn't this obvious in real-life because the dependency tends to be hidden and sometimes deadlocks involve more than three parties.
Part of the problem is that there is no way to find out what a CompletableStage
is waiting on. This is because an operation references a CompletableStage
, not the other way around.
Most debuggers provide some level of deadlock detection nowadays, but this only applies to threads. How does one debug deadlocks between CompletableStage chains?
回答1:
I ended up doing the following:
At the end of each
CompletionStage
chain, schedule an event that will get fired after a timeout:Set<Object> knownDeadlocks = ConcurrentHashMap.newKeySet(); // ... Future<?> deadlockListener = scope.getScheduler().schedule(() -> { if (knownDeadlocks.add(Throwables.getStackTraceAsString(context))) log.warn("Possible deadlock", context); }, DEADLOCK_DURATION.toMillis(), TimeUnit.MILLISECONDS);
Use
CompletionStage.handle()
to disable deadlockListener if the stage completes as expected:return stage.handle((value, throwable) -> { // WARNING: By design, CompletionStage.whenComplete() suppresses any exceptions thrown by its argument, so we use handle() instead. deadlockListener.cancel(false); if (throwable == null) return value; return rethrowException(throwable); });
For completeness, you also have:
/** * Rethrows a {@code Throwable}, wrapping it in {@code CompletionException} if it isn't already wrapped. * * @param <T> the return type expected by the caller * @param throwable a Throwable * @return an undefined value (the method always throws an exception) * @throws CompletionException wraps {@code throwable} */ public <T> T rethrowException(Throwable throwable) { if (throwable instanceof CompletionException) throw (CompletionException) throwable; if (throwable == null) throwable = new NullPointerException("throwable may not be null"); // According to https://stackoverflow.com/a/49261367/14731 some methods do not wrap exceptions throw new CompletionException(throwable); }
来源:https://stackoverflow.com/questions/50126257/how-to-debug-completablestage-deadlocks