slackapi / java-slack-sdk

Slack Developer Kit (including Bolt for Java) for any JVM language

Home Page:https://slack.dev/java-slack-sdk/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to disable retry in Socket Mode?

akshayamaldhure opened this issue · comments

I'm trying to build a bot listener service in Java by listening to the AppMentionEvent, but I see that whenever the task takes more than 3000 milliseconds, I receive the messages multiple times.

Reproducible in:

implementation("com.slack.api:bolt-socket-mode:1.36.1")
implementation("javax.websocket:javax.websocket-api:1.1")
implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client:1.19")
implementation("org.slf4j:slf4j-simple:1.7.36")

The Slack SDK version

|    +--- com.slack.api:slack-api-client:1.1.0 -> 1.36.1
|    |    +--- com.slack.api:slack-api-model:1.36.1
+--- com.slack.api:bolt-socket-mode:1.36.1
|    +--- com.slack.api:slack-api-model:1.36.1 (*)
|    +--- com.slack.api:slack-api-client:1.36.1 (*)
|    +--- com.slack.api:slack-app-backend:1.36.1
|    |    +--- com.slack.api:slack-api-model:1.36.1 (*)
|    |    +--- com.slack.api:slack-api-client:1.36.1 (*)
|    \--- com.slack.api:bolt:1.36.1
|         +--- com.slack.api:slack-api-model:1.36.1 (*)
|         +--- com.slack.api:slack-api-client:1.36.1 (*)
|         +--- com.slack.api:slack-app-backend:1.36.1 (*)
+--- com.slack.api:bolt-socket-mode:1.36.1 (n)
|    +--- com.slack.api:slack-api-client:1.1.0 -> 1.36.1
|    |    +--- com.slack.api:slack-api-model:1.36.1
+--- com.slack.api:bolt-socket-mode:1.36.1
|    +--- com.slack.api:slack-api-model:1.36.1 (*)
|    +--- com.slack.api:slack-api-client:1.36.1 (*)
|    +--- com.slack.api:slack-app-backend:1.36.1
|    |    +--- com.slack.api:slack-api-model:1.36.1 (*)
|    |    +--- com.slack.api:slack-api-client:1.36.1 (*)
|    \--- com.slack.api:bolt:1.36.1
|         +--- com.slack.api:slack-api-model:1.36.1 (*)
|         +--- com.slack.api:slack-api-client:1.36.1 (*)
|         +--- com.slack.api:slack-app-backend:1.36.1 (*)
|    +--- com.slack.api:slack-api-client:1.1.0 -> 1.36.1
|    |    +--- com.slack.api:slack-api-model:1.36.1
+--- com.slack.api:bolt-socket-mode:1.36.1
|    +--- com.slack.api:slack-api-model:1.36.1 (*)
|    +--- com.slack.api:slack-api-client:1.36.1 (*)
|    +--- com.slack.api:slack-app-backend:1.36.1
|    |    +--- com.slack.api:slack-api-model:1.36.1 (*)
|    |    +--- com.slack.api:slack-api-client:1.36.1 (*)
|    \--- com.slack.api:bolt:1.36.1
|         +--- com.slack.api:slack-api-model:1.36.1 (*)
|         +--- com.slack.api:slack-api-client:1.36.1 (*)
|         +--- com.slack.api:slack-app-backend:1.36.1 (*)
|    +--- com.slack.api:slack-api-client:1.1.0 -> 1.36.1
|    |    +--- com.slack.api:slack-api-model:1.36.1
+--- com.slack.api:bolt-socket-mode:1.36.1
|    +--- com.slack.api:slack-api-model:1.36.1 (*)
|    +--- com.slack.api:slack-api-client:1.36.1 (*)
|    +--- com.slack.api:slack-app-backend:1.36.1
|    |    +--- com.slack.api:slack-api-model:1.36.1 (*)
|    |    +--- com.slack.api:slack-api-client:1.36.1 (*)
|    \--- com.slack.api:bolt:1.36.1
|         +--- com.slack.api:slack-api-model:1.36.1 (*)
|         +--- com.slack.api:slack-api-client:1.36.1 (*)
|         +--- com.slack.api:slack-app-backend:1.36.1 (*)

Java Runtime version

openjdk version "11.0.17" 2022-10-18 LTS

OS info

ProductName: macOS
ProductVersion: 14.1.1
BuildVersion: 23B81
Darwin Kernel Version 23.1.0: Mon Oct 9 21:27:24 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T6000

Steps to reproduce:

package slackBot;

import com.slack.api.bolt.App;
import com.slack.api.bolt.socket_mode.SocketModeApp;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
import com.slack.api.model.event.AppMentionEvent;

public class Main {

    public static void main(String[] args) throws Exception {
        // App expects an env variable: SLACK_BOT_TOKEN
        App app = new App();

        final String MESSAGE_TEXT_SEPARATOR = " ";

        app.event(AppMentionEvent.class, (payload, context) -> {
            AppMentionEvent event = payload.getEvent();
            String eventMessageText = payload.getEvent().getText();
            context.logger.info("Received message: " + eventMessageText);
            ChatPostMessageResponse replyMessage;
            String command = eventMessageText.split(MESSAGE_TEXT_SEPARATOR)[1];
            switch (command) {
                case "timeout":
                    try {
                        Thread.sleep(4000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    replyMessage = context.client().chatPostMessage(r -> r.channel(event.getChannel()).threadTs(event.getTs()).text("We have timed out."));
                    break;
                case "hi":
                    replyMessage = context.client().chatPostMessage(r -> r.channel(event.getChannel()).threadTs(event.getTs()).text("Hey there"));
                    break;
                default:
                    replyMessage = context.client().chatPostMessage(r -> r.channel(event.getChannel()).threadTs(event.getTs()).text("I don't understand what you're saying."));
                    break;
            }
            if (replyMessage == null || !replyMessage.isOk()) {
                assert replyMessage != null;
                context.logger.error("chat.postMessage failed: " + replyMessage.getError());
            } else {
                context.logger.info("Responded to the " + command + " related message");
            }
            return context.ack();
        });

        // SocketModeApp expects an env variable: SLACK_APP_TOKEN
        new SocketModeApp(app).start();
    }

}

Expected result:

I'm looking for some way to avoid the automatic retries that the Slack SDK is doing.

Actual result:

I tried to do context.getAdditionalValues().put("X-Slack-No-Retry", "1"); to no avail. I always get multiple responses in case of the timeout command I defined in my code. But that never happens for the other commands in the code.

Screenshot 2023-11-28 at 4 10 29 PM
Screenshot 2023-11-28 at 4 11 23 PM

Hi @akshayamaldhure, thanks for asking the question.

Instead of disabling the retries, we recomend using app.executorService().submit( ... ) to run time-consuming tasks in your listener. See #982 (comment) for more details.

If your app has an additional overhead outside the listener function (e.g., cold-start time when spinning up app instance), which can badly affect the response time, improving the runtime performance will be also necessary to make the app to stably function.

I hope this helps.

Hi @akshayamaldhure, thanks for asking the question.

Instead of disabling the retries, we recomend using app.executorService().submit( ... ) to run time-consuming tasks in your listener. See #982 (comment) for more details.

If your app has an additional overhead outside the listener function (e.g., cold-start time when spinning up app instance), which can badly affect the response time, improving the runtime performance will be also necessary to make the app to stably function.

I hope this helps.

Thanks @seratch for the quick response to my question. Could you please point me to a code example that uses app.executorService().submit( ... ) in Java? Basically, I'm trying to understand how app.executorService().submit( ... ) is supposed to work with AppMentionEvent.

Thanks a lot @seratch once again. Modifying my code like this helps.

package slackBot;

import com.slack.api.bolt.App;
import com.slack.api.bolt.socket_mode.SocketModeApp;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
import com.slack.api.model.event.AppMentionEvent;

public class Main {

    public static void main(String[] args) throws Exception {
        // App expects an env variable: SLACK_BOT_TOKEN
        App app = new App();

        final String MESSAGE_TEXT_SEPARATOR = " ";

        app.event(AppMentionEvent.class, (payload, context) -> {
            AppMentionEvent event = payload.getEvent();
            String eventMessageText = payload.getEvent().getText();
            context.logger.info("Received message: " + eventMessageText);
            String command = eventMessageText.split(MESSAGE_TEXT_SEPARATOR)[1];
            app.executorService().submit(() -> {
                ChatPostMessageResponse replyMessage;
                try {
                    switch (command) {
                        case "timeout":
                            try {
                                Thread.sleep(4000);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                            replyMessage = context.client().chatPostMessage(r -> r.channel(event.getChannel()).threadTs(event.getTs()).text("We have timed out."));
                            break;
                        case "hi":
                            replyMessage = context.client().chatPostMessage(r -> r.channel(event.getChannel()).threadTs(event.getTs()).text("Hey there"));
                            break;
                        default:
                            replyMessage = context.client().chatPostMessage(r -> r.channel(event.getChannel()).threadTs(event.getTs()).text("I don't understand what you're saying."));
                            break;
                    }
                    if (replyMessage == null || !replyMessage.isOk()) {
                        assert replyMessage != null;
                        context.logger.error("chat.postMessage failed: " + replyMessage.getError());
                    } else {
                        context.logger.info("Responded to the " + command + " related message");
                    }
                } catch (Exception e) {
                    context.logger.error("Something went wrong: " + e.getMessage());
                }
            });
            return context.ack();
        });

        // SocketModeApp expects an env variable: SLACK_APP_TOKEN
        new SocketModeApp(app).start();
    }

}