#1802 Traffic channels stuck in TEARDOWN. Resolves: (#1829)

1. Tuner channel processing runnables were being interruptibly shutdown, preventing the channel from clearing any owned locks (ie DMRTrafficChannelManager) causing thread deadlock and eventually causing out of memory situation or tying up all threads in the thread pool to the point that the application stopped processing any data.

2. DMR Traffic Channel Manager was erroneously processing non-owned traffic channel events.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2024-02-09 06:05:19 -05:00
committed by GitHub
parent 0491b08818
commit 6b5a1fb827
21 changed files with 122 additions and 265 deletions

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* 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.channel.state;
@@ -47,9 +44,37 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
protected Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
protected Listener<DecoderStateEvent> mDecoderStateListener;
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
private boolean mRunning;
public abstract DecoderType getDecoderType();
/**
* Implements module start and sets the mRunning flag to true so that messages can be processed.
*/
@Override
public void start()
{
mRunning = true;
}
/**
* Implements the module stop and sets the mRunning flag to false to stop message processing
*/
@Override
public void stop()
{
mRunning = false;
}
/**
* Indicates if this module is running and can process/pass messages down to sub-class implementations.
* @return true if running
*/
public boolean isRunning()
{
return mRunning;
}
/**
* Provides subclass reference to the decode event broadcaster
*/
@@ -145,4 +170,22 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
}
}
/**
* Message listener that only passes messages while we're running. This is important because each of the decoders
* can process blocks of samples and that can result in additional messages being generated even after shutdown
* and so we shut off the processing of decoded messages when we're commanded to stop. The sample processing thread
* cannot be shutdown or forcefully interrupted because downstream decoder and channel states may have acquired
* locks that have to be properly released.
*/
private class MessageListener implements Listener<IMessage>
{
@Override
public void receive(IMessage message)
{
if(isRunning())
{
receive(message);
}
}
}
}

View File

@@ -1,102 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.channel.state;
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.string.SimpleStringIdentifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Basic decoder channel state - provides the minimum channel state functionality
* to support an always un-squelched audio decoder.
*/
public class AlwaysUnsquelchedDecoderState extends DecoderState
{
private final static Logger mLog = LoggerFactory.getLogger(AlwaysUnsquelchedDecoderState.class);
private static final String NO_SQUELCH = "No Squelch";
private Identifier mChannelNameIdentifier;
private DecoderType mDecoderType;
private String mChannelName;
public AlwaysUnsquelchedDecoderState(DecoderType decoderType, String channelName)
{
mDecoderType = decoderType;
mChannelName = (channelName != null && !channelName.isEmpty()) ? channelName : decoderType.name() + " CHANNEL";
mChannelNameIdentifier = new SimpleStringIdentifier(mChannelName, IdentifierClass.USER, Form.CHANNEL_NAME, Role.TO);
}
@Override
public void init()
{
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append("Activity Summary\n");
sb.append("\tDecoder:\t");
sb.append(mDecoderType);
sb.append("\n\n");
return sb.toString();
}
@Override
public void receive(IMessage t)
{
/* Not implemented */
}
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{
if(event.getEvent() == Event.REQUEST_RESET)
{
getIdentifierCollection().update(mChannelNameIdentifier);
}
}
@Override
public DecoderType getDecoderType()
{
return mDecoderType;
}
@Override
public void start()
{
broadcast(new DecoderStateEvent(this, Event.REQUEST_ALWAYS_UNSQUELCH, State.IDLE));
getIdentifierCollection().update(mChannelNameIdentifier);
}
@Override
public void stop()
{
}
}

View File

@@ -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
@@ -54,6 +54,7 @@ public abstract class DecoderState extends AbstractDecoderState
@Override
public void start()
{
super.start();
//Broadcast the existing identifiers (as add events) so that they can be received by external listeners
mIdentifierCollection.broadcastIdentifiers();
}

View File

@@ -636,6 +636,8 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
processingChain.removeFrequencyChangeListener(channel);
channel.resetFrequencyCorrection();
//Notify all processing chains that this channel is shutting down so that if this is a traffic channel,
//the owning parent channel's traffic channel manager can cleanup it's accounting.
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
mChannelEventBroadcaster.removeListener(processingChain);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -145,11 +145,10 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
@Override
public void start()
{
super.start();
getIdentifierCollection().update(getChannelNameIdentifier());
}
@Override
public void stop() {}
@Override
public void init() {}
@Override

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -115,9 +115,4 @@ public class DCSDecoderState extends DecoderState
public void init()
{
}
@Override
public void stop()
{
}
}

View File

@@ -1472,6 +1472,8 @@ public class DMRDecoderState extends TimeslotDecoderState
@Override
public void start()
{
super.start();
//Change the default (45-second) traffic channel timeout to 1 second
if(mChannel.isTrafficChannel())
{
@@ -1483,9 +1485,4 @@ public class DMRDecoderState extends TimeslotDecoderState
public void init()
{
}
@Override
public void stop()
{
}
}

View File

@@ -201,6 +201,9 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
}
mAvailableTrafficChannels.addAll(trafficChannelList);
//Keep track of the complete list so that we can check channel events to determine if the channel event
//is for a traffic channel owned by this traffic channel manager.
mAllocatedTrafficChannels = Collections.unmodifiableList(trafficChannelList);
}
}
@@ -795,7 +798,10 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
}
/**
* Process traffic channel processing stop or processing start rejected events.
/**
* Process channel events from the ChannelProcessingManager to account for owned child traffic channels.
* Note: this method sees events for ALL channels and not just DMR channels managed by this instance.
*
* @param channelEvent to process
*/
@Override
@@ -803,7 +809,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
Channel channel = channelEvent.getChannel();
if(channel.isTrafficChannel())
//Only process the event if it's one of the traffic channels managed by this instance.
if(mAllocatedTrafficChannels.contains(channel))
{
switch(channelEvent.getEvent())
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -53,20 +53,7 @@ public class Fleetsync2DecoderState extends DecoderState
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
public void init() {}
@Override
public void receive(IMessage message)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -150,20 +150,5 @@ public class LJ1200DecoderState extends DecoderState
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
public void init() {}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -586,19 +586,7 @@ public class LTRNetDecoderState extends DecoderState
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
public void init() {}
/**
* Resets the decoder state after a call or other decode event

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -153,19 +153,7 @@ public class LTRStandardDecoderState extends DecoderState
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
public void init() {}
/**
* Performs a temporal reset following a call or other decode event

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -58,22 +58,7 @@ public class MDCDecoderState extends DecoderState
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
public void init() {}
@Override
public void receive(IMessage message)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -220,6 +220,7 @@ public class MPT1327DecoderState extends DecoderState
@Override
public void start()
{
super.start();
//Send call start event for traffic channels to unsquelch the audio. Decoded return to channel message
//or fade timeout expire will end the call event.
if(mChannelType == ChannelType.TRAFFIC)
@@ -228,18 +229,8 @@ public class MPT1327DecoderState extends DecoderState
broadcast(new DecoderStateEvent(this, Event.START, State.CALL));
}
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
public void init() {}
protected void resetState()
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -301,6 +301,13 @@ public class MPT1327TrafficChannelManager extends TrafficChannelManager implemen
*/
public class TrafficChannelTeardownMonitor implements Listener<ChannelEvent>
{
/**
* Process channel events from the ChannelProcessingManager to account for owned child traffic channels.
* Note: this method sees events for ALL channels and not just MPT1327 channels managed by this instance.
*
* @param channelEvent to process
*/
@Override
public synchronized void receive(ChannelEvent channelEvent)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -794,6 +794,12 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
}
}
/**
* Process channel events from the ChannelProcessingManager to account for owned child traffic channels.
* Note: this method sees events for ALL channels and not just P25 channels managed by this instance.
*
* @param channelEvent to process
*/
@Override
public synchronized void receive(ChannelEvent channelEvent)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -1948,6 +1948,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
@Override
public void start()
{
super.start();
mPatchGroupManager.clear();
//Change the default (45-second) traffic channel timeout to 1 second
@@ -1965,6 +1966,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
@Override
public void stop()
{
super.stop();
mPatchGroupManager.clear();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -1161,6 +1161,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
@Override
public void start()
{
super.start();
mPatchGroupManager.clear();
//Change the default (45-second) traffic channel timeout to 1 second
@@ -1178,6 +1179,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
@Override
public void stop()
{
super.stop();
mPatchGroupManager.clear();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -327,22 +327,7 @@ public class PassportDecoderState extends DecoderState
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
public void init() {}
protected void resetState()
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -58,22 +58,7 @@ public class Tait1200DecoderState extends DecoderState
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void init()
{
}
public void init() {}
@Override
public void receive(IMessage message)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -106,7 +106,9 @@ public class Dispatcher<E> implements Listener<E>
{
if(mScheduledFuture != null)
{
mScheduledFuture.cancel(true);
//Note: this has to be false because downstream implementations may have acquired locks and they must
//be able to release those locks or we'll get a deadlock situation.
mScheduledFuture.cancel(false);
}
if(mExecutorService != null)
@@ -132,7 +134,9 @@ public class Dispatcher<E> implements Listener<E>
{
if(mScheduledFuture != null)
{
mScheduledFuture.cancel(true);
//Note: this has to be false because downstream implementations may have acquired locks and they must
//be able to release those locks or we'll get a deadlock situation.
mScheduledFuture.cancel(false);
mScheduledFuture = null;
mQueue.clear();
}