1

When i call sendDM() the second time i get a deadlock warning. How can i fix this?

Im using the JDA Api and im trying to get the Message which i sent with the RestAction .complete()

    private void start(){

        sendDM(getP1().getUser());
        sendDM(getP2().getUser());

        EmbedBuilder embedBuilder = new EmbedBuilder();
        textChannel.sendMessage(p1.getUser().getAsMention() + " " + p2.getUser().getAsMention()).queue();
        textChannel.sendMessage(embedBuilder.setTitle("**Match has started - #" + getGameId() + "**")
                        .addField("Info", "ID: " + getGameId() + "\nMap: 7189-0031-5500" + "\nStatus: " + getStatus().toString(),true)
                        .addField("Player 1", p1.getUser().getAsMention() + " - Host", false)
                        .addField("Player 2", p2.getUser().getAsMention(), false)
                        .addField("Lobby", textChannel.getAsMention(), false)
                        .setFooter("Both participants will receive a Private Message where they can submit match results", null).build())
                .queue();


    }

    private void sendDM(User user){

        EmbedBuilder embedBuilder = new EmbedBuilder();
        user.openPrivateChannel().queue((channel) ->
        {
            channel.sendMessage(p1.getUser().getAsMention() + " " + p2.getUser().getAsMention()).queue();
            Message message = channel.sendMessage(embedBuilder.setTitle("**Match has started - #" + getGameId() + "**")
                    .addField("Info", "ID: " + getGameId() + "\nMap: 7189-0031-5500" + "\nStatus: " + getStatus().toString(),true)
                    .addField("Player 1", p1.getUser().getAsMention() + " - Host", false)
                    .addField("Player 2", p2.getUser().getAsMention(), false)
                    .addField("Lobby", textChannel.getAsMention(), false)
                    .addField("**If you win**", ":white_check_mark:", true)
                    .addField("**If you loose**", ":x:", true)
                    .setFooter("Both participants will receive a Private Message where they can submit match results", null).build())
                    .complete();
            message.addReaction("\u274C").complete();
            message.addReaction("\u2705").complete();

        });

    }

I expect it to just return the Message but i get this Error:

[ForkJoinPool.commonPool-worker-1] ERROR net.dv8tion.jda.api.requests.RestAction - Encountered error while processing success consumer
java.lang.IllegalStateException: Preventing use of complete() in callback threads! This operation can be a deadlock cause
    at net.dv8tion.jda.internal.requests.RestActionImpl.complete(RestActionImpl.java:187)
    at net.dv8tion.jda.api.requests.RestAction.complete(RestAction.java:357)
    at match.Match.lambda$sendDM$0(Match.java:71)
    at net.dv8tion.jda.api.requests.Request.lambda$onSuccess$0(Request.java:83)
    at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

Gamal Hassan
  • 31
  • 2
  • 3

2 Answers2

1

There is only one callback thread that is used to run callbacks.*

Your call user.openPrivateChannel().queue((channel) -> {...} is run in this single callback thread.

Your call message.addReaction("\u274C").complete(); blocks until it has completed, but it would run on the same single callback thread, which is already busy running the outer callback that wants to run complete()... you see the problem. This is detected under the hood and an exception gets thrown to prevent this deadlock from happening.

Simply use message.addReaction("\u274C").queue(); instead. queue() returns immediately, so that the callback thread gets a chance to run it after your outer callback has finished.

See docs for complete() and queue().


*See these troubleshooting docs here:

Since we decided to use a single-thread pool (1) we only have one thread to execute callbacks. This thread is used by the first callback (2) and cannot be used for the second callback (3).

Due to this reason we simply don't allow using complete() in any callback threads at all. If you use callbacks you should use queue().

(Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß)

Max Vollmer
  • 8,412
  • 9
  • 28
  • 43
0

You should decide to either stick to just blocking or just asynchronous. I recommend keeping it asynchronous and using submit() to avoid nested callbacks:

user.openPrivateChannel().submit() // CompletableFuture<PrivateChannel>
    .thenCompose((channel) -> {
        channel.sendMessage(ignored).queue();
        return channel.sendMessage(embed).submit(); // CompletableFuture<Message>
    })
    .thenCompose((message) -> CompletableFuture.allOf( // combine 2 futures to 1
        message.addReaction(a).submit(),
        message.addReaction(b).submit())) // CompletableFuture<Void>
    .whenComplete((v, error) -> {
        if (error != null) error.printStackTrace(); // handle errors
    });

As correctly pointed out by Max Vollmer, you can't use complete() inside of a queue() callback because it will deadlock the thread of the callback. complete() works by running on the same thread used to handle the callback which is currently busy waiting for the complete call to finish which is waiting on the queue callback to finish, this is an endless cycle that will never be completed. [1]

Minn
  • 5,688
  • 2
  • 15
  • 42