Skip to content

Commit db1a28b

Browse files
authored
🐛 verdi process repair: fix ZMQ broker support (#7350)
Since the ZMQ broker runs inside the daemon and must be stopped for repair, write revival tasks directly to its on-disk `PersistentQueue`; the broker picks them up next time the daemon starts. The alternative would be to spin up a transient broker just to enqueue these tasks, which adds start/stop ceremony and lifecycle races without changing the end state on disk. Also replace RabbitMQ-specific wording in user-facing messages and test assertions with broker-agnostic terms.
1 parent ad4a70a commit db1a28b

3 files changed

Lines changed: 38 additions & 16 deletions

File tree

‎src/aiida/cmdline/commands/cmd_process.py‎

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,9 @@ def process_repair(manager, broker, dry_run, force):
518518
N.B.: This command requires the daemon to be stopped.
519519
520520
This command queries the database to find all "active" processes, meaning those that haven't yet reached a terminal
521-
state, and cross-references them with the active process tasks in the process queue of RabbitMQ. Any active process
522-
that does not have a corresponding process task can be considered a zombie, as it will never be picked up by a
523-
daemon worker to complete it and will effectively be "stuck". Any process task that does not correspond to an active
521+
state, and cross-references them with the active process tasks in the broker's task queue. Any active process that
522+
does not have a corresponding process task can be considered a zombie, as it will never be picked up by a daemon
523+
worker to complete it and will effectively be "stuck". Any process task that does not correspond to an active
524524
process is useless and should be discarded. Finally, duplicate process tasks are also problematic and are discarded.
525525
"""
526526
from aiida.engine.processes.control import get_active_processes, get_process_tasks, iterate_process_tasks
@@ -579,10 +579,10 @@ def process_repair(manager, broker, dry_run, force):
579579
echo.echo(set_active_processes.difference(set_process_tasks))
580580

581581
if not state_inconsistent:
582-
echo.echo_success('No inconsistencies detected between database and RabbitMQ.')
582+
echo.echo_success('No inconsistencies detected between database and broker.')
583583
return
584584

585-
echo.echo_warning('Inconsistencies detected between database and RabbitMQ.')
585+
echo.echo_warning('Inconsistencies detected between database and broker.')
586586

587587
if dry_run:
588588
echo.echo_critical('This was a dry-run, no changes will be made.')
@@ -600,11 +600,33 @@ def process_repair(manager, broker, dry_run, force):
600600
echo.echo_report(f'Acknowledged task `{pid}`')
601601

602602
# Revive zombie processes that no longer have a process task
603-
process_controller = manager.get_process_controller()
604-
for pid in set_active_processes:
605-
if pid not in set_process_tasks:
606-
process_controller.continue_process(pid)
607-
echo.echo_report(f'Revived process `{pid}`')
603+
zombies = set_active_processes.difference(set_process_tasks)
604+
if zombies:
605+
from aiida.brokers.zmq.broker import ZmqBroker
606+
607+
if isinstance(broker, ZmqBroker):
608+
# For ZMQ, the broker runs inside the daemon (which must be stopped for repair).
609+
# Write revival tasks directly to the persistent queue on disk — the broker will
610+
# pick them up when the daemon is restarted. This avoids starting a temporary
611+
# broker process and the timing issues that come with it.
612+
import uuid
613+
614+
from plumpy.process_comms import create_continue_body
615+
616+
from aiida.brokers.zmq.queue import PersistentQueue
617+
618+
queue_path = broker.storage_path / 'tasks'
619+
queue = PersistentQueue(queue_path)
620+
for pid in zombies:
621+
task_id = uuid.uuid4().hex
622+
body = create_continue_body(pid=pid, nowait=True)
623+
queue.push(task_id, {'body': body, 'no_reply': True})
624+
echo.echo_report(f'Revived process `{pid}`')
625+
else:
626+
process_controller = manager.get_process_controller()
627+
for pid in zombies:
628+
process_controller.continue_process(pid)
629+
echo.echo_report(f'Revived process `{pid}`')
608630

609631

610632
@verdi_process.command('dump')

‎tests/cmdline/commands/test_process.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,7 @@ def test_process_repair_consistent(monkeypatch, run_cli_command):
10211021
monkeypatch.setattr(process_control, 'get_process_tasks', lambda *args: [1, 2, 3])
10221022

10231023
result = run_cli_command(cmd_process.process_repair, use_subprocess=False)
1024-
assert 'No inconsistencies detected between database and RabbitMQ.' in result.output
1024+
assert 'No inconsistencies detected between database and broker.' in result.output
10251025

10261026

10271027
@pytest.mark.usefixtures('stopped_daemon_client')
@@ -1032,7 +1032,7 @@ def test_process_repair_duplicate_tasks(monkeypatch, run_cli_command):
10321032

10331033
result = run_cli_command(cmd_process.process_repair, use_subprocess=False)
10341034
assert 'There are duplicates process tasks:' in result.output
1035-
assert 'Inconsistencies detected between database and RabbitMQ.' in result.output
1035+
assert 'Inconsistencies detected between database and broker.' in result.output
10361036

10371037

10381038
@pytest.mark.usefixtures('stopped_daemon_client')
@@ -1043,7 +1043,7 @@ def test_process_repair_additional_tasks(monkeypatch, run_cli_command):
10431043

10441044
result = run_cli_command(cmd_process.process_repair, use_subprocess=False)
10451045
assert 'There are process tasks for terminated processes:' in result.output
1046-
assert 'Inconsistencies detected between database and RabbitMQ.' in result.output
1046+
assert 'Inconsistencies detected between database and broker.' in result.output
10471047
assert 'Attempting to fix inconsistencies' in result.output
10481048

10491049

@@ -1055,7 +1055,7 @@ def test_process_repair_missing_tasks(monkeypatch, run_cli_command):
10551055

10561056
result = run_cli_command(cmd_process.process_repair, use_subprocess=False)
10571057
assert 'There are active processes without process task:' in result.output
1058-
assert 'Inconsistencies detected between database and RabbitMQ.' in result.output
1058+
assert 'Inconsistencies detected between database and broker.' in result.output
10591059
assert 'Attempting to fix inconsistencies' in result.output
10601060

10611061

@@ -1066,7 +1066,7 @@ def test_process_repair_dry_run(monkeypatch, run_cli_command):
10661066
monkeypatch.setattr(process_control, 'get_process_tasks', lambda *args: [1, 2])
10671067

10681068
result = run_cli_command(cmd_process.process_repair, ['--dry-run'], raises=True, use_subprocess=False)
1069-
assert 'Inconsistencies detected between database and RabbitMQ.' in result.output
1069+
assert 'Inconsistencies detected between database and broker.' in result.output
10701070
assert 'This was a dry-run, no changes will be made.' in result.output
10711071

10721072

‎tests/cmdline/commands/test_rabbitmq.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_tasks_analyze_consistent(monkeypatch, run_cli_command):
6060
monkeypatch.setattr(control, 'get_process_tasks', lambda *args: [1, 2, 3])
6161

6262
result = run_cli_command(cmd_rabbitmq.cmd_tasks_analyze, use_subprocess=False)
63-
assert 'No inconsistencies detected between database and RabbitMQ.' in result.output
63+
assert 'No inconsistencies detected between database and broker.' in result.output
6464

6565

6666
@pytest.mark.usefixtures('stopped_daemon_client')

0 commit comments

Comments
 (0)