react-native-safe-area-context icon indicating copy to clipboard operation
react-native-safe-area-context copied to clipboard

[Android] Slow screen opening after react-native update

Open OrlovMaks opened this issue 2 years ago • 66 comments

Hey, everybody! After updating react-native (and all libraries) from version 0.70.11 to 0.72.6, I got a problem that screens with SafeAreaView open slowly and without animation. I couldn't find absolutely no information on how to fix it.

In the video the first screen "Security" is in SafeAreaView, the second screen "Your rank" is just View. https://github.com/th3rdwave/react-native-safe-area-context/assets/93343100/45623a6f-a038-4372-837a-abfa7466b8e8

My App component is wrapped with <SafeAreaProvider initialMetrics={initialWindowMetrics}> (I tried with and without the initialMetrics parameter).

My package.json { "react": "18.2.0", "react-native": "0.72.6", "react-native-safe-area-context": "4.7.4", "react-native-screens": "3.27.0", "react-native-reanimated": "3.5.4", "@react-navigation/bottom-tabs": "6.5.11", "@react-navigation/native": "6.1.9", "@react-navigation/stack": "6.3.20",

OrlovMaks avatar Oct 26 '23 14:10 OrlovMaks

Maybe a long shot but, do you by any chance have reduce motion enabled on the device/simulator?

efstathiosntonas avatar Oct 26 '23 19:10 efstathiosntonas

I see this as-well something like a 500ms delay on rendering whatever is inside safe view RN 0.72.4 looks like only on Android - and Reduce Motion is not enabled My other App on RN 0.71.6 works fine

17Amir17 avatar Oct 27 '23 10:10 17Amir17

There's not really anything to go off here. If you manage to make a minimal reproduction - or better yet, find the actual issue - let us know.

jacobp100 avatar Oct 27 '23 10:10 jacobp100

@jacobp100 Tomorrow I will make a copy of the application to the public repository and provide a link to it. Unfortunately, I myself could not solve this problem.

OrlovMaks avatar Oct 27 '23 12:10 OrlovMaks

@jacobp100 @17Amir17 I prepared a repository with the project from which I removed everything except the structure of my navigation: https://github.com/OrlovMaks/sample_safeAreaView_issue

While I was preparing this project I found a problem (check the screenshot): I have detachInactiveScreens={false} everywhere and I make navigation from WalletStackNavigator to general StackNavigator if I remove detachInactiveScreens from this general stack everything works. https://github.com/th3rdwave/react-native-safe-area-context/assets/93343100/4225fb1f-275d-4252-aefc-ac4d0ad50316

OrlovMaks avatar Oct 28 '23 11:10 OrlovMaks

Right so is it a problem with this library or another?

jacobp100 avatar Oct 28 '23 19:10 jacobp100

I have this issue with BottomSheetModal. I created https://github.com/17Amir17/SafeAreaContext to reproduce. From the comments in my codebase the reason we have a SafeArea in the modal is because of a rare cutoff of some android device.

https://github.com/th3rdwave/react-native-safe-area-context/assets/36531255/56a39589-92a9-4971-9a2b-d9967909dc22

I'm not really sure how to debug this further but would love to help @jacobp100 if you have any directions

17Amir17 avatar Oct 29 '23 08:10 17Amir17

@jacobp100 I'm seeing this issue too. It seems like it's because is we are hitting this timeout in SafeAreaView and that freezes the UI for 500ms

https://github.com/th3rdwave/react-native-safe-area-context/blob/b086b0ef5043836b72c6430cca1641641694fbce/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt#L97-L99

I debugged a little bit and it seems like the code in runOnNativeModulesQueueThread below is never able to acquire the lock, because the lock is being held the entire time we are waiting in the while loop right below, so it doesn't complete until the timeout ends.

https://github.com/th3rdwave/react-native-safe-area-context/blob/b086b0ef5043836b72c6430cca1641641694fbce/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt#L77-L95

I'm not sure what the best fix here is though.

mgerdes avatar Nov 01 '23 13:11 mgerdes

Good catch on that. If you're able to get some flame graph and figure out what exactly is blocking the main thread, that would be helpful. Is this something that shows up only in development - or production too?

jacobp100 avatar Nov 01 '23 15:11 jacobp100

Good catch on that. If you're able to get some flame graph and figure out what exactly is blocking the main thread, that would be helpful. Is this something that shows up only in development - or production too?

I think the waitForReactLayout in SafeAreaView.kt is what's blocking the main thread. I added these log statements to try to see what's happening

Screenshot 2023-11-01 at 1 18 43 PM

and this was the result:

2023-11-01 13:03:54.865 runOnNativeModulesQueueThread 0
2023-11-01 13:03:54.865 main thread 0
2023-11-01 13:03:54.866 awaitNanos 0
2023-11-01 13:03:55.367 awaitNanos 1
2023-11-01 13:03:55.367 main thread 1
2023-11-01 13:03:55.367 Timed out waiting for layout.
2023-11-01 13:03:55.377 native thread 0
2023-11-01 13:03:55.377 native thread 1

So if I'm looking at this right it seems like the code in runOnNativeModulesQueueThread that's supposed to run on the native thread isn't run while the main thread is blocked and that makes waitForReactLayout block the main thread the entire time.

mgerdes avatar Nov 01 '23 17:11 mgerdes

If you increase the max time nanos to something much longer (say 10s), does it finish before? Or do we have a deadlock?

jacobp100 avatar Nov 01 '23 18:11 jacobp100

If you increase the max time nanos to something much longer (say 10s), does it finish before? Or do we have a deadlock?

Yea, I just upped it to 10s and the whole app freezes for those 10 seconds it's waiting.

This is what I was logging:

2023-11-01 14:22:29.130  runOnNativeModulesQueueThread 0
2023-11-01 14:22:29.130  main thread 0
2023-11-01 14:22:29.130  awaitNanos 0
2023-11-01 14:22:39.131  awaitNanos 1
2023-11-01 14:22:39.131  main thread 1
2023-11-01 14:22:39.131  Timed out waiting for layout.
2023-11-01 14:22:39.140  native thread 0
2023-11-01 14:22:39.140  native thread 1

So yea, it looks like something isn't letting the code on the native thread run until the main thread times out waiting.

mgerdes avatar Nov 01 '23 18:11 mgerdes

What’s your setup? Version of react native? Are you using fabric?

Also could you check the value of getReactContext(this).isOnNativeModulesQueueThread() (at the start of the function - outside the runOnNativeModulesQueueThread)

jacobp100 avatar Nov 01 '23 19:11 jacobp100

We are on react-native 0.72.5. No we are not using fabric.

I printed the value of getReactContext(this).isOnNativeModulesQueueThread at the start of the function - outside of runOnNativeModulesQueueThread, and it returns false.

mgerdes avatar Nov 01 '23 20:11 mgerdes

Try adding a printStackTrace in the runOnNativeModulesQueueThread function in the react native library code and see if there’s something else that runs around that time. It sounds like this and another task are in deadlock

jacobp100 avatar Nov 01 '23 20:11 jacobp100

Here's what the stacktrace looks like in runOnNativeModulesQueueThread

java.lang.Exception: Stack trace
	at java.lang.Thread.dumpStack(Thread.java:1615)
	at com.th3rdwave.safeareacontext.SafeAreaView.waitForReactLayout(SafeAreaView.kt:71)
	at com.th3rdwave.safeareacontext.SafeAreaView.updateInsets(SafeAreaView.kt:52)
	at com.th3rdwave.safeareacontext.SafeAreaView.maybeUpdateInsets(SafeAreaView.kt:113)
	at com.th3rdwave.safeareacontext.SafeAreaView.onAttachedToWindow(SafeAreaView.kt:134)
	at android.view.View.dispatchAttachedToWindow(View.java:21357)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3491)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3498)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3498)
	at android.view.ViewGroup.addViewInner(ViewGroup.java:5291)
	at android.view.ViewGroup.addView(ViewGroup.java:5077)
	at com.facebook.react.views.view.ReactViewGroup.addView(ReactViewGroup.java:516)
	at android.view.ViewGroup.addView(ViewGroup.java:5017)
	at com.facebook.react.uimanager.ViewGroupManager.addView(ViewGroupManager.java:38)
	at com.facebook.react.uimanager.NativeViewHierarchyManager.manageChildren(NativeViewHierarchyManager.java:533)
	at com.swmansion.reanimated.layoutReanimation.ReanimatedNativeHierarchyManager.manageChildren(ReanimatedNativeHierarchyManager.java:300)
	at com.facebook.react.uimanager.UIViewOperationQueue$ManageChildrenOperation.execute(UIViewOperationQueue.java:217)
	at com.facebook.react.uimanager.UIViewOperationQueue$1.run(UIViewOperationQueue.java:915)
	at com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:1026)
	at com.facebook.react.uimanager.UIViewOperationQueue.-$$Nest$mflushPendingBatches(Unknown Source:0)
	at com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:1086)
	at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:29)
	at com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:175)
	at com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame(ChoreographerCompat.java:85)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1229)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
	at android.view.Choreographer.doCallbacks(Choreographer.java:899)
	at android.view.Choreographer.doFrame(Choreographer.java:827)
	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
	at android.os.Handler.handleCallback(Handler.java:942)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loopOnce(Looper.java:201)
	at android.os.Looper.loop(Looper.java:288)
	at android.app.ActivityThread.main(ActivityThread.java:7918)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

mgerdes avatar Nov 01 '23 22:11 mgerdes

Do you have a few traces before and after that?

jacobp100 avatar Nov 01 '23 22:11 jacobp100

I'm not sure what you mean? Before and after it's inside runOnNativeModulesQueueThread? Or still inside of runOnNativeModulesQueueThread?

mgerdes avatar Nov 01 '23 22:11 mgerdes

I think some other functions - presumably outside of this library - will be calling runOnNativeModulesQueueThread before or after this call. If there are, what are they?

jacobp100 avatar Nov 02 '23 09:11 jacobp100

Hi. I got the same issue on iOS. Temporary fixed it using useSafeAreaInsets and paddings instead of SafeAreaView .

smokfyz avatar Nov 02 '23 19:11 smokfyz

facing the same problem after updating to RN 0.72+ including 0.73-alpha(s).. this is in combination with react-native-navigation from wix.

enahum avatar Nov 20 '23 07:11 enahum

Experiencing the same issue after upgrading to RN 0.72 (from 0.68). @jacobp100 is there anything I can do to help you debug this?

danilobuerger avatar Dec 27 '23 22:12 danilobuerger

Somebody needs to look into it. There’s only two people that actively maintain this, and I don’t think either of us will investigate this - so if you want this fixed, do take a look for yourself. We are of course on hand to answer any questions etc.

jacobp100 avatar Dec 28 '23 10:12 jacobp100

So what I figured out is that

uiManager.uiImplementation.dispatchViewUpdates(-1)

scheduled on the native module queue is called after the lock times out. I would assume that this means that waitForReactLayout blocks the execution of anything on the native modules queue.

danilobuerger avatar Dec 28 '23 12:12 danilobuerger

@jacobp100 I found the offending code 🥳

In SafeAreaView we call

uiManager.setViewLocalData(id, localData)

before

waitForReactLayout()

This runs

mUIImplementation.setViewLocalData(tag, data);

on the native modules thread.

This in turn calls

dispatchViewUpdatesIfNeeded() -> dispatchViewUpdates()

Since rn 72 (https://github.com/facebook/react-native/commit/bc766ec7f8b18ddc0ff72a2fff5783eeeff24857) we have

if (getRootViewNum() <= 0) {
  // If there are no RootViews registered, there will be no View updates to dispatch.
  // This is a hack to prevent this from being called when Fabric is used everywhere.
  // This should no longer be necessary in Bridgeless Mode.
  return;
}

guarding the entry and this in turn waits for the main thread where we are already waiting for this thread. Deadlock.

danilobuerger avatar Dec 28 '23 13:12 danilobuerger

🎉 do you know what the fix is?

jacobp100 avatar Dec 28 '23 13:12 jacobp100

Sadly no idea yet. The problem is that waitForReactLayout is called inside NativeViewHierarchyManager.manageChildren (see stack trace above) and that synchronizes on the same object as getRootViewNum so it will always deadlock. Maybe @feiyin0719 or @kelset from the original PR introducing this know more 🤷‍♂️

danilobuerger avatar Dec 28 '23 19:12 danilobuerger

My experience with core dev is they’re too busy to get back to you. If you find a work around though, we here are good at merging and releasing PRs. It sounds like you’re really close!

jacobp100 avatar Dec 28 '23 21:12 jacobp100

https://github.com/th3rdwave/react-native-safe-area-context/blob/f7e7e20b930c1e414ea34ede5d7aa1c406edc36a/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt#L53-L60

Sadly there doesn't seem to be a way to properly dirty a yoga node from java

I'm not certain this is the case - see this:

https://github.com/facebook/react-native/blob/b5e08e80d90b6d03d1f49f0674c01f03ee300c46/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java#L318-L319C14

I think it has to happen in the shadow node, so might need a slight refactor. But maybe this could avoid the deadlock?

jacobp100 avatar Dec 29 '23 12:12 jacobp100

@jacobp100 I found the offending code 🥳

In SafeAreaView we call

uiManager.setViewLocalData(id, localData)

before

waitForReactLayout()

This runs

mUIImplementation.setViewLocalData(tag, data);

on the native modules thread.

This in turn calls

dispatchViewUpdatesIfNeeded() -> dispatchViewUpdates()

Since rn 72 (facebook/react-native@bc766ec) we have

if (getRootViewNum() <= 0) {
  // If there are no RootViews registered, there will be no View updates to dispatch.
  // This is a hack to prevent this from being called when Fabric is used everywhere.
  // This should no longer be necessary in Bridgeless Mode.
  return;
}

guarding the entry and this in turn waits for the main thread where we are already waiting for this thread. Deadlock.

maybe we can move getRootViewNum to UIManager.onBatchComplete to let it run as before

feiyin0719 avatar Dec 29 '23 14:12 feiyin0719