-
Notifications
You must be signed in to change notification settings - Fork 218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #1409 Socket Mode: Slow message consumption when listeners do not immediately return ack() #1411
Fix #1409 Socket Mode: Slow message consumption when listeners do not immediately return ack() #1411
Conversation
…s do not immediately return ack()
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1411 +/- ##
============================================
- Coverage 73.59% 73.33% -0.27%
- Complexity 4384 4386 +2
============================================
Files 476 476
Lines 14171 14223 +52
Branches 1430 1434 +4
============================================
+ Hits 10429 10430 +1
- Misses 2901 2950 +49
- Partials 841 843 +2 ☔ View full report in Codecov by Sentry. |
private SocketModeApp( | ||
Supplier<SocketModeClient> clientFactory, | ||
App app, | ||
ExecutorService executorService |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for hiding ExecutorService initialisation?
IMO, it would be nice to be able to provide any type of executor service. This way consumers of the library could specify service with virtual threads, for example, or something completely custom (with metrics collection, for example)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We encourage making a daemon thread one like this code does. Are you planning to use a different one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'd like to keep this parameter hidden from developers for now, but we may consider accepting ExecutorService in future updates. Thanks for your comment!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elegant implementation for a complex problem 💯
This looks good to me, left a question in a comment but should not be blocking
if (executorService != null) { | ||
// asynchronous | ||
executorService.execute(() -> runBoltApp( | ||
message, app, client, requestParser, errorHandler, gson)); | ||
} else { | ||
// synchronous | ||
runBoltApp(message, app, client, requestParser, errorHandler, gson); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would there be any shortcomings from always using an executorService
where the default concurrency = 1
Other then maintaining backwards compatible behavior is there a use case where
runBoltApp(message, app, client, requestParser, errorHandler, gson);
is preferred over
ExecutorService executorService = buildExecutorService(1);
executorService.execute(() -> runBoltApp(message, app, client, requestParser, errorHandler, gson));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion! Indeed, this approach is also an option, but for now I'd like to avoid moving the runBoltApp to a different thread, even though it's a slight difference. When we make the concurrent mode the default, simplifying the code as you suggest is worth considering.
This pull request resolves #1409 by adding a new "concurrency" option to the bolt-socket-mode and bolt-jakarta-socket-mode libraries.
The current implementation delays picking a new queued WebSocket message until the preceding operation returns an ack() WebSocket message to Slack. For context, we recommend that developers move all time-consuming tasks to
app.executorService().execute
(or a similar solution). If an app is built this way, there should be no issue with this mechanism. However, if a listener code takes a long time for a particular scenario, this situation can negatively impact the runtime performance of subsequent event handling. You can reproduce the issue with the following code snippet:With the new concurrency option, Bolt apps can resiliently handle events at a constant pace (as long as there are available threads in the thread pool).
We won't change the default behavior this time, but it's worth considering enabling concurrency by default in future releases.
Category (place an
x
in each of the[ ]
)Requirements
Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you agree to those rules.