From 11300663bc89fe05643a1ad053d21085c31980c0 Mon Sep 17 00:00:00 2001 From: Ao Li Date: Wed, 19 Jun 2024 11:01:25 -0400 Subject: [PATCH] update. --- .../cmu/pasta/fray/core/GlobalContext.kt | 10 ++- .../main/kotlin/cmu/pasta/fray/core/Main.kt | 2 + .../cmu/pasta/fray/core/RuntimeDelegate.kt | 61 +++++++++++--- .../kotlin/cmu/pasta/fray/core/TestRunner.kt | 3 +- .../fray/core/{ => command}/Configuration.kt | 82 ++++++++----------- .../cmu/pasta/fray/core/command/Executor.kt | 47 +++++++++++ .../visitors/CountDownLatchInstrumenter.kt | 2 +- .../visitors/TimedWaitInstrumenter.kt | 5 ++ .../pasta/fray/it/IntegrationTestRunner.java | 20 +++-- .../java/cmu/pasta/fray/runtime/Delegate.java | 8 +- .../java/cmu/pasta/fray/runtime/Runtime.java | 6 +- 11 files changed, 174 insertions(+), 72 deletions(-) rename core/src/main/kotlin/cmu/pasta/fray/core/{ => command}/Configuration.kt (72%) create mode 100644 core/src/main/kotlin/cmu/pasta/fray/core/command/Executor.kt diff --git a/core/src/main/kotlin/cmu/pasta/fray/core/GlobalContext.kt b/core/src/main/kotlin/cmu/pasta/fray/core/GlobalContext.kt index dee6789a..b9884b87 100644 --- a/core/src/main/kotlin/cmu/pasta/fray/core/GlobalContext.kt +++ b/core/src/main/kotlin/cmu/pasta/fray/core/GlobalContext.kt @@ -1,5 +1,6 @@ package cmu.pasta.fray.core +import cmu.pasta.fray.core.command.Configuration import cmu.pasta.fray.core.concurrency.HelperThread import cmu.pasta.fray.core.concurrency.SynchronizationManager import cmu.pasta.fray.core.concurrency.locks.CountDownLatchManager @@ -65,7 +66,7 @@ object GlobalContext { } fun reportError(e: Throwable) { - if (!bugFound && !config!!.ignoreUnhandledExceptions) { + if (!bugFound && !config!!.executionInfo.ignoreUnhandledExceptions) { bugFound = true val sw = StringWriter() sw.append("Error found: ${e}\n") @@ -616,7 +617,8 @@ object GlobalContext { } fun fieldOperation(obj: Any?, owner: String, name: String, type: MemoryOpType) { - if (!config!!.interleaveMemoryOps && !volatileManager.isVolatile(owner, name)) return + if (!config!!.executionInfo.interleaveMemoryOps && !volatileManager.isVolatile(owner, name)) + return val objIds = mutableListOf() if (obj != null) { objIds.add(System.identityHashCode(obj)) @@ -633,7 +635,7 @@ object GlobalContext { } fun arrayOperation(obj: Any, index: Int, type: MemoryOpType) { - if (!config!!.interleaveMemoryOps) return + if (!config!!.executionInfo.interleaveMemoryOps) return val objId = System.identityHashCode(obj) memoryOperation((31 * objId) + index, type) } @@ -769,7 +771,7 @@ object GlobalContext { } step += 1 - if (step > config!!.maxScheduledStep && + if (step > config!!.executionInfo.maxScheduledStep && !currentThread.isExiting && Thread.currentThread() !is HelperThread && !(mainExiting && currentThreadId == mainThreadId)) { diff --git a/core/src/main/kotlin/cmu/pasta/fray/core/Main.kt b/core/src/main/kotlin/cmu/pasta/fray/core/Main.kt index 9bb34f5c..a39b1192 100644 --- a/core/src/main/kotlin/cmu/pasta/fray/core/Main.kt +++ b/core/src/main/kotlin/cmu/pasta/fray/core/Main.kt @@ -1,5 +1,7 @@ package cmu.pasta.fray.core +import cmu.pasta.fray.core.command.MainCommand + fun main(args: Array) { val config = MainCommand().apply { main(args) }.toConfiguration() val runner = TestRunner(config) diff --git a/core/src/main/kotlin/cmu/pasta/fray/core/RuntimeDelegate.kt b/core/src/main/kotlin/cmu/pasta/fray/core/RuntimeDelegate.kt index 9d95ca31..70ba8c37 100644 --- a/core/src/main/kotlin/cmu/pasta/fray/core/RuntimeDelegate.kt +++ b/core/src/main/kotlin/cmu/pasta/fray/core/RuntimeDelegate.kt @@ -545,6 +545,16 @@ class RuntimeDelegate : Delegate() { } } + override fun onLatchAwaitTimeout(latch: CountDownLatch, timeout: Long, unit: TimeUnit): Boolean { + if (GlobalContext.config!!.executionInfo.timedOpAsYield) { + onYield() + return false + } else { + latch.await() + return true + } + } + override fun onLatchAwaitDone(latch: CountDownLatch) { onSkipMethodDone("Latch.await") if (checkEntered()) return @@ -608,33 +618,64 @@ class RuntimeDelegate : Delegate() { } override fun onThreadParkNanos(nanos: Long) { - LockSupport.park() + if (GlobalContext.config!!.executionInfo.timedOpAsYield) { + onYield() + } else { + LockSupport.park() + } } override fun onThreadParkUntil(nanos: Long) { - LockSupport.park() + if (GlobalContext.config!!.executionInfo.timedOpAsYield) { + onYield() + } else { + LockSupport.park() + } } override fun onThreadParkNanosWithBlocker(blocker: Any?, nanos: Long) { - LockSupport.park(blocker) + if (GlobalContext.config!!.executionInfo.timedOpAsYield) { + onYield() + } else { + LockSupport.park(blocker) + } } override fun onThreadParkUntilWithBlocker(blocker: Any?, nanos: Long) { - LockSupport.park(blocker) + if (GlobalContext.config!!.executionInfo.timedOpAsYield) { + onYield() + } else { + LockSupport.park(blocker) + } } override fun onConditionAwaitTime(o: Condition, time: Long, unit: TimeUnit): Boolean { - o.await() - return true + if (GlobalContext.config!!.executionInfo.timedOpAsYield) { + onYield() + return false + } else { + o.await() + return true + } } override fun onConditionAwaitNanos(o: Condition, nanos: Long): Long { - o.await() - return 0 + if (GlobalContext.config!!.executionInfo.timedOpAsYield) { + onYield() + return 0 + } else { + o.await() + return nanos + } } override fun onConditionAwaitUntil(o: Condition, deadline: Date): Boolean { - o.await() - return true + if (GlobalContext.config!!.executionInfo.timedOpAsYield) { + onYield() + return false + } else { + o.await() + return true + } } } diff --git a/core/src/main/kotlin/cmu/pasta/fray/core/TestRunner.kt b/core/src/main/kotlin/cmu/pasta/fray/core/TestRunner.kt index 3409c70d..7a75f287 100644 --- a/core/src/main/kotlin/cmu/pasta/fray/core/TestRunner.kt +++ b/core/src/main/kotlin/cmu/pasta/fray/core/TestRunner.kt @@ -1,5 +1,6 @@ package cmu.pasta.fray.core +import cmu.pasta.fray.core.command.Configuration import cmu.pasta.fray.core.logger.ConsoleLogger import cmu.pasta.fray.runtime.Runtime import java.nio.file.Paths @@ -35,7 +36,7 @@ class TestRunner(val config: Configuration) { try { Runtime.DELEGATE = RuntimeDelegate() Runtime.start() - config.exec() + config.executionInfo.executor.execute() Runtime.onMainExit() } catch (e: Throwable) { Runtime.onReportError(e) diff --git a/core/src/main/kotlin/cmu/pasta/fray/core/Configuration.kt b/core/src/main/kotlin/cmu/pasta/fray/core/command/Configuration.kt similarity index 72% rename from core/src/main/kotlin/cmu/pasta/fray/core/Configuration.kt rename to core/src/main/kotlin/cmu/pasta/fray/core/command/Configuration.kt index 645f1d97..1b4e4641 100644 --- a/core/src/main/kotlin/cmu/pasta/fray/core/Configuration.kt +++ b/core/src/main/kotlin/cmu/pasta/fray/core/command/Configuration.kt @@ -1,4 +1,4 @@ -package cmu.pasta.fray.core +package cmu.pasta.fray.core.command import cmu.pasta.fray.core.logger.CsvLogger import cmu.pasta.fray.core.logger.JsonLogger @@ -9,26 +9,29 @@ import com.github.ajalt.clikt.parameters.groups.OptionGroup import com.github.ajalt.clikt.parameters.groups.defaultByName import com.github.ajalt.clikt.parameters.groups.groupChoice import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.types.boolean import com.github.ajalt.clikt.parameters.types.file import com.github.ajalt.clikt.parameters.types.int -import java.net.URI -import java.net.URLClassLoader import java.util.* +import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass @Serializable data class ExecutionInfo( - val clazz: String, - val method: String, - val args: List, - val classpaths: List -) {} + @Polymorphic val executor: Executor, + val ignoreUnhandledExceptions: Boolean, + val timedOpAsYield: Boolean, + val interleaveMemoryOps: Boolean, + val maxScheduledStep: Int, +) sealed class ExecutionConfig(name: String) : OptionGroup(name) { open fun getExecutionInfo(): ExecutionInfo { - return ExecutionInfo("", "", emptyList(), emptyList()) + return ExecutionInfo( + MethodExecutor("", "", emptyList(), emptyList()), false, false, false, 10000000) } } @@ -43,9 +46,18 @@ class CliExecutionConfig : ExecutionConfig("cli") { option("-cp", "--classpath", help = "Arguments passed to target application") .split(":") .default(emptyList()) + val timedOpAsYield by option("-t", "--timed-op-as-yield").flag() + val ignoreUnhandledExceptions by option("-e", "--ignore-unhandled-exceptions").flag() + val interleaveMemoryOps by option("-m", "--memory").flag() + val maxScheduledStep by option("-s", "--max-scheduled-step").int().default(10000) override fun getExecutionInfo(): ExecutionInfo { - return ExecutionInfo(clazz, method, targetArgs, classpaths) + return ExecutionInfo( + MethodExecutor(clazz, method, targetArgs, classpaths), + ignoreUnhandledExceptions, + timedOpAsYield, + interleaveMemoryOps, + maxScheduledStep) } } @@ -53,7 +65,14 @@ class JsonExecutionConfig : ExecutionConfig("json") { val path by option("--config-path").file().required() override fun getExecutionInfo(): ExecutionInfo { - return Json.decodeFromString(path.readText()) + val module = SerializersModule { + polymorphic(Executor::class) { + subclass(MethodExecutor::class) + defaultDeserializer { MethodExecutor.serializer() } + } + } + val json = Json { serializersModule = module } + return json.decodeFromString(path.readText()) } } @@ -118,13 +137,9 @@ class PCT : ScheduleAlgorithm("pct") { class MainCommand : CliktCommand() { val report by option("-o").default("/tmp/report") val iter by option("-i", "--iter", help = "Number of iterations").int().default(1) - val fullSchedule by option("-f", "--full").boolean().default(false) + val fullSchedule by option("-f", "--full").flag() val logger by option("-l", "--logger").groupChoice("json" to JsonLoggerOption(), "csv" to CsvLoggerOption()) - val interleaveMemoryOps by option("-m", "--memory").boolean().default(false) - val maxScheduledStep by option("-s", "--max-scheduled-step").int().default(10000) - val ignoreUnhandledExceptions by - option("-e", "--ignore-unhandled-exceptions").boolean().default(false) val scheduler by option() .groupChoice( @@ -145,48 +160,21 @@ class MainCommand : CliktCommand() { fun toConfiguration(): Configuration { val executionInfo = runConfig.getExecutionInfo() - val exec = { - val classLoader = - URLClassLoader( - executionInfo.classpaths.map { it -> URI("file://$it").toURL() }.toTypedArray(), - Thread.currentThread().contextClassLoader) - Thread.currentThread().contextClassLoader = classLoader - val clazz = Class.forName(executionInfo.clazz, true, classLoader) - if (executionInfo.args.isEmpty() && executionInfo.method != "main") { - val m = clazz.getMethod(executionInfo.method) - if (m.modifiers and java.lang.reflect.Modifier.STATIC == 0) { - val obj = clazz.getConstructor().newInstance() - m.invoke(obj) - } else { - m.invoke(null) - } - } else { - val m = clazz.getMethod(executionInfo.method, Array::class.java) - m.invoke(null, executionInfo.args.toTypedArray()) - } - Unit - } return Configuration( - exec, + executionInfo, report, iter, scheduler!!.getScheduler(), fullSchedule, - logger!!.getLogger(report, fullSchedule), - interleaveMemoryOps, - ignoreUnhandledExceptions, - maxScheduledStep) + logger!!.getLogger(report, fullSchedule)) } } data class Configuration( - val exec: () -> Unit, + val executionInfo: ExecutionInfo, val report: String, val iter: Int, val scheduler: Scheduler, val fullSchedule: Boolean, val logger: LoggerBase, - val interleaveMemoryOps: Boolean, - val ignoreUnhandledExceptions: Boolean, - val maxScheduledStep: Int, ) {} diff --git a/core/src/main/kotlin/cmu/pasta/fray/core/command/Executor.kt b/core/src/main/kotlin/cmu/pasta/fray/core/command/Executor.kt new file mode 100644 index 00000000..3fd0b56a --- /dev/null +++ b/core/src/main/kotlin/cmu/pasta/fray/core/command/Executor.kt @@ -0,0 +1,47 @@ +package cmu.pasta.fray.core.command + +import java.net.URI +import java.net.URLClassLoader +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +sealed interface Executor { + fun execute() +} + +@Serializable +@SerialName("executor") +data class MethodExecutor( + val clazz: String, + val method: String, + val args: List, + val classpaths: List +) : Executor { + override fun execute() { + val classLoader = + URLClassLoader( + classpaths.map { it -> URI("file://$it").toURL() }.toTypedArray(), + Thread.currentThread().contextClassLoader) + Thread.currentThread().contextClassLoader = classLoader + val clazz = Class.forName(clazz, true, classLoader) + if (args.isEmpty() && method != "main") { + val m = clazz.getMethod(method) + if (m.modifiers and java.lang.reflect.Modifier.STATIC == 0) { + val obj = clazz.getConstructor().newInstance() + m.invoke(obj) + } else { + m.invoke(null) + } + } else { + val m = clazz.getMethod(method, Array::class.java) + m.invoke(null, args.toTypedArray()) + } + } +} + +class LambdaExecutor(val lambda: () -> Unit) : Executor { + override fun execute() { + lambda() + } +} diff --git a/instrumentation/src/main/kotlin/cmu/pasta/fray/instrumentation/visitors/CountDownLatchInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/fray/instrumentation/visitors/CountDownLatchInstrumenter.kt index 1b2eb782..46c64be8 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/fray/instrumentation/visitors/CountDownLatchInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/fray/instrumentation/visitors/CountDownLatchInstrumenter.kt @@ -15,7 +15,7 @@ class CountDownLatchInstrumenter(cv: ClassVisitor) : signature: String?, exceptions: Array? ): MethodVisitor { - if (name == "await") { + if (name == "await" && descriptor == "()V") { val eMv = MethodEnterVisitor(mv, Runtime::onLatchAwait, access, name, descriptor, true, false) return MethodExitVisitor( eMv, Runtime::onLatchAwaitDone, access, name, descriptor, true, false, true) diff --git a/instrumentation/src/main/kotlin/cmu/pasta/fray/instrumentation/visitors/TimedWaitInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/fray/instrumentation/visitors/TimedWaitInstrumenter.kt index 5e898b8d..fef70a31 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/fray/instrumentation/visitors/TimedWaitInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/fray/instrumentation/visitors/TimedWaitInstrumenter.kt @@ -38,6 +38,11 @@ class TimedWaitInstrumenter(cv: ClassVisitor) : ClassVisitor(ASM9, cv) { Runtime::onConditionAwaitUntil } } + if (owner == "java/util/concurrent/CountDownLatch" && + name == "await" && + descriptor == "(JLjava/util/concurrent/TimeUnit;)Z") { + return Runtime::onLatchAwaitTimeout + } return null } diff --git a/integration-tests/src/test/java/cmu/pasta/fray/it/IntegrationTestRunner.java b/integration-tests/src/test/java/cmu/pasta/fray/it/IntegrationTestRunner.java index 6398bbf1..b584909d 100644 --- a/integration-tests/src/test/java/cmu/pasta/fray/it/IntegrationTestRunner.java +++ b/integration-tests/src/test/java/cmu/pasta/fray/it/IntegrationTestRunner.java @@ -2,7 +2,9 @@ import cmu.pasta.fray.core.*; -import cmu.pasta.fray.core.logger.CsvLogger; +import cmu.pasta.fray.core.command.Configuration; +import cmu.pasta.fray.core.command.ExecutionInfo; +import cmu.pasta.fray.core.command.LambdaExecutor; import cmu.pasta.fray.core.logger.JsonLogger; import cmu.pasta.fray.core.scheduler.FifoScheduler; import cmu.pasta.fray.core.scheduler.Scheduler; @@ -26,15 +28,21 @@ public String runTest(Function0 exec, Scheduler scheduler, int iter) { EventLogger logger = new EventLogger(); GlobalContext.INSTANCE.getLoggers().add(logger); Configuration config = new Configuration( - exec, + new ExecutionInfo( + new LambdaExecutor(() -> { + exec.invoke(); + return null; + }), + false, + true, + false, + 10000 + ), "/tmp/report", iter, scheduler, true, - new JsonLogger("/tmp/report", false), - false, - false, - 10000 + new JsonLogger("/tmp/report", false) ); TestRunner runner = new TestRunner(config); runner.run(); diff --git a/runtime/src/main/java/cmu/pasta/fray/runtime/Delegate.java b/runtime/src/main/java/cmu/pasta/fray/runtime/Delegate.java index a45787ad..48c835cb 100644 --- a/runtime/src/main/java/cmu/pasta/fray/runtime/Delegate.java +++ b/runtime/src/main/java/cmu/pasta/fray/runtime/Delegate.java @@ -173,6 +173,10 @@ public void onSemaphoreReducePermitsDone() { public void onLatchAwait(CountDownLatch latch) { } + public boolean onLatchAwaitTimeout(CountDownLatch latch, long timeout, TimeUnit unit) throws InterruptedException { + return latch.await(timeout, unit); + } + public void onLatchAwaitDone(CountDownLatch latch) { } @@ -213,8 +217,8 @@ public void onThreadParkUntil(long nanos) { LockSupport.parkUntil(nanos); } - public void onThreadParkUntilWithBlocker(Object blocker, long nanos) { - LockSupport.parkUntil(blocker, nanos); + public void onThreadParkUntilWithBlocker(Object blocker, long until) { + LockSupport.parkUntil(blocker, until); } public void onThreadInterruptDone(Thread t) { diff --git a/runtime/src/main/java/cmu/pasta/fray/runtime/Runtime.java b/runtime/src/main/java/cmu/pasta/fray/runtime/Runtime.java index 66df75b9..c73c9b81 100644 --- a/runtime/src/main/java/cmu/pasta/fray/runtime/Runtime.java +++ b/runtime/src/main/java/cmu/pasta/fray/runtime/Runtime.java @@ -258,6 +258,10 @@ public static void onLatchAwait(CountDownLatch latch) { DELEGATE.onLatchAwait(latch); } + public static boolean onLatchAwaitTimeout(CountDownLatch latch, long timeout, TimeUnit unit) throws InterruptedException { + return DELEGATE.onLatchAwaitTimeout(latch, timeout, unit); + } + public static void onLatchAwaitDone(CountDownLatch latch) { DELEGATE.onLatchAwaitDone(latch); } @@ -299,7 +303,7 @@ public static void onThreadParkUntil(long deadline) { } public static void onThreadParkNanosWithBlocker(Object blocker, long nanos) { - DELEGATE.onThreadParkUntilWithBlocker(blocker, nanos); + DELEGATE.onThreadParkNanosWithBlocker(blocker, nanos); } public static void onThreadParkUntilWithBlocker(Object blocker, long deadline) {