fix(image): fix data race in imageURLLoaderForURL: and imageDataDecoderForData:#57297
Open
mfazekas wants to merge 1 commit into
Open
fix(image): fix data race in imageURLLoaderForURL: and imageDataDecoderForData:#57297mfazekas wants to merge 1 commit into
mfazekas wants to merge 1 commit into
Conversation
…rForData imageURLLoaderForURL: had a broken double-checked lock on _loaders: the outer if (!_loaders) guard and the for-loop iteration both ran without _loadersMutex, while the assignment ran inside it. imageDataDecoderForData: had no lock at all on _decoders. Both methods write the ivar in two steps (raw list then sorted), so a concurrent reader could observe the intermediate value and iterate a concurrently-released array, crashing with EXC_BAD_ACCESS in objc_release. Fix mirrors the setUp() pattern from react#46153: add std::atomic<BOOL> flags (_loadersReady / _decodersReady) as acquire/release guards, protect the slow path with _loadersMutex, and iterate a local strong ref captured under the lock so the shared ivar is never read outside it. Verified with a reproducer (crashes 5/5 without the fix, 0/10 with it): https://github.com/mfazekas/rn-rctimageloader-dcl-repro Fixes: react#57296 See also: react#46115, react#46153
|
Warning Missing Test Plan Please add a "## Test Plan" section to your PR description. A Test Plan lets us know how these changes were tested. |
This was referenced Jun 20, 2026
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
imageURLLoaderForURL:had a broken double-checked lock on_loaders: the outerif (!_loaders)guard and theforloop iteration both ran without_loadersMutex, while the assignment ran inside it.imageDataDecoderForData:had no lock at all on_decoders. Both methods write the ivar in two steps (raw list then sorted), so a concurrent reader could observe the intermediate value and iterate a concurrently-released array, crashing withEXC_BAD_ACCESSinobjc_releaseon a GCD worker thread.Verified with a reproducer — crashes 5/5 without the fix, 0/10 with it: https://github.com/mfazekas/rn-rctimageloader-dcl-repro
Fix
The fix mirrors the
setUp()pattern from #46153: addstd::atomic<BOOL>flags (_loadersReady/_decodersReady) as acquire/release DCL guards, protect the slow path with_loadersMutex, and iterate a local strong ref captured under the lock so the shared ivar is never read outside it.Two simpler alternatives were considered and rejected:
_loadersMutexon every call, even after init): correct but ~40× slower on the hot path —imageURLLoaderForURL:is called for every image load.dispatch_once: correct and lock-free after init, but involves a function call into libdispatch on every call.The atomic DCL approach matches what
setUp()already does and compiles to a singleldar(load-acquire) instruction on ARM64 on the hot path — effectively free after first use.Related
Fixes #57296
See also #46115, #46153