@@ -37,6 +37,10 @@ <h1 class="title">Module <code>slack_bolt.adapter.django.handler</code></h1>
3737from slack_bolt.error import BoltError
3838from slack_bolt.lazy_listener import ThreadLazyListenerRunner
3939from slack_bolt.lazy_listener.internals import build_runnable_function
40+ from slack_bolt.listener.listener_start_handler import (
41+ ListenerStartHandler,
42+ DefaultListenerStartHandler,
43+ )
4044from slack_bolt.listener.listener_completion_handler import (
4145 ListenerCompletionHandler,
4246 DefaultListenerCompletionHandler,
@@ -82,26 +86,37 @@ <h1 class="title">Module <code>slack_bolt.adapter.django.handler</code></h1>
8286 return resp
8387
8488
85- from django.db import connections
89+ from django.db import close_old_connections
8690
8791
88- def release_thread_local_connections(logger: Logger, execution_type : str):
89- connections.close_all ()
92+ def release_thread_local_connections(logger: Logger, execution_timing : str):
93+ close_old_connections ()
9094 if logger.level <= logging.DEBUG:
9195 current: Thread = current_thread()
9296 logger.debug(
93- f"Released thread-bound DB connections (thread name: {current.name}, execution type: {execution_type})"
97+ "Released thread-bound old DB connections "
98+ f"(thread name: {current.name}, execution timing: {execution_timing})"
9499 )
95100
96101
102+ class DjangoListenerStartHandler(ListenerStartHandler):
103+ """Django sets DB connections as a thread-local variable per thread.
104+ If the thread is not managed on the Django app side, the connections won't be released by Django.
105+ This handler releases the connections every time a ThreadListenerRunner execution completes.
106+ """
107+
108+ def handle(self, request: BoltRequest, response: Optional[BoltResponse]) -> None:
109+ release_thread_local_connections(request.context.logger, "listener-start")
110+
111+
97112class DjangoListenerCompletionHandler(ListenerCompletionHandler):
98113 """Django sets DB connections as a thread-local variable per thread.
99114 If the thread is not managed on the Django app side, the connections won't be released by Django.
100115 This handler releases the connections every time a ThreadListenerRunner execution completes.
101116 """
102117
103118 def handle(self, request: BoltRequest, response: Optional[BoltResponse]) -> None:
104- release_thread_local_connections(request.context.logger, "listener")
119+ release_thread_local_connections(request.context.logger, "listener-completion ")
105120
106121
107122class DjangoThreadLazyListenerRunner(ThreadLazyListenerRunner):
@@ -113,11 +128,14 @@ <h1 class="title">Module <code>slack_bolt.adapter.django.handler</code></h1>
113128 )
114129
115130 def wrapped_func():
131+ release_thread_local_connections(
132+ request.context.logger, "before-lazy-listener"
133+ )
116134 try:
117135 func()
118136 finally:
119137 release_thread_local_connections(
120- request.context.logger, "lazy-listener"
138+ request.context.logger, "lazy-listener-completion "
121139 )
122140
123141 self.executor.submit(wrapped_func)
@@ -144,18 +162,39 @@ <h1 class="title">Module <code>slack_bolt.adapter.django.handler</code></h1>
144162 self.app.logger.debug("App.process_before_response is set to True")
145163 return
146164
165+ current_start_handler = listener_runner.listener_start_handler
166+ if current_start_handler is not None and not isinstance(
167+ current_start_handler, DefaultListenerStartHandler
168+ ):
169+ # As we run release_thread_local_connections() before listener executions,
170+ # it's okay to skip calling the same connection clean-up method at the listener completion.
171+ message = """As you've already set app.listener_runner.listener_start_handler to your own one,
172+ Bolt skipped to set it to slack_sdk.adapter.django.DjangoListenerStartHandler.
173+
174+ If you go with your own handler here, we highly recommend having the following lines of code
175+ in your handle() method to clean up unmanaged stale/old database connections:
176+
177+ from django.db import close_old_connections
178+ close_old_connections()
179+ """
180+ self.app.logger.info(message)
181+ else:
182+ # for proper management of thread-local Django DB connections
183+ self.app.listener_runner.listener_start_handler = (
184+ DjangoListenerStartHandler()
185+ )
186+ self.app.logger.debug("DjangoListenerStartHandler has been enabled")
187+
147188 current_completion_handler = listener_runner.listener_completion_handler
148189 if current_completion_handler is not None and not isinstance(
149190 current_completion_handler, DefaultListenerCompletionHandler
150191 ):
192+ # As we run release_thread_local_connections() before listener executions,
193+ # it's okay to skip calling the same connection clean-up method at the listener completion.
151194 message = """As you've already set app.listener_runner.listener_completion_handler to your own one,
152195 Bolt skipped to set it to slack_sdk.adapter.django.DjangoListenerCompletionHandler.
153- We strongly recommend having the following lines of code in your listener_completion_handler:
154-
155- from django.db import connections
156- connections.close_all()
157196 """
158- self.app.logger.warning (message)
197+ self.app.logger.info (message)
159198 return
160199 # for proper management of thread-local Django DB connections
161200 self.app.listener_runner.listener_completion_handler = (
@@ -188,20 +227,21 @@ <h1 class="title">Module <code>slack_bolt.adapter.django.handler</code></h1>
188227< h2 class ="section-title " id ="header-functions "> Functions</ h2 >
189228< dl >
190229< dt id ="slack_bolt.adapter.django.handler.release_thread_local_connections "> < code class ="name flex ">
191- < span > def < span class ="ident "> release_thread_local_connections</ span > </ span > (< span > logger: logging.Logger, execution_type : str)</ span >
230+ < span > def < span class ="ident "> release_thread_local_connections</ span > </ span > (< span > logger: logging.Logger, execution_timing : str)</ span >
192231</ code > </ dt >
193232< dd >
194233< div class ="desc "> </ div >
195234< details class ="source ">
196235< summary >
197236< span > Expand source code</ span >
198237</ summary >
199- < pre > < code class ="python "> def release_thread_local_connections(logger: Logger, execution_type : str):
200- connections.close_all ()
238+ < pre > < code class ="python "> def release_thread_local_connections(logger: Logger, execution_timing : str):
239+ close_old_connections ()
201240 if logger.level <= logging.DEBUG:
202241 current: Thread = current_thread()
203242 logger.debug(
204- f"Released thread-bound DB connections (thread name: {current.name}, execution type: {execution_type})"
243+ "Released thread-bound old DB connections "
244+ f"(thread name: {current.name}, execution timing: {execution_timing})"
205245 )</ code > </ pre >
206246</ details >
207247</ dd >
@@ -281,7 +321,7 @@ <h2 class="section-title" id="header-classes">Classes</h2>
281321 """
282322
283323 def handle(self, request: BoltRequest, response: Optional[BoltResponse]) -> None:
284- release_thread_local_connections(request.context.logger, "listener")</ code > </ pre >
324+ release_thread_local_connections(request.context.logger, "listener-completion ")</ code > </ pre >
285325</ details >
286326< h3 > Ancestors</ h3 >
287327< ul class ="hlist ">
@@ -296,6 +336,39 @@ <h3>Inherited members</h3>
296336</ li >
297337</ ul >
298338</ dd >
339+ < dt id ="slack_bolt.adapter.django.handler.DjangoListenerStartHandler "> < code class ="flex name class ">
340+ < span > class < span class ="ident "> DjangoListenerStartHandler</ span > </ span >
341+ </ code > </ dt >
342+ < dd >
343+ < div class ="desc "> < p > Django sets DB connections as a thread-local variable per thread.
344+ If the thread is not managed on the Django app side, the connections won't be released by Django.
345+ This handler releases the connections every time a ThreadListenerRunner execution completes.</ p > </ div >
346+ < details class ="source ">
347+ < summary >
348+ < span > Expand source code</ span >
349+ </ summary >
350+ < pre > < code class ="python "> class DjangoListenerStartHandler(ListenerStartHandler):
351+ """Django sets DB connections as a thread-local variable per thread.
352+ If the thread is not managed on the Django app side, the connections won't be released by Django.
353+ This handler releases the connections every time a ThreadListenerRunner execution completes.
354+ """
355+
356+ def handle(self, request: BoltRequest, response: Optional[BoltResponse]) -> None:
357+ release_thread_local_connections(request.context.logger, "listener-start")</ code > </ pre >
358+ </ details >
359+ < h3 > Ancestors</ h3 >
360+ < ul class ="hlist ">
361+ < li > < a title ="slack_bolt.listener.listener_start_handler.ListenerStartHandler " href ="../../listener/listener_start_handler.html#slack_bolt.listener.listener_start_handler.ListenerStartHandler "> ListenerStartHandler</ a > </ li >
362+ </ ul >
363+ < h3 > Inherited members</ h3 >
364+ < ul class ="hlist ">
365+ < li > < code > < b > < a title ="slack_bolt.listener.listener_start_handler.ListenerStartHandler " href ="../../listener/listener_start_handler.html#slack_bolt.listener.listener_start_handler.ListenerStartHandler "> ListenerStartHandler</ a > </ b > </ code > :
366+ < ul class ="hlist ">
367+ < li > < code > < a title ="slack_bolt.listener.listener_start_handler.ListenerStartHandler.handle " href ="../../listener/listener_start_handler.html#slack_bolt.listener.listener_start_handler.ListenerStartHandler.handle "> handle</ a > </ code > </ li >
368+ </ ul >
369+ </ li >
370+ </ ul >
371+ </ dd >
299372< dt id ="slack_bolt.adapter.django.handler.DjangoThreadLazyListenerRunner "> < code class ="flex name class ">
300373< span > class < span class ="ident "> DjangoThreadLazyListenerRunner</ span > </ span >
301374< span > (</ span > < span > logger: logging.Logger, executor: concurrent.futures._base.Executor)</ span >
@@ -315,11 +388,14 @@ <h3>Inherited members</h3>
315388 )
316389
317390 def wrapped_func():
391+ release_thread_local_connections(
392+ request.context.logger, "before-lazy-listener"
393+ )
318394 try:
319395 func()
320396 finally:
321397 release_thread_local_connections(
322- request.context.logger, "lazy-listener"
398+ request.context.logger, "lazy-listener-completion "
323399 )
324400
325401 self.executor.submit(wrapped_func)</ code > </ pre >
@@ -377,18 +453,39 @@ <h3>Inherited members</h3>
377453 self.app.logger.debug("App.process_before_response is set to True")
378454 return
379455
456+ current_start_handler = listener_runner.listener_start_handler
457+ if current_start_handler is not None and not isinstance(
458+ current_start_handler, DefaultListenerStartHandler
459+ ):
460+ # As we run release_thread_local_connections() before listener executions,
461+ # it's okay to skip calling the same connection clean-up method at the listener completion.
462+ message = """As you've already set app.listener_runner.listener_start_handler to your own one,
463+ Bolt skipped to set it to slack_sdk.adapter.django.DjangoListenerStartHandler.
464+
465+ If you go with your own handler here, we highly recommend having the following lines of code
466+ in your handle() method to clean up unmanaged stale/old database connections:
467+
468+ from django.db import close_old_connections
469+ close_old_connections()
470+ """
471+ self.app.logger.info(message)
472+ else:
473+ # for proper management of thread-local Django DB connections
474+ self.app.listener_runner.listener_start_handler = (
475+ DjangoListenerStartHandler()
476+ )
477+ self.app.logger.debug("DjangoListenerStartHandler has been enabled")
478+
380479 current_completion_handler = listener_runner.listener_completion_handler
381480 if current_completion_handler is not None and not isinstance(
382481 current_completion_handler, DefaultListenerCompletionHandler
383482 ):
483+ # As we run release_thread_local_connections() before listener executions,
484+ # it's okay to skip calling the same connection clean-up method at the listener completion.
384485 message = """As you've already set app.listener_runner.listener_completion_handler to your own one,
385486 Bolt skipped to set it to slack_sdk.adapter.django.DjangoListenerCompletionHandler.
386- We strongly recommend having the following lines of code in your listener_completion_handler:
387-
388- from django.db import connections
389- connections.close_all()
390487 """
391- self.app.logger.warning (message)
488+ self.app.logger.info (message)
392489 return
393490 # for proper management of thread-local Django DB connections
394491 self.app.listener_runner.listener_completion_handler = (
@@ -469,6 +566,9 @@ <h1>Index</h1>
469566< h4 > < code > < a title ="slack_bolt.adapter.django.handler.DjangoListenerCompletionHandler " href ="#slack_bolt.adapter.django.handler.DjangoListenerCompletionHandler "> DjangoListenerCompletionHandler</ a > </ code > </ h4 >
470567</ li >
471568< li >
569+ < h4 > < code > < a title ="slack_bolt.adapter.django.handler.DjangoListenerStartHandler " href ="#slack_bolt.adapter.django.handler.DjangoListenerStartHandler "> DjangoListenerStartHandler</ a > </ code > </ h4 >
570+ </ li >
571+ < li >
472572< h4 > < code > < a title ="slack_bolt.adapter.django.handler.DjangoThreadLazyListenerRunner " href ="#slack_bolt.adapter.django.handler.DjangoThreadLazyListenerRunner "> DjangoThreadLazyListenerRunner</ a > </ code > </ h4 >
473573< ul class ="">
474574< li > < code > < a title ="slack_bolt.adapter.django.handler.DjangoThreadLazyListenerRunner.logger " href ="#slack_bolt.adapter.django.handler.DjangoThreadLazyListenerRunner.logger "> logger</ a > </ code > </ li >
0 commit comments