-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: possible event-related deadlocks with some providers (#1314)
* Move event emitting off the main thread to avoid deadlocks When stacking event emitting inside an EventProvider, when using sychronization the EventProvider can deadlock, to avoid this move the event emitting of the main thread. Signed-off-by: Philipp Fehre <[email protected]> * Test fixes Test provider should respect the init-delay during all test Signed-off-by: Philipp Fehre <[email protected]> * Add timeout to EventProviderTest With the events being executed on a different thread, we need to wait to make sure the thread is scheduled to have the events emitted. Signed-off-by: Philipp Fehre <[email protected]> * Don't reuse the JVM Process Signed-off-by: Philipp Fehre <[email protected]> --------- Signed-off-by: Philipp Fehre <[email protected]> Co-authored-by: Philipp Fehre <[email protected]> Co-authored-by: Michael Beemer <[email protected]> Co-authored-by: Todd Baert <[email protected]>
- Loading branch information
1 parent
08c38fb
commit c33ac2d
Showing
6 changed files
with
163 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package dev.openfeature.sdk.testutils; | ||
|
||
import dev.openfeature.sdk.EvaluationContext; | ||
import dev.openfeature.sdk.EventProvider; | ||
import dev.openfeature.sdk.Metadata; | ||
import dev.openfeature.sdk.ProviderEvaluation; | ||
import dev.openfeature.sdk.ProviderEvent; | ||
import dev.openfeature.sdk.ProviderEventDetails; | ||
import dev.openfeature.sdk.Value; | ||
import java.util.function.Consumer; | ||
|
||
public class TestStackedEmitCallsProvider extends EventProvider { | ||
private final NestedBlockingEmitter nestedBlockingEmitter = new NestedBlockingEmitter(this::onProviderEvent); | ||
|
||
@Override | ||
public Metadata getMetadata() { | ||
return () -> getClass().getSimpleName(); | ||
} | ||
|
||
@Override | ||
public void initialize(EvaluationContext evaluationContext) throws Exception { | ||
synchronized (nestedBlockingEmitter) { | ||
nestedBlockingEmitter.init(); | ||
while (!nestedBlockingEmitter.isReady()) { | ||
try { | ||
nestedBlockingEmitter.wait(); | ||
} catch (InterruptedException e) { | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void onProviderEvent(ProviderEvent providerEvent) { | ||
synchronized (nestedBlockingEmitter) { | ||
if (providerEvent == ProviderEvent.PROVIDER_READY) { | ||
nestedBlockingEmitter.setReady(); | ||
/* | ||
* This line deadlocked in the original implementation without the emitterExecutor see | ||
* https://github.com/open-feature/java-sdk/issues/1299 | ||
*/ | ||
emitProviderReady(ProviderEventDetails.builder().build()); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { | ||
throw new UnsupportedOperationException("Unimplemented method 'getBooleanEvaluation'"); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { | ||
throw new UnsupportedOperationException("Unimplemented method 'getStringEvaluation'"); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { | ||
throw new UnsupportedOperationException("Unimplemented method 'getIntegerEvaluation'"); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { | ||
throw new UnsupportedOperationException("Unimplemented method 'getDoubleEvaluation'"); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { | ||
throw new UnsupportedOperationException("Unimplemented method 'getObjectEvaluation'"); | ||
} | ||
|
||
static class NestedBlockingEmitter { | ||
|
||
private final Consumer<ProviderEvent> emitProviderEvent; | ||
private volatile boolean isReady; | ||
|
||
public NestedBlockingEmitter(Consumer<ProviderEvent> emitProviderEvent) { | ||
this.emitProviderEvent = emitProviderEvent; | ||
} | ||
|
||
public void init() { | ||
// run init outside monitored thread | ||
new Thread(() -> { | ||
try { | ||
Thread.sleep(500); | ||
} catch (InterruptedException e) { | ||
throw new RuntimeException(e); | ||
} | ||
|
||
emitProviderEvent.accept(ProviderEvent.PROVIDER_READY); | ||
}) | ||
.start(); | ||
} | ||
|
||
public boolean isReady() { | ||
return isReady; | ||
} | ||
|
||
public synchronized void setReady() { | ||
isReady = true; | ||
this.notifyAll(); | ||
} | ||
} | ||
} |