Skip to content

Commit db3387f

Browse files
ten9876claude
andcommitted
fix(qso-recorder): lock-free fast path on audio feeds (#3895 review #3)
feedTxAudio/feedRxAudio took m_writeMutex *before* their fast-path checks. The new CW record pump calls feedTxAudio ~100x/s from the real-time audio thread; if the GUI thread holds m_writeMutex during stopRecording/finalize disk I/O, the audio thread blocks inside the real-time path -> xrun. (RX feed has the same shape and is fed continuously from the RX audio thread.) Make m_recording std::atomic<bool> and check the atomics (m_recording + m_transmitting) lock-free before taking the mutex, returning early in the common not-recording / wrong-direction case. The post-lock re-check stays authoritative, so behavior is unchanged — only the lock is skipped when there's nothing to write. Per maintainer call on the other items: #1 (CW pump architecture) is not a blocker; #2 (RecordingMode "Client" default) is approved as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9433921 commit db3387f

2 files changed

Lines changed: 15 additions & 1 deletion

File tree

src/core/QsoRecorder.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,13 @@ static QByteArray float32ToInt16(const QByteArray& pcm)
125125

126126
void QsoRecorder::feedRxAudio(const QByteArray& pcm)
127127
{
128+
// Lock-free fast path off the real-time audio thread: skip the mutex when
129+
// we're not recording an RX over, so a GUI thread holding m_writeMutex
130+
// during finalize/stop can't stall audio. The post-lock check stays
131+
// authoritative (m_recording/m_transmitting can flip after this read).
132+
if (!m_recording.load(std::memory_order_acquire)
133+
|| m_transmitting.load(std::memory_order_acquire))
134+
return;
128135
std::lock_guard<std::mutex> lock(m_writeMutex);
129136
// While transmitting the radio mutes RX (this stream would be silence), and
130137
// the TX monitor is recorded instead — skip RX so the two don't double-write
@@ -137,6 +144,13 @@ void QsoRecorder::feedRxAudio(const QByteArray& pcm)
137144

138145
void QsoRecorder::feedTxAudio(const QByteArray& int16Stereo)
139146
{
147+
// Lock-free fast path: the CW record pump calls this ~100×/s from the
148+
// real-time audio thread. Skip the mutex when we're not recording a TX over
149+
// so a GUI thread holding m_writeMutex during finalize/stop can't stall
150+
// audio (xrun). The post-lock check stays authoritative.
151+
if (!m_recording.load(std::memory_order_acquire)
152+
|| !m_transmitting.load(std::memory_order_acquire))
153+
return;
140154
std::lock_guard<std::mutex> lock(m_writeMutex);
141155
// Only capture the TX monitor while actually transmitting (the tap can fire
142156
// whenever mic capture runs), so RX and TX never both write.

src/core/QsoRecorder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ private slots:
133133
bool preparePlaybackPcm(int sinkRateHz);
134134

135135
// Recording state
136-
bool m_recording{false};
136+
std::atomic<bool> m_recording{false}; // checked lock-free on the audio feed fast path
137137
std::atomic<bool> m_transmitting{false}; // MOX state; gates RX vs TX writes (#3556)
138138
QFile* m_file{nullptr};
139139
QDateTime m_startTime;

0 commit comments

Comments
 (0)