I’m using Backendless iOS SDK to implement the real-time messaging function in my app. I noticed there is something wrong with message listener when user presses the home button and moves the app to the background. Hope you could take a look. Detailed information are as follows.
Problem Description
In RTClient.swift, in the subscribe method, there is self._lock.lock() action. The condition of unlock is either self.socketConnected or execute self.connectSocket and unlock in the connected handler. Notice the self.connectSocket is an async method and usually it’ll take time.
However, when user press home button and the app goes to background, there is a chance that the connection is lost. Therefore the self.needResubscribe will be set to true. App will call subscribe once and onConnectionHandlers will call subscribe again because self.needResubscribe is true. Thus the deadlock.
Backendless Version (3.x / 5.x, Online / Managed / Pro )
I start a message listener in my ‘ChatRoom’ view controller. Then I press the home button and the app goes to background. After a while when I re-enter the app, the listener should work, or at least I can add a new listener to make the real-time chat continue.
Actual Behavior
After re-entering the app, the listener won’t receive new messages anymore. I didn’t close the app for the entire process.
I can’t add new listener either. When I call addCustomObjectMessageListener, nothing happened. No error code, response handled isn’t called, error handler isn’t called.
I tried removing all listeners before the app goes to background and add listener when app reopens, still not work.
This not just happens to MessageListener, also happens to realtime database listener.
Reproducible Test Case
The Real time chat sample code provided in the code generation section is a good example. It has the same problem. When the app goes to background and re-enters into foreground, the chat is dead. Publish still works but the message listener is dead.
Hi Olha, Thanks for replying. My two cents, in onConnectionHandlers method, the lock is only released when socket is on connect and self.needResubscribe is false. For all other cases such as connect error cases, the lock is still held by the thread. Especially when self.needResubscribe is true, subscribe is called again and deadlock occurs.
Not sure if this makes sense. Happy to discuss further.
Thank you for clarification.
Could you please attаch a simple code snippet that reproduces it as you described?
This problem will be investigated as soon as possible.
Hi Olha, thanks for your reply. The sample is very simple. Substitute the API key and app Key before running.
To reproduce the bug: There are a couple of ways to reproduce the bugs. I recommend to run this sample on a real device so it’s easier to reproduce.
Option 1. press any button to start a channel. Then press the home button to make the app to the background. Just open whatever another app, say Google, do some search request. Then switch back to this sample app, the listener will be dead. Error could be connection closed by server. code=1000, type=protocolError. Then nothing you can do to restart the channel, even if you want to start a different new channel, it’s not gonna work.
Option 2. Press Chat One button or Chat Two button really quick, e.g. 10 times in a row. the listener will be dead too. Error could be Tried emitting when not connected. Still, there’s nothing we could do to restart the channel except close the app and reopen.
The point is, whenever something error with the socket happens, The whole real-time system is dead. It’s really common and easy to run into those errors in practice.
I assume the rt connection listeners are exactly what you need:
func addConnectionListener() {
let channelConnectListener = channel?.addConnectListener(responseHandler: {
print("Channel \(self.channel?.channelName ?? "...") is connected")
}, errorHandler: { fault in
print("Connection fault: \(fault.message ?? "")")
})
let connectListener = Backendless.shared.rt.addConnectEventListener(responseHandler: {
print("Socket connected")
// wait until socket is connected and then do smth
})
let connectErrorListener = Backendless.shared.rt.addConnectErrorEventListener(responseHandler: { reason in
print("Socket cannot be connected: \(reason)")
})
let disconnectListener = Backendless.shared.rt.addDisсonnectEventListener(responseHandler: { reason in
print("Socket disconnected: \(reason)")
})
let reconnectListener = Backendless.shared.rt.addReconnectAttemptEventListener(responseHandler: { reconnectAttempt in
print("Trying to reconnect...")
})
}
E.g. when you press the Chat one button, socket tries to connect. It may take 1-2 seconds or so, so you shouldn’t trigger any other socket methods until it is connected.
After your application enters background, socket is disconnected. But it tries to reconnect after app enters foreground, and you’ll see the connectListener’s response right after it is connected again (or error, if not).
Hi @olhadanylova,
Thanks for your detailed explanation and sample code. I tried this and also dug into the source code, it worked.
I really didn’t think adding listeners would actually change the program behavior. It seems only by adding connectErrorListener and disconnectListener can this program trigger the reconnect. Otherwise the socket is just dead. I’d suggest making that clear in the documentation, which can save users much trouble.
This is quite a tricky problem for me. Thanks for solving this for me.
Best!
Evan