Skip to content

[iOS] Fix data races in RCTImageLoader and RCTNetworkTask with shared atomic counters #45114

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
wants to merge 16 commits into from

Conversation

hakonk
Copy link
Contributor

@hakonk hakonk commented Jun 22, 2024

Summary:

In order to fix the data races described in #44715, I propose a simple solution by leveraging shared counter functions wherein std::atomic is the backing for the integer values.

Changelog:

[iOS] [Fixed] - Implement shared atomic counters and replace static integers in RCTImageLoader and RCTNetworkTask that were accessed concurrently, which in some cases lead to data races.

Test Plan:

Added unit tests for the counters in RCTSharedCounterTests.

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Jun 22, 2024
@analysis-bot
Copy link

analysis-bot commented Jun 22, 2024

Platform Engine Arch Size (bytes) Diff
android hermes arm64-v8a 20,315,962 -1,046
android hermes armeabi-v7a n/a --
android hermes x86 n/a --
android hermes x86_64 n/a --
android jsc arm64-v8a 23,513,278 -371
android jsc armeabi-v7a n/a --
android jsc x86 n/a --
android jsc x86_64 n/a --

Base commit: e320ab4
Branch: main

Copy link
Contributor

@cipolleschi cipolleschi left a comment

Choose a reason for hiding this comment

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

Great catch and smart solution!
I left some questions and a few minor changes requested.

@@ -863,6 +867,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CC = "";
Copy link
Contributor

Choose a reason for hiding this comment

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

This change should be reverted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

typedef NSUInteger (^NSUIntegerCounter)(void);
typedef uint64_t (^UInt64Counter)(void);

RCT_EXTERN NSUIntegerCounter RCTCreateAtomicNSUIntegerCounter(void);
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the approach, but I'm a bit confused by the API namig.
We have an API that creates an atomicCounter and that returns a function that returns the counter an increment it.

It's a bit confusing that we use the RCTCreateAtomicNSUIntegerCounter to actually return a function that is getAndIncrementCounter.
By calling RCTCreateAtomicNSUIntegerCounter I expect to get a counter as a result not a function to increment a counter. 🤔

Can we think of a better name for this API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case of RCTNetworkTask:24, we create a static counter like so:

static const auto generateRequestId = RCTCreateAtomicNSUIntegerCounter();

That is, the counter function is not static unless it is held statically. In turn, calling on generateRequestId will yield an incremented value. I believe it is more appropriate to name the variable that holds the block generateRequestId, as calling this block indeed increments the counter, while RCTCreateAtomicNSUIntegerCounter creates a new counter block where count is initialized to 0 for each call.

#include <memory>

NSUIntegerCounter RCTCreateAtomicNSUIntegerCounter(void) {
auto count = std::make_shared<std::atomic<NSUInteger>>(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that the approach is very clever, but talking about memory management, when is the std::atomic destroyed?

Because as far as I can see, the count is captured by the closure. So I'd say that when there are no closures that keep the reference alive, the shared pointer will be deleted. Correct?

Can we add a test to make sure that this is actually happening?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding is that this is correct. I have attempted to allow for testing this via this commit

@hakonk
Copy link
Contributor Author

hakonk commented Jun 25, 2024

Hi @cipolleschi, thanks for the feedback! I will look into the comments and think of improvements. Will push changes when I have the time.

@hakonk
Copy link
Contributor Author

hakonk commented Jun 27, 2024

@cipolleschi please see the proposed changes above

Copy link
Contributor

@cipolleschi cipolleschi left a comment

Choose a reason for hiding this comment

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

Thanks for verifying that the memory is deallocated.
For maintainability I'd rather remove the spy code, so the code is clearer and easier to understand and maintain.
Thanks for putting extra work in verifying the memory management! ❤️

@cipolleschi
Copy link
Contributor

(also, remember to rebase on top of main, please! 🙏 )

@javache
Copy link
Member

javache commented Jun 27, 2024

Since we already use Objective-C++ quite widely across the codebase, can we just replace this with std::atomic?

@hakonk
Copy link
Contributor Author

hakonk commented Jun 27, 2024

Since we already use Objective-C++ quite widely across the codebase, can we just replace this with std::atomic?

We certainly can. My approach was to generalize the solution, seeing it as one could make use of atomic counter functions for cases to come. I believe testing becomes easier as well, as one does not have to for example instantiate a class wherein a static count variable is used.

Is it more preferable from your point of view not to introduce the counters?

Copy link
Member

@javache javache left a comment

Choose a reason for hiding this comment

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

I'd rather rely on standard C++ fundamentals and use those directly, rather than using wrappers which involve shared_ptr, which have their own.

In pure Objective-C, we should also be able to use OSAtomicIncrement64 as a much simpler solution.

@hakonk
Copy link
Contributor Author

hakonk commented Jun 28, 2024

I'd rather rely on standard C++ fundamentals and use those directly, rather than using wrappers which involve shared_ptr, which have their own.

In pure Objective-C, we should also be able to use OSAtomicIncrement64 as a much simpler solution.

@javache I have revised according to your review. Please see changes.

@facebook-github-bot
Copy link
Contributor

@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@facebook-github-bot facebook-github-bot added the Merged This PR has been merged. label Jun 28, 2024
@facebook-github-bot
Copy link
Contributor

@javache merged this pull request in ffc16fc.

Copy link

This pull request was successfully merged by @hakonk in ffc16fc.

When will my fix make it into a release? | How to file a pick request?

blakef pushed a commit that referenced this pull request Jul 15, 2024
…tomic counters (#45114)

Summary:
In order to fix the data races described in #44715, I propose a simple solution by leveraging shared counter functions wherein `std::atomic` is the backing for the integer values.

## Changelog:

[iOS] [Fixed] - Implement shared atomic counters and replace static integers in `RCTImageLoader` and `RCTNetworkTask` that were accessed concurrently, which in some cases lead to data races.

Pull Request resolved: #45114

Test Plan: Added unit tests for the counters in `RCTSharedCounterTests`.

Reviewed By: cipolleschi

Differential Revision: D59155076

Pulled By: javache

fbshipit-source-id: f73afce6a816ad3226ed8c123cb2ccf4183549a0
Titozzz pushed a commit that referenced this pull request Jul 22, 2024
…tomic counters (#45114)

Summary:
In order to fix the data races described in #44715, I propose a simple solution by leveraging shared counter functions wherein `std::atomic` is the backing for the integer values.

## Changelog:

[iOS] [Fixed] - Implement shared atomic counters and replace static integers in `RCTImageLoader` and `RCTNetworkTask` that were accessed concurrently, which in some cases lead to data races.

Pull Request resolved: #45114

Test Plan: Added unit tests for the counters in `RCTSharedCounterTests`.

Reviewed By: cipolleschi

Differential Revision: D59155076

Pulled By: javache

fbshipit-source-id: f73afce6a816ad3226ed8c123cb2ccf4183549a0
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants