Skip to content

Commit b7ed041

Browse files
ryanprayogotoddbaertbeeme1mr
authored
fix: Use ConcurrentHashMap for InMemoryProvider (#1057)
* fix: Use ConcurrentHashMap for InMemoryProvider Signed-off-by: Ryan Prayogo <[email protected]> * fix: make the flags field variable final Signed-off-by: Ryan Prayogo <[email protected]> * chore: Use Collections.singletonList instead of Arrays.asList Signed-off-by: Ryan Prayogo <[email protected]> * chore: Update javadoc and parameter name Signed-off-by: Ryan Prayogo <[email protected]> * fixup: await verify Signed-off-by: Todd Baert <[email protected]> * fixup: remove test, key diffing Signed-off-by: Todd Baert <[email protected]> --------- Signed-off-by: Ryan Prayogo <[email protected]> Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Todd Baert <[email protected]> Co-authored-by: Michael Beemer <[email protected]>
1 parent a81957f commit b7ed041

File tree

2 files changed

+48
-25
lines changed

2 files changed

+48
-25
lines changed

src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
package dev.openfeature.sdk.providers.memory;
22

3-
import java.util.ArrayList;
4-
import java.util.Arrays;
5-
import java.util.HashMap;
6-
import java.util.HashSet;
7-
import java.util.Map;
8-
import java.util.Set;
9-
103
import dev.openfeature.sdk.EvaluationContext;
114
import dev.openfeature.sdk.EventProvider;
125
import dev.openfeature.sdk.Metadata;
@@ -24,6 +17,13 @@
2417
import lombok.SneakyThrows;
2518
import lombok.extern.slf4j.Slf4j;
2619

20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.HashSet;
23+
import java.util.Map;
24+
import java.util.Set;
25+
import java.util.concurrent.ConcurrentHashMap;
26+
2727
/**
2828
* In-memory provider.
2929
*/
@@ -33,7 +33,7 @@ public class InMemoryProvider extends EventProvider {
3333
@Getter
3434
private static final String NAME = "InMemoryProvider";
3535

36-
private Map<String, Flag<?>> flags;
36+
private final Map<String, Flag<?>> flags;
3737

3838
@Getter
3939
private ProviderState state = ProviderState.NOT_READY;
@@ -44,11 +44,11 @@ public Metadata getMetadata() {
4444
}
4545

4646
public InMemoryProvider(Map<String, Flag<?>> flags) {
47-
this.flags = new HashMap<>(flags);
47+
this.flags = new ConcurrentHashMap<>(flags);
4848
}
4949

5050
/**
51-
* Initialize the provider.
51+
* Initializes the provider.
5252
* @param evaluationContext evaluation context
5353
* @throws Exception on error
5454
*/
@@ -60,14 +60,15 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
6060
}
6161

6262
/**
63-
* Updating provider flags configuration, replacing existing flags.
64-
* @param flags the flags to use instead of the previous flags.
63+
* Updates the provider flags configuration.
64+
* For existing flags, the new configurations replace the old one.
65+
* For new flags, they are added to the configuration.
66+
* @param newFlags the new flag configurations
6567
*/
66-
public void updateFlags(Map<String, Flag<?>> flags) {
67-
Set<String> flagsChanged = new HashSet<>();
68-
flagsChanged.addAll(this.flags.keySet());
69-
flagsChanged.addAll(flags.keySet());
70-
this.flags = new HashMap<>(flags);
68+
public void updateFlags(Map<String, Flag<?>> newFlags) {
69+
Set<String> flagsChanged = new HashSet<>(newFlags.keySet());
70+
this.flags.putAll(newFlags);
71+
7172
ProviderEventDetails details = ProviderEventDetails.builder()
7273
.flagsChanged(new ArrayList<>(flagsChanged))
7374
.message("flags changed")
@@ -76,13 +77,15 @@ public void updateFlags(Map<String, Flag<?>> flags) {
7677
}
7778

7879
/**
79-
* Updating provider flags configuration with adding or updating a flag.
80-
* @param flag the flag to update. If a flag with this key already exists, new flag replaces it.
80+
* Updates a single provider flag configuration.
81+
* For existing flag, the new configuration replaces the old one.
82+
* For new flag, they are added to the configuration.
83+
* @param newFlag the flag to update
8184
*/
82-
public void updateFlag(String flagKey, Flag<?> flag) {
83-
this.flags.put(flagKey, flag);
85+
public void updateFlag(String flagKey, Flag<?> newFlag) {
86+
this.flags.put(flagKey, newFlag);
8487
ProviderEventDetails details = ProviderEventDetails.builder()
85-
.flagsChanged(Arrays.asList(flagKey))
88+
.flagsChanged(Collections.singletonList(flagKey))
8689
.message("flag added/updated")
8790
.build();
8891
emitProviderConfigurationChanged(details);

src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,30 @@
22

33
import com.google.common.collect.ImmutableMap;
44
import dev.openfeature.sdk.Client;
5+
import dev.openfeature.sdk.EventDetails;
56
import dev.openfeature.sdk.ImmutableContext;
67
import dev.openfeature.sdk.OpenFeatureAPI;
78
import dev.openfeature.sdk.Value;
89
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
910
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
1011
import dev.openfeature.sdk.exceptions.TypeMismatchError;
1112
import lombok.SneakyThrows;
12-
import org.junit.jupiter.api.BeforeAll;
13+
import org.junit.jupiter.api.BeforeEach;
1314
import org.junit.jupiter.api.Test;
1415

1516
import java.util.HashMap;
1617
import java.util.Map;
18+
import java.util.function.Consumer;
1719

1820
import static dev.openfeature.sdk.Structure.mapToStructure;
1921
import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
2022
import static org.junit.jupiter.api.Assertions.assertEquals;
2123
import static org.junit.jupiter.api.Assertions.assertThrows;
2224
import static org.junit.jupiter.api.Assertions.assertTrue;
25+
import static org.awaitility.Awaitility.await;
2326
import static org.mockito.ArgumentMatchers.any;
27+
import static org.mockito.ArgumentMatchers.argThat;
28+
import static org.mockito.Mockito.mock;
2429
import static org.mockito.Mockito.spy;
2530
import static org.mockito.Mockito.times;
2631
import static org.mockito.Mockito.verify;
@@ -32,8 +37,8 @@ class InMemoryProviderTest {
3237
private static InMemoryProvider provider;
3338

3439
@SneakyThrows
35-
@BeforeAll
36-
static void beforeAll() {
40+
@BeforeEach
41+
void beforeEach() {
3742
Map<String, Flag<?>> flags = buildFlags();
3843
provider = spy(new InMemoryProvider(flags));
3944
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> {});
@@ -105,4 +110,19 @@ void shouldThrowIfNotInitialized() {
105110
// ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client
106111
assertThrows(ProviderNotReadyError.class, ()-> inMemoryProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext()));
107112
}
113+
114+
@SuppressWarnings("unchecked")
115+
@Test
116+
void emitChangedFlagsOnlyIfThereAreChangedFlags() {
117+
Consumer<EventDetails> handler = mock(Consumer.class);
118+
Map<String, Flag<?>> flags = buildFlags();
119+
120+
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler);
121+
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
122+
123+
provider.updateFlags(flags);
124+
125+
await().untilAsserted(() -> verify(handler, times(1))
126+
.accept(argThat(details -> details.getFlagsChanged().size() == buildFlags().size())));
127+
}
108128
}

0 commit comments

Comments
 (0)