Skip to content

Commit fdd8595

Browse files
authored
Merge pull request #8 from zhangzhw8/feat_support_retry_times
feat: support MAX_DBCONN_RETRY_TIMES
2 parents 91244a5 + 6c8b824 commit fdd8595

File tree

4 files changed

+47
-23
lines changed

4 files changed

+47
-23
lines changed

README.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ Both signals send a parameter ``dbwrapper`` which points to the current instance
6868
of ``django.db.backends.base.BaseDatabaseWrapper`` which allows the signal
6969
receiver to act on the database connection.
7070

71+
Settings
72+
-------
73+
Here’s a list of settings available in django-dbconn-retry and their default values.
74+
You can change the value in your ``settings.py``.
75+
76+
=========================== ==================================================
77+
Setting Description
78+
=========================== ==================================================
79+
``MAX_DBCONN_RETRY_TIMES`` Default: `1`
80+
The max times which django-dbconn-retry will try.
81+
=========================== ==================================================
7182

7283
License
7384
=======

django_dbconn_retry/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# -* encoding: utf-8 *-
22
import django
33

4-
54
from django_dbconn_retry.apps import pre_reconnect, post_reconnect, monkeypatch_django, DjangoIntegration
65

76

django_dbconn_retry/apps.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22

33
from django.apps.config import AppConfig
4+
from django.conf import settings
45
from django.db.backends.base import base as django_db_base
56
from django.dispatch import Signal
67

@@ -34,6 +35,8 @@
3435

3536
def monkeypatch_django() -> None:
3637
def ensure_connection_with_retries(self: django_db_base.BaseDatabaseWrapper) -> None:
38+
self._max_dbconn_retry_times = getattr(settings, "MAX_DBCONN_RETRY_TIMES", 1)
39+
3740
if self.connection is not None and hasattr(self.connection, 'closed') and self.connection.closed:
3841
_log.debug("failed connection detected")
3942
self.connection = None
@@ -45,15 +48,22 @@ def ensure_connection_with_retries(self: django_db_base.BaseDatabaseWrapper) ->
4548
self.connect()
4649
except Exception as e:
4750
if isinstance(e, _operror_types):
48-
if hasattr(self, "_connection_retries") and self._connection_retries >= 1:
51+
if (
52+
hasattr(self, "_connection_retries") and
53+
self._connection_retries >= self._max_dbconn_retry_times
54+
):
4955
_log.error("Reconnecting to the database didn't help %s", str(e))
5056
del self._in_connecting
5157
post_reconnect.send(self.__class__, dbwrapper=self)
5258
raise
5359
else:
5460
_log.info("Database connection failed. Refreshing...")
5561
# mark the retry
56-
self._connection_retries = 1
62+
try:
63+
self._connection_retries += 1
64+
except AttributeError:
65+
self._connection_retries = 1
66+
5767
# ensure that we retry the connection. Sometimes .closed isn't set correctly.
5868
self.connection = None
5969
del self._in_connecting

django_dbconn_retry/tests/__init__.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -* encoding: utf-8 *-
2+
import random
23
import sys
34
import logging
45

@@ -10,7 +11,7 @@
1011

1112
from django.db.backends.base.base import BaseDatabaseWrapper
1213
from django.db import connection, OperationalError, transaction
13-
from django.test import TestCase
14+
from django.test import TestCase, override_settings
1415

1516

1617
logging.basicConfig(stream=sys.stderr)
@@ -24,6 +25,8 @@ class FullErrorTests(TestCase):
2425
database connections reliably fail. If I had been able to think of
2526
a better way, I'd have used it.
2627
"""
28+
max_dbconn_retry_times = random.randint(1, 100)
29+
2730
def test_getting_root(self) -> None:
2831
self.client.get('/')
2932

@@ -38,26 +41,31 @@ def tearDown(self) -> None:
3841
BaseDatabaseWrapper.connect = self.s_connect
3942
del BaseDatabaseWrapper.connection
4043

41-
def test_prehook(self) -> None:
42-
cb = Mock(name='pre_reconnect_hook')
43-
ddr.pre_reconnect.connect(cb)
44+
def do_assert(self, cb):
4445
self.assertRaises(OperationalError, connection.ensure_connection)
4546
self.assertTrue(cb.called)
47+
self.assertEqual(connection._connection_retries, self.max_dbconn_retry_times)
4648
del connection._connection_retries
4749

50+
@override_settings(MAX_DBCONN_RETRY_TIMES=max_dbconn_retry_times)
51+
def test_prehook(self) -> None:
52+
cb = Mock(name='pre_reconnect_hook')
53+
ddr.pre_reconnect.connect(cb)
54+
self.do_assert(cb)
55+
56+
@override_settings(MAX_DBCONN_RETRY_TIMES=max_dbconn_retry_times)
4857
def test_posthook(self) -> None:
4958
cb = Mock(name='post_reconnect_hook')
5059
ddr.post_reconnect.connect(cb)
51-
self.assertRaises(OperationalError, connection.ensure_connection)
52-
self.assertTrue(cb.called)
53-
del connection._connection_retries
60+
self.do_assert(cb)
5461

5562

5663
def fix_connection(sender: type, *, dbwrapper: BaseDatabaseWrapper, **kwargs: Any) -> None:
5764
dbwrapper.connect = dbwrapper.s_connect
5865

5966

6067
class ReconnectTests(TestCase):
68+
6169
@classmethod
6270
def tearDownClass(cls) -> None:
6371
return
@@ -67,10 +75,7 @@ def test_ensure_closed(self) -> None:
6775
connection.close()
6876
self.assertFalse(connection.is_usable()) # should be true after setUp
6977

70-
def test_prehook(self) -> None:
71-
cb = Mock(name='pre_reconnect_hook')
72-
ddr.pre_reconnect.connect(fix_connection)
73-
ddr.pre_reconnect.connect(cb)
78+
def do_assert(self, cb):
7479
from django.db import connection
7580
connection.close()
7681
connection.s_connect = connection.connect
@@ -80,17 +85,16 @@ def test_prehook(self) -> None:
8085
ReconnectTests.cls_atomics['default'].__enter__()
8186
self.assertTrue(cb.called)
8287
self.assertTrue(connection.is_usable())
88+
self.assertEqual(connection._connection_retries, 0)
89+
90+
def test_prehook(self) -> None:
91+
cb = Mock(name='pre_reconnect_hook')
92+
ddr.pre_reconnect.connect(fix_connection)
93+
ddr.pre_reconnect.connect(cb)
94+
self.do_assert(cb)
8395

8496
def test_posthook(self) -> None:
8597
cb = Mock(name='post_reconnect_hook')
8698
ddr.pre_reconnect.connect(fix_connection)
8799
ddr.post_reconnect.connect(cb)
88-
from django.db import connection
89-
connection.close()
90-
connection.s_connect = connection.connect
91-
connection.connect = Mock(side_effect=OperationalError('reconnect testing'))
92-
connection.ensure_connection()
93-
ReconnectTests.cls_atomics['default'] = transaction.atomic(using='default')
94-
ReconnectTests.cls_atomics['default'].__enter__()
95-
self.assertTrue(cb.called)
96-
self.assertTrue(connection.is_usable())
100+
self.do_assert(cb)

0 commit comments

Comments
 (0)