Skip to content

Commit 8aed8e4

Browse files
toddbaertjonathannorris
authored andcommitted
fixup: vmlens test, remove nullable, tighten provider-repo logic
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
1 parent 0350273 commit 8aed8e4

6 files changed

Lines changed: 56 additions & 35 deletions

File tree

src/main/java/dev/openfeature/sdk/FeatureProvider.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5-
import javax.annotation.Nullable;
65

76
/**
87
* The interface implemented by upstream flag providers to resolve flags for
@@ -199,7 +198,7 @@ default void initialize(EvaluationContext evaluationContext) throws Exception {
199198
* @param evaluationContext the global evaluation context
200199
* @param domain the bound domain, or {@code null} for the default provider
201200
*/
202-
default void initialize(EvaluationContext evaluationContext, @Nullable String domain) throws Exception {
201+
default void initialize(EvaluationContext evaluationContext, String domain) throws Exception {
203202
initialize(evaluationContext);
204203
}
205204

src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import dev.openfeature.sdk.exceptions.OpenFeatureError;
44
import java.util.concurrent.atomic.AtomicBoolean;
55
import java.util.concurrent.atomic.AtomicReference;
6-
import javax.annotation.Nullable;
76
import lombok.extern.slf4j.Slf4j;
87

98
@Slf4j
@@ -23,7 +22,7 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
2322
initialize(evaluationContext, null);
2423
}
2524

26-
public void initialize(EvaluationContext evaluationContext, @Nullable String domain) throws Exception {
25+
public void initialize(EvaluationContext evaluationContext, String domain) throws Exception {
2726
if (isInitialized.getAndSet(true)) {
2827
return;
2928
}

src/main/java/dev/openfeature/sdk/ProviderRepository.java

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import java.util.function.Consumer;
1818
import java.util.stream.Collectors;
1919
import java.util.stream.Stream;
20-
import javax.annotation.Nullable;
2120
import lombok.extern.slf4j.Slf4j;
2221

2322
@Slf4j
@@ -155,7 +154,7 @@ public void setProvider(
155154
}
156155

157156
private void prepareAndInitializeProvider(
158-
@Nullable String domain,
157+
String domain,
159158
FeatureProvider newProvider,
160159
Consumer<FeatureProvider> afterSet,
161160
Consumer<FeatureProvider> afterInit,
@@ -196,28 +195,22 @@ private void prepareAndInitializeProvider(
196195
}
197196
}
198197

199-
private void validateDomainScopedBinding(@Nullable String domain, FeatureProvider newProvider) {
198+
private void validateDomainScopedBinding(String domain, FeatureProvider newProvider) {
200199
if (!newProvider.isDomainScoped()) {
201200
return;
202201
}
203202

204-
boolean currentlyDefault = isDefaultProviderInstance(newProvider);
205-
List<String> boundNamedDomains = getBoundDomainsForProviderInstance(newProvider);
206-
207-
if (!currentlyDefault && boundNamedDomains.isEmpty()) {
203+
// a re-set to the identical binding is always allowed (it's a no-op)
204+
boolean alreadyBoundHere = domain == null
205+
? isDefaultProviderInstance(newProvider)
206+
: getBoundDomainsForProviderInstance(newProvider).contains(domain);
207+
if (alreadyBoundHere) {
208208
return;
209209
}
210210

211-
if (domain == null) {
212-
if (!currentlyDefault) {
213-
throw new IllegalArgumentException("Domain-scoped provider cannot be bound to more than one domain");
214-
}
215-
return;
216-
}
217-
if (boundNamedDomains.contains(domain)) {
218-
return;
219-
}
220-
if (!boundNamedDomains.isEmpty() || currentlyDefault) {
211+
// any other existing binding means this instance would span more than one domain
212+
if (isDefaultProviderInstance(newProvider)
213+
|| !getBoundDomainsForProviderInstance(newProvider).isEmpty()) {
221214
throw new IllegalArgumentException("Domain-scoped provider cannot be bound to more than one domain");
222215
}
223216
}
@@ -254,7 +247,7 @@ private boolean matchesProvider(FeatureProvider registered, FeatureProvider cand
254247
}
255248

256249
private void initializeProvider(
257-
@Nullable String domain,
250+
String domain,
258251
FeatureProviderStateManager newManager,
259252
Consumer<FeatureProvider> afterInit,
260253
Consumer<FeatureProvider> afterShutdown,

src/main/java/dev/openfeature/sdk/multiprovider/MultiProvider.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.concurrent.ExecutorService;
2020
import java.util.concurrent.Executors;
2121
import java.util.concurrent.Future;
22-
import javax.annotation.Nullable;
2322
import lombok.Getter;
2423
import lombok.extern.slf4j.Slf4j;
2524

@@ -77,9 +76,8 @@ protected static Map<String, FeatureProvider> buildProviders(List<FeatureProvide
7776
}
7877

7978
/**
80-
* Initialize the provider.
79+
* {@inheritDoc}
8180
*
82-
* @param evaluationContext evaluation context
8381
* @throws Exception on error (e.g. wrapped {@link java.util.concurrent.ExecutionException}
8482
* from a failing provider)
8583
*/
@@ -89,15 +87,13 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
8987
}
9088

9189
/**
92-
* Initialize the provider with the bound domain, if any.
90+
* {@inheritDoc}
9391
*
94-
* @param evaluationContext evaluation context
95-
* @param domain the bound domain, or {@code null} for the default provider
9692
* @throws Exception on error (e.g. wrapped {@link java.util.concurrent.ExecutionException}
9793
* from a failing provider)
9894
*/
9995
@Override
100-
public void initialize(EvaluationContext evaluationContext, @Nullable String domain) throws Exception {
96+
public void initialize(EvaluationContext evaluationContext, String domain) throws Exception {
10197
var metadataBuilder = MultiProviderMetadata.builder().name(NAME);
10298
HashMap<String, Metadata> providersMetadata = new HashMap<>();
10399

src/test/java/dev/openfeature/sdk/DomainScopedProviderSpecTest.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ void tearDown() {
4343
+ "`domain`, rejecting any attempt to bind an already-bound instance to an additional `domain`.")
4444
@Test
4545
@DisplayName("rejects binding a domain-scoped provider to a second named domain")
46-
void rejectsBindingDomainScopedProviderToSecondNamedDomain() {
46+
void rejectsBindingDomainScopedProviderToSecondNamedDomain() throws Exception {
4747
DomainScopedTestProvider provider = new DomainScopedTestProvider();
4848

49-
api.setProvider(DOMAIN_A, provider);
49+
api.setProviderAndWait(DOMAIN_A, provider);
5050

5151
assertThatThrownBy(() -> api.setProvider(DOMAIN_B, provider))
5252
.isInstanceOf(IllegalArgumentException.class)
5353
.hasMessageContaining("Domain-scoped provider cannot be bound to more than one domain");
54+
assertThat(provider.initCount()).isOne();
5455
}
5556

5657
@Specification(
@@ -59,14 +60,15 @@ void rejectsBindingDomainScopedProviderToSecondNamedDomain() {
5960
+ "`domain`, rejecting any attempt to bind an already-bound instance to an additional `domain`.")
6061
@Test
6162
@DisplayName("rejects binding a domain-scoped named provider as the default provider")
62-
void rejectsBindingDomainScopedNamedProviderAsDefault() {
63+
void rejectsBindingDomainScopedNamedProviderAsDefault() throws Exception {
6364
DomainScopedTestProvider provider = new DomainScopedTestProvider();
6465

65-
api.setProvider(DOMAIN_A, provider);
66+
api.setProviderAndWait(DOMAIN_A, provider);
6667

6768
assertThatThrownBy(() -> api.setProvider(provider))
6869
.isInstanceOf(IllegalArgumentException.class)
6970
.hasMessageContaining("Domain-scoped provider cannot be bound to more than one domain");
71+
assertThat(provider.initCount()).isOne();
7072
}
7173

7274
@Specification(
@@ -75,14 +77,15 @@ void rejectsBindingDomainScopedNamedProviderAsDefault() {
7577
+ "`domain`, rejecting any attempt to bind an already-bound instance to an additional `domain`.")
7678
@Test
7779
@DisplayName("rejects binding a domain-scoped default provider to a named domain")
78-
void rejectsBindingDomainScopedDefaultProviderToNamedDomain() {
80+
void rejectsBindingDomainScopedDefaultProviderToNamedDomain() throws Exception {
7981
DomainScopedTestProvider provider = new DomainScopedTestProvider();
8082

81-
api.setProvider(provider);
83+
api.setProviderAndWait(provider);
8284

8385
assertThatThrownBy(() -> api.setProvider(DOMAIN_A, provider))
8486
.isInstanceOf(IllegalArgumentException.class)
8587
.hasMessageContaining("Domain-scoped provider cannot be bound to more than one domain");
88+
assertThat(provider.initCount()).isOne();
8689
}
8790

8891
@Test

src/test/java/dev/openfeature/sdk/vmlens/ProviderRepositoryCT.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,35 @@ void concurrentShutdown_multipleProvidersShutdownExactlyOnce() throws Exception
156156
}
157157
}
158158
}
159+
160+
@Test
161+
void concurrentRegistrationToSameDomain_exactlyOneBoundAndLoserShutDown() throws Exception {
162+
final String domain = "domain-1";
163+
try (AllInterleavings allInterleavings = new AllInterleavings("Concurrent registration to the same domain")) {
164+
while (allInterleavings.hasNext()) {
165+
AtomicInteger provider1ShutdownCount = new AtomicInteger(0);
166+
AtomicInteger provider2ShutdownCount = new AtomicInteger(0);
167+
FeatureProvider provider1 = createMockedProvider("provider-1", provider1ShutdownCount);
168+
FeatureProvider provider2 = createMockedProvider("provider-2", provider2ShutdownCount);
169+
OpenFeatureAPI api = OpenFeatureAPITestUtil.createAPI();
170+
171+
// two different providers race to bind to the same domain
172+
Runner.runParallel(
173+
() -> api.setProviderAndWait(domain, provider1),
174+
() -> api.setProviderAndWait(domain, provider2));
175+
176+
// the domain resolves to exactly one of the two providers
177+
FeatureProvider bound = api.getProvider(domain);
178+
assertThat(bound).isIn(provider1, provider2);
179+
180+
// the provider that lost the race is shut down exactly once; the winner is left running
181+
AtomicInteger winner = bound == provider1 ? provider1ShutdownCount : provider2ShutdownCount;
182+
AtomicInteger loser = bound == provider1 ? provider2ShutdownCount : provider1ShutdownCount;
183+
assertThat(loser.get())
184+
.as("Losing provider shut down exactly once")
185+
.isEqualTo(1);
186+
assertThat(winner.get()).as("Winning provider not shut down").isZero();
187+
}
188+
}
189+
}
159190
}

0 commit comments

Comments
 (0)