Skip to content

Commit b9f3afe

Browse files
author
Philipp Fehre
committed
Move StackedEmitCallsProvider to testUtils
Signed-off-by: Philipp Fehre <[email protected]>
1 parent 12fac55 commit b9f3afe

File tree

4 files changed

+115
-101
lines changed

4 files changed

+115
-101
lines changed

Diff for: src/main/java/dev/openfeature/sdk/EventProvider.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
@Slf4j
2222
public abstract class EventProvider implements FeatureProvider {
2323
private EventProviderListener eventProviderListener;
24-
private static final int SHUTDOWN_TIMEOUT_SECONDS = 3;
25-
private static final ExecutorService emitterExecutor = Executors.newCachedThreadPool(runnable -> {
24+
private final ExecutorService emitterExecutor = Executors.newCachedThreadPool(runnable -> {
2625
final Thread thread = new Thread(runnable);
2726
thread.setDaemon(true);
2827
return thread;
@@ -65,7 +64,7 @@ void detach() {
6564
public void shutdown() {
6665
emitterExecutor.shutdown();
6766
try {
68-
if (!emitterExecutor.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
67+
if (!emitterExecutor.awaitTermination(EventSupport.SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
6968
log.warn("Emitter executor did not terminate before the timeout period had elapsed");
7069
emitterExecutor.shutdownNow();
7170
}

Diff for: src/main/java/dev/openfeature/sdk/EventSupport.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
@Slf4j
2020
class EventSupport {
2121

22+
public static final int SHUTDOWN_TIMEOUT_SECONDS = 3;
23+
2224
// we use a v4 uuid as a "placeholder" for anonymous clients, since
2325
// ConcurrentHashMap doesn't support nulls
2426
private static final String defaultClientUuid = UUID.randomUUID().toString();
25-
private static final int SHUTDOWN_TIMEOUT_SECONDS = 3;
2627
private final Map<String, HandlerStore> handlerStores = new ConcurrentHashMap<>();
2728
private final HandlerStore globalHandlerStore = new HandlerStore();
2829
private final ExecutorService taskExecutor = Executors.newCachedThreadPool(runnable -> {

Diff for: src/test/java/dev/openfeature/sdk/EventProviderTest.java

+8-97
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import static org.mockito.Mockito.verify;
99

1010
import dev.openfeature.sdk.internal.TriConsumer;
11-
import java.util.function.Consumer;
11+
import dev.openfeature.sdk.testutils.TestStackedEmitCallsProvider;
12+
import io.cucumber.java.AfterAll;
1213
import lombok.SneakyThrows;
1314
import org.junit.jupiter.api.BeforeEach;
1415
import org.junit.jupiter.api.DisplayName;
@@ -26,6 +27,11 @@ void setup() {
2627
eventProvider.initialize(null);
2728
}
2829

30+
@AfterAll
31+
public static void resetDefaultProvider() {
32+
OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
33+
}
34+
2935
@Test
3036
@DisplayName("should run attached onEmit with emitters")
3137
void emitsEventsWhenAttached() {
@@ -85,105 +91,10 @@ void doesNotThrowWhenOnEmitSame() {
8591
@Timeout(value = 2, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
8692
@DisplayName("should not deadlock on emit called during emit")
8793
void doesNotDeadlockOnEmitStackedCalls() {
88-
StackedEmitCallsProvider provider = new StackedEmitCallsProvider();
94+
TestStackedEmitCallsProvider provider = new TestStackedEmitCallsProvider();
8995
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
9096
}
9197

92-
static class StackedEmitCallsProvider extends EventProvider {
93-
private final NestedBlockingEmitter nestedBlockingEmitter = new NestedBlockingEmitter(this::onProviderEvent);
94-
95-
@Override
96-
public Metadata getMetadata() {
97-
return () -> getClass().getSimpleName();
98-
}
99-
100-
@Override
101-
public void initialize(EvaluationContext evaluationContext) throws Exception {
102-
synchronized (nestedBlockingEmitter) {
103-
nestedBlockingEmitter.init();
104-
while (!nestedBlockingEmitter.isReady()) {
105-
try {
106-
nestedBlockingEmitter.wait();
107-
} catch (InterruptedException e) {
108-
}
109-
}
110-
}
111-
}
112-
113-
private void onProviderEvent(ProviderEvent providerEvent) {
114-
synchronized (nestedBlockingEmitter) {
115-
if (providerEvent == ProviderEvent.PROVIDER_READY) {
116-
nestedBlockingEmitter.setReady();
117-
/*
118-
* This line deadlocked in the original implementation without the emitterExecutor see
119-
* https://github.com/open-feature/java-sdk/issues/1299
120-
*/
121-
emitProviderReady(ProviderEventDetails.builder().build());
122-
}
123-
}
124-
}
125-
126-
@Override
127-
public ProviderEvaluation<Boolean> getBooleanEvaluation(
128-
String key, Boolean defaultValue, EvaluationContext ctx) {
129-
throw new UnsupportedOperationException("Unimplemented method 'getBooleanEvaluation'");
130-
}
131-
132-
@Override
133-
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
134-
throw new UnsupportedOperationException("Unimplemented method 'getStringEvaluation'");
135-
}
136-
137-
@Override
138-
public ProviderEvaluation<Integer> getIntegerEvaluation(
139-
String key, Integer defaultValue, EvaluationContext ctx) {
140-
throw new UnsupportedOperationException("Unimplemented method 'getIntegerEvaluation'");
141-
}
142-
143-
@Override
144-
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
145-
throw new UnsupportedOperationException("Unimplemented method 'getDoubleEvaluation'");
146-
}
147-
148-
@Override
149-
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
150-
throw new UnsupportedOperationException("Unimplemented method 'getObjectEvaluation'");
151-
}
152-
}
153-
154-
static class NestedBlockingEmitter {
155-
156-
private final Consumer<ProviderEvent> emitProviderEvent;
157-
private volatile boolean isReady;
158-
159-
public NestedBlockingEmitter(Consumer<ProviderEvent> emitProviderEvent) {
160-
this.emitProviderEvent = emitProviderEvent;
161-
}
162-
163-
public void init() {
164-
// run init outside monitored thread
165-
new Thread(() -> {
166-
try {
167-
Thread.sleep(500);
168-
} catch (InterruptedException e) {
169-
throw new RuntimeException(e);
170-
}
171-
172-
emitProviderEvent.accept(ProviderEvent.PROVIDER_READY);
173-
})
174-
.start();
175-
}
176-
177-
public boolean isReady() {
178-
return isReady;
179-
}
180-
181-
public synchronized void setReady() {
182-
isReady = true;
183-
this.notifyAll();
184-
}
185-
}
186-
18798
static class TestEventProvider extends EventProvider {
18899

189100
private static final String NAME = "TestEventProvider";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package dev.openfeature.sdk.testutils;
2+
3+
import dev.openfeature.sdk.EvaluationContext;
4+
import dev.openfeature.sdk.EventProvider;
5+
import dev.openfeature.sdk.Metadata;
6+
import dev.openfeature.sdk.ProviderEvaluation;
7+
import dev.openfeature.sdk.ProviderEvent;
8+
import dev.openfeature.sdk.ProviderEventDetails;
9+
import dev.openfeature.sdk.Value;
10+
import java.util.function.Consumer;
11+
12+
public class TestStackedEmitCallsProvider extends EventProvider {
13+
private final NestedBlockingEmitter nestedBlockingEmitter = new NestedBlockingEmitter(this::onProviderEvent);
14+
15+
@Override
16+
public Metadata getMetadata() {
17+
return () -> getClass().getSimpleName();
18+
}
19+
20+
@Override
21+
public void initialize(EvaluationContext evaluationContext) throws Exception {
22+
synchronized (nestedBlockingEmitter) {
23+
nestedBlockingEmitter.init();
24+
while (!nestedBlockingEmitter.isReady()) {
25+
try {
26+
nestedBlockingEmitter.wait();
27+
} catch (InterruptedException e) {
28+
}
29+
}
30+
}
31+
}
32+
33+
private void onProviderEvent(ProviderEvent providerEvent) {
34+
synchronized (nestedBlockingEmitter) {
35+
if (providerEvent == ProviderEvent.PROVIDER_READY) {
36+
nestedBlockingEmitter.setReady();
37+
/*
38+
* This line deadlocked in the original implementation without the emitterExecutor see
39+
* https://github.com/open-feature/java-sdk/issues/1299
40+
*/
41+
emitProviderReady(ProviderEventDetails.builder().build());
42+
}
43+
}
44+
}
45+
46+
@Override
47+
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
48+
throw new UnsupportedOperationException("Unimplemented method 'getBooleanEvaluation'");
49+
}
50+
51+
@Override
52+
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
53+
throw new UnsupportedOperationException("Unimplemented method 'getStringEvaluation'");
54+
}
55+
56+
@Override
57+
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
58+
throw new UnsupportedOperationException("Unimplemented method 'getIntegerEvaluation'");
59+
}
60+
61+
@Override
62+
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
63+
throw new UnsupportedOperationException("Unimplemented method 'getDoubleEvaluation'");
64+
}
65+
66+
@Override
67+
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
68+
throw new UnsupportedOperationException("Unimplemented method 'getObjectEvaluation'");
69+
}
70+
71+
static class NestedBlockingEmitter {
72+
73+
private final Consumer<ProviderEvent> emitProviderEvent;
74+
private volatile boolean isReady;
75+
76+
public NestedBlockingEmitter(Consumer<ProviderEvent> emitProviderEvent) {
77+
this.emitProviderEvent = emitProviderEvent;
78+
}
79+
80+
public void init() {
81+
// run init outside monitored thread
82+
new Thread(() -> {
83+
try {
84+
Thread.sleep(500);
85+
} catch (InterruptedException e) {
86+
throw new RuntimeException(e);
87+
}
88+
89+
emitProviderEvent.accept(ProviderEvent.PROVIDER_READY);
90+
})
91+
.start();
92+
}
93+
94+
public boolean isReady() {
95+
return isReady;
96+
}
97+
98+
public synchronized void setReady() {
99+
isReady = true;
100+
this.notifyAll();
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)