Skip to content

Commit d2ef4e5

Browse files
committed
Add jcstress, Lincheck, and vmlens infrastructure with CancellationToken examples
https://claude.ai/code/session_015uTXAT22RdG6vUGc8Vg4X5
1 parent 8f099ef commit d2ef4e5

3 files changed

Lines changed: 169 additions & 0 deletions

File tree

pom.xml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ 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>
5356
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
5457
<project.build.outputTimestamp>${git.commit.time}</project.build.outputTimestamp>
5558
</properties>
@@ -127,6 +130,18 @@ SPDX-License-Identifier: MIT
127130
<version>1.37</version>
128131
<scope>test</scope>
129132
</dependency>
133+
<dependency>
134+
<groupId>org.openjdk.jcstress</groupId>
135+
<artifactId>jcstress-core</artifactId>
136+
<version>${jcstress.version}</version>
137+
<scope>test</scope>
138+
</dependency>
139+
<dependency>
140+
<groupId>org.jetbrains.kotlinx</groupId>
141+
<artifactId>lincheck-jvm</artifactId>
142+
<version>${lincheck.version}</version>
143+
<scope>test</scope>
144+
</dependency>
130145
</dependencies>
131146

132147
<build>
@@ -162,6 +177,23 @@ SPDX-License-Identifier: MIT
162177
<testTarget>21</testTarget>
163178
</configuration>
164179
<executions>
180+
<execution>
181+
<id>default-testCompile</id>
182+
<configuration>
183+
<annotationProcessorPaths>
184+
<path>
185+
<groupId>org.openjdk.jcstress</groupId>
186+
<artifactId>jcstress-core</artifactId>
187+
<version>${jcstress.version}</version>
188+
</path>
189+
<path>
190+
<groupId>org.openjdk.jmh</groupId>
191+
<artifactId>jmh-generator-annprocess</artifactId>
192+
<version>1.37</version>
193+
</path>
194+
</annotationProcessorPaths>
195+
</configuration>
196+
</execution>
165197
<!-- We have to perform a separate build pass for cuda
166198
classifier -->
167199
<execution>
@@ -364,6 +396,26 @@ SPDX-License-Identifier: MIT
364396
<mainClass>org.openjdk.jmh.Main</mainClass>
365397
<classpathScope>test</classpathScope>
366398
</configuration>
399+
<executions>
400+
<execution>
401+
<id>jcstress</id>
402+
<phase>test</phase>
403+
<goals><goal>exec</goal></goals>
404+
<configuration>
405+
<skip>${skipTests}</skip>
406+
<executable>${java.home}/bin/java</executable>
407+
<classpathScope>test</classpathScope>
408+
<arguments>
409+
<argument>-classpath</argument>
410+
<classpath/>
411+
<argument>org.openjdk.jcstress.Main</argument>
412+
<argument>-v</argument>
413+
<argument>-m</argument>
414+
<argument>default</argument>
415+
</arguments>
416+
</configuration>
417+
</execution>
418+
</executions>
367419
</plugin>
368420
</plugins>
369421
</build>
@@ -465,5 +517,46 @@ SPDX-License-Identifier: MIT
465517
</plugins>
466518
</build>
467519
</profile>
520+
<profile>
521+
<id>vmlens</id>
522+
<dependencies>
523+
<dependency>
524+
<groupId>com.vmlens</groupId>
525+
<artifactId>api</artifactId>
526+
<version>${vmlens.version}</version>
527+
<scope>test</scope>
528+
</dependency>
529+
</dependencies>
530+
<build>
531+
<plugins>
532+
<plugin>
533+
<groupId>com.vmlens</groupId>
534+
<artifactId>vmlens-maven-plugin</artifactId>
535+
<version>${vmlens.version}</version>
536+
<configuration>
537+
<!--
538+
Lincheck generates its own TestThreadExecution class on the fly.
539+
That bytecode clashes with vmlens's load-time instrumentation
540+
(java.lang.VerifyError). Skip the Lincheck test under vmlens;
541+
the default test job still runs it.
542+
**/*$* is the plugin default - kept to preserve inner-class skip.
543+
-->
544+
<excludes>
545+
<exclude>**/*$*</exclude>
546+
<exclude>**/CancellationTokenLincheckTest.java</exclude>
547+
</excludes>
548+
</configuration>
549+
<executions>
550+
<execution>
551+
<id>vmlens-test</id>
552+
<goals>
553+
<goal>test</goal>
554+
</goals>
555+
</execution>
556+
</executions>
557+
</plugin>
558+
</plugins>
559+
</build>
560+
</profile>
468561
</profiles>
469562
</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+
}
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)