Summary
We're seeing another Android fatal crash in the OpenIAP Google purchase flow.
This one is different from the already fixed ProductManager.getOrQuery crash from #88 / #89.
The crash happens in dev.hyo.openiap.OpenIapModule.onPurchasesUpdated during the requestPurchase flow and looks like another double-resume of a coroutine continuation.
Impact
- 25 occurrences
- 25 users impacted
- fatal crash in production
- unresolved / ongoing
Environment
react-native-iap: 14.7.12
- OpenIAP Google version resolved by
react-native-iap: 1.3.28
- Android only
- Seen in production in purchase flow (
ProxyBillingActivity visible in app context)
Error
IllegalStateException: Already resumed, but proposed with update []
Stacktrace
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
at java.lang.reflect.Method.invoke(Method.java)
at android.app.ActivityThread.main(ActivityThread.java:8641)
at android.os.Looper.loop(Looper.java:313)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Handler.handleCallback(Handler.java:938)
at com.android.billingclient.api.zzan.run(...)
at com.android.billingclient.api.BillingClientImpl.zzV(...)
at dev.hyo.openiap.OpenIapModule.onPurchasesUpdated(...)
at dev.hyo.openiap.OpenIapModule$requestPurchase$1$purchases$1$10$1.invoke(...)
at dev.hyo.openiap.OpenIapModule$requestPurchase$1$purchases$1$10$1.invoke(...)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(...)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core$default(...)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core(...)
at kotlinx.coroutines.CancellableContinuationImpl.b(...)
Suspected root cause
This looks like a separate double-resume race in the purchase flow, not in product querying.
From the current Android OpenIAP purchase logic, requestPurchase stores a continuation callback in something equivalent to:
suspendCancellableCoroutine<List<Purchase>> { continuation ->
currentPurchaseCallback = { result ->
if (continuation.isActive) {
continuation.resume(result.getOrDefault(emptyList()))
}
}
// launch billing flow...
}
Then onPurchasesUpdated(...) later invokes that callback:
currentPurchaseCallback?.invoke(Result.success(mapped))
// ...
currentPurchaseCallback = null
Because currentPurchaseCallback is cleared only after invocation, it seems possible that onPurchasesUpdated(...) may trigger the same callback more than once before it is nulled out, or that multiple billing callbacks race with each other and attempt to resume the same continuation twice.
That would match this crash:
IllegalStateException: Already resumed, but proposed with update []
Expected behavior
The purchase flow should never crash the app if Google Play Billing triggers repeated or late callbacks.
If the continuation has already been resumed or cancelled, subsequent callbacks should be ignored safely.
Actual behavior
The app crashes with a fatal IllegalStateException from CancellableContinuationImpl during purchase handling.
Reproduction notes
We do not yet have a stable local repro, but production data suggests it happens during the purchase flow after Billing launches:
- Start requestPurchase
- Billing flow opens (ProxyBillingActivity)
- onPurchasesUpdated(...) is triggered
- The same continuation appears to be resumed more than once
- App crashes
Suggested direction
It may help to make the purchase callback single-use before invoking it, for example by capturing and nulling it first:
val callback = currentPurchaseCallback
currentPurchaseCallback = null
callback?.invoke(Result.success(mapped))
or by otherwise guaranteeing that the same continuation cannot be resumed twice even if multiple billing callbacks arrive.
Summary
We're seeing another Android fatal crash in the OpenIAP Google purchase flow.
This one is different from the already fixed
ProductManager.getOrQuerycrash from #88 / #89.The crash happens in
dev.hyo.openiap.OpenIapModule.onPurchasesUpdatedduring therequestPurchaseflow and looks like another double-resume of a coroutine continuation.Impact
Environment
react-native-iap:14.7.12react-native-iap:1.3.28ProxyBillingActivityvisible in app context)Error
Stacktrace
Suspected root cause
This looks like a separate double-resume race in the purchase flow, not in product querying.
From the current Android OpenIAP purchase logic, requestPurchase stores a continuation callback in something equivalent to:
Then onPurchasesUpdated(...) later invokes that callback:
Because currentPurchaseCallback is cleared only after invocation, it seems possible that onPurchasesUpdated(...) may trigger the same callback more than once before it is nulled out, or that multiple billing callbacks race with each other and attempt to resume the same continuation twice.
That would match this crash:
Expected behavior
The purchase flow should never crash the app if Google Play Billing triggers repeated or late callbacks.
If the continuation has already been resumed or cancelled, subsequent callbacks should be ignored safely.
Actual behavior
The app crashes with a fatal IllegalStateException from CancellableContinuationImpl during purchase handling.
Reproduction notes
We do not yet have a stable local repro, but production data suggests it happens during the purchase flow after Billing launches:
Suggested direction
It may help to make the purchase callback single-use before invoking it, for example by capturing and nulling it first:
or by otherwise guaranteeing that the same continuation cannot be resumed twice even if multiple billing callbacks arrive.