Skip to content

Commit fadd43c

Browse files
authored
Fix Java 11 performance bug (#316)
Closes #308 --------- Signed-off-by: Evgeniy Moiseenko <[email protected]>
1 parent ecede82 commit fadd43c

15 files changed

+190
-107
lines changed

build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ tasks {
110110
fun Test.configureJvmTestCommon() {
111111
maxParallelForks = 1
112112
maxHeapSize = "6g"
113-
val instrumentAllClassesInModelCheckingMode: String by project
114-
if (instrumentAllClassesInModelCheckingMode.toBoolean()) {
115-
systemProperty("lincheck.instrumentAllClassesInModelCheckingMode", "true")
113+
val instrumentAllClasses: String by project
114+
if (instrumentAllClasses.toBoolean()) {
115+
systemProperty("lincheck.instrumentAllClasses", "true")
116116
}
117117
val extraArgs = mutableListOf<String>()
118118
val withEventIdSequentialCheck: String by project

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ lastCopyrightYear=2023
1717

1818
jdkToolchainVersion=17
1919
runAllTestsInSeparateJVMs=false
20-
instrumentAllClassesInModelCheckingMode=false
20+
instrumentAllClasses=false
2121
withEventIdSequentialCheck=false
2222

2323
kotlinVersion=1.9.21

src/jvm/main/org/jetbrains/kotlinx/lincheck/LinChecker.kt

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,32 @@ class LinChecker (private val testClass: Class<*>, options: Options<*, *>?) {
4242
* @throws LincheckAssertionError if the testing data structure is incorrect.
4343
*/
4444
fun check() {
45-
val failure = checkImpl() ?: return
46-
throw LincheckAssertionError(failure)
45+
checkImpl { failure ->
46+
if (failure != null) throw LincheckAssertionError(failure)
47+
}
4748
}
4849

4950
/**
50-
* @return TestReport with information about concurrent test run.
51+
* Runs Lincheck to check the tested class under given configurations.
52+
*
53+
* @param cont Optional continuation taking [LincheckFailure] as an argument.
54+
* The continuation is run in the context when Lincheck java-agent is still attached.
55+
* @return [LincheckFailure] if a failure is discovered, null otherwise.
5156
*/
5257
@Synchronized // never run Lincheck tests in parallel
53-
internal fun checkImpl(): LincheckFailure? {
58+
internal fun checkImpl(cont: LincheckFailureContinuation? = null): LincheckFailure? {
5459
check(testConfigurations.isNotEmpty()) { "No Lincheck test configuration to run" }
5560
lincheckVerificationStarted()
5661
for (testCfg in testConfigurations) {
5762
withLincheckJavaAgent(testCfg.instrumentationMode) {
5863
val failure = testCfg.checkImpl()
59-
if (failure != null) return failure
64+
if (failure != null) {
65+
if (cont != null) cont(failure)
66+
return failure
67+
}
6068
}
6169
}
70+
if (cont != null) cont(null)
6271
return null
6372
}
6473

@@ -203,6 +212,36 @@ fun <O : Options<O, *>> O.check(testClass: Class<*>) = LinChecker.check(testClas
203212
*/
204213
fun <O : Options<O, *>> O.check(testClass: KClass<*>) = this.check(testClass.java)
205214

206-
internal fun <O : Options<O, *>> O.checkImpl(testClass: Class<*>) = LinChecker(testClass, this).checkImpl()
215+
/**
216+
* Runs Lincheck to check the tested class under given configurations.
217+
*
218+
* @param testClass Tested class.
219+
* @return [LincheckFailure] if a failure is discovered, null otherwise.
220+
*/
221+
internal fun <O : Options<O, *>> O.checkImpl(testClass: Class<*>): LincheckFailure? =
222+
LinChecker(testClass, this).checkImpl()
223+
224+
/**
225+
* Runs Lincheck to check the tested class under given configurations.
226+
*
227+
* Takes the [LincheckFailureContinuation] as an argument.
228+
* This is required due to current limitations of our testing infrastructure.
229+
* Some tests need to inspect the internals of the failure object
230+
* (for example, the stack traces of exceptions thrown during the execution).
231+
* However, because Lincheck dynamically installs java-agent and then uninstalls it,
232+
* this process can invalidate some internal state of the failure object
233+
* (for example, the source code mapping information in the stack traces is typically lost).
234+
* To overcome this problem, we run the continuation in the context when Lincheck java-agent is still attached.
235+
*
236+
* @param testClass Tested class.
237+
* @param cont Continuation taking [LincheckFailure] as an argument.
238+
* The continuation is run in the context when Lincheck java-agent is still attached.
239+
* @return [LincheckFailure] if a failure is discovered, null otherwise.
240+
*/
241+
internal fun <O : Options<O, *>> O.checkImpl(testClass: Class<*>, cont: LincheckFailureContinuation) {
242+
LinChecker(testClass, this).checkImpl(cont)
243+
}
244+
245+
internal typealias LincheckFailureContinuation = (LincheckFailure?) -> Unit
207246

208247
internal const val NO_OPERATION_ERROR_MESSAGE = "You must specify at least one operation to test. Please refer to the user guide: https://kotlinlang.org/docs/introduction.html"

src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.objectweb.asm.commons.*
1818
import org.jetbrains.kotlinx.lincheck.transformation.InstrumentationMode.*
1919
import org.jetbrains.kotlinx.lincheck.transformation.transformers.*
2020
import sun.nio.ch.lincheck.*
21+
import kotlin.collections.HashSet
2122

2223
internal class LincheckClassVisitor(
2324
private val instrumentationMode: InstrumentationMode,
@@ -26,8 +27,8 @@ internal class LincheckClassVisitor(
2627
private val ideaPluginEnabled = ideaPluginEnabled()
2728
private var classVersion = 0
2829

29-
private lateinit var fileName: String
30-
private lateinit var className: String
30+
private var fileName: String = ""
31+
private var className: String = "" // internal class name
3132

3233
override fun visitField(
3334
access: Int,
@@ -73,7 +74,7 @@ internal class LincheckClassVisitor(
7374
if (access and ACC_NATIVE != 0) return mv
7475
if (instrumentationMode == STRESS) {
7576
return if (methodName != "<clinit>" && methodName != "<init>") {
76-
CoroutineCancellabilitySupportTransformer(mv, access, methodName, desc)
77+
CoroutineCancellabilitySupportTransformer(mv, access, className, methodName, desc)
7778
} else {
7879
mv
7980
}
@@ -103,7 +104,7 @@ internal class LincheckClassVisitor(
103104
}
104105
mv = JSRInlinerAdapter(mv, access, methodName, desc, signature, exceptions)
105106
mv = TryCatchBlockSorter(mv, access, methodName, desc, signature, exceptions)
106-
mv = CoroutineCancellabilitySupportTransformer(mv, access, methodName, desc)
107+
mv = CoroutineCancellabilitySupportTransformer(mv, access, className, methodName, desc)
107108
if (access and ACC_SYNCHRONIZED != 0) {
108109
mv = SynchronizedMethodTransformer(fileName, className, methodName, mv.newAdapter(), classVersion)
109110
}
@@ -192,4 +193,9 @@ private class WrapMethodInIgnoredSectionTransformer(
192193
}
193194
visitInsn(opcode)
194195
}
195-
}
196+
}
197+
198+
// Set storing canonical names of the classes that call internal coroutine functions;
199+
// it is used to optimize class re-transformation in stress mode by remembering
200+
// exactly what classes need to be re-transformed (only the coroutines calling classes)
201+
internal val coroutineCallingClasses = HashSet<String>()

0 commit comments

Comments
 (0)