GRPC-Swift Idle Stream cause next RPC call to fail with Unavailable (14) due to transientFailure

Viewed 385

In our iOS app we are consuming a server-side stream, that has regular idle periods without any data being sent. Short time after being idle, if another unary call is being triggered, it results in "unavailable (14): Transport became inactive"

The connection state just before the call is still ready , but at the time of call it changes to transientFailure, and right after it reconnects (transientFailure -> connecting -> ready)

From the ErrorDelegate i get this error at the time of triggering the unary call:

NoSuchStream(streamID: HTTP2StreamID(31), location: "<DERIVED_DATA_PATH...>/SourcePackages/checkouts/swift-nio-http2/Sources/NIOHTTP2/ConnectionStateMachine/ConnectionStreamsState.swift:298")

Is there any possible way to detect that the stream has issues BEFORE i try to perform another RPC that will fail due to inactive connection? I've tried to set up keepalive and some other connection settings, but so far no luck.

My connection setup and some sample code:

        let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)
        let keepalive = ClientConnectionKeepalive(
            interval: .seconds(15),
            timeout: .seconds(10),
            permitWithoutCalls: true
        )
        
        self.connection = ClientConnection
            .usingPlatformAppropriateTLS(for: group)
            .withKeepalive(keepalive)
            .withCallStartBehavior(.waitsForConnectivity)
            .withErrorDelegate(self)
            .withConnectivityStateDelegate(self)
            .connect(host: host, port: port)

The stream:

        self.service = Pricing_PricingClient(channel: self.connection)
        self.grpc.tickStream = self.service.ticksStream(streamRequest,
                                                        callOptions:
                                                            CallOptions.init(
                                                                logger: logger
                                                            )
        ){ tick in
           callback(tick)
        }

Sample unary call that fails after the stream is idle for a while:

        let symbolsRequest: Pricing_SymbolsRequest = .with {
            $0.serverID = Int32(serverID)
            $0.symbols = requestSymbols
        }
        
        let call = service.symbolsData(symbolsRequest)
        let status = try! call.status.recover { _ in .processingError }.wait()
        if status.code != .ok {
            print("RPC failed: \(String(describing: status))")
            // TODO: retry policy ??
        }
        do {
            let response = try call.response.wait()
            callback(response)
        } catch { }

0 Answers
Related