#1243 Updates USB tuner error handling to prevent stack overflow when tuner error occurs while streaming. (#1247)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-05-09 07:16:35 -04:00
committed by GitHub
parent 46a916a945
commit 8bfb1d5470
11 changed files with 222 additions and 138 deletions

View File

@@ -25,6 +25,7 @@ import io.github.dsheirer.channel.metadata.ChannelAndMetadata;
import io.github.dsheirer.channel.metadata.ChannelMetadata;
import io.github.dsheirer.channel.metadata.ChannelMetadataModel;
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
import io.github.dsheirer.controller.channel.event.PreloadDataContent;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.identifier.Form;
@@ -46,12 +47,9 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.util.ThreadPool;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.GraphicsEnvironment;
import java.util.ArrayList;
import java.util.List;
@@ -62,6 +60,9 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Channel processing manager handles all starting and stopping of channel decoding. A processing chain is created
@@ -124,7 +125,20 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
*/
private boolean isProcessing(Channel channel)
{
return mProcessingChains.containsKey(channel) && mProcessingChains.get(channel).isProcessing();
boolean isProcessing = false;
mLock.lock();
try
{
isProcessing = mProcessingChains.containsKey(channel) && mProcessingChains.get(channel).isProcessing();
}
finally
{
mLock.unlock();
}
return isProcessing;
}
/**
@@ -153,18 +167,60 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
*/
public Channel getChannel(ProcessingChain processingChain)
{
Channel channel = null;
if(processingChain != null)
{
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChains.entrySet())
mLock.lock();
try
{
if(entry.getValue() == processingChain)
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChains.entrySet())
{
return entry.getKey();
if(entry.getValue() == processingChain)
{
channel = entry.getKey();
break;
}
}
}
finally
{
mLock.unlock();
}
}
return null;
return channel;
}
/**
* Retrieves the channel associated with the processing chain that is consuming from the tuner channel source
* @param tunerChannelSource to find the channel
* @return channel
*/
private Channel getChannel(TunerChannelSource tunerChannelSource)
{
Channel channel = null;
mLock.lock();
try
{
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChains.entrySet())
{
if(entry.getValue().hasSource(tunerChannelSource))
{
channel = entry.getKey();
break;
}
}
}
finally
{
mLock.unlock();
}
return channel;
}
/**
@@ -273,6 +329,28 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
}
}
/**
* Request to stop a channel that is currently processing
* @param request with the tuner channel source feeding the channel to be stopped.
*/
@Subscribe
public void stopChannelRequest(ChannelStopProcessingRequest request)
{
Channel channel = getChannel(request.getTunerChannelSource());
if(channel != null)
{
try
{
stop(channel);
}
catch(ChannelException ce)
{
mLog.error("Error stopping channel [" + channel + "]", ce);
}
}
}
/**
* Stops the specified channel
* @param channel to stop
@@ -720,9 +798,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
{
@Override public void receive(SourceEvent sourceEvent)
{
if((sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_ERROR_STATE ||
sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_TUNER_SHUTDOWN) &&
sourceEvent.getSource() != null)
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_ERROR_STATE && sourceEvent.getSource() != null)
{
Channel toShutdown = null;
@@ -750,10 +826,6 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
{
mLog.warn("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
}
else if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_TUNER_SHUTDOWN)
{
mLog.warn("Tuner removal detected - stopping channel [" + toShutdown.getName() + "]");
}
else
{
mLog.warn("Source event error - stopping channel [" + toShutdown.getName() + "]");

View File

@@ -0,0 +1,44 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.controller.channel.event;
import io.github.dsheirer.module.ModuleEventBusMessage;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
/**
* Request to stop processing a channel for a specific tuner channel source
*/
public class ChannelStopProcessingRequest extends ModuleEventBusMessage
{
private TunerChannelSource mTunerChannelSource;
/**
* Constructs an instance
* @param tunerChannelSource for the channel to stop
*/
public ChannelStopProcessingRequest(TunerChannelSource tunerChannelSource)
{
mTunerChannelSource = tunerChannelSource;
}
public TunerChannelSource getTunerChannelSource()
{
return mTunerChannelSource;
}
}

View File

@@ -21,11 +21,13 @@ package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.buffer.INativeBufferProvider;
import io.github.dsheirer.buffer.NativeBufferPoisonPill;
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.channelizer.output.OneChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
@@ -37,10 +39,6 @@ import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.util.Dispatcher;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -51,6 +49,9 @@ import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Polyphase Channel Manager is a DDC channel manager and complex buffer queue/processor for a tuner. This class
@@ -136,14 +137,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
for(TunerChannelSource tunerChannelSource: toStop)
{
try
{
tunerChannelSource.process(SourceEvent.tunerShutdown(tunerChannelSource));
}
catch(SourceException se)
{
mLog.error("Error stopping tuner channel source for tuner shutdown");
}
MyEventBus.getGlobalEventBus().post(new ChannelStopProcessingRequest(tunerChannelSource));
}
}

