Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a5d528a
Add Error Prone and NullAway to the compiler pipeline
claude May 24, 2026
005fc65
Upgrade Error Prone to 2.49.0 and NullAway to 0.13.4
claude May 24, 2026
2539a03
Add REUSE sidecar license for .mvn/jvm.config
claude May 24, 2026
f9a010b
Fix CodeQL autobuild and restore jcstress/JMH annotation processors
claude May 24, 2026
edce1e8
Add Spotless with Palantir Java Format
claude May 24, 2026
5d3216f
Document JMH benchmark invocation and -prof gc/async usage
claude May 24, 2026
9bf969a
Add SpotBugs with fb-contrib and findsecbugs plugins
claude May 24, 2026
1d8fe58
Annotate buffer field and trim path with @GuardedBy("bufferLock")
claude May 24, 2026
896a16f
Disable GuardedBy check for test compilation
claude May 24, 2026
c06b88b
Add ArchUnit architecture tests
claude May 24, 2026
e4096fc
Add Awaitility and ConcurrentUnit as test dependencies
claude May 24, 2026
b675638
Add Lincheck linearizability test for non-blocking operations
claude May 24, 2026
a7c1210
Add opt-in vmlens profile for interleaving analysis
claude May 24, 2026
ca36cee
Run vmlens as a parallel CI job alongside the default test job
claude May 24, 2026
f171ae1
Refactor two concurrency tests to use Awaitility and ConcurrentUnit
claude May 24, 2026
4f25581
Move vmlens Lincheck exclude to the vmlens-maven-plugin config
claude May 24, 2026
65f6c03
Narrow concurrentReadWrite_stressTest throws clause from Throwable to…
claude May 24, 2026
699abb6
Address blocking SonarCloud findings in tests
claude May 24, 2026
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
17 changes: 17 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ jobs:
path: target/site/jacoco/jacoco.xml
if-no-files-found: ignore

vmlens:
name: Test (vmlens interleavings)
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with: { java-version: '21', distribution: temurin, cache: maven }
- name: Test under vmlens
run: mvn --batch-mode -Pvmlens test -Dmaven.javadoc.skip=true
- uses: actions/upload-artifact@v7
if: always()
with:
name: vmlens-report
path: target/vmlens-report/
if-no-files-found: ignore

report:
name: Report
needs: [test]
Expand Down
10 changes: 10 additions & 0 deletions .mvn/jvm.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
3 changes: 3 additions & 0 deletions .mvn/jvm.config.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2014-2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>

SPDX-License-Identifier: Apache-2.0
28 changes: 28 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,37 @@ mvn test -Dtest=StreamBufferTest#testSimpleRoundTrip
mvn org.pitest:pitest-maven:mutationCoverage
```

**Run JMH benchmarks:**

JMH benchmarks live in `src/test/java/net/ladenthin/streambuffer/benchmark/` (e.g. `StreamBufferThroughputBenchmark`). They are not executed by `mvn test`; invoke them directly via the `exec-maven-plugin` whose default `mainClass` is `org.openjdk.jmh.Main`:

```bash
# All benchmarks
mvn test-compile exec:java

# Filter by regex (class or method name)
mvn test-compile exec:java -Dexec.args="StreamBufferThroughput"

# Allocation profile (built-in, no extra setup)
mvn test-compile exec:java -Dexec.args="StreamBufferThroughput -prof gc"

# CPU profile via async-profiler (set ASYNC_PROFILER_LIB to libasyncProfiler.so)
mvn test-compile exec:java \
-Dexec.args="StreamBufferThroughput -prof async:libPath=$ASYNC_PROFILER_LIB;output=flamegraph"
```

`-prof gc` reports `gc.alloc.rate.norm` (bytes allocated per op) — useful for spotting hidden allocations on the read/write hot paths. `-prof async` produces flamegraphs and requires async-profiler installed locally; CI does not run it.

`mvn test` also runs:
- **jqwik properties** (`StreamBufferProperties`) — picked up by Surefire as a JUnit 5 engine.
- **jcstress** tests under `net.ladenthin.streambuffer.jcstress` — executed in a forked JVM via `exec-maven-plugin` in the `test` phase (`-m quick` mode).
- **Lincheck** linearizability test (`StreamBufferLincheckTest`) over the non-blocking subset (`write`, `available`, `close`, `isClosed`).

**Opt-in vmlens interleaving analysis:**
```bash
mvn -Pvmlens test
```
The `vmlens` profile pulls in `com.vmlens:api` and runs the `vmlens-maven-plugin` during the `test` phase. Tests using `com.vmlens.api.AllInterleavings` are then driven through every possible thread interleaving. The profile is off by default — vmlens overhead is too high for every build.

## Architecture

Expand Down
188 changes: 188 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ SPDX-License-Identifier: Apache-2.0
<java.test.version>21</java.test.version>
<jcstress.version>0.16</jcstress.version>
<jqwik.version>1.9.2</jqwik.version>
<errorprone.version>2.49.0</errorprone.version>
<nullaway.version>0.13.4</nullaway.version>
<spotless.version>2.46.1</spotless.version>
<palantir-java-format.version>2.66.0</palantir-java-format.version>
<spotbugs.version>4.8.6.6</spotbugs.version>
<fb-contrib.version>7.6.4</fb-contrib.version>
<findsecbugs.version>1.13.0</findsecbugs.version>
<archunit.version>1.3.0</archunit.version>
<awaitility.version>4.2.2</awaitility.version>
<concurrentunit.version>0.4.6</concurrentunit.version>
<lincheck.version>2.39</lincheck.version>
<vmlens.version>1.2.28</vmlens.version>
<project.build.outputTimestamp>${git.commit.time}</project.build.outputTimestamp>
</properties>
<inceptionYear>2014</inceptionYear>
Expand Down Expand Up @@ -73,6 +85,11 @@ SPDX-License-Identifier: Apache-2.0
</snapshotRepository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
<version>${errorprone.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down Expand Up @@ -109,6 +126,30 @@ SPDX-License-Identifier: Apache-2.0
<version>${jcstress.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>${archunit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${awaitility.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>concurrentunit</artifactId>
<version>${concurrentunit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>lincheck-jvm</artifactId>
<version>${lincheck.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down Expand Up @@ -147,7 +188,51 @@ SPDX-License-Identifier: Apache-2.0
<testSource>${java.test.version}</testSource>
<testTarget>${java.test.version}</testTarget>
<encoding>${project.build.sourceEncoding}</encoding>
<showWarnings>true</showWarnings>
<compilerArgs>
<arg>-XDaddTypeAnnotationsToSymbol=true</arg>
<arg>-XDcompilePolicy=simple</arg>
<arg>--should-stop=ifError=FLOW</arg>
<arg>-Xplugin:ErrorProne -Xep:NullAway:ERROR -XepOpt:NullAway:AnnotatedPackages=net.ladenthin</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>${errorprone.version}</version>
</path>
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
<version>${nullaway.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
<executions>
<execution>
<id>default-testCompile</id>
<configuration>
<compilerArgs>
<arg>-XDaddTypeAnnotationsToSymbol=true</arg>
<arg>-XDcompilePolicy=simple</arg>
<arg>--should-stop=ifError=FLOW</arg>
<arg>-Xplugin:ErrorProne -Xep:NullAway:OFF -Xep:GuardedBy:OFF</arg>
</compilerArgs>
<annotationProcessorPaths combine.children="append">
<path>
<groupId>org.openjdk.jcstress</groupId>
<artifactId>jcstress-core</artifactId>
<version>${jcstress.version}</version>
</path>
<path>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down Expand Up @@ -244,6 +329,7 @@ SPDX-License-Identifier: Apache-2.0
<phase>test</phase>
<goals><goal>exec</goal></goals>
<configuration>
<skip>${skipTests}</skip>
<executable>${java.home}/bin/java</executable>
<classpathScope>test</classpathScope>
<arguments>
Expand All @@ -258,6 +344,67 @@ SPDX-License-Identifier: Apache-2.0
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${spotbugs.version}</version>
<configuration>
<effort>Default</effort>
<threshold>Default</threshold>
<failOnError>true</failOnError>
<includeTests>false</includeTests>
<excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>
<plugins>
<plugin>
<groupId>com.mebigfatguy.fb-contrib</groupId>
<artifactId>fb-contrib</artifactId>
<version>${fb-contrib.version}</version>
</plugin>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>${findsecbugs.version}</version>
</plugin>
</plugins>
</configuration>
<executions>
<execution>
<id>spotbugs-check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>${spotless.version}</version>
<configuration>
<java>
<includes>
<include>src/main/java/**/*.java</include>
<include>src/test/java/**/*.java</include>
</includes>
<palantirJavaFormat>
<version>${palantir-java-format.version}</version>
</palantirJavaFormat>
<removeUnusedImports/>
<trimTrailingWhitespace/>
<endWithNewline/>
</java>
</configuration>
<executions>
<execution>
<id>spotless-check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
Expand All @@ -284,6 +431,47 @@ SPDX-License-Identifier: Apache-2.0
</build>

<profiles>
<profile>
<id>vmlens</id>
<dependencies>
<dependency>
<groupId>com.vmlens</groupId>
<artifactId>api</artifactId>
<version>${vmlens.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.vmlens</groupId>
<artifactId>vmlens-maven-plugin</artifactId>
<version>${vmlens.version}</version>
<configuration>
<!--
Lincheck generates its own TestThreadExecution class on the fly.
That bytecode clashes with vmlens's load-time instrumentation
(java.lang.VerifyError). Skip the Lincheck test under vmlens;
the default test job still runs it.
**/*$* is the plugin default - kept to preserve inner-class skip.
-->
<excludes>
<exclude>**/*$*</exclude>
<exclude>**/StreamBufferLincheckTest.java</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>vmlens-test</id>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>release</id>
<build>
Expand Down
25 changes: 25 additions & 0 deletions spotbugs-exclude.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2014-2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>

SPDX-License-Identifier: Apache-2.0
-->
<FindBugsFilter
xmlns="https://github.com/spotbugs/filter/3.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/4.8.6/spotbugs/etc/findbugsfilter.xsd">

<!--
getOutputStream() / getInputStream() intentionally expose the
SBOutputStream / SBInputStream fields - those streams ARE the
public API of StreamBuffer.
-->
<Match>
<Class name="net.ladenthin.streambuffer.StreamBuffer"/>
<Or>
<Method name="getOutputStream"/>
<Method name="getInputStream"/>
</Or>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
</FindBugsFilter>
Loading
Loading