#1487 Updates hot code classes to remove usage of reentrant locks for thread synchronization. (#1488)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2023-03-11 16:04:55 -05:00
committed by GitHub
parent 2d6c5c2331
commit f0dc80437c
4 changed files with 164 additions and 179 deletions

View File

@@ -44,7 +44,6 @@ import java.util.Map;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.math3.util.FastMath; import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -481,7 +480,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
public class NativeBufferReceiver implements Listener<INativeBuffer> public class NativeBufferReceiver implements Listener<INativeBuffer>
{ {
private boolean mOutputProcessorUpdateRequired = false; private boolean mOutputProcessorUpdateRequired = false;
private ReentrantLock mUpdateFlagLock = new ReentrantLock();
/** /**
* Processes tuner center frequency change source events to flag when output processors need updating. * Processes tuner center frequency change source events to flag when output processors need updating.
@@ -493,18 +491,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(mChannelCalculator.getCenterFrequency() != frequency) if(mChannelCalculator.getCenterFrequency() != frequency)
{ {
mUpdateFlagLock.lock(); //Update the channel calculator frequency so that it's ready when the output processor update occurs
mChannelCalculator.setCenterFrequency(frequency);
try mOutputProcessorUpdateRequired = true;
{
//Update the channel calculator frequency so that it's ready when the output processor update occurs
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
}
finally
{
mUpdateFlagLock.unlock();
}
} }
} }
@@ -516,19 +505,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
@Override @Override
public void receive(INativeBuffer nativeBuffer) public void receive(INativeBuffer nativeBuffer)
{ {
mUpdateFlagLock.lock(); if(mOutputProcessorUpdateRequired)
try
{ {
if(mOutputProcessorUpdateRequired) try
{ {
updateOutputProcessors(); updateOutputProcessors();
mOutputProcessorUpdateRequired = false;
} }
} catch(Exception e)
finally {
{ mLog.error("Error updating polyphase channel output processors");
mUpdateFlagLock.unlock(); }
mOutputProcessorUpdateRequired = false;
} }
if(mPolyphaseChannelizer != null) if(mPolyphaseChannelizer != null)

View File

@@ -28,12 +28,10 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat; import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Polyphase Channelizer's Tuner Channel Source implementation. Wraps a ChannelOutputProcessor instance and * Polyphase Channelizer's Tuner Channel Source implementation. Wraps a ChannelOutputProcessor instance and
@@ -47,12 +45,10 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor; private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
private double mChannelSampleRate; private double mChannelSampleRate;
private long mIndexCenterFrequency; private long mIndexCenterFrequency;
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
private List<Integer> mOutputProcessorIndexes = new ArrayList<>(); private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
private double mTunerSampleRate; private double mTunerSampleRate;
private double mTunerCenterFrequency; private double mTunerCenterFrequency;
private PendingOutputProcessorUpdate mPendingOutputProcessorUpdate;
/** /**
* Constructs an instance * Constructs an instance
@@ -69,7 +65,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
super(producerSourceEventListener, tunerChannel); super(producerSourceEventListener, tunerChannel);
mChannelSampleRate = channelCalculator.getChannelSampleRate(); mChannelSampleRate = channelCalculator.getChannelSampleRate();
mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS); mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS);
updateOutputProcessor(channelCalculator, filterManager); doUpdateOutputProcessor(channelCalculator, filterManager);
} }
/** /**
@@ -150,102 +146,108 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
} }
/** /**
* Updates the output processor whenever the source tuner's center frequency changes. * Queues a request to update the output processor whenever the source tuner's center frequency changes.
* @param channelCalculator providing access to updated tuner center frequency, sample rate, etc. * @param channelCalculator providing access to updated tuner center frequency, sample rate, etc.
* @param filterManager for designing and caching synthesis filters * @param filterManager for designing and caching synthesis filters
*/ */
public void updateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager) throws IllegalArgumentException public void updateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager)
throws IllegalArgumentException
{
mPendingOutputProcessorUpdate = new PendingOutputProcessorUpdate(channelCalculator, filterManager);
}
/**
* Performs the output processor update. This method must be invoked on the channel results calling thread to
* ensure we don't have thread contention on the output processor.
* @param channelCalculator for updates
* @param filterManager for updates
* @throws IllegalArgumentException if there's an issue.
*/
public void doUpdateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager)
throws IllegalArgumentException
{ {
String errorMessage = null; String errorMessage = null;
mOutputProcessorLock.lock(); //If a change in sample rate or center frequency makes this channel no longer viable, then the channel
//calculator will throw an IllegalArgException ... handled below
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
try mOutputProcessorIndexes.clear();
mOutputProcessorIndexes.addAll(indexes);
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
mTunerSampleRate = channelCalculator.getSampleRate();
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
if(mPolyphaseChannelOutputProcessor != null && mPolyphaseChannelOutputProcessor.getInputChannelCount() == indexes.size())
{ {
//If a change in sample rate or center frequency makes this channel no longer viable, then the channel mPolyphaseChannelOutputProcessor.setPolyphaseChannelIndices(indexes);
//calculator will throw an IllegalArgException ... handled below
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
mOutputProcessorIndexes.clear(); if(indexes.size() > 1)
mOutputProcessorIndexes.addAll(indexes);
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
mTunerSampleRate = channelCalculator.getSampleRate();
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
if(mPolyphaseChannelOutputProcessor != null && mPolyphaseChannelOutputProcessor.getInputChannelCount() == indexes.size())
{ {
mPolyphaseChannelOutputProcessor.setPolyphaseChannelIndices(indexes); try
if(indexes.size() > 1)
{ {
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), indexes.size());
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
}
catch(FilterDesignException fde)
{
mLog.error("Error creating an updated synthesis filter for the channel output processor");
errorMessage ="Cannot update output processor - unable to design synthesis filter for [" +
indexes.size() + "] channel indices - channel sample rate [" + channelCalculator.getChannelSampleRate() +
"] channel bandwidth [" + channelCalculator.getChannelBandwidth() + "]";
}
}
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
else //Create a new output processor.
{
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.setListener(null);
mPolyphaseChannelOutputProcessor.stop();
}
mPolyphaseChannelOutputProcessor = null;
switch(indexes.size())
{
case 1:
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
indexes, channelCalculator.getChannelCount());
mPolyphaseChannelOutputProcessor.setListener(this);
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
mPolyphaseChannelOutputProcessor.start();
break;
case 2:
try try
{ {
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(), float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), indexes.size()); channelCalculator.getChannelBandwidth(), 2);
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter); mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
} indexes, filter, channelCalculator.getChannelCount());
catch(FilterDesignException fde)
{
mLog.error("Error creating an updated synthesis filter for the channel output processor");
errorMessage ="Cannot update output processor - unable to design synthesis filter for [" +
indexes.size() + "] channel indices";
}
}
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
else //Create a new output processor.
{
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.setListener(null);
mPolyphaseChannelOutputProcessor.stop();
}
mPolyphaseChannelOutputProcessor = null;
switch(indexes.size())
{
case 1:
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
indexes, channelCalculator.getChannelCount());
mPolyphaseChannelOutputProcessor.setListener(this); mPolyphaseChannelOutputProcessor.setListener(this);
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset()); mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
mPolyphaseChannelOutputProcessor.start(); mPolyphaseChannelOutputProcessor.start();
break; }
case 2: catch(FilterDesignException fde)
try {
{ errorMessage = "Cannot create new output processor - unable to design synthesis filter for [" +
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(), indexes.size() + "] channel indices";
channelCalculator.getChannelBandwidth(), 2); mLog.error("Error creating a synthesis filter for a new channel output processor");
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(), }
indexes, filter, channelCalculator.getChannelCount()); break;
mPolyphaseChannelOutputProcessor.setListener(this); default:
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset()); mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
mPolyphaseChannelOutputProcessor.start(); mLog.info(channelCalculator.toString());
} errorMessage = "Unable to create new channel output processor - unexpected channel index size: " +
catch(FilterDesignException fde) indexes.size();
{
errorMessage = "Cannot create new output processor - unable to design synthesis filter for [" +
indexes.size() + "] channel indices";
mLog.error("Error creating a synthesis filter for a new channel output processor");
}
break;
default:
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
mLog.info(channelCalculator.toString());
errorMessage = "Unable to create new channel output processor - unexpected channel index size: " +
indexes.size();
}
} }
} }
finally
{
mOutputProcessorLock.unlock();
}
//Unlikely, but if we had an error designing a synthesis filter, throw an exception //Unlikely, but if we had an error designing a synthesis filter, throw an exception
if(errorMessage != null) if(errorMessage != null)
@@ -264,7 +266,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
*/ */
public void receiveChannelResults(List<float[]> channelResultsList, long currentSamplesTimestamp) public void receiveChannelResults(List<float[]> channelResultsList, long currentSamplesTimestamp)
{ {
mOutputProcessorLock.lock(); if(mPendingOutputProcessorUpdate != null)
{
ChannelCalculator channelCalculator = mPendingOutputProcessorUpdate.getChannelCalculator();
SynthesisFilterManager filterManager = mPendingOutputProcessorUpdate.getSynthesisFilterManager();
mPendingOutputProcessorUpdate = null;
doUpdateOutputProcessor(channelCalculator, filterManager);
}
try try
{ {
@@ -273,9 +281,9 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList, currentSamplesTimestamp); mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList, currentSamplesTimestamp);
} }
} }
finally catch(Exception e)
{ {
mOutputProcessorLock.unlock(); mLog.error("Error processing channel results", e);
} }
} }
@@ -344,4 +352,40 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " + return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
getTunerChannel().getFrequency(); getTunerChannel().getFrequency();
} }
/**
* Support classes needed to update the output processor.
*/
public class PendingOutputProcessorUpdate
{
private final ChannelCalculator mChannelCalculator;
private final SynthesisFilterManager mSynthesisFilterManager;
/**
* Constructs an instance
* @param channelCalculator for support
* @param synthesisFilterManager for support
*/
public PendingOutputProcessorUpdate(ChannelCalculator channelCalculator, SynthesisFilterManager synthesisFilterManager)
{
mChannelCalculator = channelCalculator;
mSynthesisFilterManager = synthesisFilterManager;
}
/**
* Channel calculator
*/
public ChannelCalculator getChannelCalculator()
{
return mChannelCalculator;
}
/**
* Channel calculator
*/
public SynthesisFilterManager getSynthesisFilterManager()
{
return mSynthesisFilterManager;
}
}
} }

