#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 @Override
public void broadcast(T audioSegment) public void broadcast(T audioSegment)
{ {
for(Listener<T> listener : mListeners) for(Listener<T> listener : getListeners())
{ {
audioSegment.incrementConsumerCount(); audioSegment.incrementConsumerCount();
listener.receive(audioSegment); listener.receive(audioSegment);

View File

@@ -194,7 +194,10 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
*/ */
public void addListener(ISourceEventProcessor processor) 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantLock;
/** /**
* Broadcasts an item to multiple listeners * 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 final static Logger mLog = LoggerFactory.getLogger(Broadcaster.class);
private boolean mDebug; private boolean mDebug;
private ReentrantLock mLock = new ReentrantLock();
protected List<Listener<T>> mListeners = new CopyOnWriteArrayList<>(); private List<Listener<T>> mListeners = new ArrayList<>();
public Broadcaster() public Broadcaster()
{ {
@@ -64,7 +66,16 @@ public class Broadcaster<T> implements Listener<T>
*/ */
public void dispose() 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(); 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 * 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() 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) 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) 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() 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) 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() + "]"); for(Listener<T> listener : mListeners)
listener.receive(t); {
mLog.debug("Finished sending to listener [" + listener.getClass() + "]"); 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) mLock.unlock();
{
listener.receive(t);
}
} }
} }
} }

View File

