Skip to content

Commit 1ad3bd4

Browse files
petrprikrylauvipy
andauthored
Add support for Django Connection pool (celery#9953)
* Add support for Django Connection pool https://docs.djangoproject.com/en/dev/ref/databases/#postgresql-pool * tests for close_pool * close_pool called only if DB pool is enabled in Django settings * conn pool docs --------- Co-authored-by: Asif Saif Uddin {"Auvi":"অভি"} <[email protected]>
1 parent 2731860 commit 1ad3bd4

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

celery/fixups/django.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Django-specific customization."""
2+
import contextlib
23
import os
34
import sys
45
import warnings
@@ -201,6 +202,10 @@ def _close_database(self) -> None:
201202
for conn in self._db.connections.all():
202203
try:
203204
conn.close()
205+
pool_enabled = self._settings.DATABASES.get(conn.alias, {}).get("OPTIONS", {}).get("pool")
206+
if pool_enabled and hasattr(conn, "close_pool"):
207+
with contextlib.suppress(KeyError):
208+
conn.close_pool()
204209
except self.interface_errors:
205210
pass
206211
except self.DatabaseError as exc:

docs/django/first-steps-with-django.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ However, if your app :ref:`uses a custom task base class <task-custom-classes>`,
216216
you'll need inherit from :class:`~celery.contrib.django.task.DjangoTask` instead of
217217
:class:`~celery.app.task.Task` to get this behaviour.
218218

219+
Django Connection pool
220+
----------------------
221+
From Django 5.1+ there is built-in support for database connection pooling.
222+
If you enable it in Django ``DATABASES`` settings Celery will automatically
223+
handle connection pool closing in worker processes via ``close_pool``
224+
database backend method as
225+
`sharing connections across processes is not possible. <https://github.com/psycopg/psycopg/issues/544#issuecomment-1500886864>`_
226+
227+
You can find more about Connection pool at `Django docs. <https://docs.djangoproject.com/en/dev/ref/databases/#connection-pool>`_
228+
219229
Extensions
220230
==========
221231

t/unit/fixups/test_django.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,58 @@ def test_close_database_always_closes_connections(self):
284284
# it to optimize connection handling.
285285
conn.close_if_unusable_or_obsolete.assert_not_called()
286286

287+
def test_close_database_skip_conn_pool(self):
288+
class Connection:
289+
"""Mock connection without `close_pool` method."""
290+
alias = 'default'
291+
292+
def close(self):
293+
pass
294+
295+
with self.fixup_context(self.app) as (f, _, _):
296+
conn = Mock(spec=Connection)
297+
f._db.connections.all = Mock(return_value=[conn])
298+
f.close_database()
299+
assert not hasattr(conn, "close_pool")
300+
conn.close.assert_called_once_with()
301+
302+
def test_close_database_suppresses_close_pool_keyerror(self):
303+
with self.fixup_context(self.app) as (f, _, _):
304+
conn = Mock()
305+
conn.close_pool = Mock(side_effect=KeyError("pool already closed"))
306+
f._db.connections.all = Mock(return_value=[conn])
307+
f.close_database() # should not raise
308+
conn.close.assert_called_once_with()
309+
conn.close_pool.assert_called_once_with()
310+
311+
def test_close_database_conn_pool_based_on_settings(self):
312+
class DJSettings:
313+
DATABASES = {}
314+
315+
with self.fixup_context(self.app) as (f, _, _):
316+
conn = Mock()
317+
conn.alias = "default"
318+
conn.close_pool = Mock()
319+
f._db.connections.all = Mock(return_value=[conn])
320+
f._settings = DJSettings
321+
322+
f._settings.DATABASES["default"] = {"OPTIONS": {}}
323+
f.close_database()
324+
conn.close.assert_called_once_with()
325+
conn.close_pool.assert_not_called()
326+
327+
conn.reset_mock()
328+
f._settings.DATABASES["default"] = {"OPTIONS": {"pool": True}}
329+
f.close_database()
330+
conn.close.assert_called_once_with()
331+
conn.close_pool.assert_called_once_with()
332+
333+
conn.reset_mock()
334+
f._settings.DATABASES["default"] = {"OPTIONS": {"pool": False}}
335+
f.close_database()
336+
conn.close.assert_called_once_with()
337+
conn.close_pool.assert_not_called()
338+
287339
def test_close_cache_raises_error(self):
288340
with self.fixup_context(self.app) as (f, _, _):
289341
f._cache.close_caches.side_effect = AttributeError

0 commit comments

Comments
 (0)