Don't know where to look?

You can search all my notes here

Spy on the react-native MessageQueue in style

When your react-native app starts feeling very sluggish and it takes huge delays before it responds to user input, your MessageQueue might be stuck.

What is the MessageQueue?

In react-native you write most of your code in Javascript. This code then gets executed on a separate thread in you react-native app. The native main thread, that is responsible for displaying the UI, responding to user input and interacting with the OS, is mostly implemented by react-native and the libraries that you included.

So if your Javascript wants to update something in the UI it has to send that information to the main thread. That’s where the MessageQueue comes in. It allows the Javascript-thread to send and receive JSON-messages to and from the main tread.

Usually all of that happens behind the scenes so most of the times you will never see the MessageQueue at all. But sometimes when there are a lot of messages being passed around, the MessageQueue can become a performance bottleneck.

Typical symptoms of this are:

  • Your App responds slowly (if at all) to your user input
  • Anything that runs on the native thread will continue to work just fine. For example:
    • Native animations (useNativeDriver: true or react-native-reanimated)
    • Audiofiles playing in the background

In such a case you might want to see what’s going on inside the MessageQueue

TL/DR: Give me the code

spyOnMessageQueue.ts
typescript
// @ts-ignore
import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue.js'

type MessageQueueMessage = {
  type: 0 | 1
  module: string | null
  method: string | null
  args: unknown[]
}

// Please only do this in a __DEV__ environment
// console.log is slooooow
if (__DEV__) {
  MessageQueue.spy((msg: MessageQueueMessage) => {
    if (msg.module === 'WebSocketModule') {
      return
    }

    const direction = msg.type ? '🤖 JS -> Native' : '👨 Native -> JS'
    const functionName = [msg.module, msg.method].filter((x) => x).join('.')
    const args = (msg.args || [])
      .map((arg) => {
        const result = JSON.stringify(arg)

        // Optional: replace all JSON that's longer than 1000 chars
        // Usually that's quite helpful to increase readability
        if (result.length > 1000) {
          return '<long data>'
        }

        return result
      })
      .join(', ')

    console.log(`${direction}: ${functionName}(${args})`)
  })
}

To use this code, just place this file in the same directory as your index.js and put this line on top of your index.js: import './spyOnMessageQueue'

Last words

  • Can this remain inside my app permanently?
    Including this script comes with a big performance penalty. So I would recommend that you include this script only temporarily when you need it for debugging.
  • How about the security of this script?
    This script might log any data that is being stored inside your app. That includes confidential data! So it is important, to not enable that script in any kind of production environment. That’s why I added line 13 so that even if you forget to remove that script before building your app for production it will not activate.
  • What about Reactotron?
    I have tried to use console.tron.log instead of console.log and I would strongly advise against that. To not impact your app’s performance too much, Reactotron logs are shown with a little delay. But when you are debugging the MessageQueue, you will need the messages to be shown as fast as possible.

Comments

Post-Meta
Included files
  • readme.md
  • spyOnMessageQueue.ts
Download all