Skip to content

Commit 2100249

Browse files
seratchfilmaj
andauthored
Change to create a WebClient per request for safety (#714)
Co-authored-by: Fil Maj <[email protected]>
1 parent 9df884e commit 2100249

File tree

2 files changed

+40
-32
lines changed

2 files changed

+40
-32
lines changed

slack_bolt/app/app.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,21 +1206,25 @@ def enable_token_revocation_listeners(self) -> None:
12061206
def _init_context(self, req: BoltRequest):
12071207
req.context["logger"] = get_bolt_app_logger(app_name=self.name, base_logger=self._base_logger)
12081208
req.context["token"] = self._token
1209-
if self._token is not None:
1210-
# This WebClient instance can be safely singleton
1211-
req.context["client"] = self._client
1212-
else:
1213-
# Set a new dedicated instance for this request
1214-
client_per_request: WebClient = WebClient(
1215-
token=None, # the token will be set later
1216-
base_url=self._client.base_url,
1217-
timeout=self._client.timeout,
1218-
ssl=self._client.ssl,
1219-
proxy=self._client.proxy,
1220-
headers=self._client.headers,
1221-
team_id=req.context.team_id,
1222-
)
1223-
req.context["client"] = client_per_request
1209+
# Prior to version 1.15, when the token is static, self._client was passed to `req.context`.
1210+
# The intention was to avoid creating a new instance per request
1211+
# in the interest of runtime performance/memory footprint optimization.
1212+
# However, developers may want to replace the token held by req.context.client in some situations.
1213+
# In this case, this behavior can result in thread-unsafe data modification on `self._client`.
1214+
# (`self._client` a.k.a. `app.client` is a singleton object per an App instance)
1215+
# Thus, we've changed the behavior to create a new instance per request regardless of token argument
1216+
# in the App initialization starting v1.15.
1217+
# The overhead brought by this change is slight so that we believe that it is ignorable in any cases.
1218+
client_per_request: WebClient = WebClient(
1219+
token=self._token, # this can be None, and it can be set later on
1220+
base_url=self._client.base_url,
1221+
timeout=self._client.timeout,
1222+
ssl=self._client.ssl,
1223+
proxy=self._client.proxy,
1224+
headers=self._client.headers,
1225+
team_id=req.context.team_id,
1226+
)
1227+
req.context["client"] = client_per_request
12241228

12251229
@staticmethod
12261230
def _to_listener_functions(

slack_bolt/app/async_app.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,23 +1240,27 @@ def enable_token_revocation_listeners(self) -> None:
12401240
def _init_context(self, req: AsyncBoltRequest):
12411241
req.context["logger"] = get_bolt_app_logger(app_name=self.name, base_logger=self._base_logger)
12421242
req.context["token"] = self._token
1243-
if self._token is not None:
1244-
# This AsyncWebClient instance can be safely singleton
1245-
req.context["client"] = self._async_client
1246-
else:
1247-
# Set a new dedicated instance for this request
1248-
client_per_request: AsyncWebClient = AsyncWebClient(
1249-
token=None, # the token will be set later
1250-
base_url=self._async_client.base_url,
1251-
timeout=self._async_client.timeout,
1252-
ssl=self._async_client.ssl,
1253-
proxy=self._async_client.proxy,
1254-
session=self._async_client.session,
1255-
trust_env_in_session=self._async_client.trust_env_in_session,
1256-
headers=self._async_client.headers,
1257-
team_id=req.context.team_id,
1258-
)
1259-
req.context["client"] = client_per_request
1243+
# Prior to version 1.15, when the token is static, self._client was passed to `req.context`.
1244+
# The intention was to avoid creating a new instance per request
1245+
# in the interest of runtime performance/memory footprint optimization.
1246+
# However, developers may want to replace the token held by req.context.client in some situations.
1247+
# In this case, this behavior can result in thread-unsafe data modification on `self._client`.
1248+
# (`self._client` a.k.a. `app.client` is a singleton object per an App instance)
1249+
# Thus, we've changed the behavior to create a new instance per request regardless of token argument
1250+
# in the App initialization starting v1.15.
1251+
# The overhead brought by this change is slight so that we believe that it is ignorable in any cases.
1252+
client_per_request: AsyncWebClient = AsyncWebClient(
1253+
token=self._token, # this can be None, and it can be set later on
1254+
base_url=self._async_client.base_url,
1255+
timeout=self._async_client.timeout,
1256+
ssl=self._async_client.ssl,
1257+
proxy=self._async_client.proxy,
1258+
session=self._async_client.session,
1259+
trust_env_in_session=self._async_client.trust_env_in_session,
1260+
headers=self._async_client.headers,
1261+
team_id=req.context.team_id,
1262+
)
1263+
req.context["client"] = client_per_request
12601264

12611265
@staticmethod
12621266
def _to_listener_functions(

0 commit comments

Comments
 (0)