Skip to content

Commit bfe9b05

Browse files
authored
Merge pull request #8917 from readthedocs/humitos/celery-max-concurrency
Celery: use `on_retry` to handle `BuildMaxConcurrencyError`
2 parents c74bdbe + 1bf397e commit bfe9b05

File tree

3 files changed

+32
-23
lines changed

3 files changed

+32
-23
lines changed

readthedocs/core/utils/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ def prepare_build(
186186
project_slug=project.slug,
187187
version_slug=version.slug,
188188
)
189-
options['countdown'] = 5 * 60
190-
options['max_retries'] = 25
189+
options['countdown'] = settings.RTD_BUILDS_RETRY_DELAY
190+
options['max_retries'] = settings.RTD_BUILDS_MAX_RETRIES
191191
build.error = BuildMaxConcurrencyError.message.format(
192192
limit=max_concurrent_builds,
193193
)

readthedocs/projects/tasks/builds.py

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
BUILD_STATE_CLONING,
3232
BUILD_STATE_FINISHED,
3333
BUILD_STATE_INSTALLING,
34+
BUILD_STATE_TRIGGERED,
3435
BUILD_STATE_UPLOADING,
3536
BUILD_STATUS_FAILURE,
3637
BUILD_STATUS_SUCCESS,
@@ -218,8 +219,9 @@ class UpdateDocsTask(SyncRepositoryMixin, Task):
218219
autoretry_for = (
219220
BuildMaxConcurrencyError,
220221
)
221-
max_retries = 5 # 5 per normal builds, 25 per concurrency limited
222-
default_retry_delay = 7 * 60
222+
max_retries = settings.RTD_BUILDS_MAX_RETRIES
223+
default_retry_delay = settings.RTD_BUILDS_RETRY_DELAY
224+
retry_backoff = False
223225

224226
# Expected exceptions that will be logged as info only and not retried.
225227
# These exceptions are not sent to Sentry either because we are using
@@ -279,25 +281,12 @@ def _check_concurrency_limit(self):
279281
max_concurrent_builds = settings.RTD_MAX_CONCURRENT_BUILDS
280282

281283
if concurrency_limit_reached:
282-
# TODO: this could be handled in `on_retry` probably
283-
log.warning(
284-
'Delaying tasks due to concurrency limit.',
285-
project_slug=self.data.project.slug,
286-
version_slug=self.data.version.slug,
287-
)
288-
289-
# This is done automatically on the environment context, but
290-
# we are executing this code before creating one
291-
api_v2.build(self.data.build['id']).patch({
292-
'error': BuildMaxConcurrencyError.message.format(
284+
# By raising this exception and using ``autoretry_for``, Celery
285+
# will handle this automatically calling ``on_retry``
286+
raise BuildMaxConcurrencyError(
287+
BuildMaxConcurrencyError.message.format(
293288
limit=max_concurrent_builds,
294-
),
295-
'builder': socket.gethostname(),
296-
})
297-
self.retry(
298-
exc=BuildMaxConcurrencyError,
299-
# We want to retry this build more times
300-
max_retries=25,
289+
)
301290
)
302291

303292
def _check_duplicated_build(self):
@@ -499,7 +488,25 @@ def on_success(self, retval, task_id, args, kwargs):
499488
self.data.build['success'] = True
500489

501490
def on_retry(self, exc, task_id, args, kwargs, einfo):
502-
log.warning('Retrying this task.')
491+
"""
492+
Celery helper called when the task is retried.
493+
494+
This happens when any of the exceptions defined in ``autoretry_for``
495+
argument is raised or when ``self.retry`` is called from inside the
496+
task.
497+
498+
See https://docs.celeryproject.org/en/master/userguide/tasks.html#retrying
499+
"""
500+
log.info('Retrying this task.')
501+
502+
if isinstance(exc, BuildMaxConcurrencyError):
503+
log.warning(
504+
'Delaying tasks due to concurrency limit.',
505+
project_slug=self.data.project.slug,
506+
version_slug=self.data.version.slug,
507+
)
508+
self.data.build['error'] = exc.message
509+
self.update_build(state=BUILD_STATE_TRIGGERED)
503510

504511
def after_return(self, status, retval, task_id, args, kwargs, einfo):
505512
# Update build object

readthedocs/settings/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ def SESSION_COOKIE_SAMESITE(self):
125125
RTD_STABLE_VERBOSE_NAME = 'stable'
126126
RTD_CLEAN_AFTER_BUILD = False
127127
RTD_MAX_CONCURRENT_BUILDS = 4
128+
RTD_BUILDS_MAX_RETRIES = 25
129+
RTD_BUILDS_RETRY_DELAY = 5 * 60 # seconds
128130
RTD_BUILD_STATUS_API_NAME = 'docs/readthedocs'
129131
RTD_ANALYTICS_DEFAULT_RETENTION_DAYS = 30 * 3
130132
RTD_AUDITLOGS_DEFAULT_RETENTION_DAYS = 30 * 3

0 commit comments

Comments
 (0)