Skip to content

Commit 9752198

Browse files
fix: make the function handler timeout 5 seconds
1 parent eb3a519 commit 9752198

File tree

9 files changed

+87
-7
lines changed

9 files changed

+87
-7
lines changed

slack_bolt/app/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ def reverse_string(ack: Ack, inputs: dict, complete: Complete, fail: Fail):
946946
def __call__(*args, **kwargs):
947947
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
948948
primary_matcher = builtin_matchers.function_executed(callback_id=callback_id, base_logger=self._base_logger)
949-
return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge)
949+
return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, 5)
950950

951951
return __call__
952952

@@ -1422,6 +1422,7 @@ def _register_listener(
14221422
matchers: Optional[Sequence[Callable[..., bool]]],
14231423
middleware: Optional[Sequence[Union[Callable, Middleware]]],
14241424
auto_acknowledgement: bool = False,
1425+
acknowledgement_timeout: int = 3,
14251426
) -> Optional[Callable[..., Optional[BoltResponse]]]:
14261427
value_to_return = None
14271428
if not isinstance(functions, list):
@@ -1452,6 +1453,7 @@ def _register_listener(
14521453
matchers=listener_matchers,
14531454
middleware=listener_middleware,
14541455
auto_acknowledgement=auto_acknowledgement,
1456+
acknowledgement_timeout=acknowledgement_timeout,
14551457
base_logger=self._base_logger,
14561458
)
14571459
)

slack_bolt/app/async_app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ def __call__(*args, **kwargs):
976976
primary_matcher = builtin_matchers.function_executed(
977977
callback_id=callback_id, base_logger=self._base_logger, asyncio=True
978978
)
979-
return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge)
979+
return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, 5)
980980

981981
return __call__
982982

@@ -1456,6 +1456,7 @@ def _register_listener(
14561456
matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]],
14571457
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]],
14581458
auto_acknowledgement: bool = False,
1459+
acknowledgement_timeout: int = 3,
14591460
) -> Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]:
14601461
value_to_return = None
14611462
if not isinstance(functions, list):
@@ -1491,6 +1492,7 @@ def _register_listener(
14911492
matchers=listener_matchers,
14921493
middleware=listener_middleware,
14931494
auto_acknowledgement=auto_acknowledgement,
1495+
acknowledgement_timeout=acknowledgement_timeout,
14941496
base_logger=self._base_logger,
14951497
)
14961498
)

slack_bolt/listener/async_listener.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class AsyncListener(metaclass=ABCMeta):
1515
ack_function: Callable[..., Awaitable[BoltResponse]]
1616
lazy_functions: Sequence[Callable[..., Awaitable[None]]]
1717
auto_acknowledgement: bool
18+
acknowledgement_timeout: int
1819

1920
async def async_matches(
2021
self,
@@ -87,6 +88,7 @@ class AsyncCustomListener(AsyncListener):
8788
matchers: Sequence[AsyncListenerMatcher]
8889
middleware: Sequence[AsyncMiddleware]
8990
auto_acknowledgement: bool
91+
acknowledgement_timeout: int
9092
arg_names: MutableSequence[str]
9193
logger: Logger
9294

@@ -99,6 +101,7 @@ def __init__(
99101
matchers: Sequence[AsyncListenerMatcher],
100102
middleware: Sequence[AsyncMiddleware],
101103
auto_acknowledgement: bool = False,
104+
acknowledgement_timeout: int = 3,
102105
base_logger: Optional[Logger] = None,
103106
):
104107
self.app_name = app_name
@@ -107,6 +110,7 @@ def __init__(
107110
self.matchers = matchers
108111
self.middleware = middleware
109112
self.auto_acknowledgement = auto_acknowledgement
113+
self.acknowledgement_timeout = acknowledgement_timeout
110114
self.arg_names = get_arg_names_of_callable(ack_function)
111115
self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger)
112116

slack_bolt/listener/asyncio_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ async def run_ack_function_asynchronously(
149149
self._start_lazy_function(lazy_func, request)
150150

151151
# await for the completion of ack() in the async listener execution
152-
while ack.response is None and time.time() - starting_time <= 3:
152+
while ack.response is None and time.time() - starting_time <= listener.acknowledgement_timeout:
153153
await asyncio.sleep(0.01)
154154

155155
if response is None and ack.response is None:

slack_bolt/listener/custom_listener.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class CustomListener(Listener):
1818
matchers: Sequence[ListenerMatcher]
1919
middleware: Sequence[Middleware]
2020
auto_acknowledgement: bool
21+
acknowledgement_timeout: int = 3
2122
arg_names: MutableSequence[str]
2223
logger: Logger
2324

@@ -30,6 +31,7 @@ def __init__(
3031
matchers: Sequence[ListenerMatcher],
3132
middleware: Sequence[Middleware],
3233
auto_acknowledgement: bool = False,
34+
acknowledgement_timeout: int = 3,
3335
base_logger: Optional[Logger] = None,
3436
):
3537
self.app_name = app_name
@@ -38,6 +40,7 @@ def __init__(
3840
self.matchers = matchers
3941
self.middleware = middleware
4042
self.auto_acknowledgement = auto_acknowledgement
43+
self.acknowledgement_timeout = acknowledgement_timeout
4144
self.arg_names = get_arg_names_of_callable(ack_function)
4245
self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger)
4346

slack_bolt/listener/listener.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Listener(metaclass=ABCMeta):
1313
ack_function: Callable[..., BoltResponse]
1414
lazy_functions: Sequence[Callable[..., None]]
1515
auto_acknowledgement: bool
16+
acknowledgement_timeout: int = 3
1617

1718
def matches(
1819
self,

slack_bolt/listener/thread_runner.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@ def run_ack_function_asynchronously():
160160
self._start_lazy_function(lazy_func, request)
161161

162162
# await for the completion of ack() in the async listener execution
163-
while ack.response is None and time.time() - starting_time <= 3:
163+
print(str(starting_time))
164+
while ack.response is None and time.time() - starting_time <= listener.acknowledgement_timeout:
165+
print("we are sleeping")
164166
time.sleep(0.01)
165167

166168
if response is None and ack.response is None:

tests/scenario_tests/test_function.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import time
33
import pytest
4+
from unittest.mock import Mock
45

56
from slack_sdk.signature import SignatureVerifier
67
from slack_sdk.web import WebClient
@@ -51,6 +52,10 @@ def build_request_from_body(self, message_body: dict) -> BoltRequest:
5152
timestamp, body = str(int(time.time())), json.dumps(message_body)
5253
return BoltRequest(body=body, headers=self.build_headers(timestamp, body))
5354

55+
def setup_time_mocks(self, *, monkeypatch: pytest.MonkeyPatch, time_mock: Mock, sleep_mock: Mock):
56+
monkeypatch.setattr(time, "time", time_mock)
57+
monkeypatch.setattr(time, "sleep", sleep_mock)
58+
5459
def test_valid_callback_id_success(self):
5560
app = App(
5661
client=self.web_client,
@@ -124,20 +129,48 @@ def test_auto_acknowledge_false_with_acknowledging(self):
124129
assert response.status == 200
125130
assert_auth_test_count(self, 1)
126131

127-
def test_auto_acknowledge_false_without_acknowledging(self, caplog):
132+
def test_auto_acknowledge_false_without_acknowledging(self, caplog, monkeypatch):
128133
app = App(
129134
client=self.web_client,
130135
signing_secret=self.signing_secret,
131136
)
132137
app.function("reverse", auto_acknowledge=False)(just_no_ack)
133138

134139
request = self.build_request_from_body(function_body)
140+
self.setup_time_mocks(
141+
monkeypatch=monkeypatch,
142+
time_mock=Mock(side_effect=[current_time for current_time in range(100)]),
143+
sleep_mock=Mock(),
144+
)
135145
response = app.dispatch(request)
136146

137147
assert response.status == 404
138148
assert_auth_test_count(self, 1)
139149
assert f"WARNING {just_no_ack.__name__} didn't call ack()" in caplog.text
140150

151+
def test_function_handler_timeout(self, monkeypatch):
152+
app = App(
153+
client=self.web_client,
154+
signing_secret=self.signing_secret,
155+
)
156+
app.function("reverse", auto_acknowledge=False)(just_no_ack)
157+
request = self.build_request_from_body(function_body)
158+
159+
sleep_mock = Mock()
160+
self.setup_time_mocks(
161+
monkeypatch=monkeypatch,
162+
time_mock=Mock(side_effect=[current_time for current_time in range(100)]),
163+
sleep_mock=sleep_mock,
164+
)
165+
166+
response = app.dispatch(request)
167+
168+
assert response.status == 404
169+
assert_auth_test_count(self, 1)
170+
assert (
171+
sleep_mock.call_count == 5
172+
), f"Expected handler to time out after calling time.sleep 5 times, but it was called {sleep_mock.call_count} times"
173+
141174

142175
function_body = {
143176
"token": "verification_token",

tests/scenario_tests_async/test_function.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import time
44

55
import pytest
6+
from unittest.mock import AsyncMock, Mock
67
from slack_sdk.signature import SignatureVerifier
78
from slack_sdk.web.async_client import AsyncWebClient
89

@@ -56,6 +57,10 @@ def build_request_from_body(self, message_body: dict) -> AsyncBoltRequest:
5657
timestamp, body = str(int(time.time())), json.dumps(message_body)
5758
return AsyncBoltRequest(body=body, headers=self.build_headers(timestamp, body))
5859

60+
def setup_time_mocks(self, *, monkeypatch: pytest.MonkeyPatch, time_mock: Mock, sleep_mock: AsyncMock):
61+
monkeypatch.setattr(time, "time", time_mock)
62+
monkeypatch.setattr(asyncio, "sleep", sleep_mock)
63+
5964
@pytest.mark.asyncio
6065
async def test_mock_server_is_running(self):
6166
resp = await self.web_client.api_test()
@@ -130,19 +135,47 @@ async def test_auto_acknowledge_false_with_acknowledging(self):
130135
await assert_auth_test_count_async(self, 1)
131136

132137
@pytest.mark.asyncio
133-
async def test_auto_acknowledge_false_without_acknowledging(self, caplog):
138+
async def test_auto_acknowledge_false_without_acknowledging(self, caplog, monkeypatch):
134139
app = AsyncApp(
135140
client=self.web_client,
136141
signing_secret=self.signing_secret,
137142
)
138143
app.function("reverse", auto_acknowledge=False)(just_no_ack)
139-
140144
request = self.build_request_from_body(function_body)
145+
self.setup_time_mocks(
146+
monkeypatch=monkeypatch,
147+
time_mock=Mock(side_effect=[current_time for current_time in range(100)]),
148+
sleep_mock=AsyncMock(),
149+
)
141150
response = await app.async_dispatch(request)
142151
assert response.status == 404
143152
await assert_auth_test_count_async(self, 1)
144153
assert f"WARNING {just_no_ack.__name__} didn't call ack()" in caplog.text
145154

155+
@pytest.mark.asyncio
156+
async def test_function_handler_timeout(self, monkeypatch):
157+
app = AsyncApp(
158+
client=self.web_client,
159+
signing_secret=self.signing_secret,
160+
)
161+
app.function("reverse", auto_acknowledge=False)(just_no_ack)
162+
request = self.build_request_from_body(function_body)
163+
164+
sleep_mock = AsyncMock()
165+
self.setup_time_mocks(
166+
monkeypatch=monkeypatch,
167+
time_mock=Mock(side_effect=[current_time for current_time in range(100)]),
168+
sleep_mock=sleep_mock,
169+
)
170+
171+
response = await app.async_dispatch(request)
172+
173+
assert response.status == 404
174+
await assert_auth_test_count_async(self, 1)
175+
assert (
176+
sleep_mock.call_count == 5
177+
), f"Expected handler to time out after calling time.sleep 5 times, but it was called {sleep_mock.call_count} times"
178+
146179

147180
function_body = {
148181
"token": "verification_token",

0 commit comments

Comments
 (0)