grpc-swift icon indicating copy to clipboard operation
grpc-swift copied to clipboard

Support NIOTSNetworkEvents.WaitingForConnectivity

Open glbrntt opened this issue 4 years ago • 3 comments

Is your feature request related to a problem? Please describe it.

At the moment a client a client attempts to establish a connection it may be waiting for connectivity (e.g. when Airplane mode is enabled). During this time we still allow users to make RPCs; requests will be sent when a connection is established (if one is eventually established).

In most cases waiting for connectivity is the right thing do to, however, it can be useful to know when we're in this state and to disallow the connection from entering this state entirely.

Related Issues:

  • #831
  • #712

Describe the solution you'd like

Note: this only applies when NIOTransportServices is being used.

  • Allow users to configure whether they're willing to wait for connectivity.
  • Surface the waiting-for-connectivity events (NIOTSNetworkEvents.WaitingForConnectivity was added in https://github.com/apple/swift-nio-transport-services/pull/95) to the user via the connectivity delegate.

Describe alternatives you've considered

None.

Additional context

None.

glbrntt avatar Jun 17 '20 08:06 glbrntt

I am using 1.0.0-alpha.12 and i had the following setup to detect connectivity:

I tried to reduce the code to the minimum.

I am using SwiftUI and Combine on iPadOS btw.

This doesn't work on 1.0.0-alpha.13 - presumably the update when the connection management was rewritten killed it? How is the ConnectivityStateDelegate being used? Does it make sense to open a new issue or is this being addressed?

class MyStuff: ObservableObject {
    private(set) var client: MyClient
    private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
    private let delegate: Delegate = Delegate()
    @Published private(set) public var connectivity: ConnectivityState = .idle
    
    private var cancellables: Set<AnyCancellable> = []
    
    init(_ ip: String, port: Int) {
        let channel = ClientConnection
            .insecure(group: self.group)
            .withConnectionBackoff(maximum: .seconds(1))
            .withErrorDelegate(self.delegate)
            .withConnectivityStateDelegate(self.delegate)
            .connect(host: ip, port: port)

        self.client = MyClient(channel: channel)
        
        self.delegate.connectivity.sink(receiveValue: { newState in
            DispatchQueue.main.async {
                self.connectivity = newState
            }
        })
            .store(in: &self.cancellables)
    }
    
    deinit {
        self.cancellables.removeAll()
        do {
            try self.client.channel.close().wait()
            try self.group.syncShutdownGracefully()
        } catch {
            // don't deal with errors here
        }
    }
    
    class Delegate: ConnectivityStateDelegate, ClientErrorDelegate {
        var connectivity = CurrentValueSubject<ConnectivityState, Never>(.idle)
        
        func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) {
            self.connectivity.value = newState
        }
        
        func didCatchError(_ error: Error, logger: Logger, file: StaticString, line: Int) {
            print(error)
            print(file)
            print(line)
        }
    }
}

Thanks in advance,

Greetings Konstantin

krjw-eyev avatar Jun 19 '20 13:06 krjw-eyev

Hey @krjw-eyev, when you say "doesn't work", can you please elaborate a little? What does happen?

Lukasa avatar Jun 21 '20 11:06 Lukasa

Hey @Lukasa I have investigated this more and I was able to fix it on my end. The behaviour in 1.0.0.alpha.12 was that the delegate got .ready state even without actually calling a grpc. This behaviour changed, so I was getting .idle all the time.

I will upgrade now to 1.0.0.alpha.14 but I think there should be some kind of example or a documentation on how this exactly works because people who are not very familiar with grpc (like me) may misinterpret how the mechanism works. I really appreciate the work you have done here!

My issue obviously had nothing to do with this issue so you could either delete the comments or leave them for documentation.

Thanks

Konstantin

krjw-eyev avatar Jul 01 '20 10:07 krjw-eyev