Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example about how to kill a running task #7031

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions readthedocs/builds/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,12 @@ class Build(models.Model):
_('Cold Storage'),
help_text='Build steps stored outside the database.',
)
task_id = models.CharField(
_('Celery task id'),
max_length=36,
null=True,
blank=True,
)

# Managers
objects = BuildManager.from_queryset(BuildQuerySet)()
Expand Down
16 changes: 16 additions & 0 deletions readthedocs/builds/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,19 @@ def get_context_data(self, **kwargs):
issue_url = urlparse(issue_url).geturl()
context['issue_url'] = issue_url
return context

@method_decorator(login_required)
def post(self, request, project_slug, build_pk):
project = get_object_or_404(Project, slug=project_slug)
build = get_object_or_404(Build, pk=build_pk)

if not AdminPermission.is_admin(request.user, project):
return HttpResponseForbidden()

import signal
from celery.task.control import revoke
revoke(build.task_id, signal=signal.SIGINT, terminate=True)

return HttpResponseRedirect(
reverse('builds_detail', args=[project.slug, build.pk]),
)
5 changes: 4 additions & 1 deletion readthedocs/core/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,10 @@ def trigger_build(project, version=None, commit=None, record=True, force=False):
# Build was skipped
return (None, None)

return (update_docs_task.apply_async(), build)
task = update_docs_task.apply_async()
build.task_id = task.id
build.save()
return task, build


def send_email(
Expand Down
10 changes: 9 additions & 1 deletion readthedocs/doc_builder/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
ProjectBuildsSkippedError,
VersionLockedError,
YAMLParseError,
BuildKilled,
)


Expand Down Expand Up @@ -356,7 +357,7 @@ def run(self):
'\n\nCommand killed due to excessive memory consumption\n',
),
)
except DockerAPIError:
except (DockerAPIError, BuildKilled):
self.exit_code = -1
if self.output is None or not self.output:
self.output = _('Command exited abnormally')
Expand Down Expand Up @@ -605,6 +606,13 @@ def handle_exception(self, exc_type, exc_value, _):
elif exc_type in self.WARNING_EXCEPTIONS:
log_level_function = log.warning
self.failure = exc_value
elif exc_type is BuildKilled:
log.warning('KILLED FROM handle_exception')
log_level_function = log.warning
self.failure = 'Killed!'
self.build['state'] = BUILD_STATE_FINISHED
# self.done = True
# self.successful = False
else:
log_level_function = log.error
self.failure = exc_value
Expand Down
4 changes: 4 additions & 0 deletions readthedocs/doc_builder/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,7 @@ class MkDocsYAMLParseError(BuildEnvironmentError):
'Please follow the user guide https://www.mkdocs.org/user-guide/configuration/ '
'to configure the file properly.',
)


class BuildKilled(Exception):
pass
39 changes: 39 additions & 0 deletions readthedocs/projects/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
ProjectBuildsSkippedError,
VersionLockedError,
YAMLParseError,
BuildKilled,
)
from readthedocs.doc_builder.loader import get_builder_class
from readthedocs.doc_builder.python_environments import Conda, Virtualenv
Expand Down Expand Up @@ -536,7 +537,32 @@ def run(

:rtype: bool
"""

try:
def sigstop_received(*args, **kwargs):
log.warning('SIGSTOP received. Killing the current building.')
env_cls = DockerBuildEnvironment
# environment = env_cls(
# project=self.project,
# version=self.version,
# build=self.build,
# record=False,
# update_on_success=False,
# )
# client = environment.get_client()
# client.kill(environment.container_id)
# client.remove_container(environment.container_id)
# api_v2.build(self.build['id']).patch({
# 'error': 'Killed!',
# 'success': False,
# 'state': BUILD_STATE_FINISHED,
# })
raise BuildKilled
return False

# signal.signal(signal.SIGUSR2, sigstop_received)
signal.signal(signal.SIGINT, sigstop_received)

self.version = self.get_version(version_pk)
self.project = self.version.project
self.build = self.get_build(build_pk)
Expand Down Expand Up @@ -585,6 +611,19 @@ def run(
return False
self.run_build(record=record)
return True
except BuildKilled:
log.warning(
'Build killed by user. project=%s version=%s build=%s',
self.project.slug,
self.version.slug,
build_pk,
)
api_v2.build(self.build['id']).patch({
'error': 'Killed!',
'success': False,
'state': BUILD_STATE_FINISHED,
})
return False
except Exception:
log.exception(
'An unhandled exception was raised during build setup',
Expand Down
6 changes: 6 additions & 0 deletions readthedocs/templates/builds/build_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
{% block content %}
<div class="build build-detail" id="build-detail">

<form method="post" action="{% url "builds_detail" build.version.project.slug build.pk %}">
{% csrf_token %}
<input type="submit" value="{% trans "Cancel build" %}">
</form>


<!-- Build meta data -->
<ul class="build-meta">
<div data-bind="visible: finished()">
Expand Down