diff --git a/pom.xml b/pom.xml
index c4461155f..6241dc806 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,14 +77,14 @@
1.4.1
test
-
-
- org.mockito
- mockito-core
- ${org.mockito.version}
- test
-
-
+
org.assertj
assertj-core
@@ -167,6 +167,11 @@
1.37
test
+
+ com.vmlens
+ api
+ 1.2.11
+
@@ -176,6 +181,7 @@
+
@@ -206,7 +213,6 @@
pom
import
-
@@ -298,7 +304,24 @@
-
+
+ com.vmlens
+ vmlens-maven-plugin
+ 1.2.11
+
+
+ test
+
+ test
+
+
+
+
+
+ **/VmLensTest.java
+
+
+
@@ -327,7 +350,8 @@
com.github.spotbugs:*
org.junit*
com.tngtech.archunit*
- org.simplify4u:slf4j2-mock*
+ org.simplify4u:slf4j2-mock*
+
com.google.guava*
diff --git a/sdk.iml b/sdk.iml
new file mode 100644
index 000000000..9e3449c9d
--- /dev/null
+++ b/sdk.iml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/dev/openfeature/sdk/Awaitable.java b/src/main/java/dev/openfeature/sdk/Awaitable.java
index 7d5f477dc..e74ef0e4e 100644
--- a/src/main/java/dev/openfeature/sdk/Awaitable.java
+++ b/src/main/java/dev/openfeature/sdk/Awaitable.java
@@ -10,7 +10,7 @@ public class Awaitable {
*/
public static final Awaitable FINISHED = new Awaitable(true);
- private boolean isDone = false;
+ private volatile boolean isDone = false;
public Awaitable() {}
diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
index b5522b66a..64a0c6119 100644
--- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
+++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
@@ -1,11 +1,14 @@
package dev.openfeature.sdk;
+import com.vmlens.api.AllInterleavings;
import dev.openfeature.sdk.exceptions.ExceptionUtils;
import dev.openfeature.sdk.exceptions.FatalError;
import dev.openfeature.sdk.exceptions.GeneralError;
import dev.openfeature.sdk.exceptions.OpenFeatureError;
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
import dev.openfeature.sdk.internal.ObjectUtils;
+import dev.openfeature.sdk.providers.memory.Flag;
+import dev.openfeature.sdk.providers.memory.InMemoryProvider;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Arrays;
@@ -15,6 +18,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import lombok.Getter;
@@ -515,4 +519,117 @@ public Client removeHandler(ProviderEvent event, Consumer handler)
openfeatureApi.removeHandler(domain, event, handler);
return this;
}
+
+ static int jaVar = 0;
+
+ /**
+ * run.
+ *
+ * @param args the args
+ * @throws InterruptedException something
+ */
+ public static void main(String[] args) throws InterruptedException {
+
+ System.out.println("OpenFeatureClientTest.testUpdate");
+ final OpenFeatureAPI api = new OpenFeatureAPI();
+
+ var flags = new HashMap>();
+ flags.put("a", Flag.builder().variant("a", "def").defaultVariant("a").build());
+ flags.put("b", Flag.builder().variant("a", "as").defaultVariant("a").build());
+ flags.put("c", Flag.builder().variant("a", "dfs").defaultVariant("a").build());
+ flags.put("d", Flag.builder().variant("a", "asddd").defaultVariant("a").build());
+ api.setProviderAndWait(new InMemoryProvider(flags));
+ var i = 0;
+ var c = new AtomicInteger();
+
+ System.out.println("before try");
+ try (AllInterleavings allInterleavings = new AllInterleavings("Concurrent evaluations and hook additions")) {
+ while (allInterleavings.hasNext()) {
+ c.incrementAndGet();
+ jaVar = 0;
+ Thread first = new Thread() {
+ @Override
+ public void run() {
+ jaVar++;
+ }
+ };
+ first.start();
+ jaVar++;
+ first.join();
+ if (jaVar != 2) {
+ throw new RuntimeException("jaVar=" + jaVar);
+ }
+ /*
+ var client = api.getClient();
+ var firstReady = new Awaitable();
+ var secondReady = new Awaitable();
+ var startThreads = new Awaitable();
+ Thread first = new Thread() {
+ @Override
+ public void run() {
+ firstReady.wakeup();
+ startThreads.await();
+ client.getStringValue("a", "a");
+ }
+ };
+ Thread hookAdder = new Thread() {
+ @Override
+ public void run() {
+ secondReady.wakeup();
+ startThreads.await();
+ client.addHooks(new Hook() {});
+ }
+ };
+ hookAdder.start();
+ first.start();
+ firstReady.await();
+ secondReady.await();
+ startThreads.wakeup();
+ first.join();
+ hookAdder.join();
+*/
+ /*System.out.println("has interlereaving");
+ i++;
+ //var latch = new CountDownLatch(1);
+ var client = api.getClient();
+ var concurrentModException = new AtomicReference();
+ Thread eval = new Thread(() -> {
+ System.out.println("eval");
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {
+ }
+ client.getStringValue("a", "a");
+ client.getStringValue("b", "a");
+ client.getStringValue("c", "a");
+ client.getStringValue("d", "a");
+
+ });
+ Thread hookAdder = new Thread(() -> {
+ System.out.println("hook adder");
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {
+ }
+ client.addHooks(new Hook() {});
+
+ });
+ System.out.println("starting...");
+ //eval.start();
+ //hookAdder.start();
+ System.out.println("started");
+ Thread.sleep(200);
+ client.getStringValue("d", "a");
+ client.addHooks(new Hook() {});
+ client.getStringValue("d", "a");
+ //latch.countDown();
+ eval.join();
+ hookAdder.join();
+ System.out.println(concurrentModException.get());*/
+ }
+ }
+ System.out.println("i = " + i);
+ System.out.println("jaVar = " + jaVar);
+ System.out.println("c.get() = " + c.get());
+ }
}
diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
index 97a1417a1..ea9087255 100644
--- a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
+++ b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
@@ -2,16 +2,25 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import com.vmlens.api.AllInterleavings;
import dev.openfeature.sdk.exceptions.FatalError;
import dev.openfeature.sdk.fixtures.HookFixtures;
+import dev.openfeature.sdk.providers.memory.Flag;
+import dev.openfeature.sdk.providers.memory.InMemoryProvider;
import dev.openfeature.sdk.testutils.TestEventsProvider;
+import java.util.ConcurrentModificationException;
import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -104,4 +113,64 @@ void shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState() {
assertThat(details.getErrorCode()).isEqualTo(ErrorCode.PROVIDER_NOT_READY);
}
+
+ //@Test
+ void a() throws InterruptedException {
+ OpenFeatureAPI api = new OpenFeatureAPI();
+
+ var flags = new HashMap>();
+ flags.put("a", Flag.builder().variant("a", "def").defaultVariant("a").build());
+ flags.put("b", Flag.builder().variant("a", "as").defaultVariant("a").build());
+ flags.put("c", Flag.builder().variant("a", "dfs").defaultVariant("a").build());
+ flags.put("d", Flag.builder().variant("a", "asddd").defaultVariant("a").build());
+ api.setProviderAndWait(new InMemoryProvider(flags));
+
+ var countDownLatch = new CountDownLatch(1);
+ var client = new OpenFeatureClient(api, "name", "version");
+ var isRunning = new AtomicBoolean(true);
+ var eval = new Thread(() -> {
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ System.out.println("starting eval");
+ while (isRunning.get()) {
+ client.getStringValue("a", "def");
+ client.getStringValue("b", "def");
+ client.getStringValue("c", "def");
+ client.getStringValue("d", "def");
+ }
+ });
+ var hook = new Thread(() -> {
+ try {
+ countDownLatch.await();
+ Thread.sleep(5);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ System.out.println("starting hook");
+ while (isRunning.get()) {
+ for (int i = 0; i < 100; i++) {
+
+ client.addHooks(new Hook() {});
+ }
+
+ for (int i = 0; i < 90; i++) {
+
+ client.getHooks().remove(4);
+ }
+ }
+ });
+
+ eval.start();
+ hook.start();
+ Thread.sleep(100);
+ countDownLatch.countDown();
+ Thread.sleep(40000);
+ isRunning.set(false);
+ eval.join();
+ hook.join();
+ System.out.println(client.getHooks().size());
+ }
}
diff --git a/src/test/java/dev/openfeature/sdk/VmLensTest.java b/src/test/java/dev/openfeature/sdk/VmLensTest.java
new file mode 100644
index 000000000..59961dd87
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/VmLensTest.java
@@ -0,0 +1,44 @@
+package dev.openfeature.sdk;
+
+import com.vmlens.api.AllInterleavings;
+import com.vmlens.api.Runner;
+import dev.openfeature.sdk.providers.memory.Flag;
+import dev.openfeature.sdk.providers.memory.InMemoryProvider;
+import org.junit.jupiter.api.Test;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Javadoc.
+ */
+public class VmLensTest {
+ public static void main(String[] args) throws InterruptedException {
+ new VmLensTest().asomeMethod();
+ }
+
+ @Test
+ public void asomeMethod() throws InterruptedException {
+ var c = new AtomicInteger();
+ final OpenFeatureAPI api = new OpenFeatureAPI();
+
+ var flags = new HashMap>();
+ flags.put("a", Flag.builder().variant("a", "def").defaultVariant("a").build());
+ flags.put("b", Flag.builder().variant("a", "as").defaultVariant("a").build());
+ flags.put("c", Flag.builder().variant("a", "dfs").defaultVariant("a").build());
+ flags.put("d", Flag.builder().variant("a", "asddd").defaultVariant("a").build());
+ api.setProviderAndWait(new InMemoryProvider(flags));
+
+ try (AllInterleavings allInterleavings = new AllInterleavings("Concurrent evaluations and hook additions")) {
+ while (allInterleavings.hasNext()) {
+ var client = api.getClient();
+ c.incrementAndGet();
+ Runner.runParallel(
+ () -> client.getStringValue("a", "a"),
+ () -> client.addHooks(new Hook() {})
+ );
+ }
+ }
+ api.shutdown();
+ System.out.println("c = " + c);
+ }
+}
diff --git a/src/test/java/dev/openfeature/sdk/testutils/VmLensTest.java b/src/test/java/dev/openfeature/sdk/testutils/VmLensTest.java
new file mode 100644
index 000000000..7256dacca
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/testutils/VmLensTest.java
@@ -0,0 +1,36 @@
+package dev.openfeature.sdk.testutils;
+
+import com.vmlens.api.AllInterleavings;
+import org.junit.jupiter.api.Test;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class VmLensTest {
+ int j = 0;
+
+ @Test
+ void concurrentEvalAndHooks() throws InterruptedException {
+ var c = new AtomicInteger();
+
+ try (AllInterleavings allInterleavings = new AllInterleavings("Concurrent evaluations and hook additions")) {
+ while (allInterleavings.hasNext()) {
+ c.incrementAndGet();
+ j = 0;
+ Thread first = new Thread() {
+ @Override
+ public void run() {
+ j++;
+ }
+ };
+ first.start();
+ j++;
+ first.join();
+ if (j != 2) {
+ throw new RuntimeException("j=" + j);
+ }
+ }
+ }
+
+ System.out.println("c = " + c);
+ System.out.println("j = " + j);
+ }
+}
diff --git a/standalone/pom.xml b/standalone/pom.xml
new file mode 100644
index 000000000..b4498316b
--- /dev/null
+++ b/standalone/pom.xml
@@ -0,0 +1,137 @@
+
+
+ 4.0.0
+
+
+ com.vmlens
+ vmlens
+ 1.2.3
+
+
+ standalone
+
+ https://github.com/vmlens/vlmens
+ api to control vmlens
+
+
+
+ Apache License, Version 2.0
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ vmlens
+ https://www.vmlens.com
+
+
+
+
+
+ Thomas Krieger
+ thomas.krieger@vmlens.com
+ vmlens
+ https://www.vmlens.com
+
+
+
+
+ scm:git:git@github.com:vmlens/vlmens.git
+ scm:git:git@github.com:vmlens/vlmens.git
+ git@github.com:vmlens/vlmens.git
+
+
+
+ UTF-8
+
+
+
+
+ com.vmlens
+ sync-bug
+
+
+ info.picocli
+ picocli
+
+
+
+
+
+
+ maven-clean-plugin
+
+
+
+ src/main/resources/
+
+ **/*.jar
+
+
+
+
+
+
+ maven-resources-plugin
+ 3.3.1
+
+
+ copy-resources
+ generate-sources
+
+ copy-resources
+
+
+ src/main/resources/
+
+
+ ../vmlens-maven-plugin/_dist
+ false
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ info.picocli
+ picocli-codegen
+ 4.7.7
+
+
+
+ -Aproject=${project.groupId}/${project.artifactId}
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+
+ package
+
+ shade
+
+
+
+
+ com.vmlens.Standalone
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/standalone/src/main/java/com/vmlens/Install.java b/standalone/src/main/java/com/vmlens/Install.java
new file mode 100644
index 000000000..6fe655c17
--- /dev/null
+++ b/standalone/src/main/java/com/vmlens/Install.java
@@ -0,0 +1,18 @@
+package com.vmlens;
+
+import com.vmlens.setup.EventDirectoryAndArgLine;
+import com.vmlens.setup.Setup;
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "install", description = "Creates the agent directory. Print the vm parameter to System.out")
+public class Install implements Runnable {
+
+ @CommandLine.ParentCommand
+ private Standalone parent;
+
+ @Override
+ public void run() {
+ EventDirectoryAndArgLine result = new Setup(parent.agentDirectory, "").setup();
+ System.out.println("use " + result.argLine() + "as vm parameter");
+ }
+}
diff --git a/standalone/src/main/java/com/vmlens/Report.java b/standalone/src/main/java/com/vmlens/Report.java
new file mode 100644
index 000000000..10a7f9e1d
--- /dev/null
+++ b/standalone/src/main/java/com/vmlens/Report.java
@@ -0,0 +1,29 @@
+package com.vmlens;
+
+import com.anarsoft.race.detection.main.ProcessEvents;
+import com.vmlens.report.assertion.OnDescriptionAndLeftBeforeRightNoOp;
+import com.vmlens.report.assertion.OnEventNoOp;
+import picocli.CommandLine;
+
+import java.io.File;
+
+import static com.vmlens.setup.Setup.*;
+
+@CommandLine.Command(name = "report", description = "Creates the report.")
+public class Report implements Runnable {
+
+ @CommandLine.ParentCommand
+ private Standalone parent;
+
+ @Override
+ public void run() {
+ File eventDirectory = new File(eventDir(parent.agentDirectory) );
+ File reportDirectory = parent.reportDirectory;
+
+ new ProcessEvents(eventDirectory.toPath(),
+ reportDirectory.toPath(),
+ new OnDescriptionAndLeftBeforeRightNoOp(), new OnEventNoOp()).process();
+
+ System.out.println("Report written to: " + reportDirectory.getAbsolutePath());
+ }
+}
diff --git a/standalone/src/main/java/com/vmlens/Standalone.java b/standalone/src/main/java/com/vmlens/Standalone.java
new file mode 100644
index 000000000..af4bc7c30
--- /dev/null
+++ b/standalone/src/main/java/com/vmlens/Standalone.java
@@ -0,0 +1,38 @@
+package com.vmlens;
+
+import picocli.CommandLine;
+
+import java.io.File;
+
+import static com.vmlens.Standalone.DESCRIPTION;
+import static com.vmlens.setup.Setup.AGENT_DIRECTORY;
+import static com.vmlens.setup.Setup.REPORT_DIRECTORY;
+
+@CommandLine.Command(name = "", description = DESCRIPTION, subcommands = {
+ Install.class,
+ Report.class
+})
+public class Standalone {
+
+ public static final String DESCRIPTION = "Run tests with vmlens.\n" +
+ "1) Call install to create the agent directory. This also prints the vm parameter to System.out.\n" +
+ "2) Run your test with the given vm parameter.\n" +
+ "3) Call report to create the reports and check for data races.";
+
+ @CommandLine.Option(names = {"-a", "--agent"},
+ defaultValue = AGENT_DIRECTORY,
+ description = "The agent directory. Default: " + AGENT_DIRECTORY)
+ File agentDirectory;
+
+ @CommandLine.Option(names = {"-r", "--report"},
+ defaultValue = REPORT_DIRECTORY,
+ description = "The report directory. Default: " + REPORT_DIRECTORY)
+ File reportDirectory;
+
+
+ public static void main(String[] args) {
+ int exitCode = new CommandLine(new Standalone()).execute(args);
+ System.exit(exitCode);
+ }
+
+}
\ No newline at end of file
diff --git a/standalone/src/main/resources/agent_lib/agent.jar b/standalone/src/main/resources/agent_lib/agent.jar
new file mode 100644
index 000000000..c9fd33c96
Binary files /dev/null and b/standalone/src/main/resources/agent_lib/agent.jar differ
diff --git a/standalone/src/main/resources/agent_lib/agent_bootstrap.jar b/standalone/src/main/resources/agent_lib/agent_bootstrap.jar
new file mode 100644
index 000000000..92094cb47
Binary files /dev/null and b/standalone/src/main/resources/agent_lib/agent_bootstrap.jar differ
diff --git a/standalone/src/main/resources/agent_lib/agent_runtime.jar b/standalone/src/main/resources/agent_lib/agent_runtime.jar
new file mode 100644
index 000000000..753060c6f
Binary files /dev/null and b/standalone/src/main/resources/agent_lib/agent_runtime.jar differ
diff --git a/vmlens-agent/agent.jar b/vmlens-agent/agent.jar
new file mode 100644
index 000000000..c9fd33c96
Binary files /dev/null and b/vmlens-agent/agent.jar differ
diff --git a/vmlens-agent/agent_bootstrap.jar b/vmlens-agent/agent_bootstrap.jar
new file mode 100644
index 000000000..92094cb47
Binary files /dev/null and b/vmlens-agent/agent_bootstrap.jar differ
diff --git a/vmlens-agent/agent_runtime.jar b/vmlens-agent/agent_runtime.jar
new file mode 100644
index 000000000..753060c6f
Binary files /dev/null and b/vmlens-agent/agent_runtime.jar differ
diff --git a/vmlens-agent/run.properties b/vmlens-agent/run.properties
new file mode 100644
index 000000000..a717d6b7f
--- /dev/null
+++ b/vmlens-agent/run.properties
@@ -0,0 +1,3 @@
+#
+#Wed Jun 11 14:43:21 CEST 2025
+eventDir=Z\:\\Workspaces\\vmlens\\vmlens-agent/vmlens/
diff --git a/vmlens-agent/vmlens/agentlog.vmlens b/vmlens-agent/vmlens/agentlog.vmlens
new file mode 100644
index 000000000..6c98d5b25
Binary files /dev/null and b/vmlens-agent/vmlens/agentlog.vmlens differ
diff --git a/vmlens-agent/vmlens/control.vmlens b/vmlens-agent/vmlens/control.vmlens
new file mode 100644
index 000000000..1970906b7
Binary files /dev/null and b/vmlens-agent/vmlens/control.vmlens differ
diff --git a/vmlens-agent/vmlens/controlstatistic.vmlens b/vmlens-agent/vmlens/controlstatistic.vmlens
new file mode 100644
index 000000000..492f766c5
Binary files /dev/null and b/vmlens-agent/vmlens/controlstatistic.vmlens differ
diff --git a/vmlens-agent/vmlens/description.vmlens b/vmlens-agent/vmlens/description.vmlens
new file mode 100644
index 000000000..e31784392
Binary files /dev/null and b/vmlens-agent/vmlens/description.vmlens differ
diff --git a/vmlens-agent/vmlens/interleave.vmlens b/vmlens-agent/vmlens/interleave.vmlens
new file mode 100644
index 000000000..6dca83452
Binary files /dev/null and b/vmlens-agent/vmlens/interleave.vmlens differ
diff --git a/vmlens-agent/vmlens/interleavestatistic.vmlens b/vmlens-agent/vmlens/interleavestatistic.vmlens
new file mode 100644
index 000000000..891d8a60b
Binary files /dev/null and b/vmlens-agent/vmlens/interleavestatistic.vmlens differ
diff --git a/vmlens-agent/vmlens/method.vmlens b/vmlens-agent/vmlens/method.vmlens
new file mode 100644
index 000000000..b0143774d
Binary files /dev/null and b/vmlens-agent/vmlens/method.vmlens differ
diff --git a/vmlens-agent/vmlens/methodstatistic.vmlens b/vmlens-agent/vmlens/methodstatistic.vmlens
new file mode 100644
index 000000000..70d481c51
Binary files /dev/null and b/vmlens-agent/vmlens/methodstatistic.vmlens differ
diff --git a/vmlens-agent/vmlens/nonvolatile.vmlens b/vmlens-agent/vmlens/nonvolatile.vmlens
new file mode 100644
index 000000000..5b43a6cfb
Binary files /dev/null and b/vmlens-agent/vmlens/nonvolatile.vmlens differ
diff --git a/vmlens-agent/vmlens/nonvolatilestatistic.vmlens b/vmlens-agent/vmlens/nonvolatilestatistic.vmlens
new file mode 100644
index 000000000..15e4af6b2
Binary files /dev/null and b/vmlens-agent/vmlens/nonvolatilestatistic.vmlens differ
diff --git a/vmlens-agent/vmlens/threadandloop.vmlens b/vmlens-agent/vmlens/threadandloop.vmlens
new file mode 100644
index 000000000..5eebffa12
Binary files /dev/null and b/vmlens-agent/vmlens/threadandloop.vmlens differ