exp: implement internal queue.kill support(#7587)

fix: #7587
1. Add implement kill method for local queue class
2. Add a test to make sure the following job will be success after the
   original job was killed.
3. Some refactoring work on `exp remove`
This commit is contained in:
karajan1001
2022-07-12 21:39:49 +09:00
committed by Peter Rowlands (변기호)
parent 22deca3a6a
commit 4df5a1321d
4 changed files with 99 additions and 29 deletions
+33
View File
@@ -170,6 +170,14 @@ class BaseStashQueue(ABC):
This method blocks until the specified item has been collected. This method blocks until the specified item has been collected.
""" """
@abstractmethod
def kill(self, revs: str) -> None:
"""Kill the specified running entries in the queue.
Arguments:
revs: Stash revs or running exp name to be killed.
"""
@abstractmethod @abstractmethod
def shutdown(self, kill: bool = False): def shutdown(self, kill: bool = False):
"""Shutdown the queue worker. """Shutdown the queue worker.
@@ -495,3 +503,28 @@ class BaseStashQueue(ABC):
executor.collect_cache(exp.repo, exec_result.ref_info) executor.collect_cache(exp.repo, exec_result.ref_info)
return results return results
def get_queue_entry_by_names(
self,
exp_names: Collection[str],
) -> Dict[str, Optional[QueueEntry]]:
from scmrepo.exceptions import RevError as InternalRevError
exp_name_set = set(exp_names)
result: Dict[str, Optional[QueueEntry]] = {}
rev_entries = {}
for entry in self.iter_queued():
if entry.name in exp_name_set:
result[entry.name] = entry
else:
rev_entries[entry.stash_rev] = entry
for exp_name in exp_name_set.difference(result.keys()):
try:
rev = self.scm.resolve_rev(exp_name)
if rev in rev_entries:
result[exp_name] = rev_entries[rev]
except InternalRevError:
result[exp_name] = None
return result
+28 -1
View File
@@ -5,6 +5,7 @@ import time
from collections import defaultdict from collections import defaultdict
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Collection,
Dict, Dict,
Generator, Generator,
List, List,
@@ -20,7 +21,7 @@ from kombu.message import Message
from dvc.daemon import daemonize from dvc.daemon import daemonize
from dvc.exceptions import DvcException from dvc.exceptions import DvcException
from ..exceptions import ExpQueueEmptyError from ..exceptions import ExpQueueEmptyError, UnresolvedExpNamesError
from ..executor.base import ( from ..executor.base import (
EXEC_PID_DIR, EXEC_PID_DIR,
EXEC_TMP_DIR, EXEC_TMP_DIR,
@@ -200,6 +201,29 @@ class LocalCeleryQueue(BaseStashQueue):
pass pass
time.sleep(1) time.sleep(1)
def kill(self, revs: Collection[str]) -> None:
to_kill: Set[QueueEntry] = set()
not_active: List[str] = []
name_dict: Dict[
str, Optional[QueueEntry]
] = self.get_queue_entry_by_names(set(revs))
missing_rev: List[str] = []
active_queue_entry = set(self.iter_active())
for rev, queue_entry in name_dict.items():
if queue_entry is None:
missing_rev.append(rev)
elif queue_entry not in active_queue_entry:
not_active.append(rev)
else:
to_kill.add(queue_entry)
if missing_rev:
raise UnresolvedExpNamesError(missing_rev)
for queue_entry in to_kill:
self.proc.kill(queue_entry.name)
def _shutdown_handler(self, task_id: str = None, **kwargs): def _shutdown_handler(self, task_id: str = None, **kwargs):
if task_id in self._shutdown_task_ids: if task_id in self._shutdown_task_ids:
self._shutdown_task_ids.remove(task_id) self._shutdown_task_ids.remove(task_id)
@@ -336,5 +360,8 @@ class WorkspaceQueue(BaseStashQueue):
def get_result(self, entry: QueueEntry) -> Optional[ExecutorResult]: def get_result(self, entry: QueueEntry) -> Optional[ExecutorResult]:
raise NotImplementedError raise NotImplementedError
def kill(self, revs: Collection[str]) -> None:
raise NotImplementedError
def shutdown(self, kill: bool = False): def shutdown(self, kill: bool = False):
raise NotImplementedError raise NotImplementedError
+4 -26
View File
@@ -1,5 +1,5 @@
import logging import logging
from typing import TYPE_CHECKING, Dict, List, Mapping, Optional, Set, Union from typing import TYPE_CHECKING, Dict, List, Mapping, Optional, Union
from dvc.repo import locked from dvc.repo import locked
from dvc.repo.scm_context import scm_context from dvc.repo.scm_context import scm_context
@@ -85,7 +85,9 @@ def _resolve_exp_by_name(
commit_ref_dict[exp_ref] = exp_name commit_ref_dict[exp_ref] = exp_name
if not git_remote: if not git_remote:
_named_entries = _get_queue_entry_by_names(repo, remained) _named_entries = (
repo.experiments.celery_queue.get_queue_entry_by_names(remained)
)
for exp_name, entry in _named_entries.items(): for exp_name, entry in _named_entries.items():
if entry is not None: if entry is not None:
queue_entry_dict[exp_name] = entry queue_entry_dict[exp_name] = entry
@@ -127,30 +129,6 @@ def _clear_all_commits(repo, git_remote) -> List:
return _remove_commited_exps(repo.scm, ref_infos, git_remote) return _remove_commited_exps(repo.scm, ref_infos, git_remote)
def _get_queue_entry_by_names(
repo: "Repo",
exp_name_set: Set[str],
) -> Dict[str, Optional[QueueEntry]]:
from scmrepo.exceptions import RevError as InternalRevError
result = {}
rev_entries = {}
for entry in repo.experiments.celery_queue.iter_queued():
if entry.name in exp_name_set:
result[entry.name] = entry
else:
rev_entries[entry.stash_rev] = entry
for exp_name in exp_name_set.difference(result.keys()):
try:
rev = repo.scm.resolve_rev(exp_name)
if rev in rev_entries:
result[exp_name] = rev_entries[rev]
except InternalRevError:
result[exp_name] = None
return result
def _remove_commited_exps( def _remove_commited_exps(
scm: "Git", exp_ref_dict: Mapping[ExpRefInfo, str], remote: Optional[str] scm: "Git", exp_ref_dict: Mapping[ExpRefInfo, str], remote: Optional[str]
) -> List[str]: ) -> List[str]:
@@ -8,8 +8,8 @@ def test_shutdown_no_tasks(test_queue, mocker):
@shared_task @shared_task
def _foo(): def _foo(arg="foo"):
return "foo" return arg
def test_shutdown_active_tasks(test_queue, mocker): def test_shutdown_active_tasks(test_queue, mocker):
@@ -35,3 +35,35 @@ def test_shutdown_active_tasks(test_queue, mocker):
assert "foo" == result.get() assert "foo" == result.get()
assert result.id not in test_queue._shutdown_task_ids assert result.id not in test_queue._shutdown_task_ids
shutdown_spy.assert_called_once() shutdown_spy.assert_called_once()
def test_post_run_after_kill(test_queue, mocker):
import time
from celery import chain
sig_bar = test_queue.proc.run(
["python3", "-c", "import time; time.sleep(5)"], name="bar"
)
result_bar = sig_bar.freeze()
sig_foo = _foo.s()
result_foo = sig_foo.freeze()
run_chain = chain(sig_bar, sig_foo)
run_chain.delay()
timeout = time.time() + 10
while True:
if result_bar.status == "RUNNING":
break
if time.time() > timeout:
raise AssertionError()
assert result_foo.status == "PENDING"
test_queue.proc.kill("bar")
while True:
if result_foo.status == "SUCCESS":
break
if time.time() > timeout:
raise AssertionError()