Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix connection status issue (#2519) #3364

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions src/channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ CChannel::CChannel ( const bool bNIsServer ) :
bIsEnabled ( false ),
bIsServer ( bNIsServer ),
bIsIdentified ( false ),
bDisconnectAndDisable ( false ),
iAudioFrameSizeSamples ( DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES ),
SignalLevelMeter ( false, 0.5 ) // server mode with mono out and faster smoothing
{
Expand Down Expand Up @@ -125,7 +126,8 @@ void CChannel::SetEnable ( const bool bNEnStat )
QMutexLocker locker ( &Mutex );

// set internal parameter
bIsEnabled = bNEnStat;
bIsEnabled = bNEnStat;
bDisconnectAndDisable = false;

// The support for the packet sequence number must be reset if the client
// disconnects from a server since we do not yet know if the next server we
Expand Down Expand Up @@ -506,11 +508,24 @@ void CChannel::Disconnect()
// we only have to disconnect the channel if it is actually connected
if ( IsConnected() )
{
// for a Client, block further audio data and disable the channel as soon as Disconnect() is called
// TODO: Add reasoning from #2550

bDisconnectAndDisable = !bIsServer;

// set time out counter to a small value > 0 so that the next time a
// received audio block is queried, the disconnection is performed
// (assuming that no audio packet is received in the meantime)
iConTimeOut = 1; // a small number > 0
}
else if ( !bIsServer )
{
// For clients (?) set defaults

bDisconnectAndDisable = false;
bIsEnabled = false;
iConTimeOut = 0;
}
}

void CChannel::PutProtocolData ( const int iRecCounter, const int iRecID, const CVector<uint8_t>& vecbyMesBodyData, const CHostAddress& RecHostAddr )
Expand All @@ -534,7 +549,7 @@ EPutDataStat CChannel::PutAudioData ( const CVector<uint8_t>& vecbyData, const i
// Only process audio data if:
// - for client only: the packet comes from the server we want to talk to
// - the channel is enabled
if ( ( bIsServer || ( GetAddress() == RecHostAddr ) ) && IsEnabled() )
if ( ( bIsServer || ( GetAddress() == RecHostAddr ) ) && IsEnabled() && !bDisconnectAndDisable )
{
MutexSocketBuf.lock();
{
Expand Down Expand Up @@ -622,6 +637,11 @@ EGetDataStat CChannel::GetData ( CVector<uint8_t>& vecbyData, const int iNumByte
eGetStatus = GS_CHAN_NOW_DISCONNECTED;
iConTimeOut = 0; // make sure we do not have negative values

if ( bDisconnectAndDisable )
ann0see marked this conversation as resolved.
Show resolved Hide resolved
{
bDisconnectAndDisable = false;
bIsEnabled = false;
}
// reset network transport properties
ResetNetworkTransportProperties();
}
Expand All @@ -643,6 +663,13 @@ EGetDataStat CChannel::GetData ( CVector<uint8_t>& vecbyData, const int iNumByte
{
// channel is disconnected
eGetStatus = GS_CHAN_NOT_CONNECTED;

if ( bDisconnectAndDisable )
ann0see marked this conversation as resolved.
Show resolved Hide resolved
{
bDisconnectAndDisable = false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??! So these get set to the same values whether eGetStatus is GS_CHAN_NOW_DISCONNECTED or GS_CHAN_NOT_CONNECTED? Now I'm really confused.

bIsEnabled = false;
iConTimeOut = 0;
}
}
}
MutexSocketBuf.unlock();
Expand Down
3 changes: 2 additions & 1 deletion src/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class CChannel : public QObject

void PrepAndSendPacket ( CHighPrioSocket* pSocket, const CVector<uint8_t>& vecbyNPacket, const int iNPacketLen );

void ResetTimeOutCounter() { iConTimeOut = iConTimeOutStartVal; }
void ResetTimeOutCounter() { iConTimeOut = bDisconnectAndDisable ? 1 : iConTimeOutStartVal; }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this. Should probably be expanded into an assignment and boolean comparison.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I not enthused by the whole bDisconnectAndDisable -- I can't see the problem it's trying to fix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they wanted to introduce an intermediate state somehow to fix the button being able to get out of sync.
From #2550 (comment)

Added bDisconnectAndDisable to CChannel. (For a Client now
Channel.Disconnect() will block audio data and auto disable the channel on disconnected)

bool IsConnected() const { return iConTimeOut > 0; }
void Disconnect();

Expand Down Expand Up @@ -216,6 +216,7 @@ class CChannel : public QObject
bool bIsEnabled;
bool bIsServer;
bool bIsIdentified;
bool bDisconnectAndDisable;

int iNetwFrameSizeFact;
int iNetwFrameSize;
Expand Down
106 changes: 78 additions & 28 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
/* Implementation *************************************************************/
CClient::CClient ( const quint16 iPortNumber,
const quint16 iQosNumber,
const QString& strConnOnStartupAddress,
const QString& strMIDISetup,
const bool bNoAutoJackConnect,
const QString& strNClientName,
Expand Down Expand Up @@ -184,13 +183,6 @@ CClient::CClient ( const quint16 iPortNumber,
// start the socket (it is important to start the socket after all
// initializations and connections)
Socket.Start();

// do an immediate start if a server address is given
if ( !strConnOnStartupAddress.isEmpty() )
{
SetServerAddr ( strConnOnStartupAddress );
Start();
}
ann0see marked this conversation as resolved.
Show resolved Hide resolved
}

CClient::~CClient()
Expand Down Expand Up @@ -337,7 +329,7 @@ void CClient::CreateServerJitterBufferMessage()
void CClient::OnCLPingReceived ( CHostAddress InetAddr, int iMs )
{
// make sure we are running and the server address is correct
if ( IsRunning() && ( InetAddr == Channel.GetAddress() ) )
if ( Channel.IsEnabled() && ( InetAddr == Channel.GetAddress() ) )
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't about the channel being enabled. It's about whether the client is running. It's a different intent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the initial PR confused active channel and active client: #2550 (comment)

This difference needs documentation. IMO: Add (like for JSON-RPC) respective technical documentation before the respective methods. As end goal: Write them for every function, for now: Write docs while changing.

{
// take care of wrap arounds (if wrapping, do not use result)
const int iCurDiff = EvaluatePingMessage ( iMs );
Expand Down Expand Up @@ -626,12 +618,16 @@ QString CClient::SetSndCrdDev ( const QString strNewDev )
Sound.Start();
}

// in case of an error inform the GUI about it
if ( !strError.isEmpty() )
{
emit SoundDeviceChanged ( strError );
// due to error, disconnect
Disconnect();
ann0see marked this conversation as resolved.
Show resolved Hide resolved
}

// in case of an error, this will inform the GUI about it

emit SoundDeviceChanged();
ann0see marked this conversation as resolved.
Show resolved Hide resolved

return strError;
}

Expand Down Expand Up @@ -758,8 +754,18 @@ void CClient::OnSndCrdReinitRequest ( int iSndCrdResetType )
}
MutexDriverReinit.unlock();

if ( !strError.isEmpty() )
{
#ifndef HEADLESS
QMessageBox::critical ( 0, APP_NAME, strError, tr ( "Ok" ) );
#else
qCritical() << qUtf8Printable ( strError );
ann0see marked this conversation as resolved.
Show resolved Hide resolved
exit ( 1 );
#endif
}

// inform GUI about the sound card device change
emit SoundDeviceChanged ( strError );
emit SoundDeviceChanged();
ann0see marked this conversation as resolved.
Show resolved Hide resolved
}

void CClient::OnHandledSignal ( int sigNum )
Expand All @@ -773,11 +779,8 @@ void CClient::OnHandledSignal ( int sigNum )
{
case SIGINT:
case SIGTERM:
// if connected, terminate connection (needed for headless mode)
if ( IsRunning() )
{
Stop();
}
// if connected, terminate connection
Disconnect();

// this should trigger OnAboutToQuit
QCoreApplication::instance()->exit();
Expand Down Expand Up @@ -876,18 +879,13 @@ void CClient::Start()

void CClient::Stop()
{
// stop audio interface
Sound.Stop();
// start disconnection
// Channel.Disconnect() should automatically disable Channel as soon as disconnected.
// Note that this only works if sound is active!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why make the changes here?

Copy link
Member Author

@ann0see ann0see Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's mainly moving around the Channel.Disconnect() and Sound.Stop() code around.

This may be the actual bug fix?

Channel.Disconnect();

// disable channel
Channel.SetEnable ( false );

// wait for approx. 100 ms to make sure no audio packet is still in the
// network queue causing the channel to be reconnected right after having
// received the disconnect message (seems not to gain much, disconnect is
// still not working reliably)
QTime DieTime = QTime::currentTime().addMSecs ( 100 );
while ( QTime::currentTime() < DieTime )
QTime DieTime = QTime::currentTime().addMSecs ( 500 );
while ( ( QTime::currentTime() < DieTime ) && Channel.IsEnabled() )
{
// exclude user input events because if we use AllEvents, it happens
// that if the user initiates a connection and disconnection quickly
Expand All @@ -896,6 +894,19 @@ void CClient::Stop()
QCoreApplication::processEvents ( QEventLoop::ExcludeUserInputEvents, 100 );
}

// Now stop the audio interface
Sound.Stop();

// in case we timed out, log warning and make sure Channel is disabled
if ( Channel.IsEnabled() )
{
//### TODO: BEGIN ###//
// Add error logging
//### TODO: END ###//

Channel.SetEnable ( false );
}

// Send disconnect message to server (Since we disable our protocol
// receive mechanism with the next command, we do not evaluate any
// respond from the server, therefore we just hope that the message
Expand All @@ -908,6 +919,45 @@ void CClient::Stop()
SignalLevelMeter.Reset();
}

bool CClient::Connect ( QString strServerAddress, QString strServerName )
{
if ( !Channel.IsEnabled() )
{
// Set server address and connect if valid address was supplied
if ( SetServerAddr ( strServerAddress ) ) {

Start();

emit Connecting ( strServerName );

return true;
}
}

return false;
}

bool CClient::Disconnect()
{
if ( Channel.IsEnabled() )
{
Stop();

emit Disconnected();

return true;
}
else
{
// make sure sound is stopped too
Sound.Stop();

emit Disconnected();

return false;
}
}

void CClient::Init()
{
// check if possible frame size factors are supported
Expand Down
9 changes: 6 additions & 3 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ class CClient : public QObject
public:
CClient ( const quint16 iPortNumber,
const quint16 iQosNumber,
const QString& strConnOnStartupAddress,
const QString& strMIDISetup,
const bool bNoAutoJackConnect,
const QString& strNClientName,
Expand All @@ -121,8 +120,11 @@ class CClient : public QObject

void Start();
void Stop();
bool Connect ( QString strServerAddress, QString strServerName );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does make sense to introduce an easy to call Connect() and Disconnect() method IMO, so this is good.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having these in client.cpp rather than clientdlg.cpp is important. It's important enough to be done in a single PR on its own. I think we should start with that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. That's issue #3367

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Factored out into #3372

bool Disconnect();

bool IsRunning() { return Sound.IsRunning(); }
bool IsCallbackEntered() const { return Sound.IsCallbackEntered(); }
bool IsCallbackEntered() const { return Sound.IsCallbackEntered(); } // For OnTimerCheckAudioDeviceOk only
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted naming. May need some in depth review and discussion.

bool SetServerAddr ( QString strNAddr );

double GetLevelForMeterdBLeft() { return SignalLevelMeter.GetLevelForMeterdBLeftOrMono(); }
Expand Down Expand Up @@ -427,8 +429,9 @@ protected slots:

void CLChannelLevelListReceived ( CHostAddress InetAddr, CVector<uint16_t> vecLevelList );

void Connecting ( QString strServerName );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a missing slot.

void Disconnected();
void SoundDeviceChanged ( QString strError );
void SoundDeviceChanged();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly about removing the error message handling. Could be reverted.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above:

  • split this out to a separate PR
  • have two slots, one for GUI, one for headless, and pass the error to each

void ControllerInFaderLevel ( int iChannelIdx, int iValue );
void ControllerInPanValue ( int iChannelIdx, int iValue );
void ControllerInFaderIsSolo ( int iChannelIdx, bool bIsSolo );
Expand Down
Loading