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

Websocket won't reopen on iOS #5697

Closed
mboyd1993 opened this issue Mar 11, 2024 · 3 comments · Fixed by #5704
Closed

Websocket won't reopen on iOS #5697

mboyd1993 opened this issue Mar 11, 2024 · 3 comments · Fixed by #5704

Comments

@mboyd1993
Copy link
Contributor

Version

3.8.2

Summary

When using websockets in a multiplatform project, the websocket will never reopen on iOS after a temporary network disconnect despite implementing the .reopenWhen block when initializing the WebSocketNetworkTransport.

After the websocket is closed due to a NetworkError, the logic in the WebSocketNetworkTransport should evaluate the .reopenWhen block and attempt to open the websocket again if the block returns true. If another NetworkError is received after attempting to reopen the websocket, then .reopenWhen is called again and the process should repeat itself until the websocket is successfully opened again or the .reopenWhen block returns false.

The actual behavior is that .reopenWhen is only called one single time and even if it returns true, the websocket is never reopened and .reopenWhen is never called again. This means that any temporary network disruption causes the websocket to close permanently and any associated subscriptions get cancelled.

Steps to reproduce the behavior

I opened an issue a few weeks ago to discuss this topic before I was sure if it was a bug or not. @martinbonnin was kind enough to create a very simple demo project during our discussion and that project reproduces the bug. Note that the bug only appears when you run on a real device and not on a simulator.

  1. Launch the SubscriptionTest app on a device
  2. Observe that a websocket is successfully connected and the subscription events are used to update the current date time in the UI
  3. Remove the device's internet connection by putting it in airplane mode for example.
  4. Observe that the current date time in the UI stops updating because the websocket has been closed due to the network error
  5. Observe in the logs that .reopenWhen gets called one single time, but never again while the network is disconnected
  6. Reconnect the internet
  7. Observe that the websocket remains closed and the current date time in the UI doesn't start updating again despite returning true from .reopenWhen

Logs

helloSun Feb 25 05:59:53 GMT 2024
helloSun Feb 25 05:59:54 GMT 2024
helloSun Feb 25 05:59:55 GMT 2024
helloSun Feb 25 05:59:56 GMT 2024
helloSun Feb 25 05:59:57 GMT 2024
helloSun Feb 25 05:59:58 GMT 2024
Connection 1: received failure notification
Reopening...
Task <658C861C-C63B-4FD8-A451-18C3D38E089E>.<1> finished with error [-1,005] Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSErrorFailingURLStringKey=https://leonidas-naiwjdzjsq-od.a.run.app/subscription, NSErrorFailingURLKey=https://leonidas-naiwjdzjsq-od.a.run.app/subscription, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalWebSocketTask <658C861C-C63B-4FD8-A451-18C3D38E089E>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalWebSocketTask <658C861C-C63B-4FD8-A451-18C3D38E089E>.<1>, NSLocalizedDescription=The network connection was lost.}
@martinbonnin
Copy link
Contributor

Thanks for the detailed issue. I could reproduce. The problem lies here. When trying to open a WebSocket when offline, the code currently waits on a NSUrlSessionDelegate that is never coming because:

  • didOpenWithProtocol is called on success
  • didCloseWithCode is called on close but only if there has been a success before

I've been working on a revamp of that WebSocket code in the form of a new WebSocketNetworkTransport. It's currently incubating in the websocket-network-transport artifact. It's only in the SNAPSHOTs but if you want to try it out, you can use this commit in the reproducer as an example.

As a bonus, the new retryOnError {} API is now NWPath aware meaning it will retry the subscription sooner when connectivity is detected.

@mboyd1993
Copy link
Contributor Author

mboyd1993 commented Mar 12, 2024

Thanks @martinbonnin. I was able to track the problem down to that line in the NSURLSessionWebSocketEngine as well where it ends up waiting forever if an attempt is made to open the websocket while there's no internet connection. It's waiting forever because neither onOpen nor onClose is ever called in the connectionListener.

However, there's another optional protocol method in the NSURLSessionWebSocketDelegateProtocol called didCompleteWithError that isn't implemented in the current NSURLSessionWebSocketEngine. That's the callback that gets called when you try to open a websocket without an internet connection. I implemented that protocol method here and I've now gotten the websocket to reopen correctly in my iOS project. I'm going to submit a pull request with that little change, so take a look and let me know if you think that will work or if it might cause any other issues.

I did notice that you've been doing new work on the WebSocketNetworkTransport. I'm looking forward to trying that out once it's released, but this fix is working in our project for right now.

Copy link
Contributor

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Kotlin usage and allow us to serve you better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants