Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 4 additions & 11 deletions servlet/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ dependencies {
testImplementation libraries.javax.servlet.api

threadingTestImplementation project(':grpc-servlet'),
libraries.truth,
libraries.junit,
libraries.javax.servlet.api,
libraries.lincheck

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingCTest;
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions;
import org.jetbrains.kotlinx.lincheck.verifier.VerifierState;
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.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
Expand All @@ -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;
Expand All @@ -71,17 +73,15 @@ public class AsyncServletOutputStreamWriterConcurrencyTest extends VerifierState
public AsyncServletOutputStreamWriterConcurrencyTest() {
BiFunction<byte[], Integer, ActionItem> 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();
};

Expand All @@ -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;
}

/**
Expand All @@ -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);
Expand All @@ -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();
Expand All @@ -142,17 +143,26 @@ 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();
}
}

@Override
protected Object extractState() {
public final boolean equals(Object o) {
return o instanceof AsyncServletOutputStreamWriterConcurrencyTest
&& bytesWritten == ((AsyncServletOutputStreamWriterConcurrencyTest) o).bytesWritten;
}

@Override
public int hashCode() {
return bytesWritten;
}

Expand All @@ -169,6 +179,6 @@ public void linCheck() {
AtomicReference.class.getName())
.allMethods()
.treatAsAtomic());
LinChecker.check(AsyncServletOutputStreamWriterConcurrencyTest.class, options);
options.check(AsyncServletOutputStreamWriterConcurrencyTest.class);
}
}