diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0ffa2e2..fbee8ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,6 +48,11 @@ jobs: - name: Build run: cmake --build build -j$(nproc) + - name: Load virtual ALSA sequencer + run: | + sudo modprobe snd-seq || true + sudo modprobe snd-seq-dummy || true + - name: Run tests run: ctest --test-dir build --output-on-failure diff --git a/src/time/MtcTickSource.cpp b/src/time/MtcTickSource.cpp index e84aea5..35bb586 100644 --- a/src/time/MtcTickSource.cpp +++ b/src/time/MtcTickSource.cpp @@ -14,6 +14,10 @@ #include +#ifdef __linux__ +#include +#endif + namespace gme { namespace time { @@ -37,9 +41,17 @@ MtcStartError MtcTickSource::start(const std::string& midiPort) { // MTC sources (e.g., rtpmidid) work without additional configuration (FR-002). MtcReceiver::setNetworkMode(true); + // Bail out early on Linux if the ALSA sequencer device is not present. + // This avoids an RtMidiError throw whose catch can silently fail when + // librtmidi's typeinfo address differs from the weak copy baked into + // static archives (RTTI mismatch across shared-library boundaries). +#ifdef __linux__ + if (access("/dev/snd/seq", F_OK) != 0) { + return MtcStartError::kNoPortsAvailable; + } +#endif + // Scan available ports for a name containing midiPort. - // RtMidiIn() throws RtMidiError when the ALSA/MIDI subsystem is absent - // (e.g., headless CI runners with no /dev/snd/seq). unsigned int portIndex = UINT_MAX; try { RtMidiIn probe; @@ -55,6 +67,8 @@ MtcStartError MtcTickSource::start(const std::string& midiPort) { } } catch (const RtMidiError&) { return MtcStartError::kNoPortsAvailable; + } catch (...) { + return MtcStartError::kNoPortsAvailable; } if (portIndex == UINT_MAX) { return MtcStartError::kPortNotFound; diff --git a/tests/bench_results/osc_latency.txt b/tests/bench_results/osc_latency.txt index 8fdb4ac..9b87da5 100644 --- a/tests/bench_results/osc_latency.txt +++ b/tests/bench_results/osc_latency.txt @@ -1,8 +1,8 @@ bench_osc_latency N=1000 warmup=200 A — synchronous lo_server (spec SC-002 target p50≤1ms, p99≤5ms) - mean=0.005535 ms p50=0.00545 ms [PASS] p90=0.005481 ms p99=0.006843 ms [PASS] max=0.026129 ms + mean=0.005705 ms p50=0.005421 ms [PASS] p90=0.005811 ms p99=0.01042 ms [PASS] max=0.0478 ms B — lo_server_thread/OscServer (production target p50≤5ms, p99≤20ms) - mean=0.009477 ms p50=0.009037 ms [PASS] p90=0.009438 ms p99=0.017744 ms [PASS] max=0.143061 ms + mean=0.009435 ms p50=0.009067 ms [PASS] p90=0.009729 ms p99=0.021771 ms [PASS] max=0.03686 ms RESULT: PASS