diff --git a/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java b/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java index 5f8df916..f347eef5 100644 --- a/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java +++ b/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java @@ -140,7 +140,7 @@ public abstract class AudioOutput implements LineListener, Listener getChannelMetadata(); public abstract void updateChannelStateIdentifiers(IdentifierUpdateNotification notification); diff --git a/src/main/java/io/github/dsheirer/channel/state/MultiChannelState.java b/src/main/java/io/github/dsheirer/channel/state/MultiChannelState.java index 8e72c8ab..05f4da0e 100644 --- a/src/main/java/io/github/dsheirer/channel/state/MultiChannelState.java +++ b/src/main/java/io/github/dsheirer/channel/state/MultiChannelState.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2024 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 @@ -44,13 +44,12 @@ import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceType; import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Multi-Channel state tracks the overall state of all processing modules and decoders configured for the channel @@ -166,6 +165,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS mStateMachineMap.get(timeslot).setState(State.IDLE); break; case TEARDOWN: + mTeardownSequenceStarted = true; if(getChannel().isTrafficChannel()) { checkTeardown(); @@ -206,7 +206,16 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS { if(getChannel().isTrafficChannel()) { - broadcast(new ChannelEvent(getChannel(), ChannelEvent.Event.REQUEST_DISABLE)); + try + { + broadcast(new ChannelEvent(getChannel(), ChannelEvent.Event.REQUEST_DISABLE)); + } + catch(Throwable t) + { + mLog.error("Error broadcasting shutdown channel event", t); + } + + mTeardownSequenceStarted = true; } else { @@ -238,6 +247,20 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS } } + @Override + public boolean isTeardownState() + { + for(StateMachine stateMachine: mStateMachineMap.values()) + { + if(stateMachine.getState() == State.TEARDOWN) + { + return true; + } + } + + return false; + } + /** * Creates configuration identifiers for the channel name, system, site and alias list name. */ diff --git a/src/main/java/io/github/dsheirer/channel/state/SingleChannelState.java b/src/main/java/io/github/dsheirer/channel/state/SingleChannelState.java index c80e018b..5d57868f 100644 --- a/src/main/java/io/github/dsheirer/channel/state/SingleChannelState.java +++ b/src/main/java/io/github/dsheirer/channel/state/SingleChannelState.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2024 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 @@ -46,11 +46,10 @@ import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceType; import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.Collections; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Channel state tracks the overall state of all processing modules and decoders configured for the channel and @@ -163,9 +162,11 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder mStateMachine.setState(State.IDLE); break; case TEARDOWN: + mTeardownSequenceStarted = true; if(getChannel().isTrafficChannel()) { broadcast(new ChannelEvent(getChannel(), ChannelEvent.Event.REQUEST_DISABLE)); + mTeardownSequenceCompleted = true; } else { @@ -175,6 +176,12 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder } } + @Override + public boolean isTeardownState() + { + return mStateMachine.getState() == State.TEARDOWN; + } + @Override protected void checkState() { diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java index 4e0e2f3a..3d1dceba 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java @@ -279,15 +279,27 @@ public class ChannelProcessingManager implements Listener break; case REQUEST_DISABLE: case NOTIFICATION_DELETE: - if(channel != null && channel.isProcessing()) + if(channel != null) { try { stopProcessing(channel); } - catch(ChannelException ce) + catch(Throwable t) { - mLog.error("Error stopping channel [" + channel.getName() + "] - " + ce.getMessage()); + mLog.error("Error stopping channel [" + channel + "]", t); + } + } + else + { + try + { + throw new IllegalArgumentException("Request to disable channel must have non-null channel"); + } + catch(IllegalArgumentException iae) + { + mLog.error("Caught a [" + event.getEvent() + "] non-standard channel event - logging stack trace. " + + "This should not happen, please send this error to the developer.", iae); } } break; @@ -521,7 +533,14 @@ public class ChannelProcessingManager implements Listener if(addProcessingChain(channel, processingChain)) { - processingChain.start(); + try + { + processingChain.start(); + } + catch(Throwable t) + { + mLog.error("Error caught during processing chain startup - continuing", t); + } if(GraphicsEnvironment.isHeadless()) { @@ -627,8 +646,28 @@ public class ChannelProcessingManager implements Listener } else { - //This has to be done on the FX event thread when the playlist editor is constructed - Platform.runLater(() -> channel.setProcessing(false)); + //When we're in non-headless mode we have to change the processing property on the JavaFX + //event thread. However, if it hasn't yet been initialized (ie an FX window opened), we'll + //get an ISE. In that case, just set the property to false because there won't be any + //property listeners being triggered. + try + { + Platform.runLater(() -> { + try + { + channel.setProcessing(false); + } + catch(Exception e) + { + mLog.error("Error during channel stop while setting processing to false [" + channel + + "] - continuing channel stop process", e); + } + }); + } + catch(IllegalStateException e) + { + channel.setProcessing(false); + } } try @@ -847,6 +886,10 @@ public class ChannelProcessingManager implements Listener mLog.error("Error stopping channel [" + (toShutdown != null ? toShutdown.getName() : "unknown") + "] with source error - " + ce.getMessage()); } + catch(Throwable t) + { + mLog.error("Error stopping channel [" + toShutdown + "]", t); + } } } } @@ -875,6 +918,8 @@ public class ChannelProcessingManager implements Listener sb.append("\tProcessing Chain - Processing: ").append(chain.isProcessing()).append("\n"); AbstractChannelState state = chain.getChannelState(); sb.append("Channel State: ").append(state.getClass()).append("\n"); + sb.append(" Teardown Started:").append(state.isTeardownSequenceStarted()).append("\n"); + sb.append(" Teardown Completed:").append(state.isTeardownSequenceCompleted()).append("\n"); for(ChannelMetadata metadata: state.getChannelMetadata()) { sb.append(metadata.getDescription()).append("\n");