slackapi / bolt-python

A framework to build Slack apps using Python

Home Page:https://slack.dev/bolt-python/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Lazy Listener for Bolt Python not doing what it is supposed to

sinjachen opened this issue · comments

commented

I am trying to create a slack app that involves querying data from another service through their API and trying to host it on AWS lambda. Since querying data from another API is going to take quite a long time and slack has the 3 second time-out I've been trying to use the lazy listener, but even just copy pasting from the documentation does not really work out. What happens is that the acknowledgement goes through, but then the actual message that gets delayed by 5 seconds is not being sent. Obviously the app itself is going to be more complicated than this, but currently I am just trying to get the most basic thing to run.

Reproducible in:

pip freeze | grep slack
python --version
sw_vers && uname -v # or `ver`

The slack_bolt version

slack-bolt==1.18.1
slack-sdk==3.26.1

Python runtime version

Python 3.10.0

OS info

ProductName: macOS
ProductVersion: 14.0
BuildVersion: 23A344
Darwin Kernel Version 23.0.0: Fri Sep 15 14:41:34 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T8103

Steps to reproduce:

This is the current script I am using for my lambda function:

import os
import logging
from slack_bolt import App
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
import time

# process_before_response must be True when running on FaaS
app = App(process_before_response=True, 
          signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
          token=os.getenv("SLACK_BOT_TOKEN"))

def respond_to_slack_within_3_seconds(body, ack):
    text = body.get("text")
    if text is None or len(text) == 0:
        ack(":x: Usage: /testing this is the acknowledgement to slack")
    else:
        ack(f"Accepted! (task: {body['text']})")
    logging.info(body)
    logging.info('ack() done')
    pass

def run_long_process(respond, body):
    time.sleep(5)  # longer than 3 seconds
    respond(f"Completed! (task: {body['text']})")
    logging.info(body)
    logging.info('long run with 5 sec delay done.')

app.command("/testing")(
    ack=respond_to_slack_within_3_seconds,  # responsible for calling `ack()`
    lazy=[run_long_process]  # unable to call `ack()` / can have multiple functions
)


SlackRequestHandler.clear_all_log_handlers()
logging.basicConfig(format="%(asctime)s %(message)s", level=logging.DEBUG)

def lambda_handler(event, context):
    slack_handler = SlackRequestHandler(app=app)
    logging.info(event)
    logging.info(context)
    return slack_handler.handle(event, context)

Expected result:

I expect to receive one message informing me that my slash command was acknowledged and another one with the actual response.

Actual result:

Over slack I only receive the message that my command was acknowledged and no matter how long I wait the actual response does not come through.
Screenshot 2024-01-03 at 15 45 49
Furthermore, when I check the logs, I can also see there that my command was acknowledged but despite that the function still times out.
Screenshot 2024-01-03 at 15 49 52
Screenshot 2024-01-03 at 15 53 14
This is also the first time I am doing anything in this direction, so any help would be appreciated.

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Hi @sinjachen thanks for writing in 💯

Setting the process_before_response flag as True will simply holds off on sending an HTTP response until all the things in a listener function are done this does not change Slacks 3 second acknowledgement timeout. In other words you still need to complete your processing within 3 seconds or you will run into errors with Slack timeouts.

If you want to use FaaS for long running processes I would recommend using some sort of message broker solution like kafka, google sub/pub, amazon mq to handle these long running processes

Let me know if this helps you on your journey 🗺️

commented

Hi @WilliamBergamin
thanks for your fast response. What will it do if I set process_before_response to false? Is there a simple way to solve this issue? My understanding was that the purpose of the lazy listener function was so that longer running processes can be implemented. In my case these would take 3-4 minutes max. I'm trying to avoid having to use other services.

The purpose of process_before_response was introduced for the concept of lazy listeners that are used when running your app on FaaS or similar runtimes which do not allow you to run threads or processes after returning an HTTP response

With process_before_response set to false, calling ack() instructs the bolt framework to return an HTTP response, in theory this should also complete the FaaS process. When you set process_before_response to true this instructs bolt to delay any HTTP response until the listener completes but if the listener takes more the 3 seconds to complete you will get a Slack timeout error

If you want to keep your solution as simple as possible you can deploy it on a traditional stateful solution like an EC2 instance in this scenario you will be able to send an HTTP response as soon as you receive a request from Slack and then continue the process that could take 3-4 min

The "Task timed out after xx seconds" error indicates your default max duration for AWS Lambda function execution might be 3 seconds. I'd suggest adjusting the setting to allow longer execution. AWS Lambda allows up to 15 minutes. See also: #630 (comment)

commented

Thank you so much! That was it. It's working now :)

Great to hear!