Skip to content

fix: Reduce locking and concurrency issues #1478

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

Merged
merged 8 commits into from
Jul 4, 2025

Conversation

chrfwow
Copy link
Contributor

@chrfwow chrfwow commented Jun 12, 2025

This PR

Reduces locking and potential concurrency issues

@chrfwow chrfwow requested review from a team as code owners June 12, 2025 12:17
@@ -24,7 +26,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
private final List<Hook> apiHooks;
Copy link
Contributor Author

@chrfwow chrfwow Jun 12, 2025

Choose a reason for hiding this comment

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

This ArrayList could also be replaced with some concurrent data structure, further reducing the usage of locks and object churn, but it might be slower in some cases

@@ -396,18 +396,20 @@ public OpenFeatureAPI on(ProviderEvent event, Consumer<EventDetails> handler) {
*/
@Override
public OpenFeatureAPI removeHandler(ProviderEvent event, Consumer<EventDetails> handler) {
this.eventSupport.removeGlobalHandler(event, handler);
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
Copy link
Contributor Author

@chrfwow chrfwow Jun 12, 2025

Choose a reason for hiding this comment

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

It might be possible to remove locking on the eventSupport entirely, now that it uses concurrent collections.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Upon further review, I'm not so sure. We might get into trouble in the runHandlersForProvider, but it might also work just fine.
The problem lies rather with setting/getting the provider and its state from the providerRepository than anything we do in here.

Copy link
Member

Choose a reason for hiding this comment

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

You mean it's breaking in that the behavior changes, right (different references)? There's no associated compilation breakages here (or I'm missing something?).

If so, I don't think I would really qualify that as breaking... it's a bit of a grey area, but IMO if we don't make specific guarantees about references, we don't necessarily need to adhere to them. I think if we considered all behavior as completely unchangeable we couldn't even improve the locking 😅 .

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's what I meant

@@ -49,8 +50,7 @@ public class OpenFeatureClient implements Client {
private final List<Hook> clientHooks;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This ArrayList could also be replaced with some concurrent data structure, further reducing the usage of locks

Copy link
Member

Choose a reason for hiding this comment

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

Ya it's final unlike the evaluationContext is, so we wouldn't need to even use an atomic reference.

Do you see any reason not to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@chrfwow chrfwow changed the title Reduce locking and concurrency issues fix: Reduce locking and concurrency issues Jun 12, 2025
chrfwow added 2 commits June 16, 2025 13:43
Signed-off-by: christian.lutnik <[email protected]>
@chrfwow chrfwow self-assigned this Jun 16, 2025
Copy link

codecov bot commented Jun 16, 2025

Codecov Report

Attention: Patch coverage is 97.72727% with 1 line in your changes missing coverage. Please review.

Project coverage is 93.24%. Comparing base (08f549a) to head (0e40497).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...in/java/dev/openfeature/sdk/OpenFeatureClient.java 93.75% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1478      +/-   ##
============================================
+ Coverage     92.97%   93.24%   +0.26%     
  Complexity      488      488              
============================================
  Files            46       46              
  Lines          1182     1169      -13     
  Branches        103      103              
============================================
- Hits           1099     1090       -9     
+ Misses           53       49       -4     
  Partials         30       30              
Flag Coverage Δ
unittests 93.24% <97.72%> (+0.26%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

if (this.clientHooks.isEmpty()) {
return Collections.emptyList();
} else {
return new ArrayList<>(this.clientHooks);
Copy link
Member

Choose a reason for hiding this comment

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

This will create some additional object churn per evaluation, I guess you're worried about exposing this mutable state?

Copy link
Contributor Author

@chrfwow chrfwow Jun 18, 2025

Choose a reason for hiding this comment

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

I don't want to expose a reference to the list, as this would effectively remove the lock entirely. At least the Collections.emptyList(); should not allocate anything.
I don't think it would add churn per evaluation, as I don't use the getter during the evaluation, but use the field directly.
Also consider that this and the corresponding change in OpenFeatureAPI is technically a breaking change

Copy link
Member

@toddbaert toddbaert left a comment

Choose a reason for hiding this comment

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

*
* @return The collection of {@link Hook}s.
*/
Collection<Hook> getMutableHooks() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really don't like this method, but we need some Method where we can return a ConcurrentLinkedQueue and don't have to convert it into a list to reduce memory churn during evaluations

try (AutoCloseableLock __ = this.hooksLock.readLockAutoCloseable()) {
return this.clientHooks;
}
return new ArrayList<>(this.clientHooks);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't return the ConcurrentLinkedQueue, as this method returns a List.


mergedHooks = ObjectUtils.merge(
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getHooks());
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getMutableHooks());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Directly access the openfeatureApi's ConcurrentLinkedQueue

@toddbaert
Copy link
Member

I'll merge this tomorrow unless I hear objections.

Copy link

sonarqubecloud bot commented Jul 4, 2025

@toddbaert toddbaert merged commit ebea0fd into open-feature:main Jul 4, 2025
13 checks passed
@chrfwow chrfwow deleted the fix-sync-issues branch July 7, 2025 09:49
suvaidkhan pushed a commit to suvaidkhan/java-sdk that referenced this pull request Aug 11, 2025
* Reduce locking and concurrency issues

Signed-off-by: christian.lutnik <[email protected]>

* Reduce locking and concurrency issues

Signed-off-by: christian.lutnik <[email protected]>

* formatting

Signed-off-by: christian.lutnik <[email protected]>

* use concurrent data structure for hooks

Signed-off-by: christian.lutnik <[email protected]>

* use concurrent data structure for hooks

Signed-off-by: christian.lutnik <[email protected]>

---------

Signed-off-by: christian.lutnik <[email protected]>
Co-authored-by: Todd Baert <[email protected]>
Signed-off-by: suvaidkhan <[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.

2 participants