@@ -110,10 +110,17 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
if(getChannelSourceManager() != null) if(getChannelSourceManager() != null)
{ {
getChannelSourceManager().stopAllChannels(); getChannelSourceManager().stopAllChannels();
getChannelSourceManager().dispose();
mChannelSourceManager = null;
} }
broadcast(new TunerEvent(this, Event.NOTIFICATION_SHUTTING_DOWN)); broadcast(new TunerEvent(this, Event.NOTIFICATION_SHUTTING_DOWN));
getTunerController().stop(); 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 Broadcaster<INativeBuffer> mNativeBufferBroadcaster = new Broadcaster();
protected FrequencyController mFrequencyController; protected FrequencyController mFrequencyController;
private int mMiddleUnusableHalfBandwidth; private int mMiddleUnusableHalfBandwidth;
private double mUsableBandwidthPercentage;
private Listener<SourceEvent> mSourceEventListener;
private int mMeasuredFrequencyError; private int mMeasuredFrequencyError;
private double mUsableBandwidthPercentage;
private SourceEventListenerToProcessorAdapter mSourceEventListener;
private NativeBufferWaveRecorder mRecorder; private NativeBufferWaveRecorder mRecorder;
private ITunerErrorListener mTunerErrorListener; private ITunerErrorListener mTunerErrorListener;
public FrequencyErrorCorrectionManager mFrequencyErrorCorrectionManager;
private DecimalFormat mFrequencyErrorPPMFormat = new DecimalFormat("0.0"); private DecimalFormat mFrequencyErrorPPMFormat = new DecimalFormat("0.0");
private FrequencyErrorCorrectionManager mFrequencyErrorCorrectionManager;
/** /**
* Abstract tuner controller class. The tuner controller manages frequency bandwidth and currently tuned channels * 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; return mFrequencyErrorCorrectionManager;
} }
protected void dispose()
{
getFrequencyErrorCorrectionManager().dispose();
mNativeBufferBroadcaster.clear();
mFrequencyController.dispose();
mSourceEventListener.dispose();
mTunerErrorListener = null;
}
/** /**
* Perform startup operations * Perform startup operations
* @throws SourceException if there is an error that makes this tuner controller unusable * @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()); setFrequency(config.getFrequency());
setFrequencyCorrection(config.getFrequencyCorrection()); setFrequencyCorrection(config.getFrequencyCorrection());
getFrequencyErrorCorrectionManager().setEnabled(config.getAutoPPMCorrectionEnabled()); getFrequencyErrorCorrectionManager().setEnabled(config.getAutoPPMCorrectionEnabled());
} }
/** /**

View File

@@ -25,8 +25,9 @@ import io.github.dsheirer.source.SourceException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantLock;
public class FrequencyController public class FrequencyController
{ {
@@ -40,8 +41,8 @@ public class FrequencyController
private double mFrequencyCorrection = 0.0d; private double mFrequencyCorrection = 0.0d;
private double mSampleRate = 0.0d; private double mSampleRate = 0.0d;
private boolean mLocked = false; private boolean mLocked = false;
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
private List<ISourceEventProcessor> mProcessors = new CopyOnWriteArrayList<>(); private ReentrantLock mLock = new ReentrantLock();
/** /**
* Constructs an instance * Constructs an instance
@@ -52,6 +53,15 @@ public class FrequencyController
mTunable = tunable; 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 * 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 * 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) 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) 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 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<>(); 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 * 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; mTunerController = tunerController;
} }
/**
* Dispose of this manager instance and nullify references.
*/
public void dispose()
{
setEnabled(false);
mTunerController = null;
}
/** /**
* Enables or disables automatic frequency PPM correction. * Enables or disables automatic frequency PPM correction.
* @param enabled to true to turn on automatic PPM adjustments. * @param enabled to true to turn on automatic PPM adjustments.
@@ -80,7 +89,7 @@ public class FrequencyErrorCorrectionManager
*/ */
private void applyCorrection() private void applyCorrection()
{ {
if(mEnabled && mTunerController.hasMeasuredFrequencyError()) if(mEnabled && mTunerController != null && mTunerController.hasMeasuredFrequencyError())
{ {
double frequencyCorrection = mTunerController.getFrequencyCorrection(); double frequencyCorrection = mTunerController.getFrequencyCorrection();
frequencyCorrection -= mTunerController.getPPMFrequencyError(); 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.TunerFactory;
import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; 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 io.github.dsheirer.source.tuner.usb.USBTunerController;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -300,16 +299,6 @@ public class RTL2832TunerController extends USBTunerController
return null; 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 * Overrides updates for measured frequency error so that the updates can also be applied to the
* frequency error correction manager for automatic PPM updating. * frequency error correction manager for automatic PPM updating.
@@ -348,11 +337,11 @@ public class RTL2832TunerController extends USBTunerController
{ {
if(hasEmbeddedTuner()) if(hasEmbeddedTuner())
{ {
return TunerClass.RTL2832.toString() + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial(); return TunerClass.RTL2832 + "/" + getTunerType().getLabel() + " " + mDescriptor.getSerial();
} }
else else
{ {
return TunerClass.RTL2832.toString() + " " + mDescriptor.getSerial(); return TunerClass.RTL2832 + " " + mDescriptor.getSerial();
} }
} }
else else

View File

@@ -99,6 +99,8 @@ public class DiscoveredTunerEditor extends Editor<DiscoveredTuner> implements ID
super.setItem(tuner); super.setItem(tuner);
mEditorScroller.remove(mEditor);
if(hasItem()) if(hasItem())
{ {
mEditor = TunerFactory.getEditor(mUserPreferences, getItem(), mTunerManager); 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. * Indicates if the controls are currently being loaded with values.
*/ */
protected boolean isLoading() protected boolean isLoading()
@@ -337,7 +337,7 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
if(mEnabledButton == null) if(mEnabledButton == null)
{ {
mEnabledButton = new JButton(BUTTON_STATUS_ENABLE); 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 -> mEnabledButton.addActionListener(e ->
{ {
switch(getEnabledButton().getText()) switch(getEnabledButton().getText())
@@ -493,6 +493,11 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
*/ */
public void dispose() public void dispose()
{ {
turnOffRecorder();
getFrequencyControl().clearListeners();
getFrequencyCorrectionSpinner().removeChangeListener(mFrequencyAndCorrectionChangeListener);
if(mDiscoveredTuner != null) if(mDiscoveredTuner != null)
{ {
mDiscoveredTuner.removeTunerStatusListener(this); mDiscoveredTuner.removeTunerStatusListener(this);
@@ -501,9 +506,12 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
{ {
mDiscoveredTuner.getTuner().removeTunerEventListener(this); mDiscoveredTuner.getTuner().removeTunerEventListener(this);
} }
mDiscoveredTuner = null;
} }
turnOffRecorder(); mUserPreferences = null;
mTunerManager = null;
} }
/** /**