Skip to content

[ReactNative][Crash] stop dispatching checkout events into a dead runtime - #602

Draft
kieran-osgood-shopify wants to merge 1 commit into
kieran-osgood/android-sheet-opens-when-tappedfrom
kieran-osgood/rn-drop-dispatch-after-runtime-teardown
Draft

[ReactNative][Crash] stop dispatching checkout events into a dead runtime#602
kieran-osgood-shopify wants to merge 1 commit into
kieran-osgood/android-sheet-opens-when-tappedfrom
kieran-osgood/rn-drop-dispatch-after-runtime-teardown

Conversation

@kieran-osgood-shopify

@kieran-osgood-shopify kieran-osgood-shopify commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What changes are you making?

There was a crash on rn-android due to accessing react activity after its disposed, this manifested when closing the sheet

How to test

Open and close the sheet multiple times on main and observe the crash, on this tophat build it no longer happens


Before you merge

Important

  • I've added tests to support my implementation
  • I have read and agree with the Contribution Guidelines
  • I have read and agree with the Code of Conduct
  • I've updated the relevant platform README (platforms/swift/README.md and/or platforms/android/README.md)

Releasing a new Swift version?
  • I have bumped the version in ShopifyCheckoutKit.podspec
  • I have bumped the version in platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift
  • I have updated the SwiftPM/CocoaPods version snippets in platforms/swift/README.md (major version only)
Releasing a new Embedded Checkout Protocol version?
  • I have bumped embeddedCheckoutProtocolAndroid in platforms/android/gradle/libs.versions.toml
  • I have updated protocol/languages/kotlin/embedded-checkout-protocol/api/embedded-checkout-protocol.api if the public API changed
Releasing a new Android version?
  • I have bumped checkoutKitAndroid in platforms/android/gradle/libs.versions.toml
  • I have updated the Gradle/Maven version snippets in platforms/android/README.md

Tip

See the Contributing documentation for the full release process per platform.

### TLDR

A checkout sheet can outlive the React runtime. A lifecycle event from that orphaned sheet reached a freed C++ emitter and killed the process with `SIGSEGV`.

### What

`ShopifyCheckoutKitModule` now overrides `invalidate()` and releases the checkout listener, so a sheet that survives teardown cannot dispatch into a runtime that is going away.

`emitOnDispatch` is also routed through `emitWhenReactInstanceIsActive`, which drops the event when `hasActiveReactInstance()` is false. That guard is the backstop for any path that skips `invalidate()`. Every lifecycle event and every protocol event shares one `DispatchHandle`, so this is a single choke point.

`DispatchHandle.released` is a Java boolean and says nothing about the C++ emitter behind `emitOnDispatch`, so the Java-side release alone was not enough.

`onCatalystInstanceDestroy` is deprecated for removal, so `invalidate()` is the supported teardown hook.

### How to test

In the React Native sample, open several checkout sheets quickly, reload the JS bundle, then press Back repeatedly.

| | Before | After |
| --- | --- | --- |
| Native crashes (new tombstones) | 3 | 0 |
| Process restarts | 3 | 0 |

The dangerous path still runs after the fix, so the reproduction stays valid: the log shows `Destroying ReactInstance`, then `onCheckoutCanceled` from the orphaned sheet 7 s later. A probe build confirmed the ordering, with `invalidate()` arriving while the listener was still live.

The baseline tombstone records `SIGSEGV` / `SEGV_MAPERR` inside libc++'s `string -> shared_ptr<IAsyncEventEmitter>` map, below `NativeShopifyCheckoutKitSpec.emitOnDispatch`, `DispatchHandle.invoke`, and `CustomCheckoutListener.onCheckoutDismissed`.

No unit test accompanies this change. The failure is a native use-after-free across the JNI boundary, which a Robolectric test cannot reach, so the device reproduction above is the evidence.

Releasing the listener at the existing dismiss site is deferred to the PR that bumps the native SDK, because this module still stores a `CheckoutKitDialog`.

Assisted-By: devx/c27c154c-e79d-4f1f-9300-c3258c6f34f9

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions github-actions Bot added the #gsd:50662 Rebase Checkout Kit on UCP label Aug 6, 2026
@kieran-osgood-shopify kieran-osgood-shopify changed the title fix(react-native): stop dispatching checkout events into a dead runtime [Crash] fix(react-native): stop dispatching checkout events into a dead runtime Aug 7, 2026
@kieran-osgood-shopify kieran-osgood-shopify changed the title [Crash] fix(react-native): stop dispatching checkout events into a dead runtime [ReactNative][Crash] stop dispatching checkout events into a dead runtime Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

#gsd:50662 Rebase Checkout Kit on UCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant