Skip to content

Interrupt Handling Design

This document describes the double-Ctrl+C interruption handling for remote benchmarks.

Goals

  • Prevent accidental stops of long-running benchmarks.
  • Ensure graceful teardown of remote workloads when a stop is confirmed.
  • Provide clear UI feedback.

Architecture

The interrupt handling logic is separated into three layers:

  1. State Machine (DoubleCtrlCStateMachine)
  2. Pure logic component in lb_controller/interrupts.py.
  3. Tracks states: RUNNING -> STOP_ARMED -> STOPPING -> FINISHED.
  4. Decides action: WARN_ARM, REQUEST_STOP, or DELEGATE (allow default/force kill).

  5. Handler (SigintDoublePressHandler)

  6. Context manager that installs/restores signal.signal(SIGINT, ...).
  7. Routes signals to the state machine.
  8. Executes callbacks based on decision (on_first_sigint, on_confirmed_sigint).

  9. Orchestration (RunService, BenchmarkController)

  10. lb_app.services.run_service.RunService installs the handler and provides UI callbacks.
  11. StopToken in lb_runner.stop_token signals intent to stop across threads/processes.
  12. BenchmarkController and ControllerRunner observe the token and coordinate teardown.
  13. AnsibleRunnerExecutor interrupts active playbooks when a stop is requested.

Behavior

  • First Ctrl+C
  • State: RUNNING -> STOP_ARMED.
  • Action: log "Press Ctrl+C again to stop...". Execution continues.
  • Second Ctrl+C
  • State: STOP_ARMED -> STOPPING.
  • Action: trigger StopToken.
    • Active Ansible playbook is terminated.
    • Controller loop breaks.
    • Plugin teardown runs (non-cancellable).
    • Global teardown runs (non-cancellable).
  • Third Ctrl+C (Force)
  • State: STOPPING.
  • Action: delegate to Python default handler for a forced exit if teardown hangs.

Files

  • lb_controller/interrupts.py: state machine and handler.
  • lb_app/services/run_service.py: wiring to UI and controller.
  • lb_controller/controller.py: phase-aware stop logic.
  • lb_controller/ansible_executor.py: playbook interruption.