Skip to content

Commit

Permalink
fix attempt #6
Browse files Browse the repository at this point in the history
  • Loading branch information
Aleksandr.Potapov committed Jun 29, 2023
1 parent bab4c68 commit 3040c8e
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ import java.util.concurrent.locks.*
* possible, so that they should not be parked and unparked between invocations.
*/
internal class FixedActiveThreadsExecutor(private val nThreads: Int, runnerHash: Int) : Closeable {
/**
* Threads used in this runner.
*/
val threads: List<TestThread>

/**
* null, waiting TestThread, Runnable task, or SHUTDOWN
*/
Expand All @@ -47,10 +42,11 @@ internal class FixedActiveThreadsExecutor(private val nThreads: Int, runnerHash:
*/
private var hangDetected = false

init {
threads = (0 until nThreads).map { iThread ->
TestThread(iThread, runnerHash, testThreadRunnable(iThread)).also { it.start() }
}
/**
* Threads used in this runner.
*/
val threads = Array(nThreads) { iThread ->
TestThread(iThread, runnerHash, testThreadRunnable(iThread)).also { it.start() }
}

/**
Expand All @@ -75,24 +71,32 @@ internal class FixedActiveThreadsExecutor(private val nThreads: Int, runnerHash:
}

private fun submitTask(iThread: Int, task: Any) {
if (tasks[iThread].compareAndSet(null, task)) return
// CAS failed => a test thread is parked.
// Submit the task and unpark the waiting thread.
val thread = tasks[iThread].value as TestThread
tasks[iThread].value = task
LockSupport.unpark(thread)
val old = tasks[iThread].getAndSet(task)
if (old is TestThread) {
LockSupport.unpark(old)
}
}

private fun await(timeoutMs: Long) {
val deadline = System.currentTimeMillis() + timeoutMs
for (iThread in 0 until nThreads)
awaitTask(iThread, deadline)
var exception: Throwable? = null
for (iThread in 0 until nThreads) {
val e = awaitTask(iThread, deadline)
if (e != null) {
if (exception == null) {
exception = e
} else {
exception.addSuppressed(e)
}
}
}
exception?.let { throw ExecutionException(it) }
}

private fun awaitTask(iThread: Int, deadline: Long) {
private fun awaitTask(iThread: Int, deadline: Long): Throwable? {
val result = getResult(iThread, deadline)
// Check whether there was an exception during the execution.
if (result != DONE) throw ExecutionException(result as Throwable)
return result as? Throwable
}

private fun getResult(iThread: Int, deadline: Long): Any {
Expand All @@ -116,14 +120,14 @@ internal class FixedActiveThreadsExecutor(private val nThreads: Int, runnerHash:
}

private fun testThreadRunnable(iThread: Int) = Runnable {
loop@while (true) {
loop@ while (true) {
val task = getTask(iThread)
if (task == SHUTDOWN) return@Runnable
tasks[iThread].value = null // reset task
val runnable = task as Runnable
try {
runnable.run()
} catch(e: Throwable) {
} catch (e: Throwable) {
setResult(iThread, wrapInvalidAccessFromUnnamedModuleExceptionWithDescription(e))
continue@loop
}
Expand All @@ -138,7 +142,7 @@ internal class FixedActiveThreadsExecutor(private val nThreads: Int, runnerHash:
}
// Park until a task is stored into `tasks[iThread]`.
val currentThread = Thread.currentThread()
if (tasks[iThread].compareAndSet(null, Thread.currentThread())) {
if (tasks[iThread].compareAndSet(null, currentThread)) {
while (tasks[iThread].value === currentThread) {
LockSupport.park()
}
Expand Down Expand Up @@ -172,12 +176,13 @@ internal class FixedActiveThreadsExecutor(private val nThreads: Int, runnerHash:
}
}

class TestThread(val iThread: Int, val runnerHash: Int, r: Runnable) : Thread(r, "FixedActiveThreadsExecutor@$runnerHash-$iThread") {
class TestThread(val iThread: Int, val runnerHash: Int, r: Runnable) :
Thread(r, "FixedActiveThreadsExecutor@$runnerHash-$iThread") {
var cont: CancellableContinuation<*>? = null
}
}

private const val SPINNING_LOOP_ITERATIONS_BEFORE_PARK = 1000_000

private val SHUTDOWN = Any()
private val DONE = Any()
private val SHUTDOWN = "SHUTDOWN"
private val DONE = "DONE"
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
///*
// * Lincheck
// *
// * Copyright (C) 2019 - 2023 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.test.representation
//
//import org.jetbrains.kotlinx.lincheck.*
//import org.jetbrains.kotlinx.lincheck.strategy.*
//import org.jetbrains.kotlinx.lincheck.strategy.stress.*
//import org.jetbrains.kotlinx.lincheck.test.runner.*
//import org.junit.*
//
///**
// * This test checks that there is no old thread in thread dump.
// */
//class ThreadDumpTest {
// @Test
// fun test() {
// val iterations = 30
// repeat(iterations) {
// val options = StressOptions()
// .minimizeFailedScenario(false)
// .iterations(100_000)
// .invocationsPerIteration(1)
// .invocationTimeout(100)
// val failure = options.checkImpl(DeadlockOnSynchronizedTest::class.java)
// check(failure is DeadlockWithDumpFailure) { "${DeadlockWithDumpFailure::class.simpleName} was expected but ${failure?.javaClass} was obtained"}
// check(failure.threadDump!!.size == 2) { "thread dump for 2 threads expected, but for ${failure.threadDump.size} threads was detected"}
// }
// }
//}
/*
* Lincheck
*
* Copyright (C) 2019 - 2023 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.test.representation

import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.strategy.*
import org.jetbrains.kotlinx.lincheck.strategy.stress.*
import org.jetbrains.kotlinx.lincheck.test.runner.*
import org.junit.*

/**
* This test checks that there is no old thread in thread dump.
*/
class ThreadDumpTest {
@Test
fun test() {
val iterations = 30
repeat(iterations) {
val options = StressOptions()
.minimizeFailedScenario(false)
.iterations(100_000)
.invocationsPerIteration(1)
.invocationTimeout(100)
val failure = options.checkImpl(DeadlockOnSynchronizedTest::class.java)
check(failure is DeadlockWithDumpFailure) { "${DeadlockWithDumpFailure::class.simpleName} was expected but ${failure?.javaClass} was obtained"}
check(failure.threadDump!!.size == 2) { "thread dump for 2 threads expected, but for ${failure.threadDump.size} threads was detected"}
}
}
}
202 changes: 101 additions & 101 deletions src/jvm/test/org/jetbrains/kotlinx/lincheck/test/runner/DeadlockTests.kt
Original file line number Diff line number Diff line change
@@ -1,101 +1,101 @@
///*
// * Lincheck
// *
// * Copyright (C) 2019 - 2023 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.test.runner
//
//import org.jetbrains.kotlinx.lincheck.*
//import org.jetbrains.kotlinx.lincheck.annotations.Operation
//import org.jetbrains.kotlinx.lincheck.strategy.*
//import org.jetbrains.kotlinx.lincheck.test.AbstractLincheckTest
//import java.util.concurrent.atomic.AtomicBoolean
//
//class DeadlockOnSynchronizedTest : AbstractLincheckTest(DeadlockWithDumpFailure::class) {
// private var counter = 0
// private var lock1 = Any()
// private var lock2 = Any()
//
// @Operation
// fun inc12(): Int {
// synchronized(lock1) {
// synchronized(lock2) {
// return counter++
// }
// }
// }
//
// @Operation
// fun inc21(): Int {
// synchronized(lock2) {
// synchronized(lock1) {
// return counter++
// }
// }
// }
//
// override fun <O : Options<O, *>> O.customize() {
// minimizeFailedScenario(false)
// invocationTimeout(200)
// }
//
// override fun extractState(): Any = counter
//}
//
//class DeadlockOnSynchronizedWaitTest : AbstractLincheckTest(DeadlockWithDumpFailure::class) {
// private var lock = Object()
//
// @Operation
// fun operation() {
// synchronized(lock) {
// lock.wait()
// }
// }
//
// override fun <O : Options<O, *>> O.customize() {
// actorsBefore(0)
// minimizeFailedScenario(false)
// invocationTimeout(200)
// }
//
// override fun extractState(): Any = 0 // constant
//}
//
//class LiveLockTest : AbstractLincheckTest(DeadlockWithDumpFailure::class) {
// private var counter = 0
// private val lock1 = AtomicBoolean(false)
// private val lock2 = AtomicBoolean(false)
//
// @Operation
// fun inc12(): Int = lock1.withSpinLock {
// lock2.withSpinLock {
// counter++
// }
// }
//
// @Operation
// fun inc21(): Int = lock2.withSpinLock {
// lock1.withSpinLock {
// counter++
// }
// }
//
// override fun extractState(): Any = counter
//
// override fun <O : Options<O, *>> O.customize() {
// minimizeFailedScenario(false)
// invocationTimeout(200)
// }
//
// private fun AtomicBoolean.withSpinLock(block: () -> Int): Int {
// while (!this.compareAndSet(false, true));
// val result = block()
// this.set(false)
// return result
// }
//}
//
/*
* Lincheck
*
* Copyright (C) 2019 - 2023 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.test.runner

import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.annotations.Operation
import org.jetbrains.kotlinx.lincheck.strategy.*
import org.jetbrains.kotlinx.lincheck.test.AbstractLincheckTest
import java.util.concurrent.atomic.AtomicBoolean

class DeadlockOnSynchronizedTest : AbstractLincheckTest(DeadlockWithDumpFailure::class) {
private var counter = 0
private var lock1 = Any()
private var lock2 = Any()

@Operation
fun inc12(): Int {
synchronized(lock1) {
synchronized(lock2) {
return counter++
}
}
}

@Operation
fun inc21(): Int {
synchronized(lock2) {
synchronized(lock1) {
return counter++
}
}
}

override fun <O : Options<O, *>> O.customize() {
minimizeFailedScenario(false)
invocationTimeout(200)
}

override fun extractState(): Any = counter
}

class DeadlockOnSynchronizedWaitTest : AbstractLincheckTest(DeadlockWithDumpFailure::class) {
private var lock = Object()

@Operation
fun operation() {
synchronized(lock) {
lock.wait()
}
}

override fun <O : Options<O, *>> O.customize() {
actorsBefore(0)
minimizeFailedScenario(false)
invocationTimeout(200)
}

override fun extractState(): Any = 0 // constant
}

class LiveLockTest : AbstractLincheckTest(DeadlockWithDumpFailure::class) {
private var counter = 0
private val lock1 = AtomicBoolean(false)
private val lock2 = AtomicBoolean(false)

@Operation
fun inc12(): Int = lock1.withSpinLock {
lock2.withSpinLock {
counter++
}
}

@Operation
fun inc21(): Int = lock2.withSpinLock {
lock1.withSpinLock {
counter++
}
}

override fun extractState(): Any = counter

override fun <O : Options<O, *>> O.customize() {
minimizeFailedScenario(false)
invocationTimeout(200)
}

private fun AtomicBoolean.withSpinLock(block: () -> Int): Int {
while (!this.compareAndSet(false, true));
val result = block()
this.set(false)
return result
}
}

0 comments on commit 3040c8e

Please sign in to comment.