react-native-reanimated: Deadlock in [REANodesManager performOperations]

Description

performOperations blocks the main queue while waiting for the UIManager queue to do work. It’s possible that the UIManager queue is already blocked waiting to perform work on the main queue (See somewhat common usage of RCTUnsafeExecuteOnMainQueueSync). This can result in deadlock like the following:

Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x00000001b82e7540 semaphore_wait_trap + 8
1   libdispatch.dylib             	0x00000001809ffc00 _dispatch_sema4_wait + 28 (lock.c:139)
2   libdispatch.dylib             	0x0000000180a002b8 _dispatch_semaphore_wait_slow + 132 (semaphore.c:132)
3   MyApp                        	0x0000000104795c64 -[REANodesManager performOperations] + 284 (REANodesManager.m:261)
4   MyApp                        	0x0000000104795aa4 -[REANodesManager onAnimationFrame:] + 452 (REANodesManager.m:209)
5   QuartzCore                    	0x0000000184a5e378 CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 756 (CADisplay.mm:4177)
6   QuartzCore                    	0x0000000184a64fac display_timer_callback(__CFMachPort*, void*, long, void*) + 372 (CADisplayTimer.cpp:219)
7   CoreFoundation                	0x0000000180cff440 __CFMachPortPerform + 176 (CFMachPort.c:549)
8   CoreFoundation                	0x0000000180d427d4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 60 (CFRunLoop.c:1996)
9   CoreFoundation                	0x0000000180d45fe0 __CFRunLoopDoSource1 + 596 (CFRunLoop.c:2136)
10  CoreFoundation                	0x0000000180cffebc __CFRunLoopRun + 2380 (CFRunLoop.c:3172)
11  CoreFoundation                	0x0000000180d13468 CFRunLoopRunSpecific + 600 (CFRunLoop.c:3268)
12  GraphicsServices              	0x000000019c8b738c GSEventRunModal + 164 (GSEvent.c:2200)
13  UIKitCore                     	0x00000001836b65d0 -[UIApplication _run] + 1100 (UIApplication.m:3493)
14  UIKitCore                     	0x0000000183434f74 UIApplicationMain + 364 (UIApplication.m:5047)
15  MyApp                        	0x000000010421bf2c main + 68 (AnalyticsKey.swift:29)
16  dyld                          	0x0000000105e9daa4 start + 520 (dyldMain.cpp:879)
Thread 12:
0   libsystem_kernel.dylib        	0x00000001b82e81a4 __ulock_wait + 8
1   libdispatch.dylib             	0x0000000180a00064 _dlock_wait + 56 (lock.c:326)
2   libdispatch.dylib             	0x00000001809ffe08 _dispatch_thread_event_wait_slow + 56 (lock.c:366)
3   libdispatch.dylib             	0x0000000180a0ebac __DISPATCH_WAIT_FOR_QUEUE__ + 364 (lock.h:330)
4   libdispatch.dylib             	0x0000000180a0e754 _dispatch_sync_f_slow + 144 (queue.c:1762)
5   MyApp                        	0x0000000104849324 RCTUnsafeExecuteOnMainQueueSync + 120 (RCTUtils.m:277)
6   libdispatch.dylib             	0x00000001809ff670 _dispatch_client_callout + 20 (object.m:560)
7   libdispatch.dylib             	0x0000000180a00f18 _dispatch_once_callout + 32 (once.c:52)
8   MyApp                        	0x00000001048494e4 dispatch_once + 20 (once.h:84)
9   MyApp                        	0x00000001048494e4 RCTScreenSize + 68 (RCTUtils.m:343)
10  MyApp                        	0x0000000104815898 -[RCTModalHostShadowView insertReactSubview:atIndex:] + 124 (RCTModalHostViewManager.m:42)
11  MyApp                        	0x0000000104842ad0 RCTSetChildren + 280 (RCTUIManager.m:884)
12  MyApp                        	0x00000001048428e8 -[RCTUIManager setChildren:reactTags:] + 68 (RCTUIManager.m:865)
13  CoreFoundation                	0x0000000180d163a4 __invoking___ + 148
14  CoreFoundation                	0x0000000180d33b74 -[NSInvocation invoke] + 468 (NSForwarding.m:3378)
15  CoreFoundation                	0x0000000180d6a9d4 -[NSInvocation invokeWithTarget:] + 80 (NSForwarding.m:3475)
16  MyApp                        	0x000000010481b928 -[RCTModuleMethod invokeWithBridge:module:arguments:] + 460 (RCTModuleMethod.mm:584)
17  MyApp                        	0x000000010481df2c facebook::react::invokeInner(RCTBridge*, RCTModuleData*, unsigned int, folly::dynamic const&, int, (anonymous namespace)::SchedulingContext) + 540 (RCTNativeModule.mm:183)
18  MyApp                        	0x000000010481db5c operator() + 68 (RCTNativeModule.mm:104)
19  MyApp                        	0x000000010481db5c invocation function for block in facebook::react::RCTNativeModule::invoke(unsigned int, folly::dynamic&&, int) + 112 (RCTNativeModule.mm:95)
20  libdispatch.dylib             	0x00000001809fd924 _dispatch_call_block_and_release + 32 (init.c:1517)
21  libdispatch.dylib             	0x00000001809ff670 _dispatch_client_callout + 20 (object.m:560)
22  libdispatch.dylib             	0x0000000180a06df4 _dispatch_lane_serial_drain + 672 (inline_internal.h:2601)
23  libdispatch.dylib             	0x0000000180a07968 _dispatch_lane_invoke + 392 (queue.c:3937)
24  libdispatch.dylib             	0x0000000180a121b8 _dispatch_workloop_worker_thread + 656 (queue.c:6727)
25  libsystem_pthread.dylib       	0x00000001f181b0f4 _pthread_wqthread + 288 (pthread.c:2599)
26  libsystem_pthread.dylib       	0x00000001f181ae94 start_wqthread + 8

There is some documentation in RCTUIManagerUtils.h on the threading model of this system. There is a noteworthy piece here:

This solution assumes that the code running on UIManager queue will never explicitly block the Main queue via calling RCTUnsafeExecuteOnMainQueueSync. Otherwise, it can cause a deadlock.

What is happening in performOperations is effectively a custom implementation of RCTUnsafeExecuteOnMainQueueSync and so we get deadlock.

This logic was introduced with https://github.com/software-mansion/react-native-reanimated/pull/1215, I don’t fully understand the issue this was fixing or why this needed to be synchronously waiting, but there atleast is a connected issue with some context.

This pull request https://github.com/software-mansion/react-native-reanimated/pull/3082 could “fix” the issue by breaking the deadlock, but I’m thinking more could be improved here instead of just timing out the deadlock. It’s also not clear to me what happens when this runs asynchronously instead of synchronously.

Potentially related: https://github.com/software-mansion/react-native-reanimated/issues/2938, https://github.com/software-mansion/react-native-reanimated/issues/2327 This same logic is also on Android, which is likely the root cause of https://github.com/software-mansion/react-native-reanimated/issues/3062 and https://github.com/software-mansion/react-native-reanimated/issues/2251

Expected behavior

Never deadlock. Generally best practice is to never block the main thread.

Actual behavior & steps to reproduce

performOperations blocks the main queue waiting on the UIManager queue. It’s possible that at this same time the UIManager queue is already blocked trying to perform work on the main queue.

Snack or minimal code example

I don’t have anything other than stacktraces showing the deadlock.

Package versions

name version
react-native 0.66.4
react-native-reanimated 2.4.1 (with https://github.com/software-mansion/react-native-reanimated/pull/2681 applied)
NodeJS 16.14.2
Xcode 13.3.1

Affected platforms

  • Android (maybe)
  • iOS

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 29
  • Comments: 34 (2 by maintainers)

Most upvoted comments

Is anyone reviewing cltnschlosser’s PR? Or fixing the problem in some other way?

I can observe this issue using in the latest version (2.8.0) as well.

I have the same issue.

After merging in this PR locally, we saw a noticeable drop in iOS native app crashes over the last 90 days: Screen Shot 2022-08-04 at 10 35 03 AM

same here

same error w/ pretty similar stack trace, can only see this behaviour on iOS though. "react-native-reanimated": "^2.5.0"

“react-native-reanimated”: “^3.1.0”,

same here: 2023-05-10 02:11:32.575475+0200 baby_music[39315:2687260] *** Assertion failure in -REANodesManager uiManager:performMountingWithBlock:, /Users/simo97/Desktop/baby_music/node_modules/react-native-reanimated/ios/REANodesManager.mm:246 2023-05-10 02:11:32.576152+0200 baby_music[39315:2687260] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Mouting block is expected to not be set’ *** First throw call stack: (0x1a6776d94 0x19f82c3d0 0x1a0f21808 0x102807534 0x1029bcd94 0x1029b7bcc 0x1029b7928 0x1029b76a4 0x102806a6c 0x102807c18 0x1029bd5c4 0x105f50520 0x105f52038 0x105f5a0b0 0x105f5adf4 0x105f67c74 0x206103ddc 0x206103b7c) (lldb)

Also seeing this. iPhone7 and iPad mini. I’m still on react-native-reanimated 2.3.3 though.

Facing same issue “react-native-reanimated”: “2.17.0”

anybody here some thoughts on a workaround? I am still stuck and don’t know how to resolve the matter as it is the navigation library that causes it. 😕