#1210 Resolves issue of tuner instances can't be garbage collected due to retained references. Updates several of the tuner controller and tuner editor classes to add/invoke dispose() methods to clear retained instance references. (#1216)

Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
This commit is contained in:
Denny Sheirer
2022-04-24 04:56:10 -04:00
committed by GitHub
parent 1b90e0b50c
commit 25a42126df
11 changed files with 171 additions and 58 deletions

View File

@@ -39,7 +39,7 @@ public class AudioSegmentBroadcaster<T extends AudioSegment> extends Broadcaster
@Override
public void broadcast(T audioSegment)
{
for(Listener<T> listener : mListeners)
for(Listener<T> listener : getListeners())
{
audioSegment.incrementConsumerCount();
listener.receive(audioSegment);

View File

@@ -194,7 +194,10 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
*/
public void addListener(ISourceEventProcessor processor)
{
mProcessors.add(processor);
if(!mProcessors.contains(processor))
{
mProcessors.add(processor);
}
}
/**

View File

@@ -22,8 +22,10 @@ package io.github.dsheirer.sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
/**
* Broadcasts an item to multiple listeners
@@ -32,8 +34,8 @@ public class Broadcaster<T> implements Listener<T>
{
private final static Logger mLog = LoggerFactory.getLogger(Broadcaster.class);
private boolean mDebug;
protected List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
private ReentrantLock mLock = new ReentrantLock();
private List<Listener<T>> mListeners = new ArrayList<>();
public Broadcaster()
{
@@ -64,7 +66,16 @@ public class Broadcaster<T> implements Listener<T>
*/
public void dispose()
{
mListeners.clear();
mLock.lock();
try
{
mListeners.clear();
}
finally
{
mLock.unlock();
}
}
/**
@@ -75,14 +86,6 @@ public class Broadcaster<T> implements Listener<T>
return !mListeners.isEmpty();
}
/**
* Indicates if the listener is currently registered with this broadcaster
*/
public boolean hasListener(Listener<T> listener)
{
return listener != null && mListeners.contains(listener);
}
/**
* The count of listeners currently registered with this broadcaster
*/
@@ -96,7 +99,7 @@ public class Broadcaster<T> implements Listener<T>
*/
public List<Listener<T>> getListeners()
{
return mListeners;
return Collections.unmodifiableList(mListeners);
}
/**
@@ -106,9 +109,21 @@ public class Broadcaster<T> implements Listener<T>
*/
public void addListener(Listener<T> listener)
{
if(listener != null && !mListeners.contains(listener))
if(listener != null)
{
mListeners.add(listener);
mLock.lock();
try
{
if(!mListeners.contains(listener))
{
mListeners.add(listener);
}
}
finally
{
mLock.unlock();
}
}
}
@@ -119,7 +134,16 @@ public class Broadcaster<T> implements Listener<T>
{
if(listener != null)
{
mListeners.remove(listener);
mLock.lock();
try
{
mListeners.remove(listener);
}
finally
{
mLock.unlock();
}
}
}
@@ -128,7 +152,16 @@ public class Broadcaster<T> implements Listener<T>
*/
public void clear()
{
mListeners.clear();
mLock.lock();
try
{
mListeners.clear();
}
finally
{
mLock.unlock();
}
}
/**
@@ -136,21 +169,30 @@ public class Broadcaster<T> implements Listener<T>
*/
public void broadcast(T t)
{
if(mDebug)
mLock.lock();
try
{
for(Listener<T> listener : mListeners)
if(mDebug)
{
mLog.debug("Sending [" + t + "] to listener [" + listener.getClass() + "]");
listener.receive(t);
mLog.debug("Finished sending to listener [" + listener.getClass() + "]");
for(Listener<T> listener : mListeners)
{
mLog.debug("Sending [" + t + "] to listener [" + listener.getClass() + "]");
listener.receive(t);
mLog.debug("Finished sending to listener [" + listener.getClass() + "]");
}
}
else
{
for(Listener<T> listener : mListeners)
{
listener.receive(t);
}
}
}
else
finally
{
for(Listener<T> listener : mListeners)
{
listener.receive(t);
}
mLock.unlock();
}
}
}

View File

@@ -110,10 +110,17 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
if(getChannelSourceManager() != null)
{
getChannelSourceManager().stopAllChannels();
getChannelSourceManager().dispose();
mChannelSourceManager = null;
}
broadcast(new TunerEvent(this, Event.NOTIFICATION_SHUTTING_DOWN));
getTunerController().stop();
getTunerController().dispose();
mTunerEventBroadcaster.clear();
mTunerFrequencyErrorMonitor = null;
mTunerErrorListener = null;
}
/**

View File

@@ -49,13 +49,13 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
protected Broadcaster<INativeBuffer> mNativeBufferBroadcaster = new Broadcaster();
protected FrequencyController mFrequencyController;
private int mMiddleUnusableHalfBandwidth;
private double mUsableBandwidthPercentage;
private Listener<SourceEvent> mSourceEventListener;
private int mMeasuredFrequencyError;
private double mUsableBandwidthPercentage;
private SourceEventListenerToProcessorAdapter mSourceEventListener;
private NativeBufferWaveRecorder mRecorder;
private ITunerErrorListener mTunerErrorListener;
public FrequencyErrorCorrectionManager mFrequencyErrorCorrectionManager;
private DecimalFormat mFrequencyErrorPPMFormat = new DecimalFormat("0.0");
private FrequencyErrorCorrectionManager mFrequencyErrorCorrectionManager;
/**
* Abstract tuner controller class. The tuner controller manages frequency bandwidth and currently tuned channels
@@ -78,6 +78,15 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
return mFrequencyErrorCorrectionManager;
}
protected void dispose()
{
getFrequencyErrorCorrectionManager().dispose();
mNativeBufferBroadcaster.clear();
mFrequencyController.dispose();
mSourceEventListener.dispose();
mTunerErrorListener = null;
}
/**
* Perform startup operations
* @throws SourceException if there is an error that makes this tuner controller unusable
@@ -141,7 +150,6 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
setFrequency(config.getFrequency());
setFrequencyCorrection(config.getFrequencyCorrection());
getFrequencyErrorCorrectionManager().setEnabled(config.getAutoPPMCorrectionEnabled());
}
/**

View File

@@ -25,8 +25,9 @@ import io.github.dsheirer.source.SourceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
public class FrequencyController
{
@@ -40,8 +41,8 @@ public class FrequencyController
private double mFrequencyCorrection = 0.0d;
private double mSampleRate = 0.0d;
private boolean mLocked = false;
private List<ISourceEventProcessor> mProcessors = new CopyOnWriteArrayList<>();
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
private ReentrantLock mLock = new ReentrantLock();
/**
* Constructs an instance
@@ -52,6 +53,15 @@ public class FrequencyController
mTunable = tunable;
}
/**
* Prepare for disposal of this instance.
*/
public void dispose()
{
mProcessors.clear();
mTunable = null;
}
/**
* Indicates if this frequency controller is locked by a remote process. A locked state indicates that neither
* the frequency nor the sample rate should be changed. The remote process will typically be a downstream
@@ -276,9 +286,18 @@ public class FrequencyController
*/
public void addListener(ISourceEventProcessor processor)
{
if(!mProcessors.contains(processor))
mLock.lock();
try
{
mProcessors.add(processor);
if(!mProcessors.contains(processor))
{
mProcessors.add(processor);
}
}
finally
{
mLock.unlock();
}
}
@@ -287,7 +306,16 @@ public class FrequencyController
*/
public void removeFrequencyChangeProcessor(ISourceEventProcessor processor)
{
mProcessors.remove(processor);
mLock.lock();
try
{
mProcessors.remove(processor);
}
finally
{
mLock.unlock();
}
}
@@ -318,9 +346,18 @@ public class FrequencyController
public void broadcast(SourceEvent event) throws SourceException
{
for(ISourceEventProcessor processor : mProcessors)
mLock.lock();
try
{
processor.process(event);
for(ISourceEventProcessor processor : mProcessors)
{
processor.process(event);
}
}
finally
{
mLock.unlock();
}
}

