Repository navigation
Convert host and SRV lookups to async instead of blocking #3986
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
base: main
Are you sure you want to change the base?
Changes from all commits
4bda78d
c3e897a
4c42f34
18fbfa1
156cfed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,7 +93,8 @@ CClient::CClient ( const quint16 iPortNumber, | |
| bJitterBufferOK ( true ), | ||
| bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ), | ||
| iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ), | ||
| bRawAudioIsSupported ( false ) | ||
| bRawAudioIsSupported ( false ), | ||
| iResolverSeq ( 0 ) | ||
| { | ||
| int iOpusError; | ||
|
|
||
|
|
@@ -619,25 +620,6 @@ void CClient::SetRemoteChanPan ( const int iId, const float fPan ) | |
| StartTimerGainOrPan(); | ||
| } | ||
|
|
||
| bool CClient::SetServerAddr ( QString strNAddr ) | ||
| { | ||
| CHostAddress HostAddress; | ||
| if ( NetworkUtil::ParseNetworkAddress ( strNAddr, HostAddress, bIPv6Available ) ) | ||
| { | ||
| // apply address to the channel | ||
| Channel.SetAddress ( HostAddress ); | ||
|
|
||
| // By default, set server name to HostAddress. If using the Connect() method, this may be overwritten | ||
| SetConnectedServerName ( HostAddress.toString() ); | ||
|
|
||
| return true; | ||
| } | ||
| else | ||
| { | ||
| return false; // invalid address | ||
| } | ||
| } | ||
|
|
||
| bool CClient::GetAndResetbJitterBufferOKFlag() | ||
| { | ||
| // get the socket buffer put status flag and reset it | ||
|
|
@@ -1150,6 +1132,8 @@ void CClient::Stop() | |
| /// @emit Disconnected | ||
| void CClient::Disconnect() | ||
| { | ||
| ++iResolverSeq; // invalidate any uncompleted resolver requests | ||
|
|
||
| // Key off the connection state, not IsRunning() (which tracks the audio | ||
| // device): the two diverge while connecting and in headless mode, and on | ||
| // SIGTERM we must still send the disconnect message to the server. | ||
|
|
@@ -1162,7 +1146,7 @@ void CClient::Disconnect() | |
| /// @method | ||
| /// @brief Connects to strServerAddress. If a connection is currently requested | ||
| /// or established, that connection is terminated first. | ||
| /// @emit Connecting (strServerName) if SetServerAddr was valid. emit happens through Start(). | ||
| /// @emit Connecting (strServerName) if address was valid. emit happens through Start(). | ||
| /// Use to set CClientDlg to show being connected | ||
| /// @emit ConnectingFailed (error) if an error occurred | ||
| /// Use to display error message in CClientDlg | ||
|
|
@@ -1176,24 +1160,68 @@ void CClient::Connect ( const QString& strServerAddress, const QString& strServe | |
| // different server while connected behaves as a reconnect | ||
| Disconnect(); | ||
|
|
||
| // Set server address and connect if valid address was supplied | ||
| if ( SetServerAddr ( strServerAddress ) ) | ||
| const int seq = iResolverSeq; // for capturing the current request | ||
|
|
||
| if ( NetworkUtil::ResolveAddress ( | ||
| strServerAddress, | ||
| bIPv6Available, | ||
| this, | ||
| [this, seq] ( const QHostInfo& info, quint16 port ) { | ||
| // check that the request has not been invalidated | ||
| if ( seq == iResolverSeq ) | ||
| { | ||
| OnHostResolved ( info, port ); // handle result | ||
| } | ||
| }, | ||
| true, | ||
| true ) ) | ||
| { | ||
| SetConnectedServerName ( strServerName ); | ||
| Start(); | ||
| SetConnectedServerName ( strServerName ); // set if resolution initiated ok | ||
| } | ||
| else | ||
| { | ||
| throw CGenErr ( tr ( "Received invalid server address. Please check for typos in the provided server address." ) ); | ||
| emit ConnectingFailed ( tr ( "Received invalid server address. Please check for typos in the provided server address." ) ); | ||
| } | ||
| } | ||
| catch ( const CGenErr& generr ) | ||
| { | ||
| Stop(); | ||
| // Stop(); // XXX: probably not needed? | ||
| emit ConnectingFailed ( generr.GetErrorText() ); | ||
| } | ||
| } | ||
|
|
||
| void CClient::OnHostResolved ( const QHostInfo& info, quint16 port ) | ||
| { | ||
| if ( info.error() != QHostInfo::NoError ) | ||
| { | ||
| emit ConnectingFailed ( "Cannot connect: " + info.errorString() ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to have a tr() to allow translation? |
||
| return; | ||
| } | ||
|
|
||
| for ( const QHostAddress& HostAddr : info.addresses() ) | ||
| { | ||
| // qInfo() << "Resolved network address to" << HostAddr << "for proto" << HostAddr.protocol(); | ||
| if ( HostAddr.protocol() == QAbstractSocket::IPv4Protocol || ( HostAddr.protocol() == QAbstractSocket::IPv6Protocol && bIPv6Available ) ) | ||
|
ann0see marked this conversation as resolved.
Resolved
|
||
| { | ||
| CHostAddress HostAddress ( HostAddr, port ); | ||
| Channel.SetAddress ( HostAddress ); | ||
| // sound device start can throw an exception, which we must catch here | ||
| try | ||
| { | ||
| Start(); | ||
| } | ||
| catch ( const CGenErr& generr ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not part of this PR, but in general error handling is not great in Jamulus at the moment. So we might want to think about how to handle this in future. This is probably fine-ish (but a CGenErr is probably too general from a theoretical PoV...) |
||
| { | ||
| Stop(); | ||
| emit ConnectingFailed ( generr.GetErrorText() ); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| emit ConnectingFailed ( "Hostname returned no usable addresses" ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tr()? Moreover, I don't think this will tell the user enough. In general error messages should tell the user how they can fix the issue. I believe that we document in the style guide how error messages should look like. cc: @gilgongo ? |
||
| } | ||
|
|
||
| /// @method | ||
| /// @brief Updates the connection state and, if it changed, notifies observers. | ||
| /// @emit ConnectionStateChanged (state) when the state actually changes | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: investigate what errors can occur and then if we can get into an inconsistent state.
CC: @coderabbitai
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Analysis chain
Script executed:
Length of output: 23425
Script executed:
Length of output: 47363
@ann0seeI found aCGenErrpath that can leave the client in an inconsistent state.Connect():Disconnect()callsStop()when a connection is active. On Android,CSound::Stop()can throw if Oboe fails to stop or close a stream (src/sound/oboe/sound.cpp, Line 79). In that case,CClient::Stop()exits before it disables the channel, sends the disconnect message, or updates the connection state. TheConnect()catch reports the error but does not complete cleanup.false. DNS failure reachesOnHostResolved()throughQHostInfo. Neither is aCGenErrhandled by the outer catch.Start()runs inOnHostResolved(), so itsCGenErris handled by the callback, not the outer catch. The callback callsStop(), but that call can also throw beforeConnectingFailedis emitted.Restoring the unconditional
Stop()in the outer catch is not sufficient: it can repeat the same shutdown failure. Cleanup should handle sound-stop errors while still attempting the remaining disconnect steps.There is also a separate asynchronous state risk. A lookup remains pending while the state is
CS_DISCONNECTED, soDisconnect()does not cancel it. An older result can start a connection after a newer request or cancellation. A result can also arrive whileStop()processes events. I recommend invalidating pending attempts before disconnecting and ignoring callbacks from older attempts.Please test an Oboe stop failure, a startup failure, and delayed lookups followed by disconnect or a second connection request. This assessment is based on code inspection; I did not run the application or tests.
You are interacting with an AI system.