Skip to content

Decouple action execution from event loop (ADR-015) #516

@amiable-dev

Description

@amiable-dev

Summary

Long-running action sequences (e.g., Action::Sequence with 25 SendMidi steps) block the daemon's main tokio::select! event loop, freezing the GUI event stream for the duration of execution. This contradicts the real-time feedback goals of ADR-014.

Root Cause

ActionExecutor::execute() is synchronous and uses thread::sleep(50ms) between sequence steps. It runs inline in process_device_event() within the single tokio::select! loop. A 25-note tune blocks the event loop for ~1.25 seconds.

Proposed Solution (ADR-015)

See docs/adrs/ADR-015-async-action-execution.md for the full architecture.

Key decisions:

  • D1: Dedicated std::thread executor (Enigo thread-affinity)
  • D2: Separate control plane (AtomicBool shutdown, watch config) from data plane (crossbeam_channel actions)
  • D3: Non-blocking try_send dispatch, mapping_dropped on overflow
  • D4: Unbounded completion channel with biased select (completions prioritized)
  • D5: invocation_id for event correlation
  • D6: Split mapping_matched (immediate) / mapping_fired (on completion)
  • D7: ModeChange semantic change (deliberate — events processed under current mode during async execution)
  • D8: MIDI recursion guard via message fingerprinting (TTL ring buffer)
  • D9: Head-of-line blocking acknowledged as tradeoff
  • D10: Actions are immutable plans, config updates via watch channel
  • D11: Graceful shutdown with per-action-type cancellation semantics
  • D12: Dual latency measurement (executor-side + end-to-end)
  • D13: Terminal event lifecycle invariant (every match gets exactly one of fired/dropped/cancelled)
  • D14: Mode-change lag bounded to <1ms via biased select

Implementation Phases

  1. Executor thread infrastructure (channels, structs, thread spawn)
  2. Decouple execution (try_send dispatch, completion handler)
  3. Cancellation-aware execution (interruptible sleep, shell timeouts)
  4. MIDI recursion guard (fingerprint ring buffer)
  5. Testing & verification (11 test scenarios)

Council Review History

  • Review 1: FAIL (0.49) — 8 issues fixed
  • Review 2: FAIL (0.52) — 7 issues fixed
  • Review 3: UNCLEAR (0.62) — 7 issues fixed
  • Review 4: FAIL (0.52) — 7 issues fixed
  • Awaiting Review 5

Related

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions