diff --git a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java index d41ada7a5..161e90455 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java +++ b/bootstrap/src/sun/nio/ch/lincheck/EventTracker.java @@ -42,8 +42,6 @@ boolean beforeWriteField(Object obj, String className, String fieldName, Object boolean beforeWriteArrayElement(Object array, int index, Object value, int codeLocation); void afterWrite(); - void afterReflectiveSetter(Object receiver, Object value); - void beforeMethodCall(Object owner, String className, String methodName, int codeLocation, int methodId, Object[] params); void onMethodCallReturn(Object result); void onMethodCallException(Throwable t); diff --git a/bootstrap/src/sun/nio/ch/lincheck/Injections.java b/bootstrap/src/sun/nio/ch/lincheck/Injections.java index 5bbfb0b87..d2b92dd08 100644 --- a/bootstrap/src/sun/nio/ch/lincheck/Injections.java +++ b/bootstrap/src/sun/nio/ch/lincheck/Injections.java @@ -237,19 +237,6 @@ public static void afterWrite() { getEventTracker().afterWrite(); } - /** - * Called from the instrumented code after atomic write is performed through either - * the AtomicXXXFieldUpdater, VarHandle, or Unsafe APIs. - * Incorporates all atomic methods that can set the field (or array element) of an object, - * such as `set`, `compareAndSet`, `compareAndExchange`, etc. - * - * @param receiver The object to which field (or array element) the value is set. - * @param value The value written into [receiver] field (or array element). - */ - public static void afterReflectiveSetter(Object receiver, Object value) { - getEventTracker().afterReflectiveSetter(receiver, value); - } - /** * Called from the instrumented code before any method call. * diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt index fa8fd92d1..f867ad1d8 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt @@ -874,10 +874,6 @@ abstract class ManagedStrategy( } } - override fun afterReflectiveSetter(receiver: Any?, value: Any?) = runInIgnoredSection { - objectTracker.registerObjectLink(fromObject = receiver ?: StaticObject, toObject = value) - } - override fun getThreadLocalRandom(): Random = runInIgnoredSection { return randoms[currentThread] } @@ -937,6 +933,12 @@ abstract class ManagedStrategy( if (owner == null && atomicMethodDescriptor == null && guarantee == null) { // static method LincheckJavaAgent.ensureClassHierarchyIsTransformed(className.canonicalClassName) } + if (atomicMethodDescriptor != null && atomicMethodDescriptor.kind.isSetter) { + objectTracker.registerObjectLink( + fromObject = atomicMethodDescriptor.getAccessedObject(owner!!, params), + toObject = atomicMethodDescriptor.getSetValue(owner, params) + ) + } if (collectTrace) { traceCollector!!.checkActiveLockDetected() addBeforeMethodCallTracePoint(owner, codeLocation, methodId, className, methodName, params, atomicMethodDescriptor) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt index 84810546f..e010e821c 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckClassVisitor.kt @@ -138,9 +138,6 @@ internal class LincheckClassVisitor( mv = DeterministicHashCodeTransformer(fileName, className, methodName, mv.newAdapter()) mv = DeterministicTimeTransformer(mv.newAdapter()) mv = DeterministicRandomTransformer(fileName, className, methodName, mv.newAdapter()) - mv = UnsafeMethodTransformer(fileName, className, methodName, mv.newAdapter()) - mv = AtomicFieldUpdaterMethodTransformer(fileName, className, methodName, mv.newAdapter()) - mv = VarHandleMethodTransformer(fileName, className, methodName, mv.newAdapter()) // `SharedMemoryAccessTransformer` goes first because it relies on `AnalyzerAdapter`, // which should be put in front of the byte-code transformer chain, // so that it can correctly analyze the byte-code and compute required type-information diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ReflectiveSharedMemoryAccessTransformers.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ReflectiveSharedMemoryAccessTransformers.kt deleted file mode 100644 index 55c2fca59..000000000 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ReflectiveSharedMemoryAccessTransformers.kt +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Lincheck - * - * Copyright (C) 2019 - 2024 JetBrains s.r.o. - * - * This Source Code Form is subject to the terms of the - * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.jetbrains.kotlinx.lincheck.transformation.transformers - -import org.objectweb.asm.Type -import org.objectweb.asm.Type.INT_TYPE -import org.objectweb.asm.Type.LONG_TYPE -import org.objectweb.asm.commons.InstructionAdapter.OBJECT_TYPE -import org.objectweb.asm.commons.GeneratorAdapter -import org.jetbrains.kotlinx.lincheck.transformation.* -import sun.nio.ch.lincheck.EventTracker -import sun.nio.ch.lincheck.Injections - -/** - * [ReflectiveSharedMemoryAccessTransformer] tracks shared memory accesses performed via `AtomicFieldUpdater` (AFU) API, - * injecting invocations of corresponding [EventTracker] methods. - * - * In particular, [EventTracker.afterReflectiveSetter] method is injected to track changes - * in the objects' graph topology caused by a write to a field through AFU. - */ -internal class AtomicFieldUpdaterMethodTransformer( - fileName: String, - className: String, - methodName: String, - adapter: GeneratorAdapter, -) : ReflectiveSharedMemoryAccessTransformer(fileName, className, methodName, adapter) { - - override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) = adapter.run { - if (owner != "java/util/concurrent/atomic/AtomicReferenceFieldUpdater") { - visitMethodInsn(opcode, owner, name, desc, itf) - return - } - invokeIfInTestingCode( - original = { - visitMethodInsn(opcode, owner, name, desc, itf) - }, - code = { - when (name) { - "set", "lazySet", "getAndSet" -> { - processSetFieldMethod(name, opcode, owner, desc, itf) - } - "compareAndSet", "weakCompareAndSet" -> { - processCompareAndSetFieldMethod(name, opcode, owner, desc, itf) - } - else -> { - visitMethodInsn(opcode, owner, name, desc, itf) - } - } - } - ) - } - -} - -/** - * [VarHandleMethodTransformer] tracks shared memory accesses performed via `VarHandle` (VH) API, - * injecting invocations of corresponding [EventTracker] methods. - * - * In particular, [EventTracker.afterReflectiveSetter] method is injected to track changes - * in the object graph topology caused by a write to a field (or array element) through VH. - */ -internal class VarHandleMethodTransformer( - fileName: String, - className: String, - methodName: String, - adapter: GeneratorAdapter, -) : ReflectiveSharedMemoryAccessTransformer(fileName, className, methodName, adapter) { - - override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) = adapter.run { - if (owner != "java/lang/invoke/VarHandle") { - visitMethodInsn(opcode, owner, name, desc, itf) - return - } - invokeIfInTestingCode( - original = { - visitMethodInsn(opcode, owner, name, desc, itf) - }, - code = { - val argumentCount = Type.getArgumentCount(desc) - when (name) { - "set", "setVolatile", "setRelease", "setOpaque", - "getAndSet", "getAndSetAcquire", "getAndSetRelease" -> when (argumentCount) { - 1 -> processSetStaticFieldMethod(name, opcode, owner, desc, itf) - 2 -> processSetFieldMethod(name, opcode, owner, desc, itf) - 3 -> processSetArrayElementMethod(name, opcode, owner, desc, itf) - else -> throw IllegalStateException() - } - "compareAndSet", "weakCompareAndSet", - "weakCompareAndSetRelease", "weakCompareAndSetAcquire", "weakCompareAndSetPlain", - "compareAndExchange", "compareAndExchangeAcquire", "compareAndExchangeRelease" -> when (argumentCount) { - 2 -> processCompareAndSetStaticFieldMethod(name, opcode, owner, desc, itf) - 3 -> processCompareAndSetFieldMethod(name, opcode, owner, desc, itf) - 4 -> processCompareAndSetArrayElementMethod(name, opcode, owner, desc, itf) - else -> throw IllegalStateException() - } - else -> { - visitMethodInsn(opcode, owner, name, desc, itf) - } - } - } - ) - } - -} - -/** - * [ReflectiveSharedMemoryAccessTransformer] tracks shared memory accesses performed via `Unsafe` API, - * injecting invocations of corresponding [EventTracker] methods. - * - * In particular, [EventTracker.afterReflectiveSetter] method is injected to track changes - * in the objects' graph topology caused by a write to a field (or array element) through Unsafe. - */ -internal class UnsafeMethodTransformer( - fileName: String, - className: String, - methodName: String, - adapter: GeneratorAdapter, -) : ReflectiveSharedMemoryAccessTransformer(fileName, className, methodName, adapter) { - - override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) = adapter.run { - if (owner != "sun/misc/Unsafe" && owner != "jdk/internal/misc/Unsafe") { - visitMethodInsn(opcode, owner, name, desc, itf) - return - } - invokeIfInTestingCode( - original = { - visitMethodInsn(opcode, owner, name, desc, itf) - }, - code = { - when (name) { - "compareAndSwapObject", - "compareAndSetReference", "weakCompareAndSetReference", - "weakCompareAndSetReferenceRelease", "weakCompareAndSetReferenceAcquire", "weakCompareAndSetReferencePlain", - "compareAndExchangeReference", "compareAndExchangeReferenceAcquire", "compareAndExchangeReferenceRelease" -> { - processCompareAndSetByOffsetMethod(name, opcode, owner, desc, itf) - } - "putObject", "putObjectVolatile", "getAndSetObject", - "putReference", "putReferenceVolatile", "putReferenceRelease", "putReferenceOpaque", - "getAndSetReference", "getAndSetReferenceAcquire", "getAndSetReferenceRelease" -> { - processGetAndSetByOffsetMethod(name, opcode, owner, desc, itf) - } - else -> { - visitMethodInsn(opcode, owner, name, desc, itf) - } - } - } - ) - } - -} - -internal open class ReflectiveSharedMemoryAccessTransformer( - fileName: String, - className: String, - methodName: String, - adapter: GeneratorAdapter, -) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) { - - /** - * Process methods like *.set(receiver, value) - */ - protected fun processSetFieldMethod(name: String, opcode: Int, owner: String, desc: String, itf: Boolean) = adapter.run { - // STACK: receiver, value - val argumentTypes = Type.getArgumentTypes(desc) - val boxedTypes = arrayOf(OBJECT_TYPE, OBJECT_TYPE) - val argumentLocals = copyLocals( - valueTypes = argumentTypes, - localTypes = boxedTypes, - ) - // STACK: receiver, value - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: - loadLocals(argumentLocals, boxedTypes) - // STACK: receiver, value - invokeStatic(Injections::afterReflectiveSetter) - } - - /** - * Process methods like *.set(value) - */ - protected fun processSetStaticFieldMethod(name: String, opcode: Int, owner: String, desc: String, itf: Boolean) = adapter.run { - // STACK: value - val argumentTypes = Type.getArgumentTypes(desc) - val boxedTypes = arrayOf(OBJECT_TYPE) - val argumentLocals = copyLocals( - valueTypes = argumentTypes, - localTypes = boxedTypes, - ) - // STACK: value - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: - pushNull() - loadLocals(argumentLocals, boxedTypes) - // STACK: null, value - invokeStatic(Injections::afterReflectiveSetter) - } - - /** - * Process methods like *.set(receiver, index, value) - */ - protected fun processSetArrayElementMethod(name: String, opcode: Int, owner: String, desc: String, itf: Boolean) = adapter.run { - // STACK: receiver, index, value - val argumentTypes = Type.getArgumentTypes(desc) - val argumentLocals = copyLocals( - valueTypes = argumentTypes, - localTypes = arrayOf(OBJECT_TYPE, INT_TYPE, OBJECT_TYPE), - ) - // STACK: receiver, index, value - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: - loadLocal(argumentLocals[0]) - loadLocal(argumentLocals[2]) - // STACK: receiver, value - invokeStatic(Injections::afterReflectiveSetter) - } - - /** - * Process methods like *.compareAndSet(receiver, expectedValue, desiredValue) - */ - protected fun processCompareAndSetFieldMethod(name: String, opcode: Int, owner: String, desc: String, itf: Boolean) = adapter.run { - // STACK: receiver, expectedValue, newValue - val argumentTypes = Type.getArgumentTypes(desc) - val argumentLocals = copyLocals( - valueTypes = argumentTypes, - localTypes = arrayOf(OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE), - ) - // STACK: receiver, expectedValue, newValue - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: cas-result - loadLocal(argumentLocals[0]) - loadLocal(argumentLocals[2]) - // STACK: cas-result, receiver, newValue - invokeStatic(Injections::afterReflectiveSetter) - // STACK: cas-result - } - - /** - * Process methods like *.compareAndSet(expectedValue, desiredValue) - */ - protected fun processCompareAndSetStaticFieldMethod(name: String, opcode: Int, owner: String, desc: String, itf: Boolean) = adapter.run { - // STACK: expectedValue, newValue - val argumentTypes = Type.getArgumentTypes(desc) - val argumentLocals = copyLocals( - valueTypes = argumentTypes, - localTypes = arrayOf(OBJECT_TYPE, OBJECT_TYPE), - ) - // STACK: expectedValue, newValue - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: cas-result - pushNull() - loadLocal(argumentLocals[1]) - // STACK: cas-result, null, newValue - invokeStatic(Injections::afterReflectiveSetter) - // STACK: cas-result - } - - /** - * Process methods like *.compareAndSet(receiver, index, expectedValue, desiredValue) - */ - protected fun processCompareAndSetArrayElementMethod(name: String, opcode: Int, owner: String, desc: String, itf: Boolean) = adapter.run { - // STACK: receiver, index, expectedValue, newValue - val argumentTypes = Type.getArgumentTypes(desc) - val argumentLocals = copyLocals( - valueTypes = argumentTypes, - localTypes = arrayOf(OBJECT_TYPE, INT_TYPE, OBJECT_TYPE, OBJECT_TYPE), - ) - // STACK: receiver, index, expectedValue, newValue - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: casResult - loadLocal(argumentLocals[0]) - loadLocal(argumentLocals[3]) - // STACK: cas-result, receiver, newValue - invokeStatic(Injections::afterReflectiveSetter) - // STACK: cas-result - } - - /** - * Process methods like *.compareAndSet(receiver, offset, expectedValue, newValue) - */ - protected fun processCompareAndSetByOffsetMethod(name: String, opcode: Int, owner: String, desc: String, itf: Boolean) = adapter.run { - // STACK: receiver, offset, expectedValue, newValue - val argumentTypes = Type.getArgumentTypes(desc) - val argumentLocals = copyLocals( - valueTypes = argumentTypes, - localTypes = arrayOf(OBJECT_TYPE, LONG_TYPE, OBJECT_TYPE, OBJECT_TYPE), - ) - // STACK: receiver, offset, expectedValue, newValue - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: casResult - loadLocal(argumentLocals[0]) - loadLocal(argumentLocals[3]) - // STACK: cas-result, receiver, nextValue - invokeStatic(Injections::afterReflectiveSetter) - // STACK: cas-result - } - - /** - * Process methods like *.getAndSet(receiver, offset, newValue) - */ - protected fun processGetAndSetByOffsetMethod(name: String, opcode: Int, owner: String, desc: String, itf: Boolean) = adapter.run { - // STACK: receiver, offset, newValue - val argumentTypes = Type.getArgumentTypes(desc) - val argumentLocals = copyLocals( - valueTypes = argumentTypes, - localTypes = arrayOf(OBJECT_TYPE, LONG_TYPE, OBJECT_TYPE), - ) - // STACK: receiver, offset, newValue - visitMethodInsn(opcode, owner, name, desc, itf) - // STACK: oldValue - loadLocal(argumentLocals[0]) - loadLocal(argumentLocals[2]) - // STACK: oldValue, receiver, newValue - invokeStatic(Injections::afterReflectiveSetter) - // STACK: oldValue - } - -} \ No newline at end of file diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/AtomicMethods.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/AtomicMethods.kt index 72fbc1c48..e3aca50ae 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/AtomicMethods.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/AtomicMethods.kt @@ -13,6 +13,8 @@ package org.jetbrains.kotlinx.lincheck.util import java.util.concurrent.atomic.* import org.jetbrains.kotlinx.lincheck.util.AtomicMethodKind.* import org.jetbrains.kotlinx.lincheck.util.MemoryOrdering.* +import org.jetbrains.kotlinx.lincheck.strategy.managed.VarHandleNames +import org.jetbrains.kotlinx.lincheck.strategy.managed.VarHandleMethodType import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -32,6 +34,24 @@ internal enum class AtomicMethodKind { GET_AND_DECREMENT, DECREMENT_AND_GET; } +internal val AtomicMethodKind.isSetter get() = when (this) { + SET, + GET_AND_SET, + COMPARE_AND_SET, + WEAK_COMPARE_AND_SET, + COMPARE_AND_EXCHANGE + -> true + else -> false +} + +internal val AtomicMethodKind.isCasSetter get() = when (this) { + COMPARE_AND_SET, + WEAK_COMPARE_AND_SET, + COMPARE_AND_EXCHANGE + -> true + else -> false +} + internal enum class MemoryOrdering { PLAIN, OPAQUE, RELEASE, ACQUIRE, VOLATILE; @@ -55,6 +75,39 @@ internal fun getAtomicMethodDescriptor(obj: Any?, methodName: String): AtomicMet } } +internal fun AtomicMethodDescriptor.getAccessedObject(obj: Any, params: Array): Any = when { + (isAtomicFieldUpdater(obj) || isVarHandle(obj) || isUnsafe(obj)) -> params[0]!! + else -> obj +} + +internal fun AtomicMethodDescriptor.getSetValue(obj: Any?, params: Array): Any? { + var argOffset = 0 + // AFU case - the first argument is an accessed object + if (isAtomicFieldUpdater(obj)) + argOffset += 1 + // VarHandle case + if (isVarHandle(obj)) { + val methodType = VarHandleNames.varHandleMethodType(obj, params) + // non-static field access case - the first argument is an accessed object + if (methodType !is VarHandleMethodType.StaticVarHandleMethod) + argOffset += 1 + // array access case - there is an additional element index argument + if (methodType is VarHandleMethodType.ArrayVarHandleMethod) + argOffset += 1 + } + // Unsafe case - the first argument is an accessed object, plus an additional offset argument + if (isUnsafe(obj)) + argOffset += 2 + // Atomic arrays case - the first argument is element index + if (isAtomicArray(obj)) + argOffset += 1 + // CAS case - there is an expected value additional argument + if (kind.isCasSetter) + argOffset += 1 + + return params[argOffset] +} + internal fun isAtomic(receiver: Any?) = isAtomicJava(receiver) || isAtomicFU(receiver) diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/transformation/LocalObjectsTests.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/transformation/LocalObjectsTests.kt index c2125c0bd..b32696429 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/transformation/LocalObjectsTests.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/transformation/LocalObjectsTests.kt @@ -17,7 +17,7 @@ import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.execution.parallelResults import org.junit.Test import sun.misc.Unsafe -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater +import java.util.concurrent.atomic.* import kotlin.concurrent.Volatile import kotlin.reflect.KFunction @@ -129,11 +129,190 @@ class LocalObjectEscapeConstructorTest { } +/** + * This test checks that despite some object isn't explicitly assigned to some shared value, + * is won't be treated like a local object if we wrote it into some shared object using [AtomicReference]. + * + * If the object is incorrectly classified as a local object, + * this test would hang due to infinite spin-loop on a local object operation with + * no chances to detect a cycle and switch. + */ +class AtomicReferenceLocalObjectsTest { + + val atomicReference: AtomicReference = AtomicReference(Node(0)) + + val node: Node get() = atomicReference.get() + + @Volatile + var flag = false + + @Operation + fun checkWithSpinCycle() { + val curr = node + while (curr.value == 1) { + // spin-wait + } + } + + @Operation + fun setWithAtomicReference() = updateNode { nextNode -> + atomicReference.set(nextNode) + } + + @Operation + fun lazySetWithAtomicReference()= updateNode { nextNode -> + atomicReference.lazySet(nextNode) + } + + @Operation + fun getAndSetWithAtomicReference() = updateNode { nextNode -> + atomicReference.getAndSet(nextNode) + } + + @Operation + fun compareAndSetWithAtomicReference() = updateNode { nextNode -> + atomicReference.compareAndSet(node, nextNode) + } + + @Operation + fun weakCompareAndSetWithAtomicReference() = updateNode { nextNode -> + @Suppress("DEPRECATION") + atomicReference.weakCompareAndSet(node, nextNode) + } + + @Test + fun testAtomicReferenceSet() = testWithUpdate(::setWithAtomicReference) + + @Test + fun testAtomicReferenceLazySet() = testWithUpdate(::lazySetWithAtomicReference) + + @Test + fun testAtomicReferenceGetAndSet() = testWithUpdate(::getAndSetWithAtomicReference) + + @Test + fun testAtomicAtomicReferenceCompareAndSet() = testWithUpdate(::compareAndSetWithAtomicReference) + + @Test + fun testAtomicReferenceWeakCompareAndSet() = testWithUpdate(::weakCompareAndSetWithAtomicReference) + + private fun testWithUpdate(operation: KFunction<*>) = ModelCheckingOptions() + .iterations(0) // we will run only custom scenarios + .addCustomScenario { + parallel { + thread { actor(operation) } + thread { actor(::checkWithSpinCycle) } + } + } + .check(this::class) + + private inline fun updateNode(updateAction: (Node) -> Unit) { + val nextNode = Node(1) + nextNode.value = 1 + updateAction(nextNode) + // If we have a bug and treat nextNode as a local object, we have to insert here some switch points + // to catch a moment when `node.value` is updated to 1 + flag = true + + nextNode.value = 0 + } + +} + +/** + * This test checks that despite some object isn't explicitly assigned to some shared value, + * is won't be treated like a local object if we wrote it into some shared object using [AtomicReferenceArray]. + * + * If the object is incorrectly classified as a local object, + * this test would hang due to infinite spin-loop on a local object operation with + * no chances to detect a cycle and switch. + */ +class AtomicArrayLocalObjectsTest { + + val atomicArray: AtomicReferenceArray = AtomicReferenceArray(arrayOf(Node(0))) + + val node: Node get() = atomicArray[0] + + @Volatile + var flag = false + + @Operation + fun checkWithSpinCycle() { + val curr = node + while (curr.value == 1) { + // spin-wait + } + } + + @Operation + fun setWithAtomicArray() = updateNode { nextNode -> + atomicArray.set(0, nextNode) + } + + @Operation + fun lazySetWithAtomicArray()= updateNode { nextNode -> + atomicArray.lazySet(0, nextNode) + } + + @Operation + fun getAndSetWithAtomicArray() = updateNode { nextNode -> + atomicArray.getAndSet(0, nextNode) + } + + @Operation + fun compareAndSetWithAtomicArray() = updateNode { nextNode -> + atomicArray.compareAndSet(0, node, nextNode) + } + + @Operation + fun weakCompareAndSetWithAtomicArray() = updateNode { nextNode -> + @Suppress("DEPRECATION") + atomicArray.weakCompareAndSet(0, node, nextNode) + } + + @Test + fun testAtomicArraySet() = testWithUpdate(::setWithAtomicArray) + + @Test + fun testAtomicArrayLazySet() = testWithUpdate(::lazySetWithAtomicArray) + + @Test + fun testAtomicArrayGetAndSet() = testWithUpdate(::getAndSetWithAtomicArray) + + @Test + fun testAtomicAtomicArrayCompareAndSet() = testWithUpdate(::compareAndSetWithAtomicArray) + + @Test + fun testAtomicArrayWeakCompareAndSet() = testWithUpdate(::weakCompareAndSetWithAtomicArray) + + private fun testWithUpdate(operation: KFunction<*>) = ModelCheckingOptions() + .iterations(0) // we will run only custom scenarios + .addCustomScenario { + parallel { + thread { actor(operation) } + thread { actor(::checkWithSpinCycle) } + } + } + .check(this::class) + + private inline fun updateNode(updateAction: (Node) -> Unit) { + val nextNode = Node(1) + nextNode.value = 1 + updateAction(nextNode) + // If we have a bug and treat nextNode as a local object, we have to insert here some switch points + // to catch a moment when `node.value` is updated to 1 + flag = true + + nextNode.value = 0 + } + +} + /** * This test checks that despite some object isn't explicitly assigned to some shared value, * is won't be treated like a local object if we wrote it into some shared object using [AtomicReferenceFieldUpdater]. * - * If we hadn't such check, this test would hang due to infinite spin-loop on a local object operations with + * If the object is incorrectly classified as a local object, + * this test would hang due to infinite spin-loop on a local object operation with * no chances to detect a cycle and switch. */ class AtomicUpdaterLocalObjectsTest { @@ -146,9 +325,8 @@ class AtomicUpdaterLocalObjectsTest { ) } - @Volatile - var node: Node = Node(0) + var node: Node = Node(0) @Volatile var flag = false @@ -188,12 +366,16 @@ class AtomicUpdaterLocalObjectsTest { @Test fun testAtomicUpdaterSet() = testWithUpdate(::setWithAtomicUpdater) + @Test fun testAtomicUpdaterLazySet() = testWithUpdate(::lazySetWithAtomicUpdater) + @Test fun testAtomicUpdaterGetAndSet() = testWithUpdate(::getAndSetWithAtomicUpdater) + @Test fun testAtomicUpdaterWeakCompareAndSet() = testWithUpdate(::weakCompareAndSetWithAtomicUpdater) + @Test fun testAtomicUpdaterCompareAndSet() = testWithUpdate(::compareAndSetWithAtomicUpdater) @@ -218,14 +400,14 @@ class AtomicUpdaterLocalObjectsTest { nextNode.value = 0 } - } /** * This test checks that despite some object isn't explicitly assigned to some shared value, * is won't be treated like a local object if we wrote it into some shared object using [Unsafe]. * - * If we hadn't such check, this test would hang due to infinite spin-loop on a local object operations with + * If the object is incorrectly classified as a local object, + * this test would hang due to infinite spin-loop on a local object operation with * no chances to detect a cycle and switch. */ class UnsafeLocalObjectsTest { @@ -256,7 +438,6 @@ class UnsafeLocalObjectsTest { val offset = unsafe.objectFieldOffset(UnsafeLocalObjectsTest::class.java.getDeclaredField("node")) } - @Operation fun compareAndSetWithUnsafe() = updateNode { nextNode -> unsafe.compareAndSwapObject(this, offset, node, nextNode) diff --git a/src/jvm/test/resources/expected_logs/spin_lock/broken-cas-2-recursive-live-lock.txt b/src/jvm/test/resources/expected_logs/spin_lock/broken-cas-2-recursive-live-lock.txt index 72710c2e1..5398285ae 100644 --- a/src/jvm/test/resources/expected_logs/spin_lock/broken-cas-2-recursive-live-lock.txt +++ b/src/jvm/test/resources/expected_logs/spin_lock/broken-cas-2-recursive-live-lock.txt @@ -7,134 +7,89 @@ The following interleaving leads to the error: -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Thread 1 | Thread 2 | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| | cas2_1(0, 0, 4, 1, 0, 5): | -| | array.cas2(0,0,4,1,0,5,1) at AtomicArrayWithCAS2LiveLockTest.cas2_1(AtomicArrayWithCAS2LiveLockTest.kt:39) | -| | AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#1,false,1,false,5,null) at AtomicArrayWithCAS2.cas2(AtomicArrayWithCAS2LiveLockTest.kt:85) | -| | Descriptor#1.apply(true,1,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | -| | AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#1,true,1,false,4,null): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:118) | -| | Descriptor#1.installOrHelp(true,1,false): true at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate0.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:143) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate1.READ: 0 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:144) | -| | switch | -| cas2_0(0, 0, 2, 1, 0, 3): | | -| array.cas2(0,0,2,1,0,3,0) at AtomicArrayWithCAS2LiveLockTest.cas2_0(AtomicArrayWithCAS2LiveLockTest.kt:33) | | -| AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#3,false,0,false,5,null) at AtomicArrayWithCAS2.cas2(AtomicArrayWithCAS2LiveLockTest.kt:85) | | -| Descriptor#3.apply(true,0,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | | -| AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#3,true,0,false,4,null) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:118) | | -| Descriptor#3.installOrHelp(true,0,false) at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | | -| AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| AtomicArrayWithCAS2LiveLockTest#1.array.gate1.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:151) | | -| AtomicArrayWithCAS2LiveLockTest#1.array.gate0.READ: 1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:152) | | -| /* The following events repeat infinitely: */ | | -| ┌╶> Descriptor#2.apply(false,0,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:153) | | -| | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | | -| | status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | | -| | installOrHelp(true,0,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| └╶╶╶╶╶╶ switch (reason: active lock detected) | | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate0.WRITE(0) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:147) | -| | AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#2,false,1,false,4,null) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:161) | -| | Descriptor#2.read(0): 0 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:162) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:167) | -| | AtomicReferenceArray#1[0].compareAndSet(Descriptor#2,Descriptor#1): true at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:167) | -| | AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#1,false,1,false,4,null) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:119) | -| | Descriptor#1.installOrHelp(false,1,false) at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | AtomicReferenceArray#1[1].get(): Descriptor#4 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate0.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:143) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate1.READ: 1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:144) | -| | /* The following events repeat infinitely: */ | -| | ┌╶> Descriptor#4.apply(false,1,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:145) | -| | | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | -| | | status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | -| | | installOrHelp(true,1,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | -| | | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | | AtomicReferenceArray#1[1].get(): Descriptor#4 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | └╶╶╶╶╶╶ switch (reason: active lock detected) | -| apply(false,0,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:137) | | -| status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | | -| status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | | -| /* The following events repeat infinitely: */ | | -| ┌╶> installOrHelp(true,0,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| | AtomicReferenceArray#1[0].get(): Descriptor#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| | Descriptor#1.apply(false,0,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:137) | | -| | status.compareAndSet(SUCCESS,SUCCESS): false at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | | -| └╶╶╶╶╶╶ switch (reason: active lock detected) | | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Thread 1 | Thread 2 | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| | cas2_1(0, 0, 4, 1, 0, 5): | +| | array.cas2(0,0,4,1,0,5,1) at AtomicArrayWithCAS2LiveLockTest.cas2_1(AtomicArrayWithCAS2LiveLockTest.kt:39) | +| | AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#1,false,1,false,5,null) at AtomicArrayWithCAS2.cas2(AtomicArrayWithCAS2LiveLockTest.kt:85) | +| | Descriptor#1.apply(true,1,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | +| | AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#1,true,1,false,4,null) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:118) | +| | Descriptor#1.installOrHelp(true,1,false) at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | +| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | +| | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | +| | AtomicArrayWithCAS2LiveLockTest#1.array.gate0.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:143) | +| | switch | +| cas2_0(0, 0, 2, 1, 0, 3): | | +| array.cas2(0,0,2,1,0,3,0) at AtomicArrayWithCAS2LiveLockTest.cas2_0(AtomicArrayWithCAS2LiveLockTest.kt:33) | | +| AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#3,false,0,false,5,null) at AtomicArrayWithCAS2.cas2(AtomicArrayWithCAS2LiveLockTest.kt:85) | | +| Descriptor#3.apply(true,0,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | | +| AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#3,true,0,false,4,null) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:118) | | +| Descriptor#3.installOrHelp(true,0,false) at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | | +| AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | +| AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | +| AtomicArrayWithCAS2LiveLockTest#1.array.gate1.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:151) | | +| AtomicArrayWithCAS2LiveLockTest#1.array.gate0.READ: 1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:152) | | +| /* The following events repeat infinitely: */ | | +| ┌╶> Descriptor#2.apply(false,0,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:153) | | +| | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | | +| | status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | | +| | installOrHelp(true,0,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | | +| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | +| | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | +| └╶╶╶╶╶╶ switch (reason: active lock detected) | | +| | AtomicArrayWithCAS2LiveLockTest#1.array.gate1.READ: 1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:144) | +| | /* The following events repeat infinitely: */ | +| | ┌╶> Descriptor#2.apply(false,1,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:145) | +| | | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | +| | | status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | +| | | installOrHelp(true,1,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | +| | | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | +| | | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | +| | └╶╶╶╶╶╶ switch (reason: active lock detected) | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | All unfinished threads are in deadlock Detailed trace: -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Thread 1 | Thread 2 | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| | cas2_1(0, 0, 4, 1, 0, 5): | -| | array.cas2(0,0,4,1,0,5,1) at AtomicArrayWithCAS2LiveLockTest.cas2_1(AtomicArrayWithCAS2LiveLockTest.kt:39) | -| | AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#1,false,1,false,5,null) at AtomicArrayWithCAS2.cas2(AtomicArrayWithCAS2LiveLockTest.kt:85) | -| | Descriptor#1.apply(true,1,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | -| | AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#1,true,1,false,4,null): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:118) | -| | Descriptor#1.installOrHelp(true,1,false): true at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate0.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:143) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate1.READ: 0 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:144) | -| | switch | -| cas2_0(0, 0, 2, 1, 0, 3): | | -| array.cas2(0,0,2,1,0,3,0) at AtomicArrayWithCAS2LiveLockTest.cas2_0(AtomicArrayWithCAS2LiveLockTest.kt:33) | | -| AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#3,false,0,false,5,null) at AtomicArrayWithCAS2.cas2(AtomicArrayWithCAS2LiveLockTest.kt:85) | | -| Descriptor#3.apply(true,0,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | | -| AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#3,true,0,false,4,null) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:118) | | -| Descriptor#3.installOrHelp(true,0,false) at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | | -| AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| AtomicArrayWithCAS2LiveLockTest#1.array.gate1.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:151) | | -| AtomicArrayWithCAS2LiveLockTest#1.array.gate0.READ: 1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:152) | | -| /* The following events repeat infinitely: */ | | -| ┌╶> Descriptor#2.apply(false,0,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:153) | | -| | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | | -| | status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | | -| | installOrHelp(true,0,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| └╶╶╶╶╶╶ switch (reason: active lock detected) | | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate0.WRITE(0) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:147) | -| | AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#2,false,1,false,4,null) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:161) | -| | Descriptor#2.apply(false,1,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | -| | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:117) | -| | Descriptor#2.read(0): 0 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:162) | -| | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.read(AtomicArrayWithCAS2LiveLockTest.kt:101) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:167) | -| | AtomicReferenceArray#1[0].compareAndSet(Descriptor#2,Descriptor#1): true at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:167) | -| | AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#1,false,1,false,4,null) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:119) | -| | Descriptor#1.installOrHelp(false,1,false) at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | AtomicReferenceArray#1[1].get(): Descriptor#4 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate0.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:143) | -| | AtomicArrayWithCAS2LiveLockTest#1.array.gate1.READ: 1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:144) | -| | /* The following events repeat infinitely: */ | -| | ┌╶> Descriptor#4.apply(false,1,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:145) | -| | | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | -| | | status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | -| | | installOrHelp(true,1,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | -| | | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | | AtomicReferenceArray#1[1].get(): Descriptor#4 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | -| | └╶╶╶╶╶╶ switch (reason: active lock detected) | -| apply(false,0,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:137) | | -| status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | | -| status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | | -| /* The following events repeat infinitely: */ | | -| ┌╶> installOrHelp(true,0,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | | -| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| | AtomicReferenceArray#1[0].get(): Descriptor#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | -| | Descriptor#1.apply(false,0,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:137) | | -| | status.compareAndSet(SUCCESS,SUCCESS): false at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | | -| └╶╶╶╶╶╶ switch (reason: active lock detected) | | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Thread 1 | Thread 2 | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| | cas2_1(0, 0, 4, 1, 0, 5): | +| | array.cas2(0,0,4,1,0,5,1) at AtomicArrayWithCAS2LiveLockTest.cas2_1(AtomicArrayWithCAS2LiveLockTest.kt:39) | +| | AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#1,false,1,false,5,null) at AtomicArrayWithCAS2.cas2(AtomicArrayWithCAS2LiveLockTest.kt:85) | +| | Descriptor#1.apply(true,1,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | +| | AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#1,true,1,false,4,null) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:118) | +| | Descriptor#1.installOrHelp(true,1,false) at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | +| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | +| | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | +| | AtomicArrayWithCAS2LiveLockTest#1.array.gate0.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:143) | +| | switch | +| cas2_0(0, 0, 2, 1, 0, 3): | | +| array.cas2(0,0,2,1,0,3,0) at AtomicArrayWithCAS2LiveLockTest.cas2_0(AtomicArrayWithCAS2LiveLockTest.kt:33) | | +| AtomicArrayWithCAS2$Descriptor.apply$default(Descriptor#3,false,0,false,5,null) at AtomicArrayWithCAS2.cas2(AtomicArrayWithCAS2LiveLockTest.kt:85) | | +| Descriptor#3.apply(true,0,false) at AtomicArrayWithCAS2$Descriptor.apply$default(AtomicArrayWithCAS2LiveLockTest.kt:111) | | +| AtomicArrayWithCAS2$Descriptor.installOrHelp$default(Descriptor#3,true,0,false,4,null) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:118) | | +| Descriptor#3.installOrHelp(true,0,false) at AtomicArrayWithCAS2$Descriptor.installOrHelp$default(AtomicArrayWithCAS2LiveLockTest.kt:130) | | +| AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | +| AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | +| AtomicArrayWithCAS2LiveLockTest#1.array.gate1.WRITE(1) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:151) | | +| AtomicArrayWithCAS2LiveLockTest#1.array.gate0.READ: 1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:152) | | +| /* The following events repeat infinitely: */ | | +| ┌╶> Descriptor#2.apply(false,0,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:153) | | +| | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | | +| | status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | | +| | installOrHelp(true,0,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | | +| | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | +| | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | | +| └╶╶╶╶╶╶ switch (reason: active lock detected) | | +| | AtomicArrayWithCAS2LiveLockTest#1.array.gate1.READ: 1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:144) | +| | /* The following events repeat infinitely: */ | +| | ┌╶> Descriptor#2.apply(false,1,true) at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:145) | +| | | status.READ: SUCCESS at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:113) | +| | | status.compareAndSet(SUCCESS,SUCCESS): true at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:114) | +| | | installOrHelp(true,1,true) at AtomicArrayWithCAS2$Descriptor.apply(AtomicArrayWithCAS2LiveLockTest.kt:115) | +| | | AtomicArrayWithCAS2LiveLockTest#1.array.array.READ: AtomicReferenceArray#1 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | +| | | AtomicReferenceArray#1[0].get(): Descriptor#2 at AtomicArrayWithCAS2$Descriptor.installOrHelp(AtomicArrayWithCAS2LiveLockTest.kt:135) | +| | └╶╶╶╶╶╶ switch (reason: active lock detected) | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | All unfinished threads are in deadlock diff --git a/src/jvm/test/resources/expected_logs/suspend_trace_reporting.txt b/src/jvm/test/resources/expected_logs/suspend_trace_reporting.txt index 729d3dc27..e7cd25be0 100644 --- a/src/jvm/test/resources/expected_logs/suspend_trace_reporting.txt +++ b/src/jvm/test/resources/expected_logs/suspend_trace_reporting.txt @@ -10,35 +10,36 @@ The following interleaving leads to the error: | Thread 1 | Thread 2 | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | | bar(): -1 | -| | barStarted.WRITE(true) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:38) | -| | lock.lock(null): Unit#1 at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:130) | -| | counter.READ: 0 at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:40) | -| | counter.WRITE(1) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:40) | -| | lock.unlock(null) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:134) | +| | barStarted.WRITE(true) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:37) | +| | lock.lock(null): Unit#1 at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:193) | +| | counter.READ: 0 at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:39) | +| | counter.WRITE(1) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:39) | +| | lock.unlock(null) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:197) | | | isLocked(): true at MutexImpl.unlock(Mutex.kt:213) | | | owner.get(): null at MutexImpl.unlock(Mutex.kt:215) | | | switch | | foo() | | -| barStarted.READ: true at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:29) | | -| canEnterForbiddenBlock.WRITE(true) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:29) | | -| lock.lock(null): COROUTINE_SUSPENDED at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:120) | | +| barStarted.READ: true at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:28) | | +| canEnterForbiddenBlock.WRITE(true) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:28) | | +| lock.lock(null): COROUTINE_SUSPENDED at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:183) | | | MutexImpl.lock$suspendImpl(MutexImpl#1,null,): COROUTINE_SUSPENDED at MutexImpl.lock(Mutex.kt:0) | | | SuspendTraceReportingTest#1.lock.tryLock(null): false at MutexImpl.lock$suspendImpl(Mutex.kt:171) | | | SuspendTraceReportingTest#1.lock.lockSuspend(null): COROUTINE_SUSPENDED at MutexImpl.lock$suspendImpl(Mutex.kt:172) | | | acquire() at MutexImpl.lockSuspend(Mutex.kt:177) | | | .getResult(): COROUTINE_SUSPENDED at MutexImpl.lockSuspend(Mutex.kt:321) | | +| isReusable(): false at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:297) | | | trySuspend(): true at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:300) | | | getParentHandle(): null at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:310) | | | installParentHandle(): ChildContinuation#1 at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:311) | | | switch (reason: coroutine is suspended) | | | | owner.compareAndSet(null,): true at MutexImpl.unlock(Mutex.kt:220) | | | release() at MutexImpl.unlock(Mutex.kt:222) | -| | canEnterForbiddenBlock.READ: true at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:42) | +| | canEnterForbiddenBlock.READ: true at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:41) | | | result: -1 | -| counter.READ: 1 at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:31) | | -| counter.WRITE(2) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:31) | | -| lock.unlock(null) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:124) | | -| canEnterForbiddenBlock.WRITE(false) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:33) | | +| counter.READ: 1 at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:30) | | +| counter.WRITE(2) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:30) | | +| lock.unlock(null) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:187) | | +| canEnterForbiddenBlock.WRITE(false) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:32) | | | result: void | | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -47,8 +48,8 @@ Detailed trace: | Thread 1 | Thread 2 | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | | bar(): -1 | -| | barStarted.WRITE(true) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:38) | -| | lock.lock(null): Unit#1 at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:130) | +| | barStarted.WRITE(true) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:37) | +| | lock.lock(null): Unit#1 at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:193) | | | MutexImpl.lock$suspendImpl(MutexImpl#1,null,): Unit#1 at MutexImpl.lock(Mutex.kt:0) | | | SuspendTraceReportingTest#1.lock.tryLock(null): true at MutexImpl.lock$suspendImpl(Mutex.kt:171) | | | tryLockImpl(null): 0 at MutexImpl.tryLock(Mutex.kt:183) | @@ -57,18 +58,18 @@ Detailed trace: | | _availablePermits.compareAndSet(1,0): true at SemaphoreImpl.tryAcquire(Semaphore.kt:171) | | | owner.get(): at MutexImpl.tryLockImpl(Mutex.kt:190) | | | owner.set(null) at MutexImpl.tryLockImpl(Mutex.kt:191) | -| | counter.READ: 0 at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:40) | -| | counter.WRITE(1) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:40) | -| | lock.unlock(null) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:134) | +| | counter.READ: 0 at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:39) | +| | counter.WRITE(1) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:39) | +| | lock.unlock(null) at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:197) | | | isLocked(): true at MutexImpl.unlock(Mutex.kt:213) | | | getAvailablePermits(): 0 at MutexImpl.isLocked(Mutex.kt:149) | | | _availablePermits.get(): 0 at SemaphoreImpl.getAvailablePermits(Semaphore.kt:152) | | | owner.get(): null at MutexImpl.unlock(Mutex.kt:215) | | | switch | | foo() | | -| barStarted.READ: true at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:29) | | -| canEnterForbiddenBlock.WRITE(true) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:29) | | -| lock.lock(null): COROUTINE_SUSPENDED at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:120) | | +| barStarted.READ: true at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:28) | | +| canEnterForbiddenBlock.WRITE(true) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:28) | | +| lock.lock(null): COROUTINE_SUSPENDED at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:183) | | | MutexImpl.lock$suspendImpl(MutexImpl#1,null,): COROUTINE_SUSPENDED at MutexImpl.lock(Mutex.kt:0) | | | SuspendTraceReportingTest#1.lock.tryLock(null): false at MutexImpl.lock$suspendImpl(Mutex.kt:171) | | | tryLockImpl(null): 1 at MutexImpl.tryLock(Mutex.kt:183) | | @@ -94,6 +95,8 @@ Detailed trace: | _state.get(): Active#1 at CancellableContinuationImpl.invokeOnCancellationImpl(CancellableContinuationImpl.kt:403) | | | _state.compareAndSet(Active#1,SemaphoreSegment#1): true at CancellableContinuationImpl.invokeOnCancellationImpl(CancellableContinuationImpl.kt:407) | | | .getResult(): COROUTINE_SUSPENDED at MutexImpl.lockSuspend(Mutex.kt:321) | | +| isReusable(): false at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:297) | | +| resumeMode.READ: 1 at CancellableContinuationImpl.isReusable(CancellableContinuationImpl.kt:141) | | | trySuspend(): true at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:300) | | | _decisionAndIndex.get(): 0 at CancellableContinuationImpl.trySuspend(CancellableContinuationImpl.kt:0) | | | _decisionAndIndex.compareAndSet(0,536870912): true at CancellableContinuationImpl.trySuspend(CancellableContinuationImpl.kt:278) | | @@ -122,16 +125,23 @@ Detailed trace: | | .tryResume(Unit#1,null,token$1): at MutexImpl$CancellableContinuationWithOwner.tryResume(Mutex.kt:258) | | | tryResumeImpl(Unit#1,null,token$1): at CancellableContinuationImpl.tryResume(CancellableContinuationImpl.kt:582) | | | _state.get(): SemaphoreSegment#1 at CancellableContinuationImpl.tryResumeImpl(CancellableContinuationImpl.kt:0) | +| | resumeMode.READ: 1 at CancellableContinuationImpl.tryResumeImpl(CancellableContinuationImpl.kt:540) | | | _state.compareAndSet(SemaphoreSegment#1,CompletedContinuation#1): true at CancellableContinuationImpl.tryResumeImpl(CancellableContinuationImpl.kt:541) | | | detachChildIfNonResuable() at CancellableContinuationImpl.tryResumeImpl(CancellableContinuationImpl.kt:542) | +| | isReusable(): false at CancellableContinuationImpl.detachChildIfNonResuable(CancellableContinuationImpl.kt:565) | +| | resumeMode.READ: 1 at CancellableContinuationImpl.isReusable(CancellableContinuationImpl.kt:141) | | | detachChild$kotlinx_coroutines_core() at CancellableContinuationImpl.detachChildIfNonResuable(CancellableContinuationImpl.kt:565) | | | getParentHandle(): ChildContinuation#1 at CancellableContinuationImpl.detachChild$kotlinx_coroutines_core(CancellableContinuationImpl.kt:572) | | | _parentHandle.get(): ChildContinuation#1 at CancellableContinuationImpl.getParentHandle(CancellableContinuationImpl.kt:106) | +| | ChildContinuation#1.dispose() at CancellableContinuationImpl.detachChild$kotlinx_coroutines_core(CancellableContinuationImpl.kt:573) | +| | getJob(): JobImpl#1 at JobNode.dispose(JobSupport.kt:1351) | +| | job.READ: JobImpl#1 at JobNode.getJob(JobSupport.kt:1348) | | | _parentHandle.set(NonDisposableHandle#1) at CancellableContinuationImpl.detachChild$kotlinx_coroutines_core(CancellableContinuationImpl.kt:574) | | | owner.get(): at MutexImpl$CancellableContinuationWithOwner.tryResume(Mutex.kt:264) | | | owner.set(null) at MutexImpl$CancellableContinuationWithOwner.tryResume(Mutex.kt:265) | | | .completeResume() at SemaphoreImpl.tryResumeAcquire(Semaphore.kt:349) | | | .completeResume() at MutexImpl$CancellableContinuationWithOwner.completeResume(Mutex.kt:0) | +| | resumeMode.READ: 1 at CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:590) | | | dispatchResume(1) at CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:590) | | | tryResume(): false at CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:472) | | | _decisionAndIndex.get(): 536870912 at CancellableContinuationImpl.tryResume(CancellableContinuationImpl.kt:0) | @@ -140,11 +150,11 @@ Detailed trace: | | .takeState$kotlinx_coroutines_core(): CompletedContinuation#1 at DispatchedTaskKt.resume(DispatchedTask.kt:174) | | | getState$kotlinx_coroutines_core(): CompletedContinuation#1 at CancellableContinuationImpl.takeState$kotlinx_coroutines_core(CancellableContinuationImpl.kt:168) | | | _state.get(): CompletedContinuation#1 at CancellableContinuationImpl.getState$kotlinx_coroutines_core(CancellableContinuationImpl.kt:108) | -| | canEnterForbiddenBlock.READ: true at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:42) | +| | canEnterForbiddenBlock.READ: true at SuspendTraceReportingTest.bar(SuspendTraceReportingTest.kt:41) | | | result: -1 | -| counter.READ: 1 at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:31) | | -| counter.WRITE(2) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:31) | | -| lock.unlock(null) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:124) | | +| counter.READ: 1 at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:30) | | +| counter.WRITE(2) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:30) | | +| lock.unlock(null) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:187) | | | isLocked(): true at MutexImpl.unlock(Mutex.kt:213) | | | getAvailablePermits(): 0 at MutexImpl.isLocked(Mutex.kt:149) | | | _availablePermits.get(): 0 at SemaphoreImpl.getAvailablePermits(Semaphore.kt:152) | | @@ -152,6 +162,6 @@ Detailed trace: | owner.compareAndSet(null,): true at MutexImpl.unlock(Mutex.kt:220) | | | release() at MutexImpl.unlock(Mutex.kt:222) | | | _availablePermits.getAndIncrement(): 0 at SemaphoreImpl.release(Semaphore.kt:250) | | -| canEnterForbiddenBlock.WRITE(false) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:33) | | +| canEnterForbiddenBlock.WRITE(false) at SuspendTraceReportingTest.foo(SuspendTraceReportingTest.kt:32) | | | result: void | | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/src/jvm/test/resources/expected_logs/suspend_trace_reporting_jdk8.txt b/src/jvm/test/resources/expected_logs/suspend_trace_reporting_jdk8.txt index bd7c9b215..6b321dfc4 100644 --- a/src/jvm/test/resources/expected_logs/suspend_trace_reporting_jdk8.txt +++ b/src/jvm/test/resources/expected_logs/suspend_trace_reporting_jdk8.txt @@ -27,6 +27,7 @@ The following interleaving leads to the error: | SuspendTraceReportingTest#1.lock.lockSuspend(null): COROUTINE_SUSPENDED at MutexImpl.lock$suspendImpl(Mutex.kt:172) | | | acquire() at MutexImpl.lockSuspend(Mutex.kt:177) | | | .getResult(): COROUTINE_SUSPENDED at MutexImpl.lockSuspend(Mutex.kt:321) | | +| isReusable(): false at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:297) | | | trySuspend(): true at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:300) | | | getParentHandle(): null at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:310) | | | installParentHandle(): ChildContinuation#1 at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:311) | | @@ -94,6 +95,8 @@ Detailed trace: | _state.get(): Active#1 at CancellableContinuationImpl.invokeOnCancellationImpl(CancellableContinuationImpl.kt:403) | | | _state.compareAndSet(Active#1,SemaphoreSegment#1): true at CancellableContinuationImpl.invokeOnCancellationImpl(CancellableContinuationImpl.kt:407) | | | .getResult(): COROUTINE_SUSPENDED at MutexImpl.lockSuspend(Mutex.kt:321) | | +| isReusable(): false at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:297) | | +| resumeMode.READ: 1 at CancellableContinuationImpl.isReusable(CancellableContinuationImpl.kt:141) | | | trySuspend(): true at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:300) | | | _decisionAndIndex.get(): 0 at CancellableContinuationImpl.trySuspend(CancellableContinuationImpl.kt:0) | | | _decisionAndIndex.compareAndSet(0,536870912): true at CancellableContinuationImpl.trySuspend(CancellableContinuationImpl.kt:278) | | @@ -122,16 +125,23 @@ Detailed trace: | | .tryResume(Unit#1,null,tryResume$token$1#1): at MutexImpl$CancellableContinuationWithOwner.tryResume(Mutex.kt:258) | | | tryResumeImpl(Unit#1,null,tryResume$token$1#1): at CancellableContinuationImpl.tryResume(CancellableContinuationImpl.kt:582) | | | _state.get(): SemaphoreSegment#1 at CancellableContinuationImpl.tryResumeImpl(CancellableContinuationImpl.kt:0) | +| | resumeMode.READ: 1 at CancellableContinuationImpl.tryResumeImpl(CancellableContinuationImpl.kt:540) | | | _state.compareAndSet(SemaphoreSegment#1,CompletedContinuation#1): true at CancellableContinuationImpl.tryResumeImpl(CancellableContinuationImpl.kt:541) | | | detachChildIfNonResuable() at CancellableContinuationImpl.tryResumeImpl(CancellableContinuationImpl.kt:542) | +| | isReusable(): false at CancellableContinuationImpl.detachChildIfNonResuable(CancellableContinuationImpl.kt:565) | +| | resumeMode.READ: 1 at CancellableContinuationImpl.isReusable(CancellableContinuationImpl.kt:141) | | | detachChild$kotlinx_coroutines_core() at CancellableContinuationImpl.detachChildIfNonResuable(CancellableContinuationImpl.kt:565) | | | getParentHandle(): ChildContinuation#1 at CancellableContinuationImpl.detachChild$kotlinx_coroutines_core(CancellableContinuationImpl.kt:572) | | | _parentHandle.get(): ChildContinuation#1 at CancellableContinuationImpl.getParentHandle(CancellableContinuationImpl.kt:106) | +| | ChildContinuation#1.dispose() at CancellableContinuationImpl.detachChild$kotlinx_coroutines_core(CancellableContinuationImpl.kt:573) | +| | getJob(): JobImpl#1 at JobNode.dispose(JobSupport.kt:1351) | +| | job.READ: JobImpl#1 at JobNode.getJob(JobSupport.kt:1348) | | | _parentHandle.set(NonDisposableHandle#1) at CancellableContinuationImpl.detachChild$kotlinx_coroutines_core(CancellableContinuationImpl.kt:574) | | | owner.get(): at MutexImpl$CancellableContinuationWithOwner.tryResume(Mutex.kt:264) | | | owner.set(null) at MutexImpl$CancellableContinuationWithOwner.tryResume(Mutex.kt:265) | | | .completeResume() at SemaphoreImpl.tryResumeAcquire(Semaphore.kt:349) | | | .completeResume() at MutexImpl$CancellableContinuationWithOwner.completeResume(Mutex.kt:0) | +| | resumeMode.READ: 1 at CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:590) | | | dispatchResume(1) at CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:590) | | | tryResume(): false at CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:472) | | | _decisionAndIndex.get(): 536870912 at CancellableContinuationImpl.tryResume(CancellableContinuationImpl.kt:0) | diff --git a/src/jvm/test/resources/expected_logs/suspended_not_awoke.txt b/src/jvm/test/resources/expected_logs/suspended_not_awoke.txt index bc16ce266..9683cf5b0 100644 --- a/src/jvm/test/resources/expected_logs/suspended_not_awoke.txt +++ b/src/jvm/test/resources/expected_logs/suspended_not_awoke.txt @@ -24,6 +24,7 @@ The following interleaving leads to the error: | | BufferedChannel.access$updateCellReceive(BufferedChannel#1,ChannelSegment#1,0,0,): at BufferedChannel.receiveOnNoWaiterSuspend(BufferedChannel.kt:3575) | | | BufferedChannel.access$prepareReceiverForSuspension(BufferedChannel#1,,ChannelSegment#1,0) at BufferedChannel.receiveOnNoWaiterSuspend(BufferedChannel.kt:3578) | | | .getResult(): COROUTINE_SUSPENDED at BufferedChannel.receiveOnNoWaiterSuspend(BufferedChannel.kt:3648) | +| | isReusable(): false at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:297) | | | trySuspend(): true at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:300) | | | getParentHandle(): null at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:310) | | | installParentHandle(): ChildContinuation#1 at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:311) | @@ -74,6 +75,8 @@ Detailed trace: | | _state.get(): Active#1 at CancellableContinuationImpl.invokeOnCancellationImpl(CancellableContinuationImpl.kt:403) | | | _state.compareAndSet(Active#1,ChannelSegment#1): true at CancellableContinuationImpl.invokeOnCancellationImpl(CancellableContinuationImpl.kt:407) | | | .getResult(): COROUTINE_SUSPENDED at BufferedChannel.receiveOnNoWaiterSuspend(BufferedChannel.kt:3648) | +| | isReusable(): false at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:297) | +| | resumeMode.READ: 1 at CancellableContinuationImpl.isReusable(CancellableContinuationImpl.kt:141) | | | trySuspend(): true at CancellableContinuationImpl.getResult(CancellableContinuationImpl.kt:300) | | | _decisionAndIndex.get(): 0 at CancellableContinuationImpl.trySuspend(CancellableContinuationImpl.kt:0) | | | _decisionAndIndex.compareAndSet(0,536870912): true at CancellableContinuationImpl.trySuspend(CancellableContinuationImpl.kt:278) |