Skip to content
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

feat: events #476

Merged
merged 20 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -178,21 +178,6 @@

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<phase>validate</phase>
<id>get-cpu-count</id>
<goals>
<goal>cpu-count</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
Expand Down Expand Up @@ -261,9 +246,6 @@
<argLine>
${surefireArgLine}
</argLine>
<!-- fork a new JVM to isolate test suites, especially important with singletons -->
<forkCount>${cpu.count}</forkCount>
<reuseForks>false</reuseForks>
Comment on lines -264 to -266
Copy link
Member Author

@toddbaert toddbaert Jun 22, 2023

Choose a reason for hiding this comment

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

As witnessed by @Kavindu-Dodan the test suite is actually 3x faster without any forking at all, possibly due to JVM startup time 🤷

Copy link
Contributor

Choose a reason for hiding this comment

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

This observation can be explained with the surefire documentation [1]

The parameter reuseForks is used to define whether to terminate the spawned process after one test class and to create a new process for the next test in line (reuseForks=false), or whether to reuse the processes to execute the next tests (reuseForks=true).

forkCount helped with creating N parallel forks but, given that reuseForks was false, tests did not reuse them. This means, while tests ran in parallel forks, new tests require a new process, adding a delay between executions.

[1] - https://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html#forked-test-execution

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks!

I guessed it was something like that. It makes sense that starting up a bunch of JVMs would slow things down.

I did just try <forkCount>1C</forkCount> (which means 1 x number-of-cores), while allowing fork re-use, and the speed of the suite is unchanged from the default... so it doesn't slow things down but doesn't really help much.

<excludes>
<!-- tests to exclude -->
<exclude>${testExclusions}</exclude>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/dev/openfeature/sdk/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/**
* Interface used to resolve flags of varying types.
*/
public interface Client extends Features {
public interface Client extends Features, EventBus<Client> {
Metadata getMetadata();

/**
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/dev/openfeature/sdk/EventBus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dev.openfeature.sdk;

import java.util.function.Consumer;

/**
* Interface for attaching event handlers.
*/
public interface EventBus<T> {

/**
* Add a handler for the {@link ProviderEvent#PROVIDER_READY} event.
* Shorthand for {@link #on(ProviderEvent, Consumer)}
*
* @param handler behavior to add with this event
* @return this
*/
T onProviderReady(Consumer<EventDetails> handler);

/**
* Add a handler for the {@link ProviderEvent#PROVIDER_CONFIGURATION_CHANGED} event.
* Shorthand for {@link #on(ProviderEvent, Consumer)}
*
* @param handler behavior to add with this event
* @return this
*/
T onProviderConfigurationChanged(Consumer<EventDetails> handler);

/**
* Add a handler for the {@link ProviderEvent#PROVIDER_STALE} event.
* Shorthand for {@link #on(ProviderEvent, Consumer)}
*
* @param handler behavior to add with this event
* @return this
*/
T onProviderError(Consumer<EventDetails> handler);

/**
* Add a handler for the {@link ProviderEvent#PROVIDER_ERROR} event.
* Shorthand for {@link #on(ProviderEvent, Consumer)}
*
* @param handler behavior to add with this event
* @return this
*/
T onProviderStale(Consumer<EventDetails> handler);

/**
* Add a handler for the specified {@link ProviderEvent}.
*
* @param event event type
* @param handler behavior to add with this event
* @return this
*/
T on(ProviderEvent event, Consumer<EventDetails> handler);

/**
* Remove the previously attached handler by reference.
* If the handler doesn't exists, no-op.
*
* @param event event type
* @param handler to be removed
* @return this
*/
T removeHandler(ProviderEvent event, Consumer<EventDetails> handler);
}
28 changes: 28 additions & 0 deletions src/main/java/dev/openfeature/sdk/EventDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.openfeature.sdk;

import edu.umd.cs.findbugs.annotations.Nullable;
import lombok.Data;
import lombok.experimental.SuperBuilder;

/**
* The details of a particular event.
*/
@Data @SuperBuilder(toBuilder = true)
public class EventDetails extends ProviderEventDetails {
private String clientName;

static EventDetails fromProviderEventDetails(ProviderEventDetails providerEventDetails) {
return EventDetails.fromProviderEventDetails(providerEventDetails, null);
}

static EventDetails fromProviderEventDetails(
ProviderEventDetails providerEventDetails,
@Nullable String clientName) {
return EventDetails.builder()
.clientName(clientName)
.flagsChanged(providerEventDetails.getFlagsChanged())
.eventMetadata(providerEventDetails.getEventMetadata())
.message(providerEventDetails.getMessage())
.build();
}
}
95 changes: 95 additions & 0 deletions src/main/java/dev/openfeature/sdk/EventProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package dev.openfeature.sdk;

import dev.openfeature.sdk.internal.TriConsumer;

/**
* Abstract EventProvider. Providers must extend this class to support events.
* Emit events with {@link #emit(ProviderEvent, ProviderEventDetails)}. Please
* note that the SDK will automatically emit
* {@link ProviderEvent#PROVIDER_READY } or
* {@link ProviderEvent#PROVIDER_ERROR } accordingly when
* {@link FeatureProvider#initialize(EvaluationContext)} completes successfully
* or with error, so these events need not be emitted manually during
* initialization.
*
* @see FeatureProvider
*/
public abstract class EventProvider implements FeatureProvider {
Copy link
Member Author

@toddbaert toddbaert Jun 22, 2023

Choose a reason for hiding this comment

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

This is the class application authors must extend to use events. See javadoc above.


private TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit = null;

/**
* "Attach" this EventProvider to an SDK, which allows events to propagate from this provider.
* No-op if the same onEmit is already attached.
*
* @param onEmit the function to run when a provider emits events.
*/
void attach(TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit) {
if (this.onEmit != null && this.onEmit != onEmit) {
// if we are trying to attach this provider to a different onEmit, something has gone wrong
throw new IllegalStateException("Provider " + this.getMetadata().getName() + " is already attached.");
} else {
this.onEmit = onEmit;
}
}

/**
* "Detach" this EventProvider from an SDK, stopping propagation of all events.
*/
void detach() {
this.onEmit = null;
}

/**
* Emit the specified {@link ProviderEvent}.
*
* @param event The event type
* @param details The details of the event
*/
public void emit(ProviderEvent event, ProviderEventDetails details) {
if (this.onEmit != null) {
this.onEmit.accept(this, event, details);
}
}

/**
* Emit a {@link ProviderEvent#PROVIDER_READY} event.
* Shorthand for {@link #emit(ProviderEvent, ProviderEventDetails)}
*
* @param details The details of the event
*/
public void emitProviderReady(ProviderEventDetails details) {
emit(ProviderEvent.PROVIDER_READY, details);
}

/**
* Emit a
* {@link ProviderEvent#PROVIDER_CONFIGURATION_CHANGED}
* event. Shorthand for {@link #emit(ProviderEvent, ProviderEventDetails)}
*
* @param details The details of the event
*/
public void emitProviderConfigurationChanged(ProviderEventDetails details) {
emit(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, details);
}

/**
* Emit a {@link ProviderEvent#PROVIDER_STALE} event.
* Shorthand for {@link #emit(ProviderEvent, ProviderEventDetails)}
*
* @param details The details of the event
*/
public void emitProviderStale(ProviderEventDetails details) {
emit(ProviderEvent.PROVIDER_STALE, details);
}

/**
* Emit a {@link ProviderEvent#PROVIDER_ERROR} event.
* Shorthand for {@link #emit(ProviderEvent, ProviderEventDetails)}
*
* @param details The details of the event
*/
public void emitProviderError(ProviderEventDetails details) {
emit(ProviderEvent.PROVIDER_ERROR, details);
}
}
Loading
Loading