Repository navigation
Find a LongLivedObject in its collection without walking it - #59403
Merged
javache merged 2 commits intoOct 28, 2026
Merged
Conversation
LongLivedObjectCollection::remove walked the whole set to find the object, so releasing N pending callbacks or promises cost O(N^2). Look it up with a non-owning shared_ptr instead, which hashes and compares by the same pointer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
javache
approved these changes
Oct 23, 2026
Address review: make it explicit that the key is a non-owning alias. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@javache has imported this pull request. If you are a Meta employee, you can view this in D128033820. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
LongLivedObjectCollection(ReactCommon/react/bridging/LongLivedObject.cpp) keeps the JS callbacks and promises of native module calls alive until they're released: callbacks and promises of Objective-C TurboModules on iOS,AsyncCallbackandAsyncPromisein C++ TurboModules, and LayoutAnimation callbacks.removefound the object by walking the wholestd::unordered_setand comparing each pointer, so releasing one of N pending objects cost O(N), and releasing all of them O(N²).The set holds
std::shared_ptr<LongLivedObject>, andstd::shared_ptrhashes and compares by the pointer it stores. Soremovecan look the object up withfind, using a non-owningshared_ptrthat points to it (the aliasing constructor with an empty owner, so nothing is allocated and no reference count changes). The same entry is erased under the same lock; removing an object that isn't in the set still does nothing.In normal use only a few objects are pending at a time, so this matters when many native calls are pending at once: for example
Promise.allover a large number of native calls, or a native module whose answers pile up while its queue is busy.Changelog:
[GENERAL] [CHANGED] - Releasing a native module callback or promise no longer walks all the pending ones
Test Plan:
clang-format --dry-run --Werror(the repository's pinned version) on the changed file passes.clang++ -std=c++20 -O2againstLongLivedObject.cppadds 2,000 objects, removes an object that isn't in the collection (the size stays the same), then callsallowRelease()on all of them in random order and checks the size and that each object is destroyed after each call. It passes before and after this change.allowRelease()with N objects in the collection, on an M-series Mac:NativeSampleTurboModule.getValueWithPromise(orgetValueWithCallback) at once and awaiting all of them, timed withperformance.now(), median of 5–100 rounds per N. Two Release builds, before and after this change, installed in the order before, after, after, before, 4 launches each, on a physical iPhone 15 Pro (iOS 26.6.1). The presses were driven by Argent. Mean time per call:At 100 and 500 the ranges of the two arms overlap. With this change, the time per call no longer grows with the number of pending calls.
react/bridginggtest suite (BridgingTest); its tear-down checks that the collection is empty after each test, which coversremove.🤖 Generated with Claude Code