MQTTnet
MQTTnet copied to clipboard
Use a different timeout mechanism to handle Xamarin timeout
This is in reference to https://github.com/chkr1011/MQTTnet/issues/966.
I think the nature of the problem is that on Android, SO_TIMEOUT is set to 0 by default, meaning infinity. As a result, a call to Connect for a socket will hang indefinitely on this platform unless setSoTimeout explicitly specifies something else. However, on Windows I believe the timeout isn't infinity by default and so the ConnectAsync call will eventually fail and the retry/reconnect loop will proceed as normal.
What I've done here is essentially wrap the synchronous Connect call in a Task which can be waited on and, in the event the socket fails to connect within a specific timeframe, the socket can be disposed.
I got this error on Xamarin and was able to fix it and have all things proceed as normal with this patch.
No worries 😄 Please check the usage of the passed cancellation token (starting the ConnectAsync method from the MqttClient). It can be already used for timeouts. In my opinion we should attach a handler to that token instead. Also the value of 10 seconds is not configurable now (there is already a value at the Client options for this which should be used).
I was having the same problem until I realized I had to run the connect on a separate thread
....
Dim thread As New Thread(Sub()
MQTT_Connect(My.Settings.MQTTBroker, My.Settings.MQTTPort, My.Settings.MQTTUser, My.Settings.MQTTPassword).Wait()
End Sub
)
thread.Start()
...
Private Async Function MQTT_Connect(Server As String, Port As Integer, User As String, Password As String) As Task
Try
MQTTClient = CType(MQTTFactory.CreateMqttClient(), MqttClient)
MQTTClient.UseApplicationMessageReceivedHandler(AddressOf MessageRecieved)
MQTTClient.UseDisconnectedHandler(AddressOf ConnectionClosed)
MQTTClient.UseConnectedHandler(AddressOf ConnectionOpened)
Dim Options As New Options.MqttClientOptionsBuilder
Options.WithClientId(Guid.NewGuid().ToString()).WithTcpServer(Server, Port).WithCredentials(User,Password).WithCommunicationTimeout(TimeSpan.FromSeconds(10))
Await MQTTClient.ConnectAsync(Options.Build).ConfigureAwait(False)
Log("MQTT connected")
Catch ex As Exception
Log("MQTT connection issue:" & vbCrLf & ex.Message.ToString)
End Try
End Function
@chkr1011 I apologize for being so delayed on getting back to this PR. I am now trying to get it across the finish line (and maybe another one or two written up) soon.
A bit of background on what's going on with this: I think the underlying issue here might actually be a Xamarin/Mono problem, and not something specific to any of the code in MQTTNet nor Android/Linux as I originally thought. I just today wrote up the following Stack Overflow post using a slimmed down example app to show off the problem: https://stackoverflow.com/questions/72397435/await-hangs-forever-on-socket-connectasync-when-android-is-disconnected and I am likely going to write up a bug report with Xamarin as well in hopes it'd get some more attention. Short of walking through mono, I'm not sure what else to do. So, in summary: this might be a Xamarin thing.
To get to the point as far as MQTTNet is concerned: we could replace the async call with a synchronous call as I did already as that appears to sidestep the issue. We could then continue using the cancellation register routine to call dispose. However, it's a shame to have to do that as your code as-is is certainly cleaner looking.
I guess I'm looking to you for any opinions on how to proceed.
Update: Xamarin issue was created.
https://github.com/xamarin/Xamarin.Forms/issues/15388
Please see PR #1545. I found a way to avoid new timeouts at all. The code will make use of the passed cancellation token so that the user has full control about cancellation. Thanks for your effort and inspiration for the fix.