Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

19 Commits

Repository files navigation

java-dst

This repository is a work in progress. The intent is to create a lightweight library that provides a generalized implementation of deterministic simulation for Java using virtual threads. With the goal of enabling mostly idiomatic Java code to be tested deterministically. The approach taken in most current implementations of deterministic simulation is to constrain the API that non-deterministically interacts with your core logic to be as small as possible. Then create a bespoke simulation that stubs out that API and runs the simulation. This does work, but it is highly customized and may miss interactions outside the core logic.

The following table is an attempt to exhaustively describe the issues that need to be resolved in order do deterministic simulation testing in java.

Java Non-determinismWhyTLDRMitigations
JVM Garbage CollectionPause and cause thread reorder beyond what we can simulate.IgnoreIt doesn't have a huge effect in the future its possible to implement a GC though.
JITCan change instruction ordering at runtime and cause very subtle bugs in multithreaded applications see JCStress project for more details.Ignore/DisableDisable it or turn it down during simulation if it is causing issues and leave that state space testing to tools like JCStress.
ClassloadingSince it has to reads the file system.IgnoreClassloading doesn't happen during runtime all that often so we can ignore it.
volatileNot all possible interlieved updates to volitile variables can be simulated.Ignore/AvoidSpecifically avoid mutable volatile usage int, float, double, and long. Boolean and immutable objects are okay. Not all interleavings will be able to be tested unless a Thread.yield() is added via instrumentation before and after each access.
Thread.sleep(), Object().wait()Since it sleeps based on system time, executions get randomly inserted back into the simulation loop causing indeterminism.InstrumentCurrently requires an agent to do byte code manipulation of System time veriavles
Calendar, Date, System.nanoTime(), System.currentTimeMillis(),VM.getNanoTimeAdjustment()Returns values based on system time.InstrumentInstrument with an agent that replaces all of the underlying method calls to system calls.
LocalDate.now(), LocalTime.now(), Instant.now(), LocalDateTime.now(), ZoneDateTime.now()Returns values based on system time.InstrumentAnything that can control logic will need to be replaced by something that can have the clock replaced e.g. Instant.now(Clock).
Thread.ofPlatform(), ThreadPoolExecutor or Thread.ofVirtual()Can only control interleaving of threads with virtual threads.InstrumentReplace with SchedulableVirtualThreadFactory during simulation, Requires Java 24 or higher.
ScheduledExecutorsServiceUses system time to schedule tasks.InstrumentReplace with SimulationScheduledExecutor during simulation or instrument system time.
TimerAlternativeUse ScheduledExecutor instead.
java.io.File*Synchronous calls capture the simulation thread. They also get inserted back into the simulation loop randomly.TBDuse java.nio.file instead.
java.nio.fileIndeterminism from errors have to be simulated. Threads blocked on IO only can get scheduled after they are unblocked leading to indeterminism in which simulation loop they will be acted on.TBDStub FileSystem with something like JimFS during simulation.
java.net.SocketIndeterminism from errors have to be simulated. Threads blocked on IO only can get scheduled after they are unblocked leading to indeterminism in which simulation loop they will be acted on.TBDUse Netty instead, with netty 4.2+ we can pass SchedulableVirtualThreadFactory into the event loop and use local transport to simulate network all within the JVM.
Native system callsNot Supported
Random, SecureRandomRandomness has to be made deterministic.InstrumentInstances of Random and SecureRandom replaced with one instance of Random seeded from the simulation.
ForkJoinPool, .stream().parallel()Can only control interleaving of threads with virtual threads.TBDNeed to look into scheduling virtual threads on custom fork join pool and replacing the default system fork join pool.
External calls HTTP/2, GRPC, AMQP, MQTT, HTTP, STOMP ect...Have to stub out all external calls to drive the simulation.InstrumentNetty stubs???
External database callsHave to stub out all external calls to drive the simulation.TBDStub with h2?
ThreadLocalThe simulation is single threaded so they don't work as expected.AlternativeReplace with Scoped Values that are supported by virtual threads.
Singleton or static blocksThe simulation takes place in a single VM so if something is static and host specific it will be shared during simulationNot SupportedIts possible load multiple instances of classes that contain singletons or static blocks but has to be done in seperate classloaders.
Object.wait()/Object.notify()The ordering that notify wakes threads paused on object wait is JVM specific and there isn't a way to test other possible orderings.IgnoreCan't fully be tested using this method but should be deterministic within a given JVM
ImmutableCollections (SALT32L and REVERSE fields)The iteration order is seeded by a random number on VM start.InstrumentReplace with a seed value from our singular random instance

The main takeaways from the above table to implement deterministic simulation for an application are:

  • IO will block and resume continuations non-deterministically in a future simulation tick. This is the main issue that needs to be researched how to resolve.
  • System time needs to be avoided or instrumented at runtime with byte code weaving.
  • IO operations have to be stubbed out to be able to introduce errors.
  • Dependencies which use Alternative/Instrument/Not supported features from the above table have to be stubbed out or instrumented.
  • External calls have to be stubbed out.
  • Synchronous file IO captures the simulation virtual threads so won't simulate thread interleaving without adding Thread.yield() afterwords.
  • Everything has to be run within the simulation's virtual threads, even initializing the system.

Some bespoke implementations of deterministic simulation in java can be found in Cassandra and Kafka's Kraft.

Some good videos and articles on deterministic simulation:

About

A library for deterministically simulation testing in java

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages