From cb5055ad64423efd018b69e7bda281f6d7604c2d Mon Sep 17 00:00:00 2001 From: Alex Panchenko <440271+panchenko@users.noreply.github.com> Date: Sun, 24 Aug 2025 11:37:07 +0200 Subject: [PATCH 1/2] servlet: fix threadingTest and update lincheck --- build.gradle | 2 +- gradle/libs.versions.toml | 3 +- servlet/build.gradle | 15 ++--- ...vletOutputStreamWriterConcurrencyTest.java | 64 +++++++++++-------- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/build.gradle b/build.gradle index 50c9df4c5c8..366edf1b9e5 100644 --- a/build.gradle +++ b/build.gradle @@ -238,7 +238,7 @@ subprojects { // At a test failure, log the stack trace to the console so that we don't // have to open the HTML in a browser. - tasks.named("test").configure { + tasks.withType(Test).configureEach { testLogging { exceptionFormat = 'full' showExceptions = true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 540657b9ddb..5c3bd140ffa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -69,8 +69,7 @@ jetty-servlet = "org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.16" jetty-servlet10 = "org.eclipse.jetty:jetty-servlet:10.0.20" jsr305 = "com.google.code.findbugs:jsr305:3.0.2" junit = "junit:junit:4.13.2" -# 2.17+ require Java 11+ (not mentioned in release notes) -lincheck = "org.jetbrains.kotlinx:lincheck-jvm:2.16" +lincheck = "org.jetbrains.lincheck:lincheck:3.2" # Update notes / 2023-07-19 sergiitk: # Couldn't update to 5.4.0, updated to the last in 4.x line. Version 5.x breaks some tests. # Error log: https://github.com/grpc/grpc-java/pull/10359#issuecomment-1632834435 diff --git a/servlet/build.gradle b/servlet/build.gradle index fd5abb6f0e5..7f9cd04a57c 100644 --- a/servlet/build.gradle +++ b/servlet/build.gradle @@ -43,7 +43,7 @@ dependencies { testImplementation libraries.javax.servlet.api threadingTestImplementation project(':grpc-servlet'), - libraries.truth, + libraries.junit, libraries.javax.servlet.api, libraries.lincheck @@ -69,19 +69,12 @@ dependencies { libraries.protobuf.java } -tasks.named("test").configure { - if (JavaVersion.current().isJava9Compatible()) { - jvmArgs += [ - // required for Lincheck - '--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED', - '--add-exports=java.base/jdk.internal.util=ALL-UNNAMED', - ] - } -} - tasks.register('threadingTest', Test) { classpath = sourceSets.threadingTest.runtimeClasspath testClassesDirs = sourceSets.threadingTest.output.classesDirs + jacoco { + enabled = false + } } tasks.named("assemble").configure { diff --git a/servlet/src/threadingTest/java/io/grpc/servlet/AsyncServletOutputStreamWriterConcurrencyTest.java b/servlet/src/threadingTest/java/io/grpc/servlet/AsyncServletOutputStreamWriterConcurrencyTest.java index 61da2bf4c69..e2f40fe2878 100644 --- a/servlet/src/threadingTest/java/io/grpc/servlet/AsyncServletOutputStreamWriterConcurrencyTest.java +++ b/servlet/src/threadingTest/java/io/grpc/servlet/AsyncServletOutputStreamWriterConcurrencyTest.java @@ -16,23 +16,23 @@ package io.grpc.servlet; -import static com.google.common.truth.Truth.assertWithMessage; -import static org.jetbrains.kotlinx.lincheck.strategy.managed.ManagedStrategyGuaranteeKt.forClasses; +import static org.jetbrains.lincheck.datastructures.ManagedStrategyGuaranteeKt.forClasses; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import io.grpc.servlet.AsyncServletOutputStreamWriter.ActionItem; import io.grpc.servlet.AsyncServletOutputStreamWriter.Log; import java.io.IOException; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; -import org.jetbrains.kotlinx.lincheck.LinChecker; -import org.jetbrains.kotlinx.lincheck.annotations.OpGroupConfig; -import org.jetbrains.kotlinx.lincheck.annotations.Operation; -import org.jetbrains.kotlinx.lincheck.annotations.Param; -import org.jetbrains.kotlinx.lincheck.paramgen.BooleanGen; +import org.jetbrains.lincheck.datastructures.BooleanGen; +import org.jetbrains.lincheck.datastructures.ModelCheckingOptions; +import org.jetbrains.lincheck.datastructures.Operation; +import org.jetbrains.lincheck.datastructures.Param; import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingCTest; -import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions; -import org.jetbrains.kotlinx.lincheck.verifier.VerifierState; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -50,17 +50,19 @@ * operations are linearizable in each interleave scenario. */ @ModelCheckingCTest -@OpGroupConfig(name = "update", nonParallel = true) -@OpGroupConfig(name = "write", nonParallel = true) @Param(name = "keepReady", gen = BooleanGen.class) @RunWith(JUnit4.class) -public class AsyncServletOutputStreamWriterConcurrencyTest extends VerifierState { +public class AsyncServletOutputStreamWriterConcurrencyTest { private static final int OPERATIONS_PER_THREAD = 6; private final AsyncServletOutputStreamWriter writer; private final boolean[] keepReadyArray = new boolean[OPERATIONS_PER_THREAD]; private volatile boolean isReady; + /** + * The container initiates the first call shortly after {@code startAsync}. + */ + private final AtomicBoolean initialOnWritePossible = new AtomicBoolean(true); // when isReadyReturnedFalse, writer.onWritePossible() will be called. private volatile boolean isReadyReturnedFalse; private int producerIndex; @@ -71,17 +73,15 @@ public class AsyncServletOutputStreamWriterConcurrencyTest extends VerifierState public AsyncServletOutputStreamWriterConcurrencyTest() { BiFunction writeAction = (bytes, numBytes) -> () -> { - assertWithMessage("write should only be called while isReady() is true") - .that(isReady) - .isTrue(); + assertTrue("write should only be called while isReady() is true", isReady); // The byte to be written must equal to consumerIndex, otherwise execution order is wrong - assertWithMessage("write in wrong order").that(bytes[0]).isEqualTo((byte) consumerIndex); + assertEquals("write in wrong order", bytes[0], (byte) consumerIndex); bytesWritten++; writeOrFlush(); }; ActionItem flushAction = () -> { - assertWithMessage("flush must only be called while isReady() is true").that(isReady).isTrue(); + assertTrue("flush must only be called while isReady() is true", isReady); writeOrFlush(); }; @@ -102,12 +102,13 @@ private void writeOrFlush() { } private boolean isReady() { - if (!isReady) { - assertWithMessage("isReady() already returned false, onWritePossible() will be invoked") - .that(isReadyReturnedFalse).isFalse(); + boolean copyOfIsReady = isReady; + if (!copyOfIsReady) { + assertFalse("isReady() already returned false, onWritePossible() will be invoked", + isReadyReturnedFalse); isReadyReturnedFalse = true; } - return isReady; + return copyOfIsReady; } /** @@ -118,7 +119,7 @@ private boolean isReady() { * the ServletOutputStream should become unready if keepReady == false. */ // @com.google.errorprone.annotations.Keep - @Operation(group = "write") + @Operation(nonParallelGroup = "write") public void write(@Param(name = "keepReady") boolean keepReady) throws IOException { keepReadyArray[producerIndex] = keepReady; writer.writeBytes(new byte[]{(byte) producerIndex}, 1); @@ -133,7 +134,7 @@ public void write(@Param(name = "keepReady") boolean keepReady) throws IOExcepti * the ServletOutputStream should become unready if keepReady == false. */ // @com.google.errorprone.annotations.Keep // called by lincheck reflectively - @Operation(group = "write") + @Operation(nonParallelGroup = "write") public void flush(@Param(name = "keepReady") boolean keepReady) throws IOException { keepReadyArray[producerIndex] = keepReady; writer.flush(); @@ -142,9 +143,12 @@ public void flush(@Param(name = "keepReady") boolean keepReady) throws IOExcepti /** If the writer is not ready, let it turn ready and call writer.onWritePossible(). */ // @com.google.errorprone.annotations.Keep // called by lincheck reflectively - @Operation(group = "update") + @Operation(nonParallelGroup = "update") public void maybeOnWritePossible() throws IOException { - if (isReadyReturnedFalse) { + if (initialOnWritePossible.compareAndSet(true, false)) { + isReady = true; + writer.onWritePossible(); + } else if (isReadyReturnedFalse) { isReadyReturnedFalse = false; isReady = true; writer.onWritePossible(); @@ -152,7 +156,13 @@ public void maybeOnWritePossible() throws IOException { } @Override - protected Object extractState() { + public final boolean equals(Object o) { + return o instanceof AsyncServletOutputStreamWriterConcurrencyTest + && bytesWritten == ((AsyncServletOutputStreamWriterConcurrencyTest) o).bytesWritten; + } + + @Override + public int hashCode() { return bytesWritten; } @@ -169,6 +179,6 @@ public void linCheck() { AtomicReference.class.getName()) .allMethods() .treatAsAtomic()); - LinChecker.check(AsyncServletOutputStreamWriterConcurrencyTest.class, options); + options.check(AsyncServletOutputStreamWriterConcurrencyTest.class); } } From 7c299581b01e2faf3e322cf4dc6990c682ba4126 Mon Sep 17 00:00:00 2001 From: Alex Panchenko <440271+panchenko@users.noreply.github.com> Date: Sun, 24 Aug 2025 12:27:22 +0200 Subject: [PATCH 2/2] fix checkstyle --- .../servlet/AsyncServletOutputStreamWriterConcurrencyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servlet/src/threadingTest/java/io/grpc/servlet/AsyncServletOutputStreamWriterConcurrencyTest.java b/servlet/src/threadingTest/java/io/grpc/servlet/AsyncServletOutputStreamWriterConcurrencyTest.java index e2f40fe2878..ebe1df8c3f3 100644 --- a/servlet/src/threadingTest/java/io/grpc/servlet/AsyncServletOutputStreamWriterConcurrencyTest.java +++ b/servlet/src/threadingTest/java/io/grpc/servlet/AsyncServletOutputStreamWriterConcurrencyTest.java @@ -28,11 +28,11 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; +import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingCTest; import org.jetbrains.lincheck.datastructures.BooleanGen; import org.jetbrains.lincheck.datastructures.ModelCheckingOptions; import org.jetbrains.lincheck.datastructures.Operation; import org.jetbrains.lincheck.datastructures.Param; -import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingCTest; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4;