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

Conversation

nguyenhuy
Copy link
Contributor

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.

Sources/Misc/RateLimiter.swift Outdated Show resolved Hide resolved
As suggested by @facumenzella

Co-authored-by: Facundo Menzella <[email protected]>
@JayShortway
Copy link
Member

Hi @nguyenhuy, thanks a lot for your contribution! It makes a lot of sense. I'll make sure it gets merged!

@JayShortway JayShortway changed the base branch from main to external/nguyenhuy/lock_RateLimiter January 7, 2025 16:46
@JayShortway JayShortway changed the title Lock RateLimiter.shouldProceed() entirely to avoid race conditions [EXTERNAL] Lock RateLimiter.shouldProceed() entirely to avoid race conditions Jan 7, 2025
@JayShortway JayShortway merged commit 874c784 into RevenueCat:external/nguyenhuy/lock_RateLimiter Jan 7, 2025
@nguyenhuy
Copy link
Contributor Author

@JayShortway: That was fast! ❤️

@nguyenhuy nguyenhuy deleted the lock_RateLimiter branch January 7, 2025 18:02
JayShortway added a commit that referenced this pull request Jan 8, 2025
…nditions (#4635) via @nguyenhuy (#4637)

* [EXTERNAL] Lock RateLimiter.shouldProceed() entirely to avoid race conditions (#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

* Update Sources/Misc/RateLimiter.swift via @nguyenhuy

Co-authored-by: Huy Nguyen <[email protected]>

---------

Co-authored-by: Huy Nguyen <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug: Data race in Ratelimiter.
3 participants