Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialize CADisplayLink on UI thread #4694

Merged
merged 17 commits into from
Jul 24, 2023
Merged

Initialize CADisplayLink on UI thread #4694

merged 17 commits into from
Jul 24, 2023

Conversation

Latropos
Copy link
Contributor

@Latropos Latropos commented Jul 10, 2023

Summary

Some users have observed following thread lock:

Error log
Thread 0 - com.apple.main-thread - (TH_STATE_WAITING)
0  libsystem_kernel.dylib +0x1c88  ___psynch_mutexwait
1  libsystem_pthread.dylib +0x2110 __pthread_mutex_firstfit_lock_wait
2  libsystem_pthread.dylib +0x9314 __pthread_mutex_firstfit_lock_slow
3  QuartzCore +0xa07cc             CA::Display::DisplayLinkItem::set_preferred_fps_range(CAFrameRateRange, bool, bool)
4  QuartzCore +0x3a918             CA::Display::DisplayLink::update_timer_locked(bool)
5  QuartzCore +0x3a504             CA::Display::DisplayLink::update_timer(bool)
6  QuartzCore +0xbf40c             CA::Display::DisplayLink::callback(_CADisplayTimer*, unsigned long long, unsigned long long, unsigned long long, bool, void*)
7  QuartzCore +0x39410             display_timer_callback(__CFMachPort*, void*, long, void*)
8  CoreFoundation +0x79d50         ___CFMachPortPerform
9  CoreFoundation +0x97144         ___CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
10 CoreFoundation +0x98e34         ___CFRunLoopDoSource1
11 CoreFoundation +0x7a654         ___CFRunLoopRun
12 CoreFoundation +0x7f3e8         _CFRunLoopRunSpecific
13 GraphicsServices +0x1358        _GSEventRunModal
14 UIKitCore +0x39d6e4             -[UIApplication _run]
15 UIKitCore +0x39d348             _UIApplicationMain
16 MyApp +0x720c                 main (AppDelegate.swift:24:7)
17 dyld +0x15de8                   start

Thread 13 - com.facebook.react.JavaScript - (TH_STATE_WAITING)
0  libsystem_kernel.dylib +0x17bc  ___ulock_wait
1  libsystem_platform.dylib +0xfac __os_unfair_lock_lock_slow
2  QuartzCore +0xd09ec             CA::Display::DisplayLinkItem::update_link(__CFRunLoop*)
3  MyAppReactNative +0x205698    -[REANodesManager initWithModule:uiManager:] (REANodesManager.mm:214:3)
4  MyAppReactNative +0x200e30    -[REAModule setBridge:] (REAModule.mm:202:19)
5  Foundation +0x69788             -[NSObject(NSKeyValueCoding) setValue:forKey:]
6  MyAppReactNative +0x2c6aec    -[RCTModuleData setBridgeForInstance] (RCTModuleData.mm:267:7)
7  MyAppReactNative +0x2c6964    -[RCTModuleData setUpInstanceAndBridge:] (RCTModuleData.mm:221:7)
8  MyAppReactNative +0x2c73dc    -[RCTModuleData instance] (RCTModuleData.mm:406:7)
9  MyAppReactNative +0x2cc9f0    facebook::react::invokeInner(RCTBridge*, RCTModuleData*, unsigned int, folly::dynamic const&, int, (anonymous namespace)::SchedulingContext) (RCTNativeModule.mm:196:67)
10 MyAppReactNative +0x2cc820    facebook::react::RCTNativeModule::callSerializableNativeHook(unsigned int, folly::dynamic&&) (RCTNativeModule.mm:143:10)
11 MyAppReactNative +0x3cb198    facebook::react::JSIExecutor::nativeCallSyncHook(facebook::jsi::Value const*, unsigned long) (JSIExecutor.cpp:493:40)
12 MyAppReactNative +0x5e37f0    std::__1::function<facebook::jsi::Value (facebook::jsi::Runtime&, facebook::jsi::Value const&, facebook::jsi::Value const*, unsigned long)>::operator()(facebook::jsi::Runtime&, facebook::jsi::Value const&, facebook::jsi::Value const*, unsigned long) const (function.h:512:16)
13 hermes +0x131d8                 0x131d8 (0x130b0 + 296)
14 hermes +0x2702c                 0x2702c (0x26f94 + 152)
15 hermes +0x4625c                 0x4625c (0x45764 + 2808)
16 hermes +0x45718                 0x45718 (0x45698 + 128)
17 hermes +0x8a640                 0x8a640 (0x89f90 + 1712)
18 hermes +0x658c                  0x658c (0x64ac + 224)
19 hermes +0x63d4                  facebook::hermes::HermesRuntime::evaluateJavaScriptWithSourceMap(std::__1::shared_ptr<facebook::jsi::Buffer const> const&, std::__1::shared_ptr<facebook::jsi::Buffer const> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
20 hermes +0x76f4                  0x76f4 (0x76dc + 24)
21 MyAppReactNative +0x38e19c    facebook::jsi::WithRuntimeDecorator<facebook::react::(anonymous namespace)::ReentrancyCheck, facebook::jsi::Runtime, facebook::jsi::Runtime>::evaluateJavaScript(std::__1::shared_ptr<facebook::jsi::Buffer const> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) (decorator.h:118:20)
22 MyAppReactNative +0x3c9704    facebook::react::JSIExecutor::loadBundle(std::__1::unique_ptr<facebook::react::JSBigString const, std::__1::default_delete<facebook::react::JSBigString const> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >) (JSIExecutor.cpp:157:13)
23 MyAppReactNative +0x35f59c    facebook::react::NativeToJsBridge::loadBundle(std::__1::unique_ptr<facebook::react::RAMBundleRegistry, std::__1::default_delete<facebook::react::RAMBundleRegistry> >, std::__1::unique_ptr<facebook::react::JSBigString const, std::__1::default_delete<facebook::react::JSBigString const> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)::$_1::operator()(facebook::react::JSExecutor*) (NativeToJsBridge.cpp:146:21)
24 MyAppReactNative +0x3606ac    std::__1::__function::__func<facebook::react::NativeToJsBridge::runOnExecutorQueue(std::__1::function<void (facebook::react::JSExecutor*)>)::$_8, std::__1::allocator<facebook::react::NativeToJsBridge::runOnExecutorQueue(std::__1::function<void (facebook::react::JSExecutor*)>)::$_8>, void ()>::operator()() (function.h:512:16)
25 MyAppReactNative +0x2b6950    facebook::react::tryAndReturnError(std::__1::function<void ()> const&) (function.h:512:16)
26 MyAppReactNative +0x2c33e0    facebook::react::RCTMessageThread::tryFunc(std::__1::function<void ()> const&) (RCTMessageThread.mm:69:20)
27 MyAppReactNative +0x2c31f4    ___ZN8facebook5react16RCTMessageThread8runAsyncENSt3__18functionIFvvEEE_block_invoke (function.h:512:16)
28 CoreFoundation +0x436dc         ___CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
29 CoreFoundation +0xaa20c         ___CFRunLoopDoBlocks
30 CoreFoundation +0x7a714         ___CFRunLoopRun
31 CoreFoundation +0x7f3e8         _CFRunLoopRunSpecific
32 MyAppReactNative +0x2ac2cc    +[RCTCxxBridge runRunLoop] (RCTCxxBridge.mm:336:12)
33 Foundation +0x5b540             ___NSThread__start__
34 libsystem_pthread.dylib +0x16b4 __pthread_start

This PR moves initialisation of CADisplayLink into UI thread, which may solve the problem

Test plan

@Latropos Latropos marked this pull request as ready for review July 10, 2023 12:38
@Latropos Latropos requested a review from tomekzaw July 10, 2023 12:38
Copy link
Member

@piaskowyk piaskowyk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also need to do the same think with displayLink from KeyboardEventObserver

ios/REANodesManager.mm Outdated Show resolved Hide resolved
Co-authored-by: Krzysztof Piaskowy <krzysztof.piaskowy@swmansion.com>
@Latropos Latropos changed the title Add "RCTExecuteOnMainQueue" Initialize CADisplayLink on UI thread Jul 10, 2023
@Latropos Latropos requested a review from piaskowyk July 10, 2023 14:34
_displayLink.preferredFramesPerSecond = 120; // will fallback to 60 fps for devices without Pro Motion display
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_displayLink setPaused:true];
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here, with an async init, couldn't it be possible to init this REANodesManager and immediately try to use _displayLink before it's initialized in something like - (void)invalidate or - (void)postOnAnimation:(REAOnAnimationCallback)clb?

@Latropos Latropos requested a review from Caiopia July 11, 2023 10:12
ios/REANodesManager.mm Outdated Show resolved Hide resolved
ios/REANodesManager.mm Outdated Show resolved Hide resolved
ios/REANodesManager.mm Outdated Show resolved Hide resolved
ios/REANodesManager.mm Outdated Show resolved Hide resolved
ios/keyboardObserver/REAKeyboardEventObserver.m Outdated Show resolved Hide resolved
ios/keyboardObserver/REAKeyboardEventObserver.m Outdated Show resolved Hide resolved
ios/keyboardObserver/REAKeyboardEventObserver.m Outdated Show resolved Hide resolved
ios/keyboardObserver/REAKeyboardEventObserver.m Outdated Show resolved Hide resolved
[_displayLink setPaused:true];
__weak __typeof__(self) weakSelf = self;

RCTExecuteOnMainQueue(^void() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like a better approach here would be to update displayLink usages to access it using getter. then in the getter we'd optionally initialize. It is important that the display link is both created and accessed on the UI thread. So we could also add some assertions to verify that. Schedulinng initialization may have its own problems as we don't want to guarantee that the field wouldn't be used before the task gets executed

ios/REANodesManager.mm Outdated Show resolved Hide resolved
Aleksandra Cynk added 2 commits July 19, 2023 12:46
@Latropos Latropos requested a review from tomekzaw July 19, 2023 10:50
if (_displayLink) {
[_displayLink setPaused:true];
}
[[self getDisplayLink] setPaused:true];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[[self getDisplayLink] setPaused:true];
[[self getDisplayLink] setPaused:YES];

@@ -211,19 +216,16 @@ - (instancetype)initWithModule:(REAModule *)reanimatedModule uiManager:(RCTUIMan
_viewRegistry = [_uiManager valueForKey:@"_viewRegistry"];
_shouldFlushUpdateBuffer = false;
}
#endif
[[self getDisplayLink] setPaused:true];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[[self getDisplayLink] setPaused:true];
[[self getDisplayLink] setPaused:YES];

piaskowyk
piaskowyk previously approved these changes Jul 19, 2023
@@ -173,6 +173,18 @@ @implementation REANodesManager {
#endif
}

- (CADisplayLink *)getDisplayLink
{
RCTAssertMainQueue();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the assertion will raise an exception with this approach, since the code is not being dispatched to the main queue. Or am I missing something?

Simulator Screenshot - iPhone 14 Pro - 2023-07-19 at 22 29 24

This assertion will fail when running from a non-main thread.
Screenshot 2023-07-19 at 10 41 37 PM

@piaskowyk piaskowyk dismissed their stale review July 20, 2023 07:23

I can confirm that crash on startup

Copy link
Member

@piaskowyk piaskowyk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Application with Paper architecture crash on startup. We need to fix it before merge.
image

@Latropos Latropos added this pull request to the merge queue Jul 24, 2023
Merged via the queue into main with commit b60dea2 Jul 24, 2023
4 checks passed
@Latropos Latropos deleted the acynk/fix-lock branch July 24, 2023 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants