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

[EXTERNAL] Lock RateLimiter.shouldProceed() entirely to avoid race conditions (#4635) via @nguyenhuy #4637

Merged
merged 2 commits into from
Jan 8, 2025

Conversation

JayShortway
Copy link
Member

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-android and hybrids

Motivation

Introduced in #3709, RateLimiter is used exclusively by Purchases.syncAttributesAndOfferingsIfNeeded(completion:). The documentation of this method doesn't mention any threading requirement, so it's reasonable to assume that it can be called on any thread. This means RateLimiter needs to be thread safe, otherwise clients will run into race conditions as reported in #4440.

Description

Fortunately the RateLimiter class is quite simple.

Its contants don't need to be atomic/locked since they are thread safe by definition. Turning timestamps and index vars into atomics doesn't help much in this case because shouldProceed() needs multiple reads and writes in a "transaction".

Using a Lock to wrap the whole method is the simplest and self-contained approach so I picked it. Android is doing the same thing (here).

The lock is a non-recursive lock because there is no need for reentrant. And in principle I avoid recursive locks as much as possible since one can easily lose control over the locks, especially in more complicated use cases/codebases. This is a big problem that we have had for a long time in AsyncDisplayKit/Texture.

Unit tests remain the same because testing for race conditions is rather difficult and unpredictable. Texture has a bunch of thrash tests that kind of work but not 100%. Since the subject under test in this case is rather simple and the fix is straightforward it should work. Otherwise, we can have follow up PRs for both iOS and Android.

This should resolve #4440.

This PR also renames RateLimiterRests.swift to RateLimiterTests.swift.

Contribution

Original PR #4635 by @nguyenhuy

…nditions (#4635)

* Lock RateLimiter.shouldProceed() entirely to avoid race conditions

Android does this as well: https://github.com/RevenueCat/purchases-android/blob/eb3c8f4aa1a6993f12d6e2f93cc82d26063101a6/purchases/src/main/kotlin/com/revenuecat/purchases/utils/RateLimiter.kt#L10

* Fix typo in RateLimiterTests' file name

* Update Sources/Misc/RateLimiter.swift

As suggested by @facumenzella

Co-authored-by: Facundo Menzella <[email protected]>

---------

via @nguyenhuy
@JayShortway JayShortway added the pr:fix A bug fix label Jan 7, 2025
@JayShortway JayShortway requested a review from a team January 7, 2025 16:50
@JayShortway JayShortway self-assigned this Jan 7, 2025
@nguyenhuy
Copy link
Contributor

Linter failed:

/Users/distiller/purchases-ios/Sources/Misc/RateLimiter.swift
Formatting Test
Lines should not have trailing whitespace
Error:Line:20

Let me run the linter locally and report back if anything else needs to be fixed.

@nguyenhuy
Copy link
Contributor

nguyenhuy commented Jan 7, 2025

Confirmed that that's the only linting issue.

Sorry I didn't find it earlier. Didn't notice that fastlane setup_dev failed so the pre-commit git hook wasn't installed correctly. Ended up manually running ./scripts/pre-commit.sh on staged files myself to verify :)

Sources/Misc/RateLimiter.swift Outdated Show resolved Hide resolved
@JayShortway
Copy link
Member Author

@nguyenhuy That's alright, no worries! It's the nature of the system where CI doesn't run on forks (for good reasons). Thanks for suggesting the fix!

Copy link
Member

@joshdholtz joshdholtz left a comment

Choose a reason for hiding this comment

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

:shipit: :shipit: :shipit: :shipit: :shipit: :shipit:

Glad to have one less potential bug written by me in this codebase 🙃

@JayShortway JayShortway merged commit 13942b8 into main Jan 8, 2025
10 checks passed
@JayShortway JayShortway deleted the external/nguyenhuy/lock_RateLimiter branch January 8, 2025 13:20
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.

Bug: Data race in Ratelimiter.
3 participants