Skip to content

Commit

Permalink
CLI: Move verdi devel revive to verdi devel rabbitmq tasks revive
Browse files Browse the repository at this point in the history
The command was recently added before the `verdi devel rabbitmq`
subcommand existed. At this point it makes more sense to group it there
because it is directly related to RabbitMQ and if this service is ever
removed, this command also no longer serves a purpose.
  • Loading branch information
sphuber committed Nov 4, 2022
1 parent 26f82a8 commit 60342fe
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 75 deletions.
30 changes: 1 addition & 29 deletions aiida/cmdline/commands/cmd_devel.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from aiida import get_profile
from aiida.cmdline.commands.cmd_verdi import verdi
from aiida.cmdline.params import arguments, options, types
from aiida.cmdline.params import options, types
from aiida.cmdline.utils import decorators, echo
from aiida.common import exceptions

Expand All @@ -24,34 +24,6 @@ def verdi_devel():
"""Commands for developers."""


@verdi_devel.command('revive')
@arguments.PROCESSES()
@options.FORCE()
@decorators.only_if_daemon_running(message='The daemon has to be running for this command to work.')
def devel_revive(processes, force):
"""Revive processes that seem stuck and are no longer reachable.
Warning: Use only as a last resort after you've gone through the checklist below.
\b
1. Does ``verdi status`` indicate that both daemon and RabbitMQ are running properly?
If not, restart the daemon with ``verdi daemon restart --reset`` and restart RabbitMQ.
2. Try ``verdi process play <PID>``.
If you receive a message that the process is no longer reachable, use ``verdi devel revive <PID>``.
Details: When RabbitMQ loses the process task before the process has completed, the process is never picked up by
the daemon and will remain "stuck". ``verdi devel revive`` recreates the task, which can lead to multiple instances
of the task being executed and should thus be used with caution.
"""
from aiida.engine.processes.control import revive_processes

if not force:
echo.echo_warning('This command should only be used if you are absolutely sure the process task was lost.')
click.confirm(text='Do you want to continue?', abort=True)

revive_processes(processes)


@verdi_devel.command('check-load-time')
def devel_check_load_time():
"""Check for common indicators that slowdown `verdi`.
Expand Down
30 changes: 29 additions & 1 deletion aiida/cmdline/commands/cmd_rabbitmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import yaml

from aiida.cmdline.commands.cmd_devel import verdi_devel
from aiida.cmdline.params import options
from aiida.cmdline.params import arguments, options
from aiida.cmdline.utils import decorators, echo

if t.TYPE_CHECKING:
Expand Down Expand Up @@ -357,3 +357,31 @@ def cmd_tasks_analyze(ctx, manager, fix):
if pid not in set_process_tasks:
process_controller.continue_process(pid)
echo.echo_report(f'Revived process `{pid}`')


@cmd_tasks.command('revive')
@arguments.PROCESSES()
@options.FORCE()
@decorators.only_if_daemon_running(message='The daemon has to be running for this command to work.')
def cmd_tasks_revive(processes, force):
"""Revive processes that seem stuck and are no longer reachable.
Warning: Use only as a last resort after you've gone through the checklist below.
\b
1. Does ``verdi status`` indicate that both daemon and RabbitMQ are running properly?
If not, restart the daemon with ``verdi daemon restart --reset`` and restart RabbitMQ.
2. Try ``verdi process play <PID>``.
If you receive a message that the process is no longer reachable, use ``verdi devel revive <PID>``.
Details: When RabbitMQ loses the process task before the process has completed, the process is never picked up by
the daemon and will remain "stuck". ``verdi devel revive`` recreates the task, which can lead to multiple instances
of the task being executed and should thus be used with caution.
"""
from aiida.engine.processes.control import revive_processes

if not force:
echo.echo_warning('This command should only be used if you are absolutely sure the process task was lost.')
click.confirm(text='Do you want to continue?', abort=True)

revive_processes(processes)
4 changes: 2 additions & 2 deletions aiida/manage/tests/pytest_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,9 @@ def get_code(entry_point, executable, computer=aiida_localhost, label=None, prep

@pytest.fixture()
def daemon_client(aiida_profile):
"""Return a daemon client for the configured test profile for the test session.
"""Return a daemon client for the configured test profile for the scope of the function.
The daemon will be automatically stopped at the end of the session.
The daemon will be automatically stopped at the end of the test function.
"""
daemon_client = DaemonClient(aiida_profile._manager._profile) # pylint: disable=protected-access

Expand Down
1 change: 0 additions & 1 deletion docs/source/reference/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ Below is a list with all available subcommands.
check-undesired-imports Check that verdi does not import python modules it shouldn't.
launch-add Launch an ``ArithmeticAddCalculation``.
rabbitmq Commands to interact with RabbitMQ.
revive Revive processes that seem stuck and are no longer reachable.
run-sql Run a raw SQL command on the profile database (only...
validate-plugins Validate all plugins by checking they can be loaded.
Expand Down
43 changes: 1 addition & 42 deletions tests/cmdline/commands/test_devel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
"""Tests for ``verdi devel``."""
import re

from plumpy.process_comms import RemoteProcessThreadController
import pytest

from aiida.cmdline.commands import cmd_devel
from aiida.engine import ProcessState, submit
from aiida.orm import Int, Node, ProcessNode, QueryBuilder
from aiida.orm import Node, ProcessNode, QueryBuilder


def test_run_sql(run_cli_command):
Expand All @@ -25,45 +23,6 @@ def test_run_sql(run_cli_command):
assert str(Node.collection.count()) in result.output, result.output


@pytest.mark.usefixtures('stopped_daemon_client')
def test_revive_without_daemon(run_cli_command):
"""Test that ``verdi devel revive`` fails if no daemon is running."""
assert run_cli_command(cmd_devel.devel_revive, raises=True)


@pytest.mark.usefixtures('aiida_profile')
def test_revive(run_cli_command, monkeypatch, aiida_local_code_factory, submit_and_await, started_daemon_client):
"""Test ``verdi devel revive``."""
code = aiida_local_code_factory('core.arithmetic.add', '/bin/bash')
builder = code.get_builder()
builder.x = Int(1)
builder.y = Int(1)
builder.metadata.options.resources = {'num_machines': 1}

# Temporarily patch the ``RemoteProcessThreadController.continue_process`` method to do nothing and just return a
# completed future. This ensures that the submission creates a process node in the database but no task is sent to
# RabbitMQ and so the daemon will not start running it.
with monkeypatch.context() as context:
context.setattr(RemoteProcessThreadController, 'continue_process', lambda *args, **kwargs: None)
node = submit(builder)

# The node should now be created in the database but "stuck"
assert node.process_state == ProcessState.CREATED

# Time to revive it by recreating the task and send it to RabbitMQ
run_cli_command(cmd_devel.devel_revive, [str(node.pk), '--force'])

# Wait for the process to be picked up by the daemon and completed. If there is a problem with the code, this call
# should timeout and raise an exception
submit_and_await(node)
assert node.is_finished_ok

# Need to manually stop the daemon such that it cleans all state related to the submitted processes. It is not quite
# understood how, but leaving the daemon running, a following test that will submit to the daemon may hit an
# an exception because the node submitted here would no longer exist and the daemon would try to operate on it.
started_daemon_client.stop_daemon(wait=True)


@pytest.mark.usefixtures('aiida_profile_clean')
def test_launch_add(run_cli_command):
"""Test ``verdi devel launch-add``.
Expand Down
37 changes: 37 additions & 0 deletions tests/cmdline/commands/test_rabbitmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
# For further information please visit http://www.aiida.net #
###########################################################################
"""Tests for ``verdi devel rabbitmq``."""
from plumpy.process_comms import RemoteProcessThreadController
import pytest

from aiida.cmdline.commands import cmd_rabbitmq
from aiida.engine import ProcessState, submit
from aiida.orm import Int


def test_queues_list(run_cli_command):
Expand Down Expand Up @@ -94,3 +97,37 @@ def test_tasks_analyze_verbosity(monkeypatch, run_cli_command):
result = run_cli_command(cmd_rabbitmq.cmd_tasks_analyze, ['-v', 'INFO'], raises=True)
assert 'Active processes: [1, 2, 3, 4]' in result.output
assert 'Process tasks: [1, 2]' in result.output


@pytest.mark.usefixtures('stopped_daemon_client')
def test_tasks_revive_without_daemon(run_cli_command):
"""Test that ``tasks revive`` fails if no daemon is running."""
assert run_cli_command(cmd_rabbitmq.cmd_tasks_revive, raises=True)


@pytest.mark.usefixtures('aiida_profile', 'started_daemon_client')
def test_revive(run_cli_command, monkeypatch, aiida_local_code_factory, submit_and_await):
"""Test ``tasks revive``."""
code = aiida_local_code_factory('core.arithmetic.add', '/bin/bash')
builder = code.get_builder()
builder.x = Int(1)
builder.y = Int(1)
builder.metadata.options.resources = {'num_machines': 1}

# Temporarily patch the ``RemoteProcessThreadController.continue_process`` method to do nothing and just return a
# completed future. This ensures that the submission creates a process node in the database but no task is sent to
# RabbitMQ and so the daemon will not start running it.
with monkeypatch.context() as context:
context.setattr(RemoteProcessThreadController, 'continue_process', lambda *args, **kwargs: None)
node = submit(builder)

# The node should now be created in the database but "stuck"
assert node.process_state == ProcessState.CREATED

# Time to revive it by recreating the task and send it to RabbitMQ
run_cli_command(cmd_rabbitmq.cmd_tasks_revive, [str(node.pk), '--force'])

# Wait for the process to be picked up by the daemon and completed. If there is a problem with the code, this call
# should timeout and raise an exception
submit_and_await(node)
assert node.is_finished_ok

0 comments on commit 60342fe

Please sign in to comment.