Repository navigation
Conversation
|
@Nezz I reproduced the JS-thread queued-command crash in your fixture: 20/20 crashes with the published prebuilt and 10/10 with an unmodified source build. Applying this instance-shutdown fix to the 0.88.1 source build gave 20/20 survived-reload acknowledgements, with no crashes or timeouts. Two native ownership tests also pass. This was iOS 27.1 on an iPhone Duo simulator; could you retest on your iOS 26.5 setup? The PR leaves legacy bridge and inline JS-thread invalidation outside its scope. |
|
Thank you, I verified the fix and it's all good! |
|
@javache has imported this pull request. If you are a Meta employee, you can view this in D129319362. |
|
Internal feedback:
|
|
@javache Addressed in 4e1097f by permanently removing the scheduler's layout-animation observer on main before dispatching JS cleanup. The scheduler remains retained until that cleanup block, where it is released before the runtime. This keeps both lifetimes ordered without a synchronous main wait or another JS event-loop turn between scheduler and runtime teardown. I tested the alternative of releasing the whole scheduler on main after cleanup; an animated trial crashed while destroying queued JSI handles after the runtime had gone. That approach is not in this update. The final observer-detachment version passes three native lifecycle tests, all 12 API snapshot validations, and 20 normal plus 25 animated reloads with explicit post-reload acknowledgements and no crashes/timeouts. Twenty animation trials were instrumented: logs confirm main-thread observer removal, no subsequent ticks, then JS-thread scheduler destruction before runtime teardown. Diagnostics were removed, the clean native suite was rerun, and the 20 normal plus five additional animated trials used that clean build. Runtime validation uses the source-built 0.88.1 issue fixture on an iOS 27.1 simulator, not a full checkout-HEAD/RNTester run or device/Release validation. The PR description has the detailed scope and results. The Meta Internal-Only Changes Check currently reports that the PR and imported diff are out of sync; this commit needs re-importing into D129319362. |
Summary
Fixes #59721.
During a normal bridgeless reload,
RCTInstance::invalidatesuspends the surface presenter before JS-thread cleanup runs. Suspension can destroy the renderer scheduler and its delegate proxy while the current JS task still has rendering updates capturing the proxy by raw pointer.Retain the scheduler through that cleanup, and clear its Objective-C delegate after stopping surfaces. Before dispatching cleanup, permanently remove its layout-animation run-loop observer on main. Scheduler destruction then stays in the same JS cleanup block as runtime destruction, with the scheduler released first. This prevents both an observer callout racing teardown and queued JSI handles outliving the runtime. It adds no per-rendering-callback guard, synchronous main wait, or JS-to-main-to-JS destruction round trip.
Add three native lifecycle tests, the required CocoaPods Yoga header dependency for the scheduler import, and regenerated Apple API snapshots.
Changelog
[IOS] [FIXED] - Prevent a queued view command from accessing a destroyed scheduler delegate during a normal bridgeless reload
Test Plan
Used the issue's public
Nezz/expo-reprobranchrepro/ReloadSchedulerCrash(React Native 0.88.1), Xcode 27.1, and a dedicated iOS 27.1 iPhone Duo simulator.Scheduler::uiManagerDidDispatchCommandandRuntimeScheduler_Modern::updateRenderingon the JS thread.git diff --checkpass.An unpublished intermediate approach that released the whole scheduler on main after JS cleanup crashed in 1/20 animated trials while destroying queued JSI handles. The final fix instead detaches only the observer on main and preserves scheduler-before-runtime destruction in one JS cleanup block.
Validation stages the production changes into the report's source-built 0.88.1 fixture; it is not a complete checkout-HEAD build or full RNTester suite. The reporter's iOS 26.5 environment, physical devices, and Release builds were not tested. This addresses normal off-JS-thread bridgeless reload, not a new policy for inline JS invalidation, standalone presenter suspension, nested main run loops, repeated invalidation, the legacy bridge, or the separate shared-animation backend.