Skip to content

Fixed race condition finishing SK1 transactions#2148

Merged
NachoSoto merged 1 commit into
mainfrom
sk1-transaction-finish-completion
Dec 21, 2022
Merged

Fixed race condition finishing SK1 transactions#2148
NachoSoto merged 1 commit into
mainfrom
sk1-transaction-finish-completion

Conversation

@NachoSoto

@NachoSoto NachoSoto commented Dec 13, 2022

Copy link
Copy Markdown
Contributor

Fixes CSDK-504.

SKPaymentQueue.finishTransaction is asynchronous, but we were assuming it wasn't.
This meant that when performing multiple purchases (like consumables) in quick succession, before the first transaction was finished, the second purchase was reusing the first transaction.

Example log:

[Purchases] - INFO: :moneybag: Finishing transaction ‘0’ for product ‘consumable.10_coins’
[Purchases] - INFO: :heart_eyes_cat::moneybag: Purchased product - ‘consumable.10_coins’
[Purchases] - INFO: :moneybag: Purchasing Product ‘consumable.10_coins’ from package in Offering ‘coins’
[Purchases] - DEBUG: :information_source: StoreKit1Wrapper (0x00006000008a9530) removedTransaction: consumable.10_coins 0 1

Notice how the "removed transaction" was notified after we had already started the second purchase.

See also #2148 for an earlier fix for finishing transactions.

Fixes [CSDK-504].

`SKPaymentQueue.finishTransaction` is asynchronous, but we were assuming it wasn't.
This meant that when performing multiple purchases (like consumables) in quick succession, before the first transaction was finished, the second purchase was reusing the first transaction.

Example log:
```
[Purchases] - INFO: 💰 Finishing transaction ‘0’ for product ‘consumable.10_coins’
[Purchases] - INFO: 😻💰 Purchased product - ‘consumable.10_coins’
[Purchases] - INFO: 💰 Purchasing Product ‘consumable.10_coins’ from package in Offering ‘coins’
[Purchases] - DEBUG: ℹ️ StoreKit1Wrapper (0x00006000008a9530) removedTransaction: consumable.10_coins 0 1
```

Notice how the "removed transaction" was notified _after_ we had already started the second purchase.
@NachoSoto NachoSoto added the pr:fix A bug fix label Dec 13, 2022
@NachoSoto NachoSoto requested a review from a team December 13, 2022 22:03

@tonidero tonidero left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

// See `StoreKit1Wrapper.finishTransaction(:completion:)`.
// Technically this is a race condition, because `SKPaymentQueue.finishTransaction` is asynchronous
// In practice this method won't be used, because this class is only used in SK2 mode,
// and those transactions are finished through `SK2StoreTransaction`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log an error if this is reached then? Also, not sure if we should finish transaction in this case, if we consider it an error... But I guess we should propagate the error somehow in that case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having to have this here doesn't really say wonders about our class design, we might wanna start thinking about alternatives soon

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah for sure. This is needed because we avoid using StoreKit1Wrapper when SK2 is enabled. But turned out we did need a payment queue wrapper for some things.
And the fact that finishTransaction needs a reference to some wrapper, because SK1 transactions are finished through the queue, but SK2’s are finished directly with the transaction :/

So yeah I agree this is a smell, but due to those 2 limitations I can’t think of a way to avoid it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll merge this for now but let me know if those 2 limitations gives any of you an idea of how we could best work around them and avoid this smell.

@NachoSoto NachoSoto merged commit 1b13754 into main Dec 21, 2022
@NachoSoto NachoSoto deleted the sk1-transaction-finish-completion branch December 21, 2022 16:17
NachoSoto pushed a commit that referenced this pull request Dec 21, 2022
**This is an automatic release.**

### Bugfixes
* `ErrorUtils.purchasesError(withUntypedError:)`: handle `PublicError`s
(#2165) via NachoSoto (@NachoSoto)
* Fixed race condition finishing `SK1` transactions (#2148) via
NachoSoto (@NachoSoto)
* `IntroEligibilityStatus`: added `CustomStringConvertible`
implementation (#2182) via NachoSoto (@NachoSoto)
* `BundleSandboxEnvironmentDetector`: fixed logic for `macOS` (#2176)
via NachoSoto (@NachoSoto)
* Fixed `AttributionFetcher.adServicesToken` hanging when called in
simulator (#2157) via NachoSoto (@NachoSoto)
### Other Changes
* `Purchase Tester`: added ability to purchase products directly with
`StoreKit` (#2172) via NachoSoto (@NachoSoto)
* `DNSChecker`: simplified `NetworkError` initialization (#2166) via
NachoSoto (@NachoSoto)
* `Purchases` initialization: refactor to avoid multiple concurrent
instances in memory (#2180) via NachoSoto (@NachoSoto)
* `Purchase Tester`: added button to clear messages on logger view
(#2179) via NachoSoto (@NachoSoto)
* `NetworkOperation`: added assertion to ensure that subclasses call
completion (#2138) via NachoSoto (@NachoSoto)
* `CacheableNetworkOperation`: avoid unnecessarily creating operations
for cache hits (#2135) via NachoSoto (@NachoSoto)
* `PurchaseTester`: fixed `macOS` support (#2175) via NachoSoto
(@NachoSoto)
* `IntroEligibilityCalculator`: added log including `AppleReceipt`
(#2181) via NachoSoto (@NachoSoto)
* `Purchase Tester`: fixed scene manifest (#2170) via NachoSoto
(@NachoSoto)
* `HTTPClientTests`: refactored to use `waitUntil` (#2168) via NachoSoto
(@NachoSoto)
* `Integration Tests`: split up tests in smaller files (#2158) via
NachoSoto (@NachoSoto)
* `StoreKitRequestFetcher`: removed unnecessary dispatch (#2152) via
NachoSoto (@NachoSoto)
* `Purchase Tester`: added companion `watchOS` app (#2140) via NachoSoto
(@NachoSoto)
* `StoreKit1Wrapper`: added warning if receiving too many updated
transactions (#2117) via NachoSoto (@NachoSoto)
* `StoreKitTestHelpers`: cleaned up unnecessary log (#2177) via
NachoSoto (@NachoSoto)
* `TrialOrIntroPriceEligibilityCheckerSK1Tests`: use `waitUntilValue`
(#2173) via NachoSoto (@NachoSoto)
* `DNSChecker`: added log with resolved host (#2167) via NachoSoto
(@NachoSoto)
* `MagicWeatherSwiftUI`: updated `README` to point to workspace (#2142)
via NachoSoto (@NachoSoto)
* `Purchase Tester`: fixed `.storekit` config file reference (#2171) via
NachoSoto (@NachoSoto)
* `Purchase Tester`: fixed error alerts (#2169) via NachoSoto
(@NachoSoto)
* `CI`: don't make releases until `release-checks` pass (#2162) via
NachoSoto (@NachoSoto)
* `Fastfile`: changed `match` to `readonly` (#2161) via NachoSoto
(@NachoSoto)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants