Skip to content

Commit 4b5d5d1

Browse files
committed
Add retry logic for all management commands
- use tenacity library - log at INFO level - Remove some exception handling in linksearchtotal_collect command to enable retry Bug: T403209
1 parent f7c2364 commit 4b5d5d1

4 files changed

Lines changed: 40 additions & 18 deletions

File tree

extlinks/common/management/commands/__init__.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,46 @@
44
import logging
55
from os import remove
66
from os.path import basename
7+
import sys
8+
from tenacity import Retrying, stop_after_attempt, wait_exponential
9+
10+
logger = logging.getLogger("django")
711

812

913
class BaseCommand(DjangoBaseCommand):
1014
"""
11-
Django BaseCommand wrapper that adds file locks to management commands
15+
Django BaseCommand wrapper that adds
16+
- file locks
17+
- up to 5 retries with exponential backoff
1218
"""
1319

20+
def __init__(self, *args, **options):
21+
super().__init__(*args, **options)
22+
self.name = basename(inspect.getfile(self.__class__))
23+
self.status = 0
24+
25+
def retry_log(self, retry_state):
26+
logger.warning(f"{self.name} attempt {retry_state.attempt_number} failed")
27+
1428
def handle(self, *args, **options):
15-
lockname = basename(inspect.getfile(self.__class__))
1629
# Use a lockfile to prevent overruns.
17-
lockfile = "/tmp/{}.lock".format(lockname)
30+
logger.info(f"Executing {self.name}")
31+
lockfile = f"/tmp/{self.name}.lock"
1832
lock = FileLock(lockfile)
1933
lock.acquire()
2034
try:
21-
self._handle(*args, **options)
22-
finally:
23-
lock.release()
24-
remove(lockfile)
35+
for attempt in Retrying(
36+
after=self.retry_log,
37+
reraise=True,
38+
stop=stop_after_attempt(5),
39+
wait=wait_exponential(multiplier=1, min=60, max=300),
40+
):
41+
with attempt:
42+
self._handle(*args, **options)
43+
except Exception as e:
44+
logger.warning(f"Retries exhausted for {self.name}")
45+
logger.error(e)
46+
self.status = 1
47+
lock.release()
48+
remove(lockfile)
49+
sys.exit(self.status)

extlinks/links/management/commands/linksearchtotal_collect.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,12 @@ def _handle(self, *args, **options):
3232
total_links_dictionary = {}
3333
for i, language in enumerate(wiki_list_data):
3434
logger.info(f"connecting to db {language}")
35-
try:
36-
db = MySQLdb.connect(
37-
host=f"{language}wiki.analytics.db.svc.wikimedia.cloud",
38-
user=os.environ["REPLICA_DB_USER"],
39-
passwd=os.environ["REPLICA_DB_PASSWORD"],
40-
db=f"{language}wiki_p",
41-
)
42-
except MySQLdb.OperationalError as e:
43-
logger.error(str(e))
44-
sys.exit(1)
35+
db = MySQLdb.connect(
36+
host=f"{language}wiki.analytics.db.svc.wikimedia.cloud",
37+
user=os.environ["REPLICA_DB_USER"],
38+
passwd=os.environ["REPLICA_DB_PASSWORD"],
39+
db=f"{language}wiki_p",
40+
)
4541

4642
cur = db.cursor()
4743

extlinks/settings/logging.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
},
2626
"handlers": {
2727
"nodebug_console": {
28-
"level": "WARNING",
28+
"level": "INFO",
2929
"filters": ["require_debug_false"],
3030
"class": "logging.StreamHandler",
3131
"formatter": "django.server",

requirements/django.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pymemcache==4.0.0
88
python-dotenv==1.1.0
99
sentry-sdk==2.10.0
1010
sseclient==0.0.27
11+
tenacity==9.1.2
1112
time_machine==2.14.2
1213
python-swiftclient>=4.6.0,<5.0.0
1314
keystoneauth1>=5.9.2,<6.0.0

0 commit comments

Comments
 (0)