如何解决“此操作可能导致死锁”

时间:2019-10-20 11:25:25

标签: java discord deadlock

当我第二次调用sendDM()时,收到死锁警告。我该如何解决?

我正在使用JDA Api,并试图获取我用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();

        });

    }

我希望它只返回消息,但出现此错误:

[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)

2 个答案:

答案 0 :(得分:1)

只有一个回调线程用于运行回调。*

您的呼叫user.openPrivateChannel().queue((channel) -> {...}在此单个回调线程中运行。

您的调用message.addReaction("\u274C").complete();将一直阻塞,直到完成为止,但它将在同一个回调线程上运行,该线程已经在忙于要运行complete()的外部回调...问题。在后台可以检测到这一点,并且会抛出一个异常以防止发生这种死锁。

只需使用message.addReaction("\u274C").queue();即可。 queue()立即返回,以便在外部回调完成后,回调线程有机会运行它。

请参阅complete()queue()的文档。


*请参阅以下故障排除文档here

  

由于我们决定使用单线程池(1),因此只有一个线程可以执行回调。该线程由第一个回调(2)使用,而不能用于第二个回调(3)。

     

由于这个原因,我们根本根本不允许在任何回调线程中使用complete()。如果您使用回调,则应该使用queue()

     

(版权所有2015-2019 Austin Keener,Michael Ritter和FlorianSpieß)

答案 1 :(得分:0)

您应该决定只是阻塞还是异步。我建议保持异步并使用submit()以避免嵌套的回调:

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
    });

如Max Vollmer所正确指出的那样,您不能在complete()回调中使用queue(),因为它会死锁该回调的线程。 complete()通过在用于处理当前正忙于等待完成调用(正在等待队列回调完成)的回调的同一线程上运行而工作,这是一个永无休止的循环,永远不会完成。 [1]