Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/scatterlab/allowed-tarball-diff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,15 @@ ReactAndroid/src/main/java/com/facebook/react/views/text/TextDecorationStyle.kt
# Points the consumer's Gradle build at this fork's Android artifacts. Added file, so the
# gate sees it as a difference from upstream. See android-prebuilt.md.
scripts/android/scatterlab-prebuilt-maven.gradle

# A second SurfaceHandler::start() on a running surface hands ShadowTreeRegistry::add()
# another tree for the same SurfaceId; the registry drops it and `link_.shadowTree` is
# left dangling, so the next MountingCoordinator lookup crashes (iOS: SIGSEGV at 0x8 in
# MountingCoordinator::setMountingOverrideDelegate). -[RCTFabricSurface start] checks
# the status two async hops before start() runs, so racing callers get there twice.
# start() now ignores a surface that is not Registered, and the iOS surface keeps a
# second start out while one is in flight. Upstream
# https://github.com/react/react-native/pull/57404 (open) covers the unregistered case
# on the iOS side only. C++ reaches Android only through this fork's Android prebuilt.
ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp
React/Fabric/Surface/RCTFabricSurface.mm
19 changes: 18 additions & 1 deletion packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import "RCTFabricSurface.h"

#import <atomic>
#import <mutex>

#import <React/RCTAssert.h>
Expand Down Expand Up @@ -43,6 +44,11 @@ @implementation RCTFabricSurface {
// and we need this mutex to prevent races.
std::mutex _surfaceMutex;

// `start` returns before `SurfaceHandler::start()` runs (it hops to the main queue, then to a global one), and
// until then the status still reads `Registered`. This keeps a second `start` in that window from attaching the
// root view and starting the surface again.
std::atomic_bool _startInFlight;

// Can be accessed from the main thread only.
RCTSurfaceView *_Nullable _view;
#if !TARGET_OS_TV
Expand Down Expand Up @@ -95,7 +101,12 @@ - (void)start
{
std::lock_guard<std::mutex> lock(_surfaceMutex);

// Take the flag before reading the status, so a start that finished in between reads as `Running` here.
if (_startInFlight.exchange(true)) {
return;
}
if (_surfaceHandler->getStatus() != SurfaceHandler::Status::Registered) {
_startInFlight = false;
return;
}

Comment thread
kdwkr marked this conversation as resolved.
Expand All @@ -106,9 +117,15 @@ - (void)start
surfaceId:self->_surfaceHandler->getSurfaceId()];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
self->_surfaceHandler->start();
auto status = self->_surfaceHandler->getStatus();
self->_startInFlight = false;
[self _propagateStageChange];

[self->_surfacePresenter setupAnimationDriverWithSurfaceHandler:*self->_surfaceHandler];
// `start()` is a no-op if the surface got unregistered in the meantime (e.g. by an instance teardown), and then
// there is no ShadowTree to take a MountingCoordinator from.
if (status == SurfaceHandler::Status::Running) {
[self->_surfacePresenter setupAnimationDriverWithSurfaceHandler:*self->_surfaceHandler];
}
});
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "SurfaceHandler.h"

#include <cxxreact/TraceSection.h>
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/uimanager/UIManager.h>

Expand Down Expand Up @@ -37,8 +38,17 @@ Status SurfaceHandler::getStatus() const noexcept {
void SurfaceHandler::start() const noexcept {
TraceSection s("SurfaceHandler::start");
std::unique_lock lock(linkMutex_);
react_native_assert(
link_.status == Status::Registered && "Surface must be registered.");
// Callers can race into a second start (-[RCTFabricSurface start] checks the
// status two async hops before this runs). Going on would hand
// ShadowTreeRegistry::add() a second tree for the same SurfaceId, which it
// drops, leaving `link_.shadowTree` dangling; an unregistered surface has no
// `link_.uiManager` at all. So this is a no-op rather than an assert.
if (link_.status != Status::Registered) {
LOG(WARNING)
<< "SurfaceHandler::start ignored for a surface that is not in Registered state, surfaceId = "
<< getSurfaceId() << ", status = " << static_cast<int>(link_.status);
return;
}
react_native_assert(
getLayoutConstraints().layoutDirection != LayoutDirection::Undefined &&
"layoutDirection must be set.");
Expand Down