99from slack_bolt .error import BoltError
1010from slack_bolt .lazy_listener import ThreadLazyListenerRunner
1111from slack_bolt .lazy_listener .internals import build_runnable_function
12+ from slack_bolt .listener .listener_start_handler import (
13+ ListenerStartHandler ,
14+ DefaultListenerStartHandler ,
15+ )
1216from slack_bolt .listener .listener_completion_handler import (
1317 ListenerCompletionHandler ,
1418 DefaultListenerCompletionHandler ,
@@ -67,6 +71,16 @@ def release_thread_local_connections(logger: Logger, execution_timing: str):
6771 )
6872
6973
74+ class DjangoListenerStartHandler (ListenerStartHandler ):
75+ """Django sets DB connections as a thread-local variable per thread.
76+ If the thread is not managed on the Django app side, the connections won't be released by Django.
77+ This handler releases the connections every time a ThreadListenerRunner execution completes.
78+ """
79+
80+ def handle (self , request : BoltRequest , response : Optional [BoltResponse ]) -> None :
81+ release_thread_local_connections (request .context .logger , "listener-start" )
82+
83+
7084class DjangoListenerCompletionHandler (ListenerCompletionHandler ):
7185 """Django sets DB connections as a thread-local variable per thread.
7286 If the thread is not managed on the Django app side, the connections won't be released by Django.
@@ -86,6 +100,9 @@ def start(self, function: Callable[..., None], request: BoltRequest) -> None:
86100 )
87101
88102 def wrapped_func ():
103+ release_thread_local_connections (
104+ request .context .logger , "before-lazy-listener"
105+ )
89106 try :
90107 func ()
91108 finally :
@@ -117,6 +134,29 @@ def __init__(self, app: App): # type: ignore
117134 self .app .logger .debug ("App.process_before_response is set to True" )
118135 return
119136
137+ current_start_handler = listener_runner .listener_start_handler
138+ if current_start_handler is not None and not isinstance (
139+ current_start_handler , DefaultListenerStartHandler
140+ ):
141+ # As we run release_thread_local_connections() before listener executions,
142+ # it's okay to skip calling the same connection clean-up method at the listener completion.
143+ message = """As you've already set app.listener_runner.listener_start_handler to your own one,
144+ Bolt skipped to set it to slack_sdk.adapter.django.DjangoListenerStartHandler.
145+
146+ If you go with your own handler here, we highly recommend having the following lines of code
147+ in your handle() method to clean up unmanaged stale/old database connections:
148+
149+ from django.db import close_old_connections
150+ close_old_connections()
151+ """
152+ self .app .logger .info (message )
153+ else :
154+ # for proper management of thread-local Django DB connections
155+ self .app .listener_runner .listener_start_handler = (
156+ DjangoListenerStartHandler ()
157+ )
158+ self .app .logger .debug ("DjangoListenerStartHandler has been enabled" )
159+
120160 current_completion_handler = listener_runner .listener_completion_handler
121161 if current_completion_handler is not None and not isinstance (
122162 current_completion_handler , DefaultListenerCompletionHandler
@@ -145,13 +185,6 @@ def handle(self, req: HttpRequest) -> HttpResponse:
145185 bolt_resp = oauth_flow .handle_callback (to_bolt_request (req ))
146186 return to_django_response (bolt_resp )
147187 elif req .method == "POST" :
148- # As bolt-python utilizes threads for async `ack()` method execution,
149- # we have to manually clean old/stale Django ORM connections bound to the "unmanaged" threads
150- # Refer to https://github.com/slackapi/bolt-python/issues/280 for more details.
151- release_thread_local_connections (
152- self .app .logger , "before-listener-invocation"
153- )
154- # And then, run the App listener/lazy listener here
155188 bolt_resp : BoltResponse = self .app .dispatch (to_bolt_request (req ))
156189 return to_django_response (bolt_resp )
157190
0 commit comments