stop() tore the sample thread down with stopWork() + delete and only then
cleared m_deviceShared.m_thread. The buddy's applySettings() suspends and
resumes that thread through m_thread from another thread, without holding
this device's mutex, so it could restart the thread between stopWork() and
delete (QThread: Destroyed while thread is still running -> qFatal/abort),
or call through the pointer after the delete. The device's own
applySettings() had the same exposure on its own thread pointer, since it
does not take m_mutex either.
Seen when the Satellite Tracker loads presets into the Rx and Tx device
sets of one Pluto at AOS: abort in PlutoSDRInputThread::~PlutoSDRInputThread
<- PlutoSDRInput::stop <- DSPDeviceSourceEngine::gotoIdle.
Add DevicePlutoSDRShared::m_threadsMutex (recursive, shared by all PlutoSDR
device sets) and take it in start() around startWork()/publish, in stop()
and handleError() around unpublish + teardown (unpublishing first), in
suspendBuddies()/resumeBuddies(), and for the whole suspend -> apply ->
resume sequence in applySettings(). Also wait() before deleting the thread
and check the own-thread pointer before resuming it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both PlutoSDRInput::applySettings() and PlutoSDROutput::applySettings()
suspend their buddies' threads and resume them later:
if (buddySharedPtr && buddySharedPtr->m_threadWasRunning) {
buddySharedPtr->m_thread->startWork(); // m_thread may be null
The suspend half tests buddySharedPtr->m_thread and sets
m_threadWasRunning from it, but m_threadWasRunning is sticky state on the
buddy's shared struct: between suspend and resume the buddy's thread can
be destroyed -- its device was stopped, or a preset was loaded into it,
both of which null m_thread -- while m_threadWasRunning stays true.
startWork() is then called on a null pointer.
Seen on macOS with the Satellite Tracker, which loads a preset into the
Rx and the Tx device set of the same Pluto at AOS: the Tx applySettings
is between suspend and resume when the Rx side is rebuilt.
PlutoSDROutput::applySettings + 7284
PlutoSDROutput::handleMessage
DeviceSampleSink::handleInputMessages
MessageQueue::push
PlutoSDROutput::deserialize
DeviceAPI::loadSamplingDeviceSettings
DeviceUISet::loadDeviceSetSettings
Disassembly confirms the faulting load is m_thread's vtable read with
x0 = 0. Test m_thread, as the suspend half does.
The generated SWG setters overwrite the string pointer without deleting
what is already there, and every webapiSettingsGet and webapiReportGet
calls init() first, which has allocated one. Each call therefore leaked a
QString per string field. Measured at 117 bytes per settings GET for the
Satellite Tracker, over 20,000 requests.
Assign in place when the pointer is already set, which is what these same
functions have always done for title and reverseAPIAddress. 394 sites
across webapiFormatChannelSettings, webapiFormatDeviceSettings,
webapiFormatFeatureSettings and the three report equivalents.
The webapiReverseSend* functions are deliberately left alone: they build a
fresh SWG object whose constructor leaves the pointers null, so passing a
new QString is correct there.
After the change the same measurement is flat, at 7 bytes per GET.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Handle asynchronous errors reported by the PlutoSDR streaming threads and
transition the affected SDRangel device to a stopped and closed state.
When a PlutoSDR acquisition or generation thread reports an error, stop
and destroy the affected streaming threads and release the associated
buffers and device resources. For MIMO devices, shut down both acquisition
and generation paths and close the associated RX and TX resources so the
device is left in a consistent state.
Propagate the failure to the SDRangel device engine using the
DSPAcquisitionError and DSPGenerationError messages, including the
underlying error code and a message indicating that the PlutoSDR needs
to be restarted.
Also guard PlutoSDRInput::start() against an unavailable device parameter
object. This can occur after an asynchronous device failure has already
closed the device and prevents a subsequent start operation from
dereferencing an invalid device state.
This ensures that asynchronous failures such as a disconnected PlutoSDR
or an unrecoverable acquisition or generation error do not leave the
SDRangel device engine running against an unavailable device.
Signed-off-by: Robin Getz <rgetz503@gmail.com>
Clear the m_open flag when closing PlutoSDR input and output
devices. Previously closeDevice() released m_deviceParams but left
m_open set, leaving the object in an inconsistent state where code
could treat a closed device as still valid.
This was exposed when reloading a running PlutoSDR device, where GUI
updates could access the partially torn-down device state.
Hopefully fixes#2833 (I don't have a windows machine to test).
Signed-off-by: Robin Getz <rgetz503@gmail.com>
Query the PlutoSDR hardware for the current RX gain range and use it
to configure the GUI gain control dynamically.
The AD936x gain limits vary with LO frequency. Previously the GUI used
a fixed gain range, allowing users to select values that the hardware
would reject after changing bands. The gain slider now refreshes its
minimum, maximum, and step size whenever the device center frequency is
updated.
Also change the gain setting serialization to use a signed integer so
negative gain values are preserved for operating modes that support
them.
Changes include:
- Add DevicePlutoSDRBox::getGainRange() to read
in_voltage0_hardwaregain_available.
- Expose gain range through PlutoSDRInput.
- Refresh GUI gain limits when the device frequency changes.
- Avoid unnecessary widget updates when limits are unchanged.
- Store/restore gain as a signed value.
- Add default RX gain limit constants for fallback when the device
cannot provide its range.
Signed-off-by: Robin Getz <rgetz503@gmail.com>
Add defensive null checks before dereferencing DeviceAPI::getBuddySharedPtr()
in the PlutoSDR input and output plugins.
DeviceAPI initializes the buddy shared pointer to nullptr, so a buddy may
exist before its shared state has been attached. This could result in null
pointer dereferences during device initialization, buddy thread
suspend/resume, or settings application.
Changes include:
- Validate buddy shared pointer in openDevice() and fail gracefully if absent.
- Guard suspendBuddies() and resumeBuddies() against null shared pointers.
- Skip buddies without shared state during applySettings() while logging a
warning.
- Prevent dereferencing null buddy shared pointers when restarting buddy
threads.
These changes improve robustness during PlutoSDR buddy initialization and
avoid crashes caused by partially initialized buddy relationships.
Signed-off-by: Robin Getz <rgetz503@gmail.com>
Most plugins that use reverse API to PATCH settings updates to remote
server only do so when `useReverseAPI` is toggled, but not when the
relevant settings are being updated. So lets fix the precondition to
use the `m_useReverseAPI` flag instead.
This will use the RF bandwidth from the device, which is different
between AD9363 and AD9364.
Things are now managed like the device likes - analog low pass bandwidth
is RF (complex) bandwidth, not baseband single I or Q bandwidth.
Signed-off-by: Robin Getz <robin.getz@analog.com>
When updating firmware, the devices which have AD9364s on them, get
reset to the default of a AD9363 (tuning range of 325 to 3800 MHz).
SDRAngel assumes a AD9364, and the GUI allows you to set LO settings
that the firmware doesn't support.
This ensures that does not happen, by going out to the hardware, and
querying the device to set the min/max limits on LO.
Signed-off-by: Robin Getz <robin.getz@analog.com>