React Native Native Module looping call until crash on iOS

198 views Asked by At

I'm trying to build a gRPC client in React Native on iOS.

For context: React Native, which doesn't support gRPC directly, must call a custom Native Module from Swift, which makes the gRPC call and returns the value.

The gRPC server is a locally-compiled goLang module which uses the http2server module. I did not write the gRPC server so I cannot alter it's code.

It appears the React Native method is executing the native gRPC client call in a loop, causing Golang`s http2Server module to crash.

This gRPC client call is called from a button onPress() event, not from a loop. I've tried wrapping the gRPC calls in timeout tests to prevent calling too quickly.

My Native module has an exported function that looks like this:

@objc(SwiftNativeGrpcClient) class SwiftNativeGrpcClient: NSObject {
  // ...
  @objc func swiftGetGrpcTest(
    _ resolve: RCTPromiseResolveBlock,
    rejecter reject: RCTPromiseRejectBlock
  ) {
    print("SwiftNativeGrpcClient.swiftGetGrpcTest()")
    // connect to gRPC channel if necessary
    if (self.secureGrpcChannel == nil) {
      self.createSecureChannel()
    }
    // out of paranoia, don't let this call be used less than
    // once per second
    if (getMilliSecondsSinceLastCall() < 1000) {
      print("Method called to soon.")
      reject("0", "Method called too soon", nil)
      return
    }
    let grpcServiceClient = Service_ServiceName(channel: self.secureGrpcChannel!)
    let testRequest: Service_TestRequest = Service_TestRequest()
    // Service_TestResponse should contain a String:
    // "gRPC response success"
    let testResponse: Service_TestResponse
    let testCall = grpcServiceClient.getTest(testRequest)
    do {
        try testResponse = testCall.response.wait()
        print(testResponse)
    } catch {        ​
      ​print("RPC method 'getInfo' failed \(error)")
      ​return
    ​}
    // update the last call time to ensure this isn't being called
    // more than once per second
    self.lastCallTime = DispatchTime.now()
    resolve(getInfoResponse)
  }
  // ...
}

My React Native calls the native module like this:

const { SwiftNativeGrpcClient } = NativeModules;

export default function App() {
  const nativeGrpcClient = useRef(SwiftNativeGrpcClient)
  const [lastCallTime, setLastCallTime] = useState(new Date())

  const rnGetGrpcTest = async () => {
    try {
      const currentTime = new Date()
      console.log(`lastCallTime: ${lastCallTime}`)
      console.log(`currentTime: ${currentTime}`)
      const timeDiff = currentTime - lastCallTime
      console.log(`timeDiff: ${timeDiff} ms`)
      // Just checking... don't let this method
      // be executed more than once per second
      if (timeDiff > 1000) {
        await nativeGrpcClient.current.swiftGetGrpcTest()
      }
    } catch (error) {
      console.error(error.message)
    }
    setLastCallTime(currentTime)
  }
  // ...
}

The Xcode output looks like this ​

  1. it looks as though the gRPC client is making multiple calls to the gRPC server. You'll see the same response issued about 20 times from the React Native module emitter before crashing
2021-12-01 15:23:56.400068+0200 testApp[13091:123303] [javascript] { output: 'SwiftNativeGrpcClient.swiftGetGrpcTest()\n' }

2021-12-01 15:23:58.698908+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.699576+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.700075+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.700606+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.701067+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.701596+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.702036+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.702726+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.704172+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.704766+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.705121+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.705497+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.705833+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.715472+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
panic: 2021-12-01 15:23:58.715856+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.716342+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.716751+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.717020+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.717247+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.717510+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.718216+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
close of closed channel

goroutine 24507 [2021-12-01 15:23:58.718544+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
running]:
2021-12-01 15:23:58.718827+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.719167+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
  1. Golang's http2Server crashes during the handlePing() method after returning a response to the React Native through the native Swift module. It appears the gRPC connection was closed and then an attempt to close was made again, which is not handled by the http2server gracefully

This is the Xcode console log :

2021-12-01 15:23:58.717247+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.717510+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
2021-12-01 15:23:58.718216+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
close of closed channel
goroutine 24507 [2021-12-01 15:23:58.718544+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
running]:
goroutine 24507 [2021-12-01 15:23:58.718544+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
running]:
2021-12-01 15:23:58.718827+0200 testApp[13091:123303] [javascript] Got output from Naive Module Emitter:
2021-12-01 15:23:58.719167+0200 testApp[13091:123303] [javascript] { output: '1638365038 [INF] test.go:3294 gRPC response success\n' }
google.golang.org/grpc/internal/transport.(*http2Server).handlePing(0xc00007a1c0, 0xc003c08090)
    google.golang.org/[email protected]/internal/transport/http2_server.go:680 +0x6d
google.golang.org/grpc/internal/transport.(*http2Server).HandleStreams(0xc00015d800, 0xc0029d0f68, 0x10a742005)
    google.golang.org/[email protected]/internal/transport/http2_server.go:494 +0x31f
google.golang.org/grpc.(*Server).serveStreams(0xc000499860, {0x10b916390, 0xc00015d800})
    google.golang.org/[email protected]/server.go:742 +0x114
google.golang.org/grpc.(*Server).handleRawConn.func1()
    google.golang.org/[email protected]/server.go:703 +0x34
created by google.golang.org/grpc.(*Server).handleRawConn
    google.golang.org/[email protected]/server.go:702 +0x405
CoreSimulator 757.5 - Device: iPhone SE (2nd generation) (ECBD797A-E2B4-49F2-9DD5-BC8FB95EFACC) - Runtime: iOS 14.5 (18E182) - DeviceType: iPhone SE (2nd generation)

When I create a test project with the exact same Swift code, but without the React Native front-end, I do not experience this crash. React Native is somehow involved in the crashing behavior, possibly due to how the Native Module features function?

Does anyone have any ideas how to prevent this loop from happening?

1

There are 1 answers

0
Adonis Gaitatzis On

In case anyone else has a similar problem,

My problem was unrelated to the appearance of looping.

I needed to close the gRPC channel at the end of the Swift function. This can be done using a defer statement:

@objc(SwiftNativeGrpcClient) class SwiftNativeGrpcClient: NSObject {
  // ...
  @objc func swiftGetGrpcTest(
    _ resolve: RCTPromiseResolveBlock,
    rejecter reject: RCTPromiseRejectBlock
  ) {
    if (self.secureGrpcChannel == nil) {
      self.createSecureChannel()
    }
    let grpcServiceClient = Service_ServiceName(channel: self.secureGrpcChannel!)
    defer {
        // close the channel when the method exits
        try? grpcServiceClient.channel.close().wait()
    }
    let testRequest: Service_TestRequest = Service_TestRequest()
    // Service_TestResponse should contain a String:
    // "gRPC response success"
    let testResponse: Service_TestResponse
    let testCall = grpcServiceClient.getTest(testRequest)
    do {
        try testResponse = testCall.response.wait()
        print(testResponse)
        resolve(getInfoResponse)
    } catch {        ​
      ​print("RPC method failed \(error)")
      reject("0","RPC method failed: \(error)", nil)
      ​return
    ​}
    // update the last call time to ensure this isn't being called
    // more than once per second
  }
  // ...
}

The looping console output was caused by the interaction of two things:

  1. I was capturing standard output and routing it through an RCTEventEmitter
  2. The buffer wasn't being cleared between events.

So it appeared as if the RCTEventEmitter was receiving the gRPC success message several times.