diff --git a/bosk-testing/src/main/java/io/vena/bosk/drivers/JitterDriver.java b/bosk-testing/src/main/java/io/vena/bosk/drivers/JitterDriver.java new file mode 100644 index 00000000..bc8d2c4b --- /dev/null +++ b/bosk-testing/src/main/java/io/vena/bosk/drivers/JitterDriver.java @@ -0,0 +1,90 @@ +package io.vena.bosk.drivers; + +import io.vena.bosk.BoskDriver; +import io.vena.bosk.DriverFactory; +import io.vena.bosk.Identifier; +import io.vena.bosk.Reference; +import io.vena.bosk.StateTreeNode; +import io.vena.bosk.exceptions.InvalidTypeException; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Random; +import java.util.function.DoubleSupplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class JitterDriver implements BoskDriver { + private final BoskDriver downstream; + private final DoubleSupplier jitter; + + public static DriverFactory factory(double meanMillis, double limitMillis, long seed) { + return (b,d) -> new JitterDriver<>(d, meanMillis, limitMillis, seed); + } + + private JitterDriver(BoskDriver downstream, double meanMillis, double limitMillis, long seed) { + this.downstream = downstream; + Random random = new Random(seed); + + https://en.wikipedia.org/wiki/Exponential_distribution#Random_variate_generation + jitter = ()-> Double.min(limitMillis, + -Math.log(random.nextDouble()) * meanMillis + ); + } + + private void sleep() { + try { + long totalNanos = (long)(1e6 * jitter.getAsDouble()); + long ms = totalNanos / 1_000_000; + int nanos = (int) (totalNanos % 1_000_000); + LOGGER.trace("Sleeping for {} ms", totalNanos/1e6); + Thread.sleep(ms, nanos); + LOGGER.trace("Done sleeping"); + } catch (InterruptedException e) { + LOGGER.debug("Sleep interrupted", e); + } + } + + @Override + public R initialRoot(Type rootType) throws InvalidTypeException, IOException, InterruptedException { + sleep(); + return downstream.initialRoot(rootType); + } + + @Override + public void submitReplacement(Reference target, T newValue) { + sleep(); + downstream.submitReplacement(target, newValue); + } + + @Override + public void submitConditionalReplacement(Reference target, T newValue, Reference precondition, Identifier requiredValue) { + sleep(); + downstream.submitConditionalReplacement(target, newValue, precondition, requiredValue); + } + + @Override + public void submitInitialization(Reference target, T newValue) { + sleep(); + downstream.submitInitialization(target, newValue); + } + + @Override + public void submitDeletion(Reference target) { + sleep(); + downstream.submitDeletion(target); + } + + @Override + public void submitConditionalDeletion(Reference target, Reference precondition, Identifier requiredValue) { + sleep(); + downstream.submitConditionalDeletion(target, precondition, requiredValue); + } + + @Override + public void flush() throws IOException, InterruptedException { + sleep(); + downstream.flush(); + } + + private static final Logger LOGGER = LoggerFactory.getLogger(JitterDriver.class); +} diff --git a/bosk-testing/src/test/java/io/vena/bosk/drivers/JitterDriverConformanceTest.java b/bosk-testing/src/test/java/io/vena/bosk/drivers/JitterDriverConformanceTest.java new file mode 100644 index 00000000..76f1c258 --- /dev/null +++ b/bosk-testing/src/test/java/io/vena/bosk/drivers/JitterDriverConformanceTest.java @@ -0,0 +1,10 @@ +package io.vena.bosk.drivers; + +import org.junit.jupiter.api.BeforeEach; + +class JitterDriverConformanceTest extends DriverConformanceTest { + @BeforeEach + void setupDriverFactory() { + driverFactory = JitterDriver.factory(0.1, 0.5, 123); + } +}