How to debug CompletableStage deadlocks?

こ雲淡風輕ζ 提交于 2019-12-11 05:04:04

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!