View File

@@ -1,6 +1,6 @@
/* /*
* ***************************************************************************** * *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer * Copyright (C) 2014-2023 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -18,13 +18,11 @@
*/ */
package io.github.dsheirer.sample; package io.github.dsheirer.sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Broadcasts an item to multiple listeners * Broadcasts an item to multiple listeners
@@ -32,8 +30,7 @@ import java.util.concurrent.locks.ReentrantLock;
public class Broadcaster<T> implements Listener<T> 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 ReentrantLock mLock = new ReentrantLock(); private List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
private List<Listener<T>> mListeners = new ArrayList<>();
public Broadcaster() public Broadcaster()
{ {
@@ -90,25 +87,9 @@ public class Broadcaster<T> implements Listener<T>
*/ */
public void addListener(Listener<T> listener) public void addListener(Listener<T> listener)
{ {
if(listener != null) if(listener != null && !mListeners.contains(listener))
{ {
mLock.lock(); mListeners.add(listener);
try
{
if(!mListeners.contains(listener))
{
mListeners.add(listener);
}
}
catch(Exception e)
{
mLog.error("Unexpected error while adding listener", e);
}
finally
{
mLock.unlock();
}
} }
} }
@@ -119,20 +100,7 @@ public class Broadcaster<T> implements Listener<T>
{ {
if(listener != null) if(listener != null)
{ {
mLock.lock(); mListeners.remove(listener);
try
{
mListeners.remove(listener);
}
catch(Exception e)
{
mLog.error("Unexpected error while removing listener", e);
}
finally
{
mLock.unlock();
}
} }
} }
@@ -149,24 +117,16 @@ public class Broadcaster<T> implements Listener<T>
*/ */
public void broadcast(T t) public void broadcast(T t)
{ {
mLock.lock();
try try
{ {
List<Listener<T>> listeners = new ArrayList<>(mListeners); for(Listener<T> listener: mListeners)
for(Listener listener: listeners)
{ {
listener.receive(t); listener.receive(t);
} }
} }
catch(Exception e) catch(Exception e)
{ {
mLog.error("Unexpected error while broadcasting to listeners", e); mLog.error("Error while broadcasting [" + t.getClass() + "] to listeners");
}
finally
{
mLock.unlock();
} }
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
* ***************************************************************************** * *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer * Copyright (C) 2014-2023 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -22,10 +22,10 @@ package io.github.dsheirer.source.tuner.channel;
import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.heartbeat.HeartbeatManager; import io.github.dsheirer.source.heartbeat.HeartbeatManager;
import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.ThreadPool;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Thread-safe implementation that coordinates the flow of data/buffer stream processing with a parallel, periodic * Thread-safe implementation that coordinates the flow of data/buffer stream processing with a parallel, periodic
@@ -42,7 +42,7 @@ import java.util.concurrent.locks.ReentrantLock;
*/ */
public class StreamProcessorWithHeartbeat<T> implements Listener<T> public class StreamProcessorWithHeartbeat<T> implements Listener<T>
{ {
private ReentrantLock mLock = new ReentrantLock(); private static final Logger mLog = LoggerFactory.getLogger(StreamProcessorWithHeartbeat.class);
private HeartbeatManager mHeartbeatManager; private HeartbeatManager mHeartbeatManager;
private long mHeartbeatInterval; private long mHeartbeatInterval;
private Listener<T> mListener; private Listener<T> mListener;
@@ -88,25 +88,19 @@ public class StreamProcessorWithHeartbeat<T> implements Listener<T>
*/ */
private void fireOptionalHeartbeat() private void fireOptionalHeartbeat()
{ {
//Acquire lock only if it's not already locked, otherwise ignore heartbeat for this timer interval. try
if(mLock.tryLock())
{ {
try mHeartbeatManager.broadcast();
{ }
mHeartbeatManager.broadcast(); catch(Exception e)
} {
finally mLog.error("Error firing optional heartbeat", e);
{
mLock.unlock();
}
} }
} }
@Override @Override
public void receive(T data) public void receive(T data)
{ {
mLock.lock();
try try
{ {
mHeartbeatManager.broadcast(); mHeartbeatManager.broadcast();
@@ -118,9 +112,9 @@ public class StreamProcessorWithHeartbeat<T> implements Listener<T>
listener.receive(data); listener.receive(data);
} }
} }
finally catch(Exception e)
{ {
mLock.unlock(); mLog.error("Error distributing data to listeners following heartbeat", e);
} }
} }