Skip to content

Fix ConcurrentModificationException in IntentModule.getInitialURL re-entrancy#57667

Open
nickcernera wants to merge 1 commit into
react:mainfrom
nickcernera:fix-intentmodule-concurrent-modification
Open

Fix ConcurrentModificationException in IntentModule.getInitialURL re-entrancy#57667
nickcernera wants to merge 1 commit into
react:mainfrom
nickcernera:fix-intentmodule-concurrent-modification

Conversation

@nickcernera

Copy link
Copy Markdown

Summary:

IntentModule.onHostResume() iterates pendingOpenURLPromises (an ArrayList) directly and calls getInitialURL() for each pending promise. When getCurrentActivity() returns null at that moment — e.g. a deep link or notification tap landing mid activity-transition during a rapid pause/resume — getInitialURL() re-enters waitForActivityAndGetInitialURL(), which calls pendingOpenURLPromises.add(promise) on the very list being iterated. The next iteration then throws java.util.ConcurrentModificationException:

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
    at java.util.ArrayList$Itr.next(ArrayList.java:967)
    at com.facebook.react.modules.intent.IntentModule$waitForActivityAndGetInitialURL$1.onHostResume(IntentModule.kt:90)

The synchronized(this@IntentModule) guard does not prevent this: the re-entrancy is on the same thread, which already holds the (reentrant) lock, so no second lock acquisition happens. The crash is the ArrayList iterator's modCount check, not a cross-thread race.

The fix snapshots the pending promises into a local copy, clears the shared list, and nulls the listener before draining. Re-queued promises then land in the now-empty pendingOpenURLPromises and register a fresh listener for the next resume, instead of mutating the list being iterated. Behaviour is otherwise unchanged.

Changelog:

[ANDROID] [FIXED] - Fix ConcurrentModificationException when getInitialURL re-enters during onHostResume

Test Plan:

Added IntentModuleTest.getInitialURL_onHostResumeWithNullActivity_doesNotThrowAndPreservesPromise, a Robolectric regression test that registers a pending promise while the current activity is null, then drives onHostResume so the drain re-queues, and asserts the drain does not throw and the promise is preserved (neither resolved nor rejected).

Ran locally against main with the exact reproduction scenario:

With the fix — passes:

$ ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest \
    --tests "com.facebook.react.modules.intent.IntentModuleTest"
BUILD SUCCESSFUL
# tests=1, failures=0, errors=0

Without the fix (reverting IntentModule.kt only) — fails with the exact crash, confirming the test is a genuine regression test:

IntentModuleTest > getInitialURL_onHostResumeWithNullActivity_doesNotThrowAndPreservesPromise FAILED
    java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
        at java.util.ArrayList$Itr.next(ArrayList.java:967)
        at com.facebook.react.modules.intent.IntentModule$waitForActivityAndGetInitialURL$1.onHostResume(IntentModule.kt:90)

ktfmt (Meta style, matching the repo's ktfmt configuration) reports both changed files as already formatted.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 24, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jul 24, 2026
@meta-codesync

meta-codesync Bot commented Jul 24, 2026

Copy link
Copy Markdown

@fabriziocucci has imported this pull request. If you are a Meta employee, you can view this in D113595327.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant