Skip to content

Commit

Permalink
Fray 0.2.0 release
Browse files Browse the repository at this point in the history
Cache ReentrantRewriteLocks if they are created statically
Replace ExecutorService with a lightweight HelperThread
Save recordings to recording folder if Fray is not in explore mode
Shadow apache.common in gradle plugin
  • Loading branch information
aoli-al authored Feb 4, 2025
1 parent 5ea95de commit 8621535
Show file tree
Hide file tree
Showing 18 changed files with 174 additions and 58 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {
id("com.ncorti.ktfmt.gradle") version "0.17.0"
id("org.jetbrains.dokka") version "1.9.20"
id("org.jreleaser") version "1.16.0"
id("com.gradleup.shadow") version "9.0.0-beta7"
}

repositories {
Expand Down
63 changes: 37 additions & 26 deletions core/src/main/kotlin/org/pastalab/fray/core/RunContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ import java.io.StringWriter
import java.lang.Thread.UncaughtExceptionHandler
import java.time.Instant
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ForkJoinPool
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.LockSupport
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock
import java.util.concurrent.locks.StampedLock
import kotlin.system.exitProcess
import org.pastalab.fray.core.command.Configuration
import org.pastalab.fray.core.concurrency.HelperThread
import org.pastalab.fray.core.concurrency.ReentrantReadWriteLockCache
import org.pastalab.fray.core.concurrency.SynchronizationManager
import org.pastalab.fray.core.concurrency.operations.*
import org.pastalab.fray.core.concurrency.primitives.ConditionSignalContext
Expand Down Expand Up @@ -66,10 +66,32 @@ class RunContext(val config: Configuration) {
when (it) {
is ReentrantLock -> ReentrantLockContext()
is ReadLock -> {
throw RuntimeException("ReadLock should not be created here")
val result =
ReentrantReadWriteLockCache.getLock(it)?.let { lock ->
reentrantReadWriteLockInit(lock).first
}
if (result != null) {
result
} else {
val context = ReadLockContext()
context.writeLockContext = WriteLockContext()
context.writeLockContext.readLockContext = context
context
}
}
is WriteLock -> {
throw RuntimeException("WriteLock should not be created here")
val result =
ReentrantReadWriteLockCache.getLock(it)?.let { lock ->
reentrantReadWriteLockInit(lock).second
}
if (result != null) {
result
} else {
val context = WriteLockContext()
context.readLockContext = ReadLockContext()
context.readLockContext.writeLockContext = context
context
}
}
else -> ReentrantLockContext()
}
Expand All @@ -90,24 +112,10 @@ class RunContext(val config: Configuration) {
}
private var step = 0
val syncManager = SynchronizationManager()
var executor: ExecutorService =
Executors.newSingleThreadExecutor { r ->
object : HelperThread() {
override fun run() {
r.run()
}
}
}
var executor = HelperThread()

fun bootstrap() {
executor =
Executors.newSingleThreadExecutor { r ->
object : HelperThread() {
override fun run() {
r.run()
}
}
}
init {
executor.start()
}

fun reportError(e: Throwable) {
Expand Down Expand Up @@ -201,9 +209,6 @@ class RunContext(val config: Configuration) {

fun start() {
val t = Thread.currentThread()
// We need to submit a dummy task to trigger the executor
// thread creation
executor.submit {}
config.scheduleObservers.forEach { it.onExecutionStart() }
step = 0
bugFound = null
Expand Down Expand Up @@ -231,7 +236,7 @@ class RunContext(val config: Configuration) {

fun shutDown() {
org.pastalab.fray.runtime.Runtime.DELEGATE = org.pastalab.fray.runtime.Delegate()
executor.shutdown()
executor.stopHelperThread()
}

fun threadCreateDone(t: Thread) {
Expand Down Expand Up @@ -587,13 +592,19 @@ class RunContext(val config: Configuration) {
lockImpl(lock, false, true, canInterrupt, false)
}

fun reentrantReadWriteLockInit(readLock: ReadLock, writeLock: WriteLock) {
fun reentrantReadWriteLockInit(
lock: ReentrantReadWriteLock
): Pair<ReadLockContext, WriteLockContext> {
val readLock = lock.readLock()
val writeLock = lock.writeLock()
val writeLockContext = WriteLockContext()
val readLockContext = ReadLockContext()
readLockContext.writeLockContext = writeLockContext
writeLockContext.readLockContext = readLockContext
lockManager.addContext(readLock, readLockContext)
lockManager.addContext(writeLock, writeLockContext)
ReentrantReadWriteLockCache.registerLock(lock)
return Pair(readLockContext, writeLockContext)
}

fun unlockImpl(
Expand Down
29 changes: 17 additions & 12 deletions core/src/main/kotlin/org/pastalab/fray/core/RuntimeDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -422,18 +422,23 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg
if (onSkipRecursion.get()) {
return false
}
if (!context.registeredThreads.containsKey(Thread.currentThread().id)) {
return false
}
if (stackTrace.get().isEmpty()) {
return false
}
val last = stackTrace.get().removeLast()
if (last != signature) {
return false
onSkipRecursion.set(true)
try {
if (!context.registeredThreads.containsKey(Thread.currentThread().id)) {
return false
}
if (stackTrace.get().isEmpty()) {
return false
}
val last = stackTrace.get().removeLast()
if (last != signature) {
return false
}
skipFunctionEntered.set(skipFunctionEntered.get() - 1)
return true
} finally {
onSkipRecursion.set(false)
}
skipFunctionEntered.set(skipFunctionEntered.get() - 1)
return true
}

fun onThreadParkImpl(): Boolean {
Expand Down Expand Up @@ -509,7 +514,7 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg
}

override fun onReentrantReadWriteLockInit(lock: ReentrantReadWriteLock) {
context.reentrantReadWriteLockInit(lock.readLock(), lock.writeLock())
context.reentrantReadWriteLockInit(lock)
}

override fun onSemaphoreInit(sem: Semaphore) {
Expand Down
4 changes: 0 additions & 4 deletions core/src/main/kotlin/org/pastalab/fray/core/TestRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ class TestRunner(val config: Configuration) {
val context = RunContext(config)
var currentDivision = 1

init {
context.bootstrap()
}

fun reportProgress(iteration: Int, bugsFound: Int) {
if (config.isReplay) return
if (iteration % currentDivision == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,16 @@ data class Configuration(
val startTime = TimeSource.Monotonic.markNow()

fun saveToReportFolder(index: Int) {
Paths.get("$report/recording_$index").createDirectories()
File("$report/recording_$index/schedule.json").writeText(Json.encodeToString(scheduler))
File("$report/recording_$index/random.json").writeText(Json.encodeToString(randomnessProvider))
scheduleObservers.forEach { it.saveToReportFolder("$report/recording_$index") }
val path =
if (exploreMode) {
"$report/recording_$index"
} else {
"$report/recording"
}
Paths.get(path).createDirectories()
File("$path/schedule.json").writeText(Json.encodeToString(scheduler))
File("$path/random.json").writeText(Json.encodeToString(randomnessProvider))
scheduleObservers.forEach { it.saveToReportFolder(path) }
}

val frayLogger = FrayLogger("$report/fray.log")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
package org.pastalab.fray.core.concurrency

open class HelperThread(runnable: Runnable?) : Thread(runnable) {
constructor() : this(null)
import org.pastalab.fray.core.utils.Utils

class HelperThread : Thread("fray-helper-thread") {
var shouldStop = false
var currentRunnable: Runnable? = null

override fun run() {
while (!shouldStop) {
val job: Runnable?
synchronized(this) {
while (currentRunnable == null && !shouldStop) {
(this as Object).wait()
}
job = currentRunnable
currentRunnable = null
}
job?.run()
}
}

fun submit(runnable: Runnable) {
synchronized(this) {
Utils.verifyOrReport(currentRunnable == null, "Helper thread is already running a job")
currentRunnable = runnable
(this as Object).notify()
}
}

fun stopHelperThread() {
synchronized(this) {
shouldStop = true
(this as Object).notify()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.pastalab.fray.core.concurrency

import java.lang.ref.WeakReference
import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock
import org.pastalab.fray.core.concurrency.primitives.ReferencedContextManager

/**
* We need a static object to store the [ReadLock] and [WriteLock] because if the lock is created
* statically, we may lose track of the lock owner across test runs.
*/
object ReentrantReadWriteLockCache {
val lockCache =
ReferencedContextManager<WeakReference<ReentrantReadWriteLock>>({
throw RuntimeException("Should not be called")
})

fun getLock(obj: Any): ReentrantReadWriteLock? {
if (lockCache.hasContext(obj)) {
return lockCache.getContext(obj).get()
}
return null
}

fun registerLock(lock: ReentrantReadWriteLock) {
lockCache.addContext(lock.readLock(), WeakReference(lock))
lockCache.addContext(lock.writeLock(), WeakReference(lock))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ class ReferencedContextManager<T>(val contextProducer: (Any) -> T) {

fun getContext(obj: Any): T {
val id = System.identityHashCode(obj)
if (!objMap.containsKey(id)) {
if (!hasContext(obj)) {
objMap[id] = Pair(contextProducer(obj), IdentityPhantomReference(obj, queue))
gc()
}
return objMap[id]!!.first
}

fun hasContext(obj: Any): Boolean {
val id = System.identityHashCode(obj)
return objMap.containsKey(id)
}

fun addContext(lock: Any, context: T) {
val id = System.identityHashCode(lock)
objMap[id] = Pair(context, IdentityPhantomReference(lock, queue))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.pastalab.fray.core.utils

class FrayBackgroundExecutor {}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
kotlin.code.style=official
group=org.pastalab.fray
version=0.1.10
version=0.2.0
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
2 changes: 1 addition & 1 deletion instrumentation/agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("java")
kotlin("jvm")
id("io.github.goooler.shadow") version "8.1.7"
id("com.gradleup.shadow")
}

repositories {
Expand Down
4 changes: 2 additions & 2 deletions integration-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
plugins {
id("java")
id("java")
}

group = "org.pastalab.fray.test"

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.pastalab.fray.test.success.rwlock;

public class StaticReentrantReadWriteLockMultipleTests {
public static void main(String[] args) {
StaticReentrantReadWriteLockNormal.lock.readLock().lock();
StaticReentrantReadWriteLockNormal.lock.readLock().unlock();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.pastalab.fray.test.success.rwlock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class StaticReentrantReadWriteLockNormal {
public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public static void main(String[] args) throws InterruptedException {
lock.readLock().lock();
lock.readLock().unlock();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public void testOne() throws Throwable {
new ExecutionInfo(
new LambdaExecutor(() -> {
try {
TestTime.main(new String[]{});
CountDownLatchDeadlockUnblockMultiThread.main(new String[]{});
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import org.pastalab.fray.core.scheduler.Scheduler
)
@TestTemplate
annotation class ConcurrencyTest(
val iterations: Int = 1,
val iterations: Int = 1000,
val scheduler: KClass<out Scheduler> = POSScheduler::class,
val name: String = SHORT_DISPLAY_NAME,
val replay: String = ""
Expand Down
Loading

0 comments on commit 8621535

Please sign in to comment.