View File

@@ -32,6 +32,7 @@ import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.map.ChannelMap;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.preference.UserPreferences;
@@ -40,10 +41,6 @@ import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.util.ThreadPool;
import javafx.collections.ListChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -54,6 +51,9 @@ import java.util.ArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.collections.ListChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages all aspects of playlists and related models
@@ -104,6 +104,11 @@ public class PlaylistManager implements Listener<ChannelEvent>
mChannelModel = new ChannelModel(mAliasModel);
mChannelProcessingManager = new ChannelProcessingManager(mChannelMapModel, eventLogManager, mTunerManager,
mAliasModel, mUserPreferences);
//Register the channel processing manager to receive global channel stop processing requests so that it can
//respond to tuner shutdown (ie error) events
MyEventBus.getGlobalEventBus().register(mChannelProcessingManager);
mChannelModel.addListener(mChannelProcessingManager);
mChannelProcessingManager.addChannelEventListener(mChannelModel);

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* 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
@@ -15,17 +14,16 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
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.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Broadcasts an item to multiple listeners
@@ -33,7 +31,6 @@ import java.util.concurrent.locks.ReentrantLock;
public class Broadcaster<T> implements Listener<T>
{
private final static Logger mLog = LoggerFactory.getLogger(Broadcaster.class);
private boolean mDebug;
private ReentrantLock mLock = new ReentrantLock();
private List<Listener<T>> mListeners = new ArrayList<>();
@@ -41,14 +38,6 @@ public class Broadcaster<T> implements Listener<T>
{
}
/**
* Turns on/off debugging to troubleshoot and log which listeners are receiving an item.
*/
public void setDebug(boolean debug)
{
mDebug = debug;
}
/**
* Implements the Listener<T> interface to receive an element and broadcast that element to all registered
* listeners.
@@ -66,16 +55,7 @@ public class Broadcaster<T> implements Listener<T>
*/
public void dispose()
{
mLock.lock();
try
{
mListeners.clear();
}
finally
{
mLock.unlock();
}
clear();
}
/**
@@ -152,16 +132,7 @@ public class Broadcaster<T> implements Listener<T>
*/
public void clear()
{
mLock.lock();
try
{
mListeners.clear();
}
finally
{
mLock.unlock();
}
mListeners.clear();
}
/**
@@ -173,21 +144,11 @@ public class Broadcaster<T> implements Listener<T>
try
{
if(mDebug)
List<Listener<T>> listeners = new ArrayList<>(mListeners);
for(Listener listener: listeners)
{
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);
}
listener.receive(t);
}
}
finally

View File

@@ -28,6 +28,7 @@ import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
import io.github.dsheirer.source.tuner.manager.HeterodyneChannelSourceManager;
import io.github.dsheirer.source.tuner.manager.PolyphaseChannelSourceManager;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,6 +45,7 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
private TunerController mTunerController;
private TunerFrequencyErrorMonitor mTunerFrequencyErrorMonitor;
private ITunerErrorListener mTunerErrorListener;
private AtomicBoolean mRunning = new AtomicBoolean();
public Tuner(TunerController tunerController, ITunerErrorListener tunerErrorListener)
{
@@ -85,20 +87,25 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
*/
public void start() throws SourceException
{
try
if(mRunning.compareAndSet(false, true))
{
getTunerController().start();
}
catch(SourceException se)
{
//Rethrow the source exception
throw se;
}
catch(Exception e)
{
//Wrap any other exceptions in a new source exception
mLog.error("Error starting " + getTunerClass() + " tuner", e);
throw new SourceException("Unable to start " + getTunerClass() + " tuner", e);
try
{
getTunerController().start();
}
catch(SourceException se)
{
mRunning.set(false);
//Rethrow the source exception
throw se;
}
catch(Exception e)
{
mRunning.set(false);
//Wrap any other exceptions in a new source exception
mLog.error("Error starting " + getTunerClass() + " tuner", e);
throw new SourceException("Unable to start " + getTunerClass() + " tuner", e);
}
}
}
@@ -107,20 +114,24 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
*/
public void stop()
{
if(getChannelSourceManager() != null)
if(mRunning.compareAndSet(true, false))
{
getChannelSourceManager().stopAllChannels();
getChannelSourceManager().dispose();
mChannelSourceManager = null;
broadcast(new TunerEvent(this, Event.NOTIFICATION_SHUTTING_DOWN));
if(getChannelSourceManager() != null)
{
getChannelSourceManager().stopAllChannels();
getChannelSourceManager().dispose();
mChannelSourceManager = null;
}
getTunerController().stop();
getTunerController().dispose();
mTunerEventBroadcaster.clear();
mTunerFrequencyErrorMonitor = null;
mTunerErrorListener = null;
}
broadcast(new TunerEvent(this, Event.NOTIFICATION_SHUTTING_DOWN));
getTunerController().stop();
getTunerController().dispose();
mTunerEventBroadcaster.clear();
mTunerFrequencyErrorMonitor = null;
mTunerErrorListener = null;
}
/**

View File

@@ -172,10 +172,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
sourceEvent.setSource(this);
broadcastProducerSourceEvent(sourceEvent);
break;
case NOTIFICATION_TUNER_SHUTDOWN:
//Rebroadcast this event so the channel processing manager receives it and shuts down the channel
broadcastConsumerSourceEvent(sourceEvent);
break;
//Request events from the consumer
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setChannelFrequencyCorrection(sourceEvent.getValue().longValue());

View File

@@ -25,8 +25,9 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.util.SortedSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Interface to define the functionality of a channel source manager for handling tuner channel management and source
@@ -34,6 +35,8 @@ import java.util.SortedSet;
*/
public abstract class ChannelSourceManager implements ISourceEventProcessor
{
private Logger mLogger = LoggerFactory.getLogger(ChannelSourceManager.class);
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
/**

View File

@@ -19,7 +19,9 @@
package io.github.dsheirer.source.tuner.manager;
import io.github.dsheirer.buffer.NativeSampleDelayBuffer;
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
@@ -28,14 +30,13 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Channel provider for heterodyne and decimate method of channel provisioning.
@@ -68,14 +69,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
for(TunerChannelSource tunerChannelSource: toStop)
{
try
{
tunerChannelSource.process(SourceEvent.tunerShutdown(tunerChannelSource));
}
catch(SourceException se)
{
mLog.error("Error stopping tuner channel source for tuner shutdown");
}
MyEventBus.getGlobalEventBus().post(new ChannelStopProcessingRequest(tunerChannelSource));
}
}
@@ -278,8 +272,6 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
//The start sample stream request contains a start timestamp and the delay buffer
//will preload the channel with delayed sample buffers that either contain the
//timestamp or occur later/newer than the timestamp.
// mSampleDelayBuffer.addListener((CICTunerChannelSource)sourceEvent.getSource(),
// sourceEvent.getValue().longValue());
mSampleDelayBuffer.addListener((HalfBandTunerChannelSource)sourceEvent.getSource(),
sourceEvent.getValue().longValue());
}
@@ -287,7 +279,6 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
case REQUEST_STOP_SAMPLE_STREAM:
if(sourceEvent.getSource() instanceof HalfBandTunerChannelSource)
{
// mSampleDelayBuffer.removeListener((CICTunerChannelSource)sourceEvent.getSource());
mSampleDelayBuffer.removeListener((HalfBandTunerChannelSource)sourceEvent.getSource());
stopDelayBuffer();
}
@@ -295,7 +286,6 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
case REQUEST_SOURCE_DISPOSE:
if(sourceEvent.getSource() instanceof HalfBandTunerChannelSource)
{
// CICTunerChannelSource channelSource = (CICTunerChannelSource)sourceEvent.getSource();
HalfBandTunerChannelSource channelSource = (HalfBandTunerChannelSource) sourceEvent.getSource();
mChannelSources.remove(channelSource);
mTunerChannels.remove(channelSource.getTunerChannel());

View File

@@ -19,6 +19,8 @@
package io.github.dsheirer.source.tuner.manager;
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
@@ -27,14 +29,13 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.PassThroughChannelSource;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Pass through source manager for relatively small bandwidth complex streams that don't warrant the overhead of
@@ -62,14 +63,7 @@ public class PassThroughSourceManager extends ChannelSourceManager
for(TunerChannelSource tunerChannelSource: toStop)
{
try
{
tunerChannelSource.process(SourceEvent.tunerShutdown(tunerChannelSource));
}
catch(SourceException se)
{
mLog.error("Error stopping tuner channel source for tuner shutdown");
}
MyEventBus.getGlobalEventBus().post(new ChannelStopProcessingRequest(tunerChannelSource));
}
}

View File

@@ -31,14 +31,28 @@ import io.github.dsheirer.util.SwingUtils;
import io.github.dsheirer.util.ThreadPool;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Spectral display manager for displaying tuner spectral content.
*/
public class TunerSpectralDisplayManager implements Listener<TunerEvent>
{
private Logger mLog = LoggerFactory.getLogger(TunerSpectralDisplayManager.class);
private SpectralDisplayPanel mSpectralDisplayPanel;
private PlaylistManager mPlaylistManager;
private SettingsManager mSettingsManager;
private DiscoveredTunerModel mDiscoveredTunerModel;
/**
* Constructs an instance
* @param panel to manage
* @param playlistManager for channel updates
* @param settingsManager for settings
* @param discoveredTunerModel to access tuners
*/
public TunerSpectralDisplayManager(SpectralDisplayPanel panel, PlaylistManager playlistManager,
SettingsManager settingsManager, DiscoveredTunerModel discoveredTunerModel)
{