-
Notifications
You must be signed in to change notification settings - Fork 577
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
RJS-2852: Adding a CallInvoker
-based scheduler for Core on React Native
#6791
Conversation
m_js_call_invoker->invokeAsync( | ||
// Using low priority to avoid blocking rendering | ||
facebook::react::SchedulerPriority::LowPriority, | ||
// Wrapping the func in a shared_ptr to ensure it outlives the invocation | ||
[func = std::make_shared<util::UniqueFunction<void()>>(std::move(func))] { | ||
(*func)(); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[ptr = func.release())] { util::UniqueFunction<void()>(ptr)(); }
LowPriority seems suspect to me. We do want it to be below user input, but processing refreshes promptly is pretty important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the available values https://github.com/facebook/react-native/blob/8d0cbbf0e6435e0a29d1d5d218b93c9eef1a2dcd/packages/react-native/ReactCommon/callinvoker/ReactCommon/SchedulerPriority.h#L14-L20
enum class SchedulerPriority : int {
ImmediatePriority = 1,
UserBlockingPriority = 2,
NormalPriority = 3,
LowPriority = 4,
IdlePriority = 5,
};
Unfortunately not greatly documented 🤔
NormalPriority
might be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glancing at the implementation, it looks like it always runs the highest priority waiting task unless a lower priority task has hit a timeout (250ms for UserBlocking, 5s for Normal, 10s for Low). If there's some specific Normal priority thing that we know we need to not interrupt then using Low makes sense, but otherwise I would default to Normal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[ptr = func.release())] { util::UniqueFunction<void()>(ptr)(); }
I try to refrain from c-style castings 🙈 It seems to work, but what ensure the original function owned by func
ever gets destructed?
The docs for release
says:
If not null, the returned pointer must be used at a later point to construct a new UniqueFunction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no casts involved there. That's constructing a new UniqueFunction using the impl pointer (which takes ownership of the pointer) and then immediately invoking it. The function is then destroyed at the end of the statement as it's an unnamed temporary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how I misread that as a cast 🙈 What about the case where the lambda is dropped and never called, that would leak the function, right? (since no new UniqueFunction
takes ownership) .. although very unlikely. I like that your suggestion doesn't need to perform a heap allocation - and I've pushed an update to incorporate it 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This relies on CallInvoker guaranteeing that it'll invoke the function it's given exactly once; more than once and it's a use-after-free and zero times is a memory leak. This is generally a very safe thing to rely on as basically everything gets way more complicated otherwise.
std::shared_ptr<Scheduler> scheduler_{}; | ||
|
||
|
||
class ReactScheduler : public realm::util::Scheduler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very minor, but this type and the global variable should be in an anonymous namespace so that the symbols are not exported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a better solution that the old workaround.
Please include an entry in CHANGELOG.md
- maybe under "Internal" if you don't consider it a bug fix.
@@ -79,7 +79,7 @@ - (void)dealloc { | |||
// See https://github.com/facebook/react-native/pull/43396#issuecomment-2178586017 for context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove the comments?
@@ -79,7 +79,7 @@ - (void)dealloc { | |||
// See https://github.com/facebook/react-native/pull/43396#issuecomment-2178586017 for context | |||
// If it was, we could use the enablement of "microtasks" to avoid the overhead of calling the invokeAsync on every call from C++ into JS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove the comments?
".wireit", | ||
"packages/realm/.wireit", | ||
"integration-tests/environments/react-native-test-app/.wireit" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got annoyed by these being indexed by watchman.
CallInvoker
-based scheduler for Core on React NativeCallInvoker
-based scheduler for Core on React Native
What, How & Why?
This implements a
realm::util::Scheduler
for the React Native platform through the React Native CallInvoker API.It's my current understanding that all active implementations of this API that a React Native app will construct, utilize the RuntimeSchedulerCallInvoker which in turn drop into either RuntimeScheduler_Legacy or RuntimeScheduler_Modern, both of which have invariants that we want when Core schedules calling of listeners:
JSError
exceptions viahandleJSError
(effectively solving Refactor throwing uncaught exceptions from callbacks dispatched onto the event loop from C++ #6772 on React Native)This fixes #2655 and #3571 and #6772 on React Native (iOS and Android) and replace the workaround introduced with #4389, #4725 and 03c30e7.