Skip to content

Commit

Permalink
#1340 Updates tuner and frequency controllers to apply locking to ens…
Browse files Browse the repository at this point in the history
…ure multi-threaded access to freuqency control planes doesn't allow center tuned frequency to become indeterminant which can result in channels being mistuned and stop decoding. (#1341)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
  • Loading branch information
DSheirer and Dennis Sheirer committed Nov 20, 2022
1 parent ac61b68 commit a24c373
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 128 deletions.
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/main/java/io/github/dsheirer/source/SourceEvent.java
Expand Up @@ -197,15 +197,15 @@ public static SourceEvent errorState(Source source, String errorDescription)
/**
* Creates a new locked state change event
*/
public static SourceEvent lockedState()
public static SourceEvent lockedSampleRateState()
{
return new SourceEvent(Event.NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED, 1);
}

/**
* Creates a new unlocked state change event
*/
public static SourceEvent unlockedState()
public static SourceEvent unlockedSampleRateState()
{
return new SourceEvent(Event.NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED, 0);
}
Expand Down
182 changes: 156 additions & 26 deletions src/main/java/io/github/dsheirer/source/tuner/TunerController.java
Expand Up @@ -36,11 +36,11 @@
import io.github.dsheirer.source.tuner.frequency.FrequencyController;
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
import io.github.dsheirer.source.tuner.manager.FrequencyErrorCorrectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DecimalFormat;
import java.util.SortedSet;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener,
INativeBufferProvider, Listener<INativeBuffer>, ITunerErrorListener
Expand Down Expand Up @@ -70,6 +70,17 @@ public TunerController(ITunerErrorListener tunerErrorListener)
mFrequencyErrorCorrectionManager = new FrequencyErrorCorrectionManager(this);
}

/**
* Lock for the frequency controller. This should only be used by the channel source manager to lock access to the
* frequency controller while creating a channel source, to block multi-threaded access to the frequency controller
* which might put the center tuned frequency value in an indeterminant state.
* @return frequency controller lock
*/
public ReentrantLock getFrequencyControllerLock()
{
return mFrequencyController.getFrequencyControllerLock();
}

/**
* Frequency correction manager for this tuner controller.
*/
Expand Down Expand Up @@ -182,29 +193,30 @@ public void process(SourceEvent sourceEvent ) throws SourceException
}

/**
* Indicates if the frequency and sample rate controls are locked by another process. User interface controls
* should monitor source events and check the locked state via this method to correctly render the enabled state
* of the frequency and sample rate controls.
* Indicates if the sample rate control is locked by another process. User interface controls should monitor source
* events and check the locked state via this method to correctly render the enabled state of the sample rate control.
*
* @return true if the tuner controller is locked.
*/
public boolean isLocked()
public boolean isLockedSampleRate()
{
return mFrequencyController.isLocked();
//Note: this access is not protected by the mFrequencyControllerLock
return mFrequencyController.isSampleRateLocked();
}

/**
* Sets the frequency and sample rate locked state to the locked argument value. This should only be changed
* by a downstream consumer of samples to prevent users or other processes from modifying the center frequency
* and/or sample rate of the tuner while processing samples.
* Sets the sample rate locked state to the locked argument value. This should only be changed by a downstream
* consumer of samples to prevent users or other processes from modifying the sample rate of the tuner while
* processing samples.
*
* @param locked true to indicate the tuner controller is in a locked state, otherwise false.
*/
public void setLocked(boolean locked)
public void setLockedSampleRate(boolean locked)
{
try
{
mFrequencyController.setLocked(locked);
//Note: this access is not protected by the mFrequencyControllerLock
mFrequencyController.setSampleRateLocked(locked);
}
catch(SourceException se)
{
Expand All @@ -214,6 +226,7 @@ public void setLocked(boolean locked)

public int getBandwidth()
{
//Note: this access is not protected by the mFrequencyControllerLock
return mFrequencyController.getBandwidth();
}

Expand All @@ -225,7 +238,15 @@ public int getBandwidth()
*/
public void setFrequency(long frequency) throws SourceException
{
mFrequencyController.setFrequency(frequency);
try
{
getFrequencyControllerLock().lock();
mFrequencyController.setFrequency(frequency);
}
finally
{
getFrequencyControllerLock().unlock();
}
}

/**
Expand All @@ -235,28 +256,73 @@ public void setFrequency(long frequency) throws SourceException
*/
public long getFrequency()
{
return mFrequencyController.getFrequency();
long frequency;

try
{
getFrequencyControllerLock().lock();
frequency = mFrequencyController.getFrequency();
}
finally
{
getFrequencyControllerLock().unlock();
}

return frequency;
}

@Override
public boolean canTune(long frequency)
{
return mFrequencyController.canTune(frequency);
boolean canTune;

try
{
getFrequencyControllerLock().lock();
canTune = mFrequencyController.canTune(frequency);
}
finally
{
getFrequencyControllerLock().unlock();
}

return canTune;
}

public double getSampleRate()
{
//Note: this access is not protected by the mFrequencyControllerLock
return mFrequencyController.getSampleRate();
}

public double getFrequencyCorrection()
{
return mFrequencyController.getFrequencyCorrection();
double correction;

try
{
getFrequencyControllerLock().lock();
correction = mFrequencyController.getFrequencyCorrection();
}
finally
{
getFrequencyControllerLock().unlock();
}

return correction;
}

public void setFrequencyCorrection(double correction) throws SourceException
{
mFrequencyController.setFrequencyCorrection(correction);
try
{
getFrequencyControllerLock().lock();
mFrequencyController.setFrequencyCorrection(correction);
}
finally
{
getFrequencyControllerLock().unlock();
}
}

/**
Expand All @@ -265,7 +331,19 @@ public void setFrequencyCorrection(double correction) throws SourceException
*/
public long getMinimumFrequency()
{
return mFrequencyController.getMinimumFrequency();
long minimum;

try
{
getFrequencyControllerLock().lock();
minimum = mFrequencyController.getMinimumFrequency();
}
finally
{
getFrequencyControllerLock().unlock();
}

return minimum;
}

/**
Expand All @@ -274,7 +352,15 @@ public long getMinimumFrequency()
*/
public void setMinimumFrequency(long minimum)
{
mFrequencyController.setMinimumFrequency(minimum);
try
{
getFrequencyControllerLock().lock();
mFrequencyController.setMinimumFrequency(minimum);
}
finally
{
getFrequencyControllerLock().unlock();
}
}

/**
Expand All @@ -283,7 +369,19 @@ public void setMinimumFrequency(long minimum)
*/
public long getMaximumFrequency()
{
return mFrequencyController.getMaximumFrequency();
long maximum;

try
{
getFrequencyControllerLock().lock();
maximum = mFrequencyController.getMaximumFrequency();
}
finally
{
getFrequencyControllerLock().unlock();
}

return maximum;
}

/**
Expand All @@ -292,17 +390,49 @@ public long getMaximumFrequency()
*/
public void setMaximumFrequency(long maximum)
{
mFrequencyController.setMaximumFrequency(maximum);
try
{
getFrequencyControllerLock().lock();
mFrequencyController.setMaximumFrequency(maximum);
}
finally
{
getFrequencyControllerLock().unlock();
}
}

public long getMinTunedFrequency() throws SourceException
{
return mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
long minTuned;

try
{
getFrequencyControllerLock().lock();
minTuned = mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
}
finally
{
getFrequencyControllerLock().unlock();
}

return minTuned;
}

public long getMaxTunedFrequency() throws SourceException
{
return mFrequencyController.getFrequency() + (getUsableBandwidth() / 2);
long maxTuned;

try
{
getFrequencyControllerLock().lock();
maxTuned = mFrequencyController.getFrequency() + (getUsableBandwidth() / 2);
}
finally
{
getFrequencyControllerLock().unlock();
}

return maxTuned;
}

/**
Expand Down Expand Up @@ -425,15 +555,15 @@ public void setMeasuredFrequencyError(int measuredFrequencyError)
*/
public void addListener( ISourceEventProcessor processor )
{
mFrequencyController.addListener(processor);
mFrequencyController.addSourceEventProcessor(processor);
}

/**
* Removes the frequency change listener
*/
public void removeListener( ISourceEventProcessor processor )
{
mFrequencyController.removeFrequencyChangeProcessor(processor);
mFrequencyController.removeSourceEventProcessor(processor);
}

/**
Expand Down
Expand Up @@ -25,6 +25,9 @@
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.manager.TunerStatus;
import io.github.dsheirer.source.tuner.ui.TunerEditor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -40,9 +43,6 @@
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

/**
* Airspy tuner editor/controller
Expand Down Expand Up @@ -102,7 +102,7 @@ protected void tunerStatusUpdated()
getTunerStatusLabel().setText(status);
getButtonPanel().updateControls();
getFrequencyPanel().updateControls();
getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLocked());
getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLockedSampleRate());
getTunerInfoButton().setEnabled(hasTuner());
updateGainComponents((hasTuner() && hasConfiguration()) ? getConfiguration().getGain() : null);

Expand Down Expand Up @@ -626,7 +626,7 @@ private AirspySampleRate getSampleRate(int value)
*/
private void updateSampleRateToolTip()
{
if(hasTuner() && getTuner().getController().isLocked())
if(hasTuner() && getTuner().getController().isLockedSampleRate())
{
mSampleRateCombo.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
}
Expand Down

0 comments on commit a24c373

Please sign in to comment.