mirror of
git://git.yoctoproject.org/layerindex-web.git
synced 2025-07-19 03:49:10 +02:00
Allow stopping update task
For situations where the user launches a distro comparison update process and then shortly afterwards realises it is operating with the wrong configuration (or is otherwise broken) and is going to take a long time to finish, add a button to the task page to stop the task. This was tricky to get working, since the default behaviour of Celery's revoke() would either terminate both the Celery task process along with the update process (leaving us with no log saved to the database) or worse not even kill the update process, depending on the signal sent. To avoid this, send SIGUSR2, trap it in the task process and kill the child process, returning gracefully. To make that possible I had to rewrite runcmd() to use subprocess.Popen() instead of subprocess.check_call() as otherwise we can't get the child's PID. Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
parent
ac73780bd9
commit
d84bfd710d
|
@ -8,7 +8,7 @@ from django.conf.urls import *
|
|||
from django.views.generic import TemplateView, DetailView, ListView, RedirectView
|
||||
from django.views.defaults import page_not_found
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, HistoryListView, EditProfileFormView, AdvancedRecipeSearchView, BulkChangeView, BulkChangeSearchView, bulk_change_edit_view, bulk_change_patch_view, BulkChangeDeleteView, RecipeDetailView, RedirectParamsView, ClassicRecipeSearchView, ClassicRecipeDetailView, ClassicRecipeStatsView, LayerUpdateDetailView, UpdateListView, UpdateDetailView, StatsView, publish_view, LayerCheckListView, BBClassCheckListView, TaskStatusView, ComparisonRecipeSelectView, ComparisonRecipeSelectDetailView, task_log_view
|
||||
from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, HistoryListView, EditProfileFormView, AdvancedRecipeSearchView, BulkChangeView, BulkChangeSearchView, bulk_change_edit_view, bulk_change_patch_view, BulkChangeDeleteView, RecipeDetailView, RedirectParamsView, ClassicRecipeSearchView, ClassicRecipeDetailView, ClassicRecipeStatsView, LayerUpdateDetailView, UpdateListView, UpdateDetailView, StatsView, publish_view, LayerCheckListView, BBClassCheckListView, TaskStatusView, ComparisonRecipeSelectView, ComparisonRecipeSelectDetailView, task_log_view, task_stop_view
|
||||
from layerindex.models import LayerItem, Recipe, RecipeChangeset
|
||||
from rest_framework import routers
|
||||
from . import restviews
|
||||
|
@ -171,6 +171,9 @@ urlpatterns = [
|
|||
url(r'^tasklog/(?P<task_id>[-\w]+)/$',
|
||||
task_log_view,
|
||||
name='task_log'),
|
||||
url(r'^stoptask/(?P<task_id>[-\w]+)/$',
|
||||
task_stop_view,
|
||||
name='task_stop'),
|
||||
url(r'^ajax/layerchecklist/(?P<branch>[-\w]+)/$',
|
||||
LayerCheckListView.as_view(
|
||||
template_name='layerindex/layerchecklist.html'),
|
||||
|
|
|
@ -285,6 +285,7 @@ def parse_layer_conf(layerdir, data, logger=None):
|
|||
data = parse_conf(conf_file, data)
|
||||
data.expandVarref('LAYERDIR')
|
||||
|
||||
child_pid = 0
|
||||
def runcmd(cmd, destdir=None, printerr=True, outfile=None, logger=None):
|
||||
"""
|
||||
execute command, raise CalledProcessError if fail
|
||||
|
@ -296,10 +297,17 @@ def runcmd(cmd, destdir=None, printerr=True, outfile=None, logger=None):
|
|||
out = open(outfile, 'wb+')
|
||||
else:
|
||||
out = tempfile.TemporaryFile()
|
||||
|
||||
def onsigusr2(sig, frame):
|
||||
# Kill the child process
|
||||
os.kill(child_pid, signal.SIGTERM)
|
||||
signal.signal(signal.SIGUSR2, onsigusr2)
|
||||
try:
|
||||
try:
|
||||
subprocess.check_call(cmd, stdout=out, stderr=out, cwd=destdir, shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
proc = subprocess.Popen(cmd, stdout=out, stderr=out, cwd=destdir, shell=True)
|
||||
global child_pid
|
||||
child_pid = proc.pid
|
||||
proc.communicate()
|
||||
if proc.returncode:
|
||||
out.seek(0)
|
||||
output = out.read()
|
||||
output = output.decode('utf-8', errors='replace').strip()
|
||||
|
@ -308,6 +316,7 @@ def runcmd(cmd, destdir=None, printerr=True, outfile=None, logger=None):
|
|||
logger.error("%s" % output)
|
||||
else:
|
||||
sys.stderr.write("%s\n" % output)
|
||||
e = subprocess.CalledProcessError(proc.returncode, cmd)
|
||||
e.output = output
|
||||
raise e
|
||||
|
||||
|
@ -317,6 +326,7 @@ def runcmd(cmd, destdir=None, printerr=True, outfile=None, logger=None):
|
|||
if logger:
|
||||
logger.debug("output: %s" % output.rstrip() )
|
||||
finally:
|
||||
signal.signal(signal.SIGUSR2, signal.SIG_DFL)
|
||||
if outfile:
|
||||
out.close()
|
||||
return output
|
||||
|
|
|
@ -1412,6 +1412,17 @@ def task_log_view(request, task_id):
|
|||
response['Task-Progress'] = preader.read()
|
||||
return response
|
||||
|
||||
def task_stop_view(request, task_id):
|
||||
from celery.result import AsyncResult
|
||||
import signal
|
||||
if not request.user.is_authenticated():
|
||||
raise PermissionDenied
|
||||
|
||||
result = AsyncResult(task_id)
|
||||
result.revoke(terminate=True, signal=signal.SIGUSR2)
|
||||
return HttpResponse('terminated')
|
||||
|
||||
|
||||
class ComparisonRecipeSelectView(ClassicRecipeSearchView):
|
||||
def _can_edit(self):
|
||||
if self.request.user.is_authenticated():
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not update.finished %}
|
||||
<button id="stopbutton" class="btn btn-danger pull-right">Stop</a>
|
||||
{% endif %}
|
||||
|
||||
{% if update.comparisonrecipeupdate_set.exists %}
|
||||
<h3>Updated comparison recipes</h3>
|
||||
<ul>
|
||||
|
@ -92,6 +96,7 @@
|
|||
}).always(function () {
|
||||
if(done == '1') {
|
||||
$("#task_status_fragment").html(" (finished in " + duration + ")")
|
||||
$("#stopbutton").hide()
|
||||
}
|
||||
else {
|
||||
window.setTimeout(updateLog, 1000);
|
||||
|
@ -103,6 +108,12 @@
|
|||
scrolling = ($(this).scrollTop() + $(this).height() + 50 > $(this).prop('scrollHeight'))
|
||||
});
|
||||
|
||||
$("#stopbutton").click(function() {
|
||||
$.ajax({
|
||||
url: "{% url 'task_stop' update.task_id %}"
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
{% if not update.finished %}
|
||||
updateLog();
|
||||
|
|
Loading…
Reference in New Issue
Block a user