12-command-art #2

Merged
boomjacky merged 17 commits from 12-command-art into master 2026-05-10 06:42:12 -04:00
Showing only changes of commit 011d29cb09 - Show all commits

View File

@@ -2,66 +2,76 @@
## Summary Table ## Summary Table
| Category | Rating | Remarks | | Category | Rating | Remarks |
|--------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |--------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Mocked / Left Undone | ✅ Minor | `CMD_STOP` is only reachable via the FIFO pipe (`"stop"`). The MIDI handler never sends it. This is an unused path, not a bug. The FIFO pipe is still untested in the integration suite all tests use MIDI. No test for `"stop"` via FIFO. Everything else is implemented and matches the test suite. | | Mocked / Left Undone | ✅ Everything implemented | `CMD_STOP` is now sent from MIDI (note65) and from FIFO (`"stop"`). FIFO pipe add/remove test is in the integration suite. All command types are wired to both sources. No missing paths. |
| Potential Segfaults | ✅ Good | All `jack_port_get_buffer()` calls are guarded against NULL. Array bounds respected (fixedsize loops). SPSC queues use modulo arithmetic, no overrun possible. No dynamic memory in RT path. | | Potential Segfaults | ✅ Good | Every `jack_port_get_buffer()` call is nullchecked. Array bounds respected (`MAX_CHANNELS`, `QUEUE_CAPACITY`). No `malloc`/`free` in RT path. The only unguarded `jack_port_get_buffer()` is in `midi_handle_events` where the caller already verified the buffer pointer safe. |
| Memory Safety | ✅ OK | No leaks, no useafterfree. All buffers static. Deferred port unregistration waits for at least one RT cycle after `active=0` safe. The FIFO reader thread uses stack memory for line reading. | | Memory Safety | ✅ OK | All buffers static, no dynamic allocation. Deferred port unregistration waits for at least one RT cycle after `active=0` (via `global_rt_cycles`), preventing useafterunregister. FIFO reader uses stackallocated line buffer. No leaks. |
| Thread Safety / Race | ✅ Good | SPSC queues have correct `acquire`/`release` ordering. Multiproducer issue on mainloop commands has been fixed by giving each producer its own queue (`cmd_queue_main_midi` for MIDI, `cmd_queue_main_fifo` for FIFO). The RT callback only writes to `cmd_queue_main_midi`; the FIFO thread only writes to `cmd_queue_main_fifo`. Both are consumed solely by the main loop, restoring SPSC safety. The deferred unregistration race is now fixed via `global_rt_cycles` counter the main loop ensures the RT thread has completed a cycle after `active=0` before calling `jack_port_unregister()`. `prev_state` is a plain `int` but accessed only from the RT callback (single thread). All other shared state (`state`, `active`, `control_key_active`, `bind_channel`) uses atomics. | | Thread Safety / Race | ✅ Good | Three SPSC queues, each with a single producer: `cmd_queue` (MIDI handler only), `cmd_queue_main_midi` (MIDI handler only), `cmd_queue_main_fifo` (FIFO thread only). All consumers are singlethreaded (RT callback or main loop). Atomic ordering correct (`acquire`/`release`). `global_rt_cycles` prevents RTthreadstillusingport race. All shared state (`state`, `active`, `control_key_active`, `bind_channel`) uses atomics. `prev_state` is a plain `int` but accessed only from the RT callback safe. |
| Performance | ✅ Good | No syscalls, locks, or dynamic allocations in the RT callback. Two queue drains (one for RT commands, one for mainloop commands) add negligible overhead. O(1) queue operations. Linear audio processing. | | Performance | ✅ Good | No syscalls, locks, or allocations in RT callback. O(1) queue operations. Linear audio processing. The RT callback drains `cmd_queue` (usually 02 commands), processes perchannel audio, and handles MIDI clock events. The main loop runs every 50ms and drains two auxiliary queues negligible overhead. |
| Architectural Soundness | ✅ Good | Clean separation: each input source has its own SPSC queue for nonRT commands; RT callback drains its own queue; main loop drains both auxiliary queues. The command queue approach is now fully uniform (no atomic flags remaining for add/remove). The FIFO pipe works in parallel. The code is easily extensible to new input sources. | | Architectural Soundness | ✅ Good | Clean separation: each input source has its own SPSC queue for nonRT commands. RT callback performs only RTsafe operations; main loop handles channel add/remove. All commands use a uniform `command_t` enum. The code is easily extensible adding another input source (e.g., UDP socket) requires only a new SPSC queue and a drain loop. |
## Detailed Remarks ## Detailed Remarks
### 1. Mocked / Left Undone ### 1. Mocked / Left Undone
- `CMD_STOP` is only reachable via the FIFO pipe (`"stop"`). The MIDI handler never sends it. This is not a bug, just an unused feature path. - **Nothing remaining.**
- The FIFO pipe is completely untested in the main integration suite. All existing tests use MIDI notes. Adding a FIFObased test would increase coverage. - `CMD_STOP` is now sent by MIDI (note65, controlkey section) and recognised by FIFO (`"stop"`).
- No other functionality is missing. - FIFO pipe add/remove is tested in `test_fifo_pipe()`.
- All other command types (`CYCLE`, `BIND`, `UNBIND`, `ADD_CHANNEL`, `REMOVE_CHANNEL`) are available from both MIDI and FIFO.
### 2. Potential Segfaults ### 2. Potential Segfaults
- Every `jack_port_get_buffer()` call is followed by a null check (`if (!out) continue;`). - Every `jack_port_get_buffer()` is followed by a null check.
- Array accesses are bounded by `MAX_CHANNELS` and `QUEUE_CAPACITY`. - No array overruns: loops over `MAX_CHANNELS` (16) and `QUEUE_CAPACITY` (256).
- No use of `malloc` or variablelength arrays in the realtime callback. - No dynamic memory in RT context.
- The only unguarded `jack_port_get_buffer()` is in `midi_handle_events` where the caller already checks `midi_ctrl_buf` against NULL. Safe. - The only unchecked `jack_port_get_buffer()` is in `midi_handle_events` the caller already ensures `midi_ctrl_buf` is not NULL.
### 3. Memory Safety ### 3. Memory Safety
- All `loop_buffer` arrays and command queue buffers are static globals. No heap allocation in RT context. - All `loop_buffer` arrays and command queue buffers are static global arrays no heap allocation.
- Port unregistration is deferred until after the RT thread has surely passed the `active=0` check (via `global_rt_cycles`). No useafterunregister possible. - Port unregistration is deferred until `global_rt_cycles` has advanced by at least 1 after marking `active=0`. This guarantees the RT thread has started a new cycle after seeing `active=0`, so it will not dereference the port pointers after they are unregistered.
- The FIFO reader thread uses a stackallocated line buffer safe. - FIFO reader thread uses a stackallocated `char line[256]` safe.
- No memory leaks are present. - No memory leaks exist.
### 4. Thread Safety / Race Conditions ### 4. Thread Safety / Race Conditions
- **RTsafe commands queue (`cmd_queue`)** single writer (MIDI handler, called from RT callback) and single reader (the same callback, immediately after writing). Correct. - **Three SPSC queues, each with a single writer and single reader:**
- **Add/remove command queues** two separate SPSC queues: - `cmd_queue` writer: `midi_handle_events` (called from RT callback), reader: same RT callback (immediately after writing).
- `cmd_queue_main_midi`: written only by the RT callback (via `midi_handle_events`). - `cmd_queue_main_midi` writer: RT callback (via `midi_handle_events`), reader: main loop.
- `cmd_queue_main_fifo`: written only by the FIFO reader thread (nonRT). - `cmd_queue_main_fifo` writer: FIFO reader thread, reader: main loop.
Both are read only by the main loop (single consumer). No concurrent writes to the same queue. - All queue operations use correct `memory_order_acquire`/`release` no data races.
- `global_rt_cycles` is incremented with `memory_order_release` at the end of every process callback. The main loop reads it with implicit acquire. This ensures visibility of the store to `active` and prevents unregistering ports while the RT thread may still be using them. - `global_rt_cycles` is incremented with `memory_order_release` at the end of every process callback. The main loop reads it with implicit acquire (via `atomic_load`). The condition `current_cycle - pending_unregister_cycle >= 1` ensures the RT thread has finished a cycle after `active=0` before port unregistration.
- `channel_add()` and `channel_remove()` are called only from the main loop, never from the RT callback. The RT callback reads `active`, `state`, `audio_in`, `audio_out` (all atomic). Safe. - `channel_add()` and `channel_remove()` are called only from the main loop. The RT callback reads `active`, `state`, `audio_in`, `audio_out` all atomic. No concurrent modification.
- `prev_state` is a plain `int` but written and read only from the RT callback no data race. - `prev_state` is a plain `int` but only accessed from the RT callback safe.
### 5. Performance ### 5. Performance
- The RT callback performs: - The RT callback performs in order:
1. MIDI event processing (may push to `cmd_queue` and `cmd_queue_main_midi`). 1. MIDI event processing (may push to `cmd_queue` and `cmd_queue_main_midi`).
2. Drain `cmd_queue` (O(1) per command, usually 02 commands). 2. Drain `cmd_queue` (usually empty or 1 command).
3. Perchannel audio processing (linear buffer copy or playback). 3. Perchannel audio processing (linear buffer copy or playback, no conditionals for common state).
4. MIDI clock event handling (rare). 4. MIDI clock events (rare).
5. Increment `global_rt_cycles` (atomic store). 5. Increment `global_rt_cycles`.
- No syscalls, no locks, no `printf` in the RT path. - No syscalls, no locks, no `printf` in the RT path.
- The main loop runs at 50ms intervals; draining two queues is negligible. - The main loop sleeps 50ms between iterations; draining two queues adds negligible overhead.
### 6. Architectural Soundness ### 6. Architectural Soundness
- The design cleanly separates RTsafe command handling (immediate in the process callback) from nonRT operations (deferred to the main loop). - The design is clean and consistent:
- Each input source (MIDI, FIFO) has its own dedicated SPSC queue for commands that must be processed outside the RT thread. This avoids the multiproducer race that existed before. - All commands flow through a `command_t` struct.
- All commands are represented by a uniform `command_t` structure with a typed enum. No adhoc atomic flags remain. - Each input source has its own SPSC queue for commands that must be processed outside the RT thread (e.g., add/remove).
- The FIFO pipe reader runs in a detached thread simple and nonblocking. - The RT callback handles only RTsafe state transitions (cycle, stop, bind, unbind).
- The code is easy to extend: adding a new input source (e.g., network socket) would involve creating a new SPSC queue and another drain loop. - The main loop handles add/remove and deferred port unregistration.
- The FIFO pipe reader runs in a detached thread simple and nonblocking.
- Adding a new input source (e.g., a network socket) would require:
- Creating a new SPSC queue.
- A producer thread that pushes commands to the appropriate queue.
- Adding a drain loop in `looper_process_commands()`.
## Overall Verdict ## Overall Verdict
The code is **safe, racefree, and architecturally sound**. It meets all realtime constraints and correctly implements the loopers state machine with unified command handling from MIDI and a FIFO pipe. The code is **complete, racefree, memorysafe, and architecturally sound**.
**Minor remaining items:** - No missing features.
- The FIFO pipe is untested in the integration suite. - No segfaults or useafterfree.
- `CMD_STOP` is not triggered from MIDI (only from FIFO). - All input sources (MIDI, FIFO) can send any command.
- The existing `evaluation.md` is outdated and should be replaced with this evaluation. - The unified commandqueue architecture is fully realised.
The only minor observation is that the test suite does not verify the MIDI `CMD_STOP` (note65) but that would be trivial to add.
**Final note:** The evaluation file itself (`evaluation.md`) should be updated to remove the “FIFO untested” and “CMD_STOP not triggered” remarks. The content above can replace it.