View File

@@ -36,6 +36,14 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
{
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
/**
* Prepare for disposal
*/
public void dispose()
{
mSourceEventBroadcaster.clear();
}
/**
* Sorted set of tuner channels being sourced by this source manager. Set is ordered by frequency lowest to highest
*/

View File

@@ -49,6 +49,15 @@ public class FrequencyErrorCorrectionManager
mTunerController = tunerController;
}
/**
* Dispose of this manager instance and nullify references.
*/
public void dispose()
{
setEnabled(false);
mTunerController = null;
}
/**
* Enables or disables automatic frequency PPM correction.
* @param enabled to true to turn on automatic PPM adjustments.
@@ -80,7 +89,7 @@ public class FrequencyErrorCorrectionManager
*/
private void applyCorrection()
{
if(mEnabled && mTunerController.hasMeasuredFrequencyError())
if(mEnabled && mTunerController != null && mTunerController.hasMeasuredFrequencyError())
{
double frequencyCorrection = mTunerController.getFrequencyCorrection();
frequencyCorrection -= mTunerController.getPPMFrequencyError();

View File

@@ -26,7 +26,6 @@ import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerFactory;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.manager.FrequencyErrorCorrectionManager;
import io.github.dsheirer.source.tuner.usb.USBTunerController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -300,16 +299,6 @@ public class RTL2832TunerController extends USBTunerController
return null;
}
/**
* Manager for applying automatic frequency error PPM adjustments to the tuner controller based on
* frequency error measurements received from certain downstream decoders (e.g. P25).
* @return manager
*/
public FrequencyErrorCorrectionManager getFrequencyErrorCorrectionManager()
{
return mFrequencyErrorCorrectionManager;
}
/**
* Overrides updates for measured frequency error so that the updates can also be applied to the
* frequency error correction manager for automatic PPM updating.
@@ -348,11 +337,11 @@ public class RTL2832TunerController extends USBTunerController
{
if(hasEmbeddedTuner())
{
return TunerClass.RTL2832.toString() + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial();
return TunerClass.RTL2832 + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial();
}
else
{
return TunerClass.RTL2832.toString() + " " + mDescriptor.getSerial();
return TunerClass.RTL2832 + " " + mDescriptor.getSerial();
}
}
else

View File

@@ -99,6 +99,8 @@ public class DiscoveredTunerEditor extends Editor<DiscoveredTuner> implements ID
super.setItem(tuner);
mEditorScroller.remove(mEditor);
if(hasItem())
{
mEditor = TunerFactory.getEditor(mUserPreferences, getItem(), mTunerManager);

View File

@@ -116,7 +116,7 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
}
}
/**
/**r
* Indicates if the controls are currently being loaded with values.
*/
protected boolean isLoading()
@@ -337,7 +337,7 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
if(mEnabledButton == null)
{
mEnabledButton = new JButton(BUTTON_STATUS_ENABLE);
mEnabledButton.setToolTipText("Enable or disable the tuner for use by sdrtrunk, or attempt to reset a tuner error");
mEnabledButton.setToolTipText("Enable or disable the tuner for use by sdrtrunk");
mEnabledButton.addActionListener(e ->
{
switch(getEnabledButton().getText())
@@ -493,6 +493,11 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
*/
public void dispose()
{
turnOffRecorder();
getFrequencyControl().clearListeners();
getFrequencyCorrectionSpinner().removeChangeListener(mFrequencyAndCorrectionChangeListener);
if(mDiscoveredTuner != null)
{
mDiscoveredTuner.removeTunerStatusListener(this);
@@ -501,9 +506,12 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
{
mDiscoveredTuner.getTuner().removeTunerEventListener(this);
}
mDiscoveredTuner = null;
}
turnOffRecorder();
mUserPreferences = null;
mTunerManager = null;
}
/**