Skip to content

Guard audio callbacks during shutdown#2413

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/fix-exit-audioengine-crash
May 7, 2026
Merged

Guard audio callbacks during shutdown#2413
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/fix-exit-audioengine-crash

Conversation

@rfoust

@rfoust rfoust commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Prevent shutdown-time TX state callbacks from dereferencing m_audio after the audio worker has already been torn down.
  • Keep the existing waterfall/status updates running while skipping only audio-specific work when m_audio is null.

Crash

The reported macOS crash happened while exiting the app. During MainWindow teardown the audio worker is stopped and m_audio is set to nullptr. Later in member destruction, WanConnection::~WanConnection() disconnects the TLS socket, which emits disconnected, drives RadioModel::onDisconnected(), and emits radioTransmittingChanged(false) / related TX reset signals. The connected MainWindow lambdas still ran and called methods such as m_audio->setRadioTransmitting(tx), so the app entered AudioEngine::setRadioTransmitting() with an invalid/null this pointer and crashed on the atomic exchange shown in the report.

Fix

Add null guards around the audio calls in the TX-state callbacks (moxChanged, txAudioGateChanged, and radioTransmittingChanged). This preserves the existing shutdown order, object ownership, signal routing, and non-audio UI updates, while avoiding calls into the audio worker after it has been destroyed.

Verification

  • cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
  • cmake --build build
  • git diff --check

@rfoust rfoust requested a review from ten9876 as a code owner May 6, 2026 16:13
Copilot AI review requested due to automatic review settings May 6, 2026 16:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prevents a shutdown-time crash caused by TX-state Qt signal callbacks calling into AudioEngine after MainWindow has already torn down the audio worker and set m_audio to nullptr.

Changes:

  • Add m_audio null guards in the TransmitModel::moxChanged callback before calling AudioEngine::setTransmitting() / AudioEngine::setRadeMode().
  • Add m_audio null guards in the RadioModel::txAudioGateChanged callback before forcing TX-off audio state.
  • Add m_audio null guards in the RadioModel::radioTransmittingChanged callback before calling AudioEngine::setRadioTransmitting(), while preserving non-audio UI updates (waterfall/status).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Hi @rfoust — thanks for putting this together! Wanted to flag what's happening with the red ❌ before you spend any time digging.

TL;DR — this isn't your code

The only failed check is Cleanup artifacts, and the failing step is Delete artifacts (job log). That's a teardown step in our agent-review workflow that runs after the actual review work is done. The substantive jobs in that run all passed:

  • ✅ Prepare
  • ✅ Agent
  • ✅ Upload results
  • ❌ Cleanup artifacts ← gh api call against the artifacts endpoint flaked out

This is a known intermittent on the GitHub artifacts API (rate limit / 5xx from DELETE /actions/artifacts/{id}), not anything in your diff. No action needed on your end — it'll either self-resolve on the next push or a maintainer will rerun it.

Heads up: the real build is still running

The two checks that matter for merge — build and analyze (cpp) — were still in progress at the time of this comment. Worth keeping an eye on those once they finish. The diff is six small defensive if (m_audio) guards around setTransmitting / setRadeMode / setRadioTransmitting calls in MainWindow.cpp (lines ~1966–2014), all in lambdas connected to RadioModel signals. That's the kind of change that almost never breaks compilation, so I'd expect those checks to come back green.

One small note while you're here: each of the three lambdas only changes if m_audio is non-null, so the wider #if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE) block below the first guard is unaffected — good. If you wanted to tighten this further (totally optional), the three guarded calls in the moxChanged lambda could share a single early-return at the top, but the current shape is perfectly readable and matches the surrounding style.

Thanks again for taking the time on this — these are exactly the kind of crash-prevention guards that pay off when the audio backend hasn't initialized yet on a slow connect path. 🎯

@ten9876 ten9876 merged commit fcbba80 into aethersdr:main May 7, 2026
8 of 9 checks passed
@ten9876

ten9876 commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Claude here — really good crash report, Robbie. Walking through the destruction order to nail the QObject auto-disconnect timing was the key insight. The per-call-site guards (including separating the inner RADE guard from the outer) match the file's existing pattern cleanly. Merged.

73, Jeremy KK7GWY & Claude (AI dev partner)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants