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

fix: data race in Pistache::Rest::Router::route #1239

Merged
merged 3 commits into from
Sep 11, 2024

Conversation

tyler92
Copy link
Contributor

@tyler92 tyler92 commented Aug 31, 2024

This patch fixes a data race in Pistache::Rest::Router::route that occurs when several HTTP requests are handled in parallel. Multiple threads were writing to the same std::unordered_map (routes) without synchronization. There is no need to modify the map because the method is inherently constant; it’s sufficient to find the target element in the map without adding a default value via operator[]. Additionally, the method has been marked as const to prevent this issue in the future.

Closes: #1238

This patch fixes a data race in Pistache::Rest::Router::route that
occurs when several HTTP requests are handled in parallel.
Multiple threads were writing to the same std::unordered_map
(routes) without synchronization. There is no need to modify the
map because the method is inherently constant; it’s sufficient to
find the target element in the map without adding a default value
via operator[]. Additionally, the method has been marked as const
to prevent this issue in the future.
@tyler92
Copy link
Contributor Author

tyler92 commented Aug 31, 2024

I'm not sure if I should increment the minor version in version.txt

Copy link

codecov bot commented Aug 31, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 76.11%. Comparing base (f71275b) to head (22e6822).
Report is 12 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1239      +/-   ##
==========================================
- Coverage   77.55%   76.11%   -1.44%     
==========================================
  Files          55       58       +3     
  Lines        7615     9614    +1999     
==========================================
+ Hits         5906     7318    +1412     
- Misses       1709     2296     +587     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@kiplingw
Copy link
Member

My guess is adding a cv qualifier would change the ABI if it was a public interface. If it is, then minor should be incremented. @Tachi107?

@Tachi107
Copy link
Member

My guess is adding a cv qualifier would change the ABI if it was a public interface. If it is, then minor should be incremented. @Tachi107?

I believe it does, but in any case we don't currently follow such ABI scheme. The SONAME version is only set to the major component. I've proposed considering the minor too to ease ABI versioning in #1146, but it hasn't been merged yet.

If we merge it, then yes.

Copy link
Contributor

@dgreatwood dgreatwood left a comment

Choose a reason for hiding this comment

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

Hi @tyler92 -

I had a look at this, and came back with a question.

1/ I agree it is an issue.

2/ It's good to make hasNotFoundHandler() marked const.

3/ It took me a minute to work out why the old code for "Router::route" was changing the map. Of course, as you have realized, the critical line is:
auto& r = routes[req.method()];
since std::unordered_map<...>::operator[] "Returns a reference to the value that is mapped to a key equivalent to key or x respectively, performing an insertion if such key does not already exist."

I was wondering how we know that the code does not rely on that insertion side effect, which you've removed in this PR? After all, the insertion would only happen if "routes" does not already have an entry for "req.method()" - maybe the insertion is actually needed?

@tyler92
Copy link
Contributor Author

tyler92 commented Sep 5, 2024

@dgreatwood Hi, thank you for the feedback.

The code works the same way with or without insertion, but as you mentioned, there is a side effect involving a default-constructed element in the map, which leads to a data race.

Having a default-constructed element in the map has the same semantics as not having it.

Additionally, the nature of the route method is not mutable; there is no reason to modify the map, as it only finds a handler that matches the incoming request and passes the request to it. This is a common misuse of operator[].

@dgreatwood
Copy link
Contributor

@tyler92 -

Having a default-constructed element in the map has the same semantics as not having it.

Can we be confident that the code doesn't come along later and populate the initially-empty SegmentTreeNode that is inserted as part of the <Http::Method, SegmentTreeNode> insertion?

If so, then this looks an excellent change to me.

@tyler92
Copy link
Contributor Author

tyler92 commented Sep 5, 2024

Can we be confident that the code doesn't come along later

Yes, the const qualifier helps with it

@dgreatwood
Copy link
Contributor

Sure, in that function, yes. But I was more meaning later on in some other function / code path?

@kiplingw
Copy link
Member

kiplingw commented Sep 6, 2024 via email

@dgreatwood
Copy link
Contributor

Ah yes. If I understand your comment correctly, that is exactly what @tyler92's PR does.

We're assuming that the function is accidentally non-const.

The thing I was suggesting we check was to make sure that it is not deliberately non-const. In particular, that another code path does not later rely on the <Http::Method, SegmentTreeNode> inserted by the [] operator.

@tyler92
Copy link
Contributor Author

tyler92 commented Sep 6, 2024

Probably we can ask @Tachi107 if the method is non-const intentionally or not but I believe it's not.

@kiplingw
Copy link
Member

@dgreatwood, is this ready to merge?

@dgreatwood
Copy link
Contributor

dgreatwood commented Sep 11, 2024 via email

@kiplingw kiplingw merged commit fd5280a into pistacheio:master Sep 11, 2024
124 of 132 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Data race in Pistache::Rest::Router::route
4 participants