fix: address concurrency issues and add stress tests

This commit is contained in:
2026-03-13 16:15:07 +00:00
parent 28cf21f89a
commit a7d71293a5
7 changed files with 246 additions and 34 deletions

View File

@@ -0,0 +1,140 @@
package dev.tommyjs.futur.stress;
import dev.tommyjs.futur.promise.CompletablePromise;
import dev.tommyjs.futur.promise.PromiseFactory;
import dev.tommyjs.futur.util.ConcurrentResultArray;
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.I_Result;
import org.openjdk.jcstress.infra.results.L_Result;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class FuturStress {
private static PromiseFactory factory() {
return PromiseFactory.of(
LoggerFactory.getLogger(FuturStress.class),
Executors.newScheduledThreadPool(1)
);
}
@JCStressTest
@Outcome(id = "1", expect = Expect.ACCEPTABLE, desc = "Listener called exactly once")
@Outcome(expect = Expect.FORBIDDEN, desc = "Unexpected call count")
@State
public static class ListenerVersusComplete {
final CompletablePromise<Integer> promise;
final AtomicInteger callCount = new AtomicInteger();
public ListenerVersusComplete() {
promise = factory().unresolved();
promise.addDirectListener(v -> {});
}
@Actor
public void adder() {
promise.addDirectListener(v -> callCount.incrementAndGet());
}
@Actor
public void completer() {
promise.complete(42);
}
@Arbiter
public void arbiter(I_Result r) {
r.r1 = callCount.get();
}
}
@JCStressTest
@Outcome(id = "1", expect = Expect.ACCEPTABLE, desc = "Listener called exactly once")
@Outcome(expect = Expect.FORBIDDEN, desc = "Unexpected call count")
@State
public static class ConcurrentComplete {
final CompletablePromise<Integer> promise;
final AtomicInteger callCount = new AtomicInteger();
public ConcurrentComplete() {
promise = factory().unresolved();
promise.addDirectListener(v -> callCount.incrementAndGet());
}
@Actor
public void completer1() {
promise.complete(1);
}
@Actor
public void completer2() {
promise.complete(2);
}
@Arbiter
public void arbiter(I_Result r) {
r.r1 = callCount.get();
}
}
@JCStressTest
@Outcome(id = "2", expect = Expect.ACCEPTABLE, desc = "Both listeners called exactly once")
@Outcome(expect = Expect.FORBIDDEN, desc = "Unexpected call count")
@State
public static class ConcurrentListenerAdders {
final CompletablePromise<Integer> promise;
final AtomicInteger callCount = new AtomicInteger();
public ConcurrentListenerAdders() {
promise = factory().unresolved();
}
@Actor
public void adder1() {
promise.addDirectListener(v -> callCount.incrementAndGet());
}
@Actor
public void adder2() {
promise.addDirectListener(v -> callCount.incrementAndGet());
}
@Arbiter
public void arbiter(I_Result r) {
promise.complete(42);
r.r1 = callCount.get();
}
}
@JCStressTest
@Outcome(id = "42, 1", expect = Expect.ACCEPTABLE, desc = "Write visible")
@Outcome(id = "null, 0", expect = Expect.ACCEPTABLE, desc = "Neither visible yet")
@Outcome(id = "42, 0", expect = Expect.ACCEPTABLE, desc = "Element visible but size not yet")
@Outcome(id = "null, 1", expect = Expect.FORBIDDEN, desc = "Size visible but element not")
@State
public static class ArrayWriteRead {
final ConcurrentResultArray<Integer> array = new ConcurrentResultArray<>(4);
@Actor
public void writer() {
array.set(0, 42);
}
@Actor
public void reader(L_Result r) {
int size = array.toList().size();
Integer elem = size > 0 ? array.toList().get(0) : null;
r.r1 = elem + ", " + size;
}
}
}