CustomerInfoManager: improved thread-safety#2224
Conversation
#2224 likely fixes #1877. This change also avoids running the code being triggered in that crashing stack trace, since it's not necessary. `Purchases.delegate` setter already calls `sendCachedCustomerInfoToDelegateIfExists`, so that method was unnecessary. Removing this call to `sendCachedCustomerInfoIfAvailable` doesn't make any test fails because of that. This behavior is also verified by `PurchasesConfiguringTests`: `testFirstInitializationFromBackgroundCallsDelegateForAnonIfInfoCached` and `testSettingTheDelegateAfterInitializationSendsCachedCustomerInfo`. I also improved this by extracting it into its own method and adding an explanation for the `isApplicationBackgrounded` check.
#2231) #2224 likely fixes #1877. This change also avoids running the code being triggered in that crashing stack trace, since it's not necessary. `Purchases.delegate` setter already calls `sendCachedCustomerInfoToDelegateIfExists`, so that method was unnecessary. Removing this call to `sendCachedCustomerInfoIfAvailable` doesn't make any test fails because of that. This behavior is also verified by `PurchasesConfiguringTests`: `testFirstInitializationFromBackgroundCallsDelegateForAnonIfInfoCached` and `testSettingTheDelegateAfterInitializationSendsCachedCustomerInfo`. I also improved this by extracting it into its own method and adding an explanation for the `isApplicationBackgrounded` check.
Likely fixes #1877. Thread-safety was done through a `Lock`, but nothing ensured that we didn't access the underlying data without locking. This is done now using `Atomic<Data>` to guarantee that all reads and writes go through this lock.
b8ceb66 to
962e57c
Compare
Codecov Report
@@ Coverage Diff @@
## main #2224 +/- ##
=======================================
Coverage 85.80% 85.80%
=======================================
Files 183 183
Lines 12077 12077
=======================================
Hits 10363 10363
Misses 1714 1714 Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
tonidero
left a comment
There was a problem hiding this comment.
Just a question, but looks good otherwise.
| switch result { | ||
| case let .failure(error): | ||
| self.deviceCache.clearCustomerInfoCacheTimestamp(appUserID: appUserID) | ||
| self.withData { $0.deviceCache.clearCustomerInfoCacheTimestamp(appUserID: appUserID) } |
There was a problem hiding this comment.
Hmm I believe in this case the behavior would be the same and considering we are not modifying data in the Data struct, this is correct, but considering this is clearing a cache, would it semantically be better to use self.modifyData?
There was a problem hiding this comment.
In terms of naming semantics I agree, but then we'd have to ignore this inout parameter which I think would make it more confusing.
| do { | ||
| let jsonData = try JSONEncoder.default.encode(customerInfo) | ||
| self.deviceCache.cache(customerInfo: jsonData, appUserID: appUserID) | ||
| self.withData { $0.deviceCache.cache(customerInfo: jsonData, appUserID: appUserID) } |
There was a problem hiding this comment.
Same here about withData vs modifyData.
**This is an automatic release.** ### Bugfixes * `CustomerInfoManager`: improved thread-safety (#2224) via NachoSoto (@NachoSoto) ### Other Changes * `StoreKitIntegrationTests`: replaced `XCTSkipIf` with `XCTExpectFailure` (#2244) via NachoSoto (@NachoSoto) * `PurchasesOrchestrator`: changed `ReceiptRefreshPolicy.always` to `.onlyIfEmpty` after a purchase (#2245) via NachoSoto (@NachoSoto)
Likely fixes #1877.
Thread-safety was done through a
Lock, but nothing ensured that we didn't access the underlying data without locking.This is done now using
Atomic<Data>to guarantee that all reads and writes go through this lock.