Skip to content

CustomerInfoManager: improved thread-safety#2224

Merged
NachoSoto merged 1 commit into
mainfrom
customer-info-manager-thread-safety
Jan 26, 2023
Merged

CustomerInfoManager: improved thread-safety#2224
NachoSoto merged 1 commit into
mainfrom
customer-info-manager-thread-safety

Conversation

@NachoSoto

Copy link
Copy Markdown
Contributor

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.

@NachoSoto NachoSoto added the pr:fix A bug fix label Jan 18, 2023
@NachoSoto NachoSoto requested a review from a team January 18, 2023 18:13
NachoSoto added a commit that referenced this pull request Jan 18, 2023
#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.
NachoSoto added a commit that referenced this pull request Jan 18, 2023
#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.
@NachoSoto NachoSoto force-pushed the customer-info-manager-thread-safety branch from b8ceb66 to 962e57c Compare January 21, 2023 18:01
@codecov

codecov Bot commented Jan 21, 2023

Copy link
Copy Markdown

Codecov Report

Merging #2224 (4533538) into main (4533538) will not change coverage.
The diff coverage is n/a.

❗ Current head 4533538 differs from pull request most recent head 962e57c. Consider uploading reports for the commit 962e57c to get more accurate results

@@           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 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.

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) }

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.

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?

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.

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) }

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.

Same here about withData vs modifyData.

@NachoSoto NachoSoto merged commit c765b18 into main Jan 26, 2023
@NachoSoto NachoSoto deleted the customer-info-manager-thread-safety branch January 26, 2023 17:23
tonidero pushed a commit that referenced this pull request Feb 1, 2023
**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)
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.

Crash in CustomerInfoManager

2 participants