Summary:
Ensure that on Android, only Scheduler and UIManagerBinder directly retain UIManager. These existing JSI function-retained lambdas will not have owning share_ptr refs to UIManager, just raw pointers.
By the time the VM deallocates all JSI objects and UIManagerBinding goes away, it should be able to deallocate UIManager synchronously when UIManagerBinding is deallocated. The VM should not be executing any of these JSI functions at that time, so this should ensure that the raw pointer access is safe.
This should also provide some extremely small perf wins, and make our memory model easier to reason about, since we're using shared_ptr less often and now `UIManager` is only owned in two places: Scheduler and UIManagerBinding.
So, there are now only two places where UIManager can be deallocated:
1) Hermes is deallocated first, and UIManagerBinding is deallocated before Scheduler. Scheduler holds onto UIManager. Scheduler later is deallocated and frees UIManager; this would cause a crash if it's not completed before Hermes is torn down, but we currently ensure that Scheduler is torn down before Hermes. This is fine but we should document this contract, or make Scheduler not own UIManager.
2) Scheduler is deallocated first, decrementing the shared_ptr to UIManager. Later Hermes is torn down, which deallocates UIManagerBinding via the JSI pointer table, which synchronously deallocates UIManager before Hermes has shut down. This is the desired outcome.
Changelog:
[[Internal]]
Reviewed By: shergin
Differential Revision: D17872586
fbshipit-source-id: cbb25b5cd025f5f8597dc7a66073fe64edc57aa8
Summary: Storing a Bridge introducing an retain cycle, so we need to store that weakly.
Reviewed By: sammy-SC
Differential Revision: D17773698
fbshipit-source-id: 824a83a6086f9ae6efb7c458d833931954c55643
Summary: We need to have a weak semantic for the ManagedObjectWrapper, this diff implements that.
Reviewed By: sammy-SC
Differential Revision: D17773699
fbshipit-source-id: 978fa62191361c3b2c0e46c423240831ed1dd233
Summary: In D16805827, I moved `RCTImageLoader`, `RCTImageStoreManager`, and `RCTImageEditingManager` to `CoreModules`. This was necessary to turn `RCTImageLoader` into a TurboModule. However, after D17671288 landed, it's no longer necessary to have OSS NativeModules in `CoreModules`. Therefore, I'm moving these NativeModules back to `RCTImage`.
Reviewed By: PeteTheHeat
Differential Revision: D17720575
fbshipit-source-id: 44b07cfa07cbb2b87254132810f86974edc7edab
Summary:
`MountingCoordinator::waitForTransaction()` allows waiting for a new coming transaction synchronously, as efficient as waiting for a mutex.
This feature can be used to implement more high-level synchronous rendering APIs.
Reviewed By: mdvacca
Differential Revision: D17629425
fbshipit-source-id: acd91b941e4d6d43bc4518f332a1604e14506be9
Summary: We need commands to work with `LegacyViewManagerInterop`. We will need to rethink this once Fabric has command-execution pipeline in place.
Reviewed By: shergin
Differential Revision: D17787294
fbshipit-source-id: a6b3dbfae41f04e7e7f5bafb1f7b4ad0de0eedc3
Summary:
LegacyViewManagerInterop layer can now handle events as well.
We expose `eventInterceptor` from `NSComponentData`, that way we can hook into event dispatching that happens within `NSComponentData`.
Coordinator has a map of `UIView -> reactTag` which it uses to figure out to which component view to forward the event.
New `LegacyViewManagerInteropViewEventEmitter` exposes APIs to send `folly::dynamic` to javascript.
Reviewed By: shergin
Differential Revision: D17765834
fbshipit-source-id: 25e75e445b32c69ec9ab0189c4ab7fba5f8c7b5d
Summary:
ComponentDescriptor owns a coordinator which is initialised with ViewManager that it represents.
Coordinator is passed to ComponentView through state and ComponentView asks the coordinator for view.
Later, ComponentView will ask coordinator to configure view with specified props.
Reviewed By: shergin
Differential Revision: D17670444
fbshipit-source-id: e920c5c4f033884c4b539ce711286f71c887d896
Summary:
# LegacyViewManagerInterop is born
LegacyViewManagerInterop is a component that should make it possible for legacy components to work in Fabric, new renderer.
This is just a first stage that prints keys of props to screen together with component name.
Reviewed By: shergin
Differential Revision: D17552827
fbshipit-source-id: c3e062f413727729e6a9b683c60f59f0292cc63b
Summary:
We use `ViewManager.onMeasure` to perform measurements of Android views and pass the measured size back to Yoga. For Android in order to the report correct dimensions of a View, this View must be created using a Context that has a theme associated with it. Before, `onMeasure` only had ReactApplicationContext passed as the first parameter and ReactSwitch, for example, could not be measured correctly (because it uses the size of the thumb drawable, which is extracted from the current theme). This diff adds surfaceId as the first parameter of `FabricUIManager.measure`, so that we can retrieve ThemedReactContext and pass it to `ViewManager.onMeasure`.
The size of the Switch component is still incorrect, but at least the size reported back to Yoga is the same as in Paper. So there is more investigation necessary why this happens in Fabric. I will investigate and publish another diff with the fix.
Reviewed By: JoshuaGross, shergin
Differential Revision: D17625959
fbshipit-source-id: 48197a61240fb13042bef3e9f5d681acacc702fb
Summary:
In this diff we integrate the Switch component on Android in Fabric. Since the component has a custom measure function, we need to write some C++ to call the measure method in Java.
The component isn't fully functional yet (setNativeProps isn't supported in Fabric) and has some problems with measuring itself. I will fix the component in the next diffs in this stack.
Reviewed By: JoshuaGross
Differential Revision: D17571258
fbshipit-source-id: be4e201495b9b197ddec44ee3484357bfb6225a8
Summary: Until ComponentDescriptorProviderRequest is not fully implemented we need to disable the assert because it enforces constraints that we don't satisfy yet.
Reviewed By: JoshuaGross
Differential Revision: D17677856
fbshipit-source-id: bdf96a04a16981f1c639f646f254e8f3dd799419
Summary: This diff implements the core functionality of "reactive component registration". With a new API now we have a public API that allows registering a component by request on-demand. That can be helpful in some environments where loading of all components is undesirable or impossible due to generic app design constrains.
Reviewed By: mdvacca
Differential Revision: D17211914
fbshipit-source-id: 5a957a4f6541420d70952856599b827ceec63e3b
Summary:
Currently, the same ComponentDescriptor class cannot be registered as a responder for components with different names. However, we have marginal cases where we really need it. The examples are `UnimplementedView` or possible universal interop with the classic RN or any other UI framework.
This change adds a special optional argument to ComponentDescript constructor that allows implementing this functionality.
Reviewed By: fkgozali
Differential Revision: D17211915
fbshipit-source-id: 18f59e09fe06b875a8e8975b7b2ab423489238bb
Summary:
This part of the codebase is very perf sensitive and designed to work without exceptions enabled.
Most of the method was `noexcept` all the time, but some of those missing that by mistake.
Reviewed By: sammy-SC
Differential Revision: D17629426
fbshipit-source-id: b311e4b7eff8e2b7cf29518288480d3a812dda44
Summary:
`addObserver` and `removeObserver` now accepts const references instead of pointers which indicates the intent (non-nullability and non-owning) clearly. The delegate methods are also marked as `const` to designate the possible concurrent execution (`const` means "thread-safe" here).
All changes are pure syntactical, nothing really changes (besides the fact overall code quality and redability).
Reviewed By: JoshuaGross
Differential Revision: D17535395
fbshipit-source-id: b0c6c872d44fee22e38fd067ccd3320e7231c94a
Summary:
# A race condition
The practical thing of this diff is fixing a data race.
Imagine a case where a thread A calls `addObserver` and thread B calls `nativeImageResponseFailed` at the same time.
Thread A might read `status_` exclusively and store result as a local variable and then go sleep.
Then thread B starts and finishes `nativeImageResponseFailed`, it writes `status_` and notifies all observers.
Then thread B wakes up. It adds an observer to a collection of observers and finishes.
As a result, the observer from `addObserver` will never be called.
To fix this, we changed a logic a bit to lock only once per method. During the lock, we read and/or write to storage and then perform side-effects.
In contrast, previously we often locked only around the access of a particular instance variable (several times per method).
The challenge here is that idiomatic/fancy to C++/STL ways to lock mutexes don't work in our case.
# C++ idioms and readability, multiple locks for the same transaction
STL has tools to avoid calling `lock` and `unlock` methods manually (std::lock_guard<> and lamdas). Unfortunately, using that in our use case is quite problematic. That's probably possible but will lead to much less readable code and some copy-pasta in `addObserver`.
Therefore we replaced using `std::lock_guard` with simple `lock` and `unlock` where using `std::lock_guard` was problematic.
# Why we changed `shared_mutex` to a normal one?
After consolidating the locks we found that we have an only case where we can use shared lock (in `nativeImageResponseProgress`). Calling this method in real life is not concurrent, so it makes sense to replace a shared lock with a more simple and performant regular one.
Reviewed By: sammy-SC
Differential Revision: D17368739
fbshipit-source-id: 61d66fb737d8c2dc73001a80a31edaa59a16d886
Summary:
This diff contains some small improvements in `ImageResponseObserverCoordinator` which are pretty minor:
* Now we use `small_vector` instead of a normal one. In the vast majority of cases, the coordinator has only one observer, so having `small_vector` with default size `1` saves us a memory allocation (which is a dozen of allocations for a screen, not bad).
* Empty constructor and destructor were removed.
* Unnecessary copying of ImageResponse was removed. ImageResponse is a practically a shared_pointer, it has value semantic and does not need to be copied. We don't need to acquire mutex to access that.
Reviewed By: sammy-SC
Differential Revision: D17368740
fbshipit-source-id: 828e27a72b9c8ac0063c5fbda00f83ddb255309c
Summary:
If you try to use ART components with fabric, their transform is causes crash (stack trace P108110438). You can see this crash if you use RCTVideo component in fabric and tap video to reveal slider.
The cause of crash is mismatch of formats between what is expected for transform and what is being sent.
In this diff we add a check to see whether the configuration is of expected type.
Reviewed By: shergin
Differential Revision: D17181838
fbshipit-source-id: c4f3c920281a2e7f58ff0ffe1d0ec2af8249a16c
Summary:
This diff changes the value of `EmptyLayoutMetrics` to make it as unusual and useless as possible. This helps when we need to compare sub-values of `LayoutMetrics` to apply only changed ones.
For example, let say we need to make a transition between two `LayoutMetrics` values. Let's say the first (the source) one equals `EmptyLayoutMetrics`. In order to apply only changed part, we need to compare individual sub-values, like `frame` or `displayType`. Before this change, the default value of `borderWidth` was `{0, 0, 0, 0}`. So in case if the second (the destination) value is also `{0, 0, 0, 0}`, the operation will be skipped.
This is undesirable because all new values have to be applied anyway because `EmptyLayoutMetrics` designates that the actual previous values are unknowns.
This fixes some visual issues on iOS caused by this issue because recycled views sometimes have non-default layout values where the transition from `EmptyLayoutMetrics` to some arbitrary value skips some sub-values.
Reviewed By: JoshuaGross
Differential Revision: D17312176
fbshipit-source-id: 7f311baea202ec2662ca87be0ae0ae6c6dd42712
Summary:
Remove cxx from iOS project, because we have the corresponding implementation in iOS. Otherwise, it would break the logical handling of iOS part.
## Changelog
[iOS] [Fixed] - Remove cxx from iOS project
Pull Request resolved: https://github.com/facebook/react-native/pull/25392
Test Plan: Fabric enabled Tester can work like loading image.
Reviewed By: mdvacca
Differential Revision: D17285463
Pulled By: shergin
fbshipit-source-id: b414406578dcce51f3b54fd06941225efc560e7f
Summary:
The code is moved from the destructor a separate method that we now call in the Scheduler::stopSurface.
That makes the code more readable and resilient to possible races and ownership-related changes.
Reviewed By: JoshuaGross
Differential Revision: D17272294
fbshipit-source-id: 948d76d074577beb3dda6defdf09261b5c8abb98
Summary:
MountingCoordinator is a borderline between Core and Mounting. Some of Core design constraints are impossible/impractical to enforce on Mounting layer, so we have to handle all of those cases in `MountingCoordinator`.
One of the constrains is that all ShadowNodes implicitly depend on associated ComponentDescriptor instances without retaining them (retaining is expensive and creates a retain cycle).
The problem is that the Mounting layer can call `MountingCoordinator::pull()` at any moment (even after the whole Core is already destroyed). To prevent this, the owner of a `MountingCoordinator` on the Core side calls `revoke()` right before being deallocated (right before the moment the owner cannot guarantee the constraint).
Reviewed By: JoshuaGross
Differential Revision: D17272295
fbshipit-source-id: ba8b02eab8f84cce68aa65c1ad36950cd2498049
Summary: Adds support for dynamic value that are `std::vector<std::vector<RawValue>>`
Reviewed By: shergin, mdvacca
Differential Revision: D16936932
fbshipit-source-id: fc8d087f9705af9da56ccc1bd9a0537b2bfeb50d
Summary: We used this mechanism at the very early stages of Fabric. Now we do component registration differently.
Reviewed By: JoshuaGross, mdvacca
Differential Revision: D17200952
fbshipit-source-id: 16ff9d6569714a24e2a8d58dc6e60ad4d7bc1e16
Summary: This allows to implement `findNodeHandle` in Fabric world (temporary).
Reviewed By: zackargyle, JoshuaGross, mdvacca
Differential Revision: D17175953
fbshipit-source-id: c88bd1c58608450812799d4ecb4a6bf2c027c5f3
Summary: The mehtod implements the core functionality of finding a shadow node by given tag.
Reviewed By: zackargyle, JoshuaGross, mdvacca
Differential Revision: D17175955
fbshipit-source-id: 5cfbb80b2d4e54a33ca9c20a21988df4de54c800
Summary: The new methods provides a way to iterate over all registered shadow trees.
Reviewed By: zackargyle, JoshuaGross, mdvacca
Differential Revision: D17175954
fbshipit-source-id: e4e6ee5a0e0a4e4e36b99d546f8724b99559c8c2
Summary:
We don't need to have those methods virtual anymore.
Nowadays we have a pointer to a `ComponentDescriptor` in `ShadowNode`, it's simpler to get that value from there.
That's also more code-size friendly because we now can remove those methods from a template.
Reviewed By: sammy-SC
Differential Revision: D17142173
fbshipit-source-id: 9f54fbe6c1ebad3ed49843ed75a01ca5ee48c34f
Summary: We merge `events` module into `core` long time ago, so we don't need this anymore.
Reviewed By: mdvacca
Differential Revision: D17142174
fbshipit-source-id: 6e63f7f22dc3d65ed2f9cc3bd4f4776404dfe788
Summary:
The type is wrong in the constructor.
build-break
Differential Revision: D17145039
fbshipit-source-id: f6b80e38c05e60f04d029aa34baa0c55c237a39a
Summary: Support existing, backwards-compatible AndroidTextInput component for minimal support of TextInput on Android.
Reviewed By: shergin, mdvacca
Differential Revision: D17086758
fbshipit-source-id: 25726f22229e0d5dfe96eb36b386a5317601283d
Summary:
The purpose of `EventBeat` is handling an asynchronous callback to itself which is being delivered on some different thread. That brings a challenge of ensuring that the `EventBeat` object stays valid during the timeframe of callback execution. The concept of Owner helps with that.
The owner is a shared pointer that retains (probably indirectly) the `EventBeat` object. To ensure the correctness of the call, `EventBeat` retains the owner (practically creating a retain cycle) during executing the callback. In case if the pointer to the owner already null, `EventBeat` skips executing the callback.
It's impossible to retain itself directly or refer to the shared pointer to itself from a constructor. `OwnerBox` is designed to work around this issue; it allows to store the pointer later, right after the creation of some other object that owns an `EventBeat`.
Reviewed By: JoshuaGross
Differential Revision: D17128549
fbshipit-source-id: 7ed34fd865430975157fd362f51c4a3d64214430
Summary:
Seems it's more logical and safe to store ShadowTreeRegistry in UIManager than in Scheduler. We also probably will move some functionality dealing with the registry to UIManager.
But most importantly we need it to manage the ownership properly. UIManager might overlive Scheduler and still get a call to process a shadow tree. In the current configuration, it causes a crash because the registry owns by Scheduler. Moving that to UIManager solves that.
Reviewed By: JoshuaGross
Differential Revision: D17128550
fbshipit-source-id: e6735acaa11f2ed82ca17f18a45e389d79aa1a08
Summary:
This diff changes the way how `UIManager`, `UIManagerBindging`, `Scheduler` and `EventDisaptcher` refer to each other which should help with stability and reliability.
Here is the node that describes the details:
# Retaining dilemma
# Players
We have many logical of moving pieces but most of them can be abstracted by following high-level components.
* **Scheduler**
`Scheduler` is the main representation of running React Native infrastructure. Creation of it means the creation of all React Native C++ subsystems (excluding RuntimeExecutor) and destruction of that means the destruction of all dependent parts. Both processes must be thread-safe.
* **UIManager**
UIManager is a module that contains the most high-level logic of managing shadow trees. All React Renderer calls are practically implemented there.
* **UIManagerBinding**
UIManagerBinding is a representation (aka `HostObject`) of UIManager in the JavaScript world.
* **EventDispatcher**
EventDispatcher is a class that implements all logic related to dispatching events: from calling event on any thread anywhere to executing a particular JavaScript handler responsible for handling that event.
Instances of those classes have complex relationships in terms of owning each other, order of creation and destruction. The configuration of these relationships is dictated by a set of constraints that those classes need to satisfy to be constructed, accessed, and destructed in a hostile multithreaded environment. Messing with that can cause deadlocks, random crashes, suboptimal performance or memory leaks. Make sure you consider all constraints and requirements before changing that.
# Goal
We need to have a safe and reliable way to construct and destroy those objects (on any thread, in any random moment). Keep in mind that all of those objects are being accessed from random threads and have random states in any particular moment. Switching threads happens all the time, so having some state in one place does not guarantee any state in other places.
# Caveats
Let's discuss all concrete constrains that the moving pieces have to satisfy.
* **UIManagerBinding is a HostObject**
Practically that means:
1. It must be constructed "on JavaScript thread" (with exclusive access to JavaScript VM);
2. It must not be retained by other parts of the system because overliving the VM will cause a crash.
3. It can be destructed on any thread (VM does not give any guarantees here). The particular configuration guarantees that the destruction cannot be run concurrently with any JS execution though (because we never clear the reference to the host object from JavaScript side).
* **UIManager needs to be connected with UIManagerBinding and vice-versa**
Those to modules call each other to perform some UI updates or deliver events.
* **Scheduler can be deallocated on any thread at any time**
Timing and thread are up to the application side. The Scheduler must be resilient to that.
* **EventDispatcher can call UIManager at any time**
Luckily, that happens only on JavaScript thread.
* **Using weak pointers cames at a cost**
`std::weak_ptr` is a concept for managing the non-owning relationships in a safe manner. Dereferencing such pointers cames at a cost (additional object construction and atomic counters bumps). So, we should use that carefully; we cannot use shared and week pointers everywhere and assume that will work magically.
# How does this blow up?
Without describing the current configuration, here are a variety of cases that we currently observe.
1. `Scheduler` was deallocated and destroyed UIManager but VM is still running. The VM calls the UIManagerBinding, UIManagerBinding calls a method on already deallocated UIManager. Boom.
2. VM is being deallocated and deletes all host object as part of this process. Some UI event is sill in flight on some thread. The event retains UIManagerBinding via UIManager. VM cannot destroy UIManagerBinding because it's being retained. Boom.
3. VM was deallocated. `Scheduler` was deallocated. But some native state update is still in flight. It retains EventDispatcher and eventually trying to access some shadow tree that was retained by Scheduler and already dead. Boom.
That's pretty much routine endless nightmare of any low-level framework. Luckily, the good proper decisions (and iterating on that!) can solve that.
# Proposed configuration
The configuration is based on those ideas:
1. Never retain `UIManagerBinging`.
2. Never recreate `UIManagerBinging`. Create once and load lazily from JS environment on demand.
3. Consider UIManager as an object with shared ownership between JS and native. That object must be able to overlive native infra or JS VM.
4. Use EventDispatcher as a single weak representation of the JavaScript world; Never retain it strongly except by the Scheduler.
5. `UIManagerBinging` and `UIManager` can be attached or detached. `UIManagerBinging` retains `UIManager`, `UIManager` does not retain `UIManagerBinging` back. Destroying `UIManagerBinging` nulls the raw pointer to that from `UIManager`.
6. All calls from native to JavaScript can validate the pointer from `UIManager` to `UIManagerBinging` to check that the call is possible. All that calls happen on JavaScript thread.
## Stages
* **Creation process**
Creation Scheduler creates `UIManager` and scheduler asynchronous call to JavaScript to create `UIManagerBinding` and attach them. At the same time `Scheduler` creates `EventDispatcher` and makes it retains `UIManager`.
* **JavaScript-to-native invocation**
`UIManagerBinding` has a shared pointer to `UIManager` and can cheaply and safely verify that the pointer is not nullptr. Any mutation of this pointer happens on the JavaScript thread or effectively on VM destruction (non-concurrently).
* **Native-to-JavaScript invocation**
The invocation starts from retaining `EventDispatcher` (converting a weak pointer to strong one), that retains `UIManager`. Later, on the JavaScript thread, `UIManager` checks the raw pointer to `UIManagerBinding` to verify that the call can be performed safely.
# Easy ways to break the fragile balance
- Never retain `EventDispatcher` as a shared pointer. That causes a leak of UIManager and associated resources.
- Access a shared pointer to `UIManager` by value only. The simple way to break that is to specify `[=]` capture block for a lambda and access an instance variable pointing to a `UIManager` (that does not retain the pointer; make a copy on the stack and copy that to the lambda).
Reviewed By: JoshuaGross
Differential Revision: D17120333
fbshipit-source-id: 83138657683e91ceb2f48f18f30e745199c83e82
Summary: We don't use it anymore. (And that was debug only concept.)
Reviewed By: sammy-SC
Differential Revision: D17115538
fbshipit-source-id: 20aac5457e37666cbf9ca9f62cdfca411026c219
Summary: We don't use it anymore.
Reviewed By: JoshuaGross
Differential Revision: D17115539
fbshipit-source-id: e1fa5cc023cd27e53307aa0ea8eae0bad5413be3
Summary: Storing a strong shared pointer to `ComponentDescriptor` can cause a memory leak. Therefore we enforce all call sides and params to be weak pointers. The only Scheduler preserves a retaining pointer to it (to prevent preliminary deallocation).
Reviewed By: sammy-SC
Differential Revision: D17115540
fbshipit-source-id: fdea7d19f742ff04d5ba5470dd9748a5b226aa7c
Summary: That was always part of design, that's okay to get a newer state from any other state. We will need it in the future diffs.
Reviewed By: sammy-SC
Differential Revision: D17053429
fbshipit-source-id: 2174e7d6e3a1ed231f7f6e238d216d0b09ec8797