Skip to content

Commit d13a5f3

Browse files
Add concurrency testing with JCStress and Lincheck (#199)
* Fix test compilation errors: pass required prompt arg to InferenceParameters - LlamaParameterProperties and InferenceParametersBenchmark called the no-arg constructor which does not exist; updated to InferenceParameters(""). - InferenceParametersBenchmark.serializeWithSamplingParams used setStop(List) which does not exist; replaced with setStopStrings(String...). - pom.xml: add testSource/testTarget=21 to suppress obsolete -source 8 warnings during test compilation. https://claude.ai/code/session_015uTXAT22RdG6vUGc8Vg4X5 * Add jcstress, Lincheck, and vmlens infrastructure with CancellationToken examples https://claude.ai/code/session_015uTXAT22RdG6vUGc8Vg4X5 * Sync version properties and README badges with streambuffer reference setup Add jqwik.version, archunit.version, spotbugs.version, fb-contrib.version, findsecbugs.version properties; reference them from dependency/plugin declarations instead of hardcoding. Add jcstress, Lincheck, vmlens badges to README Build section; update JMH badge to use canonical openjdk.org link and hex color #25A162. https://claude.ai/code/session_015uTXAT22RdG6vUGc8Vg4X5 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4f1ef2b commit d13a5f3

6 files changed

Lines changed: 190 additions & 11 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
[![jqwik](https://img.shields.io/badge/tested%20with-jqwik-1f6feb)](https://jqwik.net)
55
[![ArchUnit](https://img.shields.io/badge/tested%20with-ArchUnit-c71a36)](https://www.archunit.org)
66
[![SpotBugs](https://img.shields.io/badge/analyzed%20with-SpotBugs-3b5998)](https://spotbugs.github.io)
7-
[![JMH](https://img.shields.io/badge/benchmarked%20with-JMH-brightgreen)](https://github.com/openjdk/jmh)
7+
[![jcstress](https://img.shields.io/badge/tested%20with-jcstress-007396)](https://openjdk.org/projects/code-tools/jcstress/)
8+
[![Lincheck](https://img.shields.io/badge/tested%20with-Lincheck-7F52FF)](https://github.com/JetBrains/lincheck)
9+
[![vmlens](https://img.shields.io/badge/tested%20with-vmlens-ff6f00)](https://vmlens.com)
10+
[![JMH](https://img.shields.io/badge/benchmarked%20with-JMH-25A162)](https://openjdk.org/projects/code-tools/jmh/)
811
[![llama.cpp b9354](https://img.shields.io/badge/llama.cpp-%23b9354-informational)](https://github.com/ggml-org/llama.cpp/releases/tag/b9354)
912
[![Publish](https://github.com/bernardladenthin/java-llama.cpp/actions/workflows/publish.yml/badge.svg)](https://github.com/bernardladenthin/java-llama.cpp/actions/workflows/publish.yml)
1013
[![CodeQL](https://github.com/bernardladenthin/java-llama.cpp/actions/workflows/codeql.yml/badge.svg)](https://github.com/bernardladenthin/java-llama.cpp/actions/workflows/codeql.yml)

pom.xml

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ SPDX-License-Identifier: MIT
5050

5151
<properties>
5252
<jna.version>5.18.1</jna.version>
53+
<jcstress.version>0.16</jcstress.version>
54+
<lincheck.version>2.39</lincheck.version>
55+
<vmlens.version>1.2.28</vmlens.version>
56+
<jqwik.version>1.9.2</jqwik.version>
57+
<archunit.version>1.3.0</archunit.version>
58+
<spotbugs.version>4.8.6.6</spotbugs.version>
59+
<fb-contrib.version>7.6.4</fb-contrib.version>
60+
<findsecbugs.version>1.13.0</findsecbugs.version>
5361
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
5462
<project.build.outputTimestamp>${git.commit.time}</project.build.outputTimestamp>
5563
</properties>
@@ -64,13 +72,13 @@ SPDX-License-Identifier: MIT
6472
<dependency>
6573
<groupId>net.jqwik</groupId>
6674
<artifactId>jqwik</artifactId>
67-
<version>1.9.2</version>
75+
<version>${jqwik.version}</version>
6876
<scope>test</scope>
6977
</dependency>
7078
<dependency>
7179
<groupId>com.tngtech.archunit</groupId>
7280
<artifactId>archunit-junit5</artifactId>
73-
<version>1.3.0</version>
81+
<version>${archunit.version}</version>
7482
<scope>test</scope>
7583
</dependency>
7684
<dependency>
@@ -127,6 +135,18 @@ SPDX-License-Identifier: MIT
127135
<version>1.37</version>
128136
<scope>test</scope>
129137
</dependency>
138+
<dependency>
139+
<groupId>org.openjdk.jcstress</groupId>
140+
<artifactId>jcstress-core</artifactId>
141+
<version>${jcstress.version}</version>
142+
<scope>test</scope>
143+
</dependency>
144+
<dependency>
145+
<groupId>org.jetbrains.kotlinx</groupId>
146+
<artifactId>lincheck-jvm</artifactId>
147+
<version>${lincheck.version}</version>
148+
<scope>test</scope>
149+
</dependency>
130150
</dependencies>
131151

132152
<build>
@@ -158,8 +178,27 @@ SPDX-License-Identifier: MIT
158178
<configuration>
159179
<source>1.8</source>
160180
<target>1.8</target>
181+
<testSource>21</testSource>
182+
<testTarget>21</testTarget>
161183
</configuration>
162184
<executions>
185+
<execution>
186+
<id>default-testCompile</id>
187+
<configuration>
188+
<annotationProcessorPaths>
189+
<path>
190+
<groupId>org.openjdk.jcstress</groupId>
191+
<artifactId>jcstress-core</artifactId>
192+
<version>${jcstress.version}</version>
193+
</path>
194+
<path>
195+
<groupId>org.openjdk.jmh</groupId>
196+
<artifactId>jmh-generator-annprocess</artifactId>
197+
<version>1.37</version>
198+
</path>
199+
</annotationProcessorPaths>
200+
</configuration>
201+
</execution>
163202
<!-- We have to perform a separate build pass for cuda
164203
classifier -->
165204
<execution>
@@ -324,7 +363,7 @@ SPDX-License-Identifier: MIT
324363
<plugin>
325364
<groupId>com.github.spotbugs</groupId>
326365
<artifactId>spotbugs-maven-plugin</artifactId>
327-
<version>4.8.6.6</version>
366+
<version>${spotbugs.version}</version>
328367
<configuration>
329368
<effort>Default</effort>
330369
<threshold>Default</threshold>
@@ -335,12 +374,12 @@ SPDX-License-Identifier: MIT
335374
<plugin>
336375
<groupId>com.mebigfatguy.fb-contrib</groupId>
337376
<artifactId>fb-contrib</artifactId>
338-
<version>7.6.4</version>
377+
<version>${fb-contrib.version}</version>
339378
</plugin>
340379
<plugin>
341380
<groupId>com.h3xstream.findsecbugs</groupId>
342381
<artifactId>findsecbugs-plugin</artifactId>
343-
<version>1.13.0</version>
382+
<version>${findsecbugs.version}</version>
344383
</plugin>
345384
</plugins>
346385
</configuration>
@@ -362,6 +401,26 @@ SPDX-License-Identifier: MIT
362401
<mainClass>org.openjdk.jmh.Main</mainClass>
363402
<classpathScope>test</classpathScope>
364403
</configuration>
404+
<executions>
405+
<execution>
406+
<id>jcstress</id>
407+
<phase>test</phase>
408+
<goals><goal>exec</goal></goals>
409+
<configuration>
410+
<skip>${skipTests}</skip>
411+
<executable>${java.home}/bin/java</executable>
412+
<classpathScope>test</classpathScope>
413+
<arguments>
414+
<argument>-classpath</argument>
415+
<classpath/>
416+
<argument>org.openjdk.jcstress.Main</argument>
417+
<argument>-v</argument>
418+
<argument>-m</argument>
419+
<argument>default</argument>
420+
</arguments>
421+
</configuration>
422+
</execution>
423+
</executions>
365424
</plugin>
366425
</plugins>
367426
</build>
@@ -463,5 +522,46 @@ SPDX-License-Identifier: MIT
463522
</plugins>
464523
</build>
465524
</profile>
525+
<profile>
526+
<id>vmlens</id>
527+
<dependencies>
528+
<dependency>
529+
<groupId>com.vmlens</groupId>
530+
<artifactId>api</artifactId>
531+
<version>${vmlens.version}</version>
532+
<scope>test</scope>
533+
</dependency>
534+
</dependencies>
535+
<build>
536+
<plugins>
537+
<plugin>
538+
<groupId>com.vmlens</groupId>
539+
<artifactId>vmlens-maven-plugin</artifactId>
540+
<version>${vmlens.version}</version>
541+
<configuration>
542+
<!--
543+
Lincheck generates its own TestThreadExecution class on the fly.
544+
That bytecode clashes with vmlens's load-time instrumentation
545+
(java.lang.VerifyError). Skip the Lincheck test under vmlens;
546+
the default test job still runs it.
547+
**/*$* is the plugin default - kept to preserve inner-class skip.
548+
-->
549+
<excludes>
550+
<exclude>**/*$*</exclude>
551+
<exclude>**/CancellationTokenLincheckTest.java</exclude>
552+
</excludes>
553+
</configuration>
554+
<executions>
555+
<execution>
556+
<id>vmlens-test</id>
557+
<goals>
558+
<goal>test</goal>
559+
</goals>
560+
</execution>
561+
</executions>
562+
</plugin>
563+
</plugins>
564+
</build>
565+
</profile>
466566
</profiles>
467567
</project>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
2+
//
3+
// SPDX-License-Identifier: MIT
4+
package net.ladenthin.llama;
5+
6+
import org.jetbrains.kotlinx.lincheck.LinChecker;
7+
import org.jetbrains.kotlinx.lincheck.annotations.Operation;
8+
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions;
9+
import org.junit.jupiter.api.Test;
10+
11+
/**
12+
* Linearizability check for {@link CancellationToken}.
13+
*
14+
* <p>Verifies that concurrent {@link CancellationToken#cancel()} and
15+
* {@link CancellationToken#isCancelled()} invocations are linearizable:
16+
* every execution must be equivalent to some sequential execution of the
17+
* same operations.</p>
18+
*/
19+
public class CancellationTokenLincheckTest {
20+
21+
private final CancellationToken token = new CancellationToken();
22+
23+
@Operation
24+
public void cancel() {
25+
token.cancel();
26+
}
27+
28+
@Operation
29+
public boolean isCancelled() {
30+
return token.isCancelled();
31+
}
32+
33+
@Test
34+
public void modelCheckingTest() {
35+
ModelCheckingOptions options = new ModelCheckingOptions()
36+
.iterations(20)
37+
.invocationsPerIteration(500)
38+
.threads(2)
39+
.actorsPerThread(3);
40+
LinChecker.check(CancellationTokenLincheckTest.class, options);
41+
}
42+
}

src/test/java/net/ladenthin/llama/LlamaParameterProperties.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ public class LlamaParameterProperties {
1111

1212
@Property
1313
boolean setTemperatureNeverThrows(@ForAll @FloatRange(min = 0.0f, max = 2.0f) float temperature) {
14-
String json = new InferenceParameters().setTemperature(temperature).toString();
14+
String json = new InferenceParameters("").setTemperature(temperature).toString();
1515
return json.contains("temperature");
1616
}
1717

1818
@Property
1919
boolean setTopPNeverThrows(@ForAll @FloatRange(min = 0.0f, max = 1.0f) float topP) {
20-
String json = new InferenceParameters().setTopP(topP).toString();
20+
String json = new InferenceParameters("").setTopP(topP).toString();
2121
return json.contains("top_p");
2222
}
2323
}

src/test/java/net/ladenthin/llama/benchmark/InferenceParametersBenchmark.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class InferenceParametersBenchmark {
4646
*/
4747
@Benchmark
4848
public void serializeDefault(Blackhole bh) {
49-
bh.consume(new InferenceParameters().toString());
49+
bh.consume(new InferenceParameters("").toString());
5050
}
5151

5252
/**
@@ -59,11 +59,11 @@ public void serializeDefault(Blackhole bh) {
5959
*/
6060
@Benchmark
6161
public void serializeWithSamplingParams(Blackhole bh) {
62-
bh.consume(new InferenceParameters()
62+
bh.consume(new InferenceParameters("")
6363
.setTemperature(0.7f)
6464
.setTopP(0.9f)
6565
.setNPredict(512)
66-
.setStop(java.util.Arrays.asList("</s>", "<|im_end|>"))
66+
.setStopStrings("</s>", "<|im_end|>")
6767
.toString());
6868
}
6969
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
2+
//
3+
// SPDX-License-Identifier: MIT
4+
package net.ladenthin.llama.jcstress;
5+
6+
import net.ladenthin.llama.CancellationToken;
7+
import org.openjdk.jcstress.annotations.Actor;
8+
import org.openjdk.jcstress.annotations.Arbiter;
9+
import org.openjdk.jcstress.annotations.Description;
10+
import org.openjdk.jcstress.annotations.Expect;
11+
import org.openjdk.jcstress.annotations.JCStressTest;
12+
import org.openjdk.jcstress.annotations.Outcome;
13+
import org.openjdk.jcstress.annotations.State;
14+
import org.openjdk.jcstress.infra.results.Z_Result;
15+
16+
@JCStressTest
17+
@Description("cancel() must be visible to the arbiter via the volatile flag.")
18+
@Outcome(id = "true", expect = Expect.ACCEPTABLE, desc = "Cancellation visible after actor completes")
19+
@Outcome(id = "false", expect = Expect.FORBIDDEN, desc = "BUG: volatile write not seen after actor finish")
20+
@State
21+
public class CancellationTokenRace {
22+
23+
private final CancellationToken token = new CancellationToken();
24+
25+
@Actor
26+
public void writer() {
27+
token.cancel();
28+
}
29+
30+
@Arbiter
31+
public void check(Z_Result r) {
32+
r.r1 = token.isCancelled();
33+
}
34+
}

0 commit comments

Comments
 (0)