Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash on launch in Purchases.readyForPromotedProduct(_:purchase:) #4582

Open
7 of 12 tasks
vrutberg opened this issue Dec 12, 2024 · 11 comments
Open
7 of 12 tasks

Crash on launch in Purchases.readyForPromotedProduct(_:purchase:) #4582

vrutberg opened this issue Dec 12, 2024 · 11 comments
Labels

Comments

@vrutberg
Copy link

We've been seeing a crash that has been occurring since the launch of iOS 18. It hasn't generated any user feedback, and it doesn't seem to happen all that often, but it has been occurring since iOS 18 was released, and it's still occurring. According to Crashlytics, it seems to be occurring shortly after launch, but we haven't been able to reproduce this issue for ourselves.

Crashlytics reports 3 different variants of this crash, I'll post the most recent here:

          Crashed: com.apple.root.default-qos.cooperative
0  libdispatch.dylib              0x63b4 _dispatch_assert_queue_fail + 120
1  libdispatch.dylib              0x633c _dispatch_assert_queue_fail + 194
2  libswift_Concurrency.dylib     0x62b78 swift_task_isCurrentExecutorImpl(swift::SerialExecutorRef) + 284
3  Buddy                          0x4fcfa4 @objc PurchaseManager.purchases(_:readyForPromotedProduct:purchase:) + 4347793316 (<compiler-generated>:4347793316)
4  Buddy                          0x782148 @objc Purchases.readyForPromotedProduct(_:purchase:) + 1583 (Purchases.swift:1583)
5  Buddy                          0x7c28c8 specialized PurchasesOrchestrator.storeKit2PurchaseIntentListener(_:purchaseIntent:) + 1124 (PurchasesOrchestrator.swift:1124)
6  libswift_Concurrency.dylib     0x60f5c swift::runJobInEstablishedExecutorContext(swift::Job*) + 252
7  libswift_Concurrency.dylib     0x62514 swift_job_runImpl(swift::Job*, swift::SerialExecutorRef) + 144
8  libdispatch.dylib              0x15ec0 _dispatch_root_queue_drain + 392
9  libdispatch.dylib              0x166c4 _dispatch_worker_thread2 + 156
10 libsystem_pthread.dylib        0x3644 _pthread_wqthread + 228
11 libsystem_pthread.dylib        0x1474 start_wqthread + 8
  1. Environment
    1. Platform: iOS
    2. SDK version: 5.12.0
    3. StoreKit version:
      • StoreKit 1 (default on versions <5.0.0. Can be enabled in versions >=5.0.0 with .with(storeKitVersion: .storeKit1))
      • StoreKit 2 (default on versions >=5.0.0)
    4. OS version: iOS 18.x
    5. Xcode version: Xcode 16.x
    6. Device and/or simulator:
      • Device
      • Simulator
    7. Environment:
      • Sandbox
      • TestFlight
      • Production
    8. How widespread is the issue: Hard to say, but we seem to have have about 200 crashes during the last month, out of about 215k MAU. So maybe it affects about 0,1% of our active users?
  2. Debug logs that reproduce the issue. Complete logs with Purchases.logLevel = .verbose will help us debug this issue.

Sorry, I don't have any debug logs from this crash.

  1. Steps to reproduce, with a description of expected vs. actual behavior

Sorry, I don't know hot to reproduce it.

  1. Other information (e.g. stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, etc.)

See stacktrace above.

  1. Additional context

The crash seems to be iOS 18 only.

@vrutberg vrutberg added the bug label Dec 12, 2024
@RCGitBot
Copy link
Contributor

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!

@fire-at-will
Copy link
Contributor

Hey @vrutberg, thanks for reporting! I'll take a look at it.

You mentioned that there are three variants of the crash. If the stacktraces from the other two variants are different, would you mind sharing them here? Thanks!

@vrutberg
Copy link
Author

Hey @fire-at-will, thanks for taking a look at it.

Absolutely, here are the other two variants:

          Crashed: com.apple.root.user-initiated-qos.cooperative
0  libdispatch.dylib              0x63b4 _dispatch_assert_queue_fail + 120
1  libdispatch.dylib              0x633c _dispatch_assert_queue_fail + 194
2  libswift_Concurrency.dylib     0x62b78 swift_task_isCurrentExecutorImpl(swift::SerialExecutorRef) + 284
3  Buddy                          0x4ebd6c @objc PurchaseManager.purchases(_:readyForPromotedProduct:purchase:) + 4379917676 (<compiler-generated>:4379917676)
4  Buddy                          0x7634d4 @objc Purchases.readyForPromotedProduct(_:purchase:) + 1555 (Purchases.swift:1555)
5  Buddy                          0x77b678 closure #1 in PurchasesOrchestrator.paymentQueueWrapper(_:shouldAddStorePayment:for:) + 843 (PurchasesOrchestrator.swift:843)
6  Buddy                          0x729950 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed Result<Set<SK2StoreProduct>, PurchasesError>) -> () + 4382267728
7  Buddy                          0x725814 specialized closure #1 in static CachingProductsManager.products<A>(with:completion:productCache:requestCache:fetcher:) + 42 (Lock.swift:42)
8  Buddy                          0x750404 closure #1 in ProductsManager.products(withIdentifiers:completion:) + 91 (ProductsManager.swift:91)
9  Buddy                          0x729950 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed Result<Set<SK2StoreProduct>, PurchasesError>) -> () + 4382267728
10 Buddy                          0x74fbec specialized closure #1 in static Async.call<A>(with:asyncMethod:) + 55 (AsyncExtensions.swift:55)
11 libswift_Concurrency.dylib     0x60f5c swift::runJobInEstablishedExecutorContext(swift::Job*) + 252
12 libswift_Concurrency.dylib     0x62514 swift_job_runImpl(swift::Job*, swift::SerialExecutorRef) + 144
13 libdispatch.dylib              0x15ec0 _dispatch_root_queue_drain + 392
14 libdispatch.dylib              0x166c4 _dispatch_worker_thread2 + 156
15 libsystem_pthread.dylib        0x3644 _pthread_wqthread + 228
16 libsystem_pthread.dylib        0x1474 start_wqthread + 8
          Crashed: com.apple.root.default-qos.cooperative
0  libdispatch.dylib              0x63b4 _dispatch_assert_queue_fail + 120
1  libdispatch.dylib              0x633c _dispatch_assert_queue_fail + 194
2  libswift_Concurrency.dylib     0x62b78 swift_task_isCurrentExecutorImpl(swift::SerialExecutorRef) + 284
3  Buddy                          0x4fcfa4 @objc PurchaseManager.purchases(_:readyForPromotedProduct:purchase:) + 4347793316 (<compiler-generated>:4347793316)
4  Buddy                          0x782148 @objc Purchases.readyForPromotedProduct(_:purchase:) + 1583 (Purchases.swift:1583)
5  Buddy                          0x7c28c8 specialized PurchasesOrchestrator.storeKit2PurchaseIntentListener(_:purchaseIntent:) + 1124 (PurchasesOrchestrator.swift:1124)
6  libswift_Concurrency.dylib     0x60f5c swift::runJobInEstablishedExecutorContext(swift::Job*) + 252
7  libswift_Concurrency.dylib     0x62514 swift_job_runImpl(swift::Job*, swift::SerialExecutorRef) + 144
8  libdispatch.dylib              0x15ec0 _dispatch_root_queue_drain + 392
9  libdispatch.dylib              0x166c4 _dispatch_worker_thread2 + 156
10 libsystem_pthread.dylib        0x3644 _pthread_wqthread + 228
11 libsystem_pthread.dylib        0x1474 start_wqthread + 8

@fire-at-will
Copy link
Contributor

Thank you @vrutberg! I'll take a look at this and get back to you on it.

@fire-at-will
Copy link
Contributor

Hey @vrutberg, would you mind sharing some of your code in your PurchaseManager class, specifically around your readyForPromotedProduct function?

It looks like this is a threading issue, and my current theory is that the SDK could be calling the PurchaseDelegate's readyForPromotedProduct function on a background thread when your code might be expecting it to be on the main thread. Is the function or the class annotated with @MainActor by any chance?

Thanks!

@catfan
Copy link

catfan commented Dec 13, 2024

We also received many crashes report with this. It all happens on iOS 18, including iOS 18.2, which was just released, and on iPhone only. But have no idea how to reproduce this crash.

SDK Version: 5.12.1

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x000000019c8a84d8
Termination Reason: SIGNAL 5 Trace/BPT trap: 5
Terminating Process: exc handler [4275]

Thread 2 Crashed:
0   libdispatch.dylib             	0x000000019c8a84d8 _dispatch_assert_queue_fail + 120 (queue.c:54)
1   libdispatch.dylib             	0x000000019c8a8460 dispatch_assert_queue + 196 (queue.c:84)
2   libswift_Concurrency.dylib    	0x00000001a029ab58 swift_task_isCurrentExecutorImpl(swift::SerialExecutorRef) + 284 (Actor.cpp:0)
3   App                          	0x00000001051858d4 _checkExpectedExecutor(_filenameStart:_filenameLength:_filenameIsASCII:_line:_executor:) + 4 (/<compiler-generated>:0)
4   App                          	0x00000001051858d4 @objc AppDelegate.purchases(_:readyForPromotedProduct:purchase:) + 108
5   App                         	0x00000001053c9678 thunk for @escaping @callee_guaranteed (@unowned Purchases, @unowned StoreProduct, @unowned @escaping @callee_unowned @convention(block) (@unowned @escaping @callee_unowned @convention(block) @Send... + 24 (/<compiler-generated>:0)
6   App                          	0x00000001053c9678 Purchases.readyForPromotedProduct(_:purchase:) + 24 (Purchases.swift:1583)
7   App                          	0x00000001053c9678 @objc Purchases.readyForPromotedProduct(_:purchase:) + 244 (/<compiler-generated>:1581)
8   App                          	0x00000001054098c8 specialized PurchasesOrchestrator.storeKit2PurchaseIntentListener(_:purchaseIntent:) + 776 (PurchasesOrchestrator.swift:1124)
9   App                          	0x0000000105408521 closure #1 in closure #1 in StoreKit2PurchaseIntentListener.listenForPurchaseIntents() + 1 (StoreKit2PurchaseIntentListener.swift:107)
10  App                         	0x0000000104ec76ad partial apply for closure #1 in closure #2 in TopSiteCollectionViewController.changeNameAlert(indexPath:) + 1
11  App                          	0x0000000104f95a41 $sxIeAgHr_xs5Error_pIegHrzo_s8SendableRzs5NeverORs_r0_lTRytSg_TG5TQ0_ + 1 (/<compiler-generated>:0)
12  App                          	0x0000000104ec76ad partial apply for closure #1 in closure #2 in TopSiteCollectionViewController.changeNameAlert(indexPath:) + 1
13  libswift_Concurrency.dylib    	0x00000001a02a1e19 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 (Task.cpp:497)

It's strange that the problem was related to an unrelated call with partial apply for closure #1 in closure #2, where it includes an async call.

Here is how we call readyForPromotedProduct.

extension AppDelegate: @preconcurrency PurchasesDelegate {

    func purchases(
        _ purchases: Purchases,
        readyForPromotedProduct product: StoreProduct,
        purchase startPurchase: @escaping StartPurchaseBlock
    ) {
        startPurchase { transaction, info, error, cancelled in

            guard let customerInfo = info else { return }

            Global.shared.customerInfo = customerInfo
        }
    }
}

PS: Our project is Swift 6 strict mode enabled.

@fire-at-will If this is relative to the @MainActor problem, there is no reason only happens on iOS 18. This is where I get confused.

@vrutberg
Copy link
Author

vrutberg commented Dec 13, 2024

@fire-at-will Thanks for getting back to me! Sure, I can absolutely share some code.

I'm not sure exactly which code it is you want me to share, the only reference I can find to readyForPromotedProduct in our code base is this delegate method implementation in PurchaseManager:

extension PurchaseManager: @preconcurrency PurchasesDelegate {
    
    func purchases(_ purchases: Purchases,
                   readyForPromotedProduct product: StoreProduct,
                   purchase makeDeferredPurchase: @escaping StartPurchaseBlock) {
        makeDeferredPurchase { (transaction, info, error, cancelled) in
            if let purchaserInfo = info {
                self.handlePurchaserInfo(purchaserInfo, completion: nil)
                if PremiumManager.shared.hasPremium {
                    OnboardingController.finishOnboarding(presentTabbar: true)
                }
            }
        }
    }
}

And yes, PurchaseManager is annotated with @MainActor, so that fits your theory about the SDK calling this method on a background thread.

Please let me know if I should be making some changes to my code, or if this is something that will be fixed in the SDK.

@catfan
Copy link

catfan commented Dec 21, 2024

@fire-at-will I can now reproduce the problem by opening the app via itms-services://?action=purchaseIntent&bundleId=<BUNDLE_ID>&productIdentifier=<SKPRODUCT_ID>.

https://developer.apple.com/documentation/storekit/testing-promoted-in-app-purchases

The app will immediately crash after the app is opened.

To stop this, I have to completely remove the readyForPromotedProduct delegate call.

Tested on SDK Version: 5.14.3

image

Hope this help.

@fire-at-will
Copy link
Contributor

fire-at-will commented Dec 24, 2024

Hey @catfan, thanks for the info! If you're able to reproduce locally, would you mind testing the branch 4582-potential-fix (#4613) locally and see if that mitigates the crash?

Thanks!

@catfan
Copy link

catfan commented Dec 25, 2024

@fire-at-will The 4582-potential-fix branch seems to fix the crash. Thanks.

To reproduce and test the crash locally, open the link itms-services://?action=purchaseIntent&bundleId=<BUNDLE_ID>&productIdentifier=<SKPRODUCT_ID> on Notes or iMessage.

@vrutberg
Copy link
Author

vrutberg commented Jan 9, 2025

Thanks for sharing your findings @catfan. Returning from holiday leave, I took a look at what's been posted in this thread, and managed to find a workaround in our project, which I thought I should share in case anyone else encounters this issue (and until there's a fix in the library).

In short, my workaround is based on creating a delegate implementation not annotated with @MainActor, and using a closure to forward any delegate method invocations to the type annotated with @MainActor.

final class NonMainActorDelegate: NSObject, PurchasesDelegate {
    var callback: (@Sendable () -> Void)?

    func purchases(
        _ purchases: Purchases,
        readyForPromotedProduct product: StoreProduct,
        purchase makeDeferredPurchase: @escaping StartPurchaseBlock
    ) {
        // do whatever you want to do here, and then call the callback
        callback?()
    }
}

This can then be used like this:

import RevenueCat

@MainActor
final class PurchaseManager {
    private let nonMainActorDelegate = NonMainActorDelegate()

    init() {
         nonMainActorDelegate.callback = { [weak self] in
            Task { @MainActor in
                // perform main thread logic here...
            }
        }

        Purchases.shared.delegate = nonMainActorDelegate
    }
}

Of course, this works for our use case, in our project, so your mileage may vary.

Furthermore, FWIW, I was also able to reproduce the crash using the itms-services://... URL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants