Skip to content

Commit b68a2a3

Browse files
committed
Build: move cancel_build to readthedocs.core.utils.__init__
Wrap the code that cancels a build and move it the module where `trigger_build` and other related helpers are.
1 parent 6739aed commit b68a2a3

File tree

2 files changed

+48
-31
lines changed

2 files changed

+48
-31
lines changed

readthedocs/builds/views.py

+3-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Views for builds app."""
22

3-
import signal
43
import textwrap
54
from urllib.parse import urlparse
65

@@ -23,10 +22,9 @@
2322
from readthedocs.builds.filters import BuildListFilter
2423
from readthedocs.builds.models import Build, Version
2524
from readthedocs.core.permissions import AdminPermission
26-
from readthedocs.core.utils import trigger_build
27-
from readthedocs.doc_builder.exceptions import BuildAppError, BuildCancelled
25+
from readthedocs.core.utils import cancel_build, trigger_build
26+
from readthedocs.doc_builder.exceptions import BuildAppError
2827
from readthedocs.projects.models import Project
29-
from readthedocs.worker import app
3028

3129
log = structlog.get_logger(__name__)
3230

@@ -167,33 +165,7 @@ def post(self, request, project_slug, build_pk):
167165
if not AdminPermission.is_admin(request.user, project):
168166
return HttpResponseForbidden()
169167

170-
# NOTE: `terminate=True` is required for the child to attend our call
171-
# immediately when it's running the build. Otherwise, it finishes the
172-
# task. However, to revoke a task that has not started yet, we don't
173-
# need it.
174-
if build.state == BUILD_STATE_TRIGGERED:
175-
# Since the task won't be executed at all, we need to update the
176-
# Build object here.
177-
terminate = False
178-
build.state = BUILD_STATE_CANCELLED
179-
build.success = False
180-
build.error = BuildCancelled.message
181-
build.length = 0
182-
build.save()
183-
else:
184-
# In this case, we left the update of the Build object to the task
185-
# itself to be executed in the `on_failure` handler.
186-
terminate = True
187-
188-
log.warning(
189-
'Canceling build.',
190-
project_slug=project.slug,
191-
version_slug=build.version.slug,
192-
build_id=build.pk,
193-
build_task_id=build.task_id,
194-
terminate=terminate,
195-
)
196-
app.control.revoke(build.task_id, signal=signal.SIGINT, terminate=terminate)
168+
cancel_build(build)
197169

198170
return HttpResponseRedirect(
199171
reverse('builds_detail', args=[project.slug, build.pk]),

readthedocs/core/utils/__init__.py

+45
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import datetime
44
import re
5+
import signal
56

67
import structlog
78
from django.conf import settings
@@ -19,9 +20,11 @@
1920
EXTERNAL,
2021
)
2122
from readthedocs.doc_builder.exceptions import (
23+
BuildCancelled,
2224
BuildMaxConcurrencyError,
2325
DuplicatedBuildError,
2426
)
27+
from readthedocs.worker import app
2528

2629
log = structlog.get_logger(__name__)
2730

@@ -249,6 +252,48 @@ def trigger_build(project, version=None, commit=None):
249252
return task, build
250253

251254

255+
def cancel_build(build):
256+
"""
257+
Cancel a triggered/running build.
258+
259+
Depending on the current state of the build, it takes one approach or the other:
260+
261+
- Triggered:
262+
update the build status and tells Celery to revoke this task.
263+
Workers will know about this and will discard it.
264+
- Running:
265+
communicate Celery to force the termination of the current build
266+
and relies on the worker to update the build's status correct.
267+
"""
268+
# NOTE: `terminate=True` is required for the child to attend our call
269+
# immediately when it's running the build. Otherwise, it finishes the
270+
# task. However, to revoke a task that has not started yet, we don't
271+
# need it.
272+
if build.state == BUILD_STATE_TRIGGERED:
273+
# Since the task won't be executed at all, we need to update the
274+
# Build object here.
275+
terminate = False
276+
build.state = BUILD_STATE_CANCELLED
277+
build.success = False
278+
build.error = BuildCancelled.message
279+
build.length = 0
280+
build.save()
281+
else:
282+
# In this case, we left the update of the Build object to the task
283+
# itself to be executed in the `on_failure` handler.
284+
terminate = True
285+
286+
log.warning(
287+
"Canceling build.",
288+
project_slug=build.project.slug,
289+
version_slug=build.version.slug,
290+
build_id=build.pk,
291+
build_task_id=build.task_id,
292+
terminate=terminate,
293+
)
294+
app.control.revoke(build.task_id, signal=signal.SIGINT, terminate=terminate)
295+
296+
252297
def send_email(
253298
recipient, subject, template, template_html, context=None, request=None,
254299
from_email=None, **kwargs

0 commit comments

Comments
 (0)