From ed287734d1975f0df2e807da4aac4f2fbcfe10ef Mon Sep 17 00:00:00 2001 From: Ao Li <5557706+aoli-al@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:06:52 -0500 Subject: [PATCH] Fix memory leaks caused by circular referencing. (#85) * Fix memory leaks caused by circurlar referencing. * Revert test changes. --- .../org/pastalab/fray/core/RunContext.kt | 5 ++--- .../org/pastalab/fray/core/RuntimeDelegate.kt | 2 -- .../fray/core/command/Configuration.kt | 3 ++- .../primitives/ConditionSignalContext.kt | 9 ++++++--- .../primitives/CountDownLatchContext.kt | 18 +++++++++++------- .../primitives/ObjectNotifyContext.kt | 7 +++++-- 6 files changed, 26 insertions(+), 18 deletions(-) diff --git a/core/src/main/kotlin/org/pastalab/fray/core/RunContext.kt b/core/src/main/kotlin/org/pastalab/fray/core/RunContext.kt index ebaa347..f7c1004 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/RunContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/RunContext.kt @@ -49,7 +49,6 @@ class RunContext(val config: Configuration) { var bugFound: Throwable? = null var mainExiting = false var nanoTime = System.nanoTime() - val terminatingThread = mutableSetOf() val hashCodeMapper = ReferencedContextManager({ config.randomnessProvider.nextInt() }) var forkJoinPool: ForkJoinPool? = null private val semaphoreManager = ReferencedContextManager { @@ -215,7 +214,7 @@ class RunContext(val config: Configuration) { fun done() { verifyOrReport(syncManager.synchronizationPoints.isEmpty()) - lockManager.done() + lockManager.done(false) signalManager.done() stampedLockManager.done() semaphoreManager.done() @@ -1012,7 +1011,7 @@ class RunContext(val config: Configuration) { } fun nanoTime(): Long { - nanoTime += TimeUnit.MILLISECONDS.convert(100, TimeUnit.NANOSECONDS) + nanoTime += TimeUnit.MILLISECONDS.convert(10000, TimeUnit.NANOSECONDS) return nanoTime } diff --git a/core/src/main/kotlin/org/pastalab/fray/core/RuntimeDelegate.kt b/core/src/main/kotlin/org/pastalab/fray/core/RuntimeDelegate.kt index b48029c..1311c34 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/RuntimeDelegate.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/RuntimeDelegate.kt @@ -491,9 +491,7 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg } override fun onReentrantReadWriteLockInit(lock: ReentrantReadWriteLock) { - if (checkEntered()) return context.reentrantReadWriteLockInit(lock.readLock(), lock.writeLock()) - entered.set(false) } override fun onSemaphoreInit(sem: Semaphore) { diff --git a/core/src/main/kotlin/org/pastalab/fray/core/command/Configuration.kt b/core/src/main/kotlin/org/pastalab/fray/core/command/Configuration.kt index b499472..eeb2232 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/command/Configuration.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/command/Configuration.kt @@ -12,6 +12,7 @@ import java.nio.file.Paths import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.createDirectories import kotlin.io.path.deleteRecursively +import kotlin.io.path.exists import kotlin.time.TimeSource import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable @@ -247,7 +248,7 @@ data class Configuration( val frayLogger = FrayLogger("$report/fray.log") init { - if (!isReplay) { + if (!isReplay || !Paths.get(report).exists()) { prepareReportPath(report) } if (System.getProperty("fray.recordSchedule", "false").toBoolean()) { diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/ConditionSignalContext.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/ConditionSignalContext.kt index 23a8f0e..10dcaa7 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/ConditionSignalContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/ConditionSignalContext.kt @@ -1,5 +1,6 @@ package org.pastalab.fray.core.concurrency.primitives +import java.lang.ref.WeakReference import java.util.concurrent.locks.Condition import java.util.concurrent.locks.Lock import org.pastalab.fray.core.ThreadContext @@ -7,14 +8,16 @@ import org.pastalab.fray.core.concurrency.operations.ConditionAwaitBlocked import org.pastalab.fray.core.concurrency.operations.ConditionWakeBlocked import org.pastalab.fray.rmi.ThreadState -class ConditionSignalContext(lockContext: LockContext, val lock: Lock, val condition: Condition) : +class ConditionSignalContext(lockContext: LockContext, lock: Lock, val condition: Condition) : SignalContext(lockContext) { + val lockReference = WeakReference(lock) + override fun sendSignalToObject() { - lock.lock() + lockReference.get()?.lock() try { condition.signalAll() } finally { - lock.unlock() + lockReference.get()?.unlock() } } diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/CountDownLatchContext.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/CountDownLatchContext.kt index d0db064..7f94603 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/CountDownLatchContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/CountDownLatchContext.kt @@ -1,5 +1,6 @@ package org.pastalab.fray.core.concurrency.primitives +import java.lang.ref.WeakReference import java.util.concurrent.CountDownLatch import org.pastalab.fray.core.ThreadContext import org.pastalab.fray.core.concurrency.SynchronizationManager @@ -12,10 +13,11 @@ import org.pastalab.fray.rmi.ThreadState * Context for a [CountDownLatch]. We have to pass syncManager here because we need to create a * synchronization point when we unblock a thread forcefully. */ -class CountDownLatchContext(val latch: CountDownLatch, val syncManager: SynchronizationManager) : +class CountDownLatchContext(latch: CountDownLatch, val syncManager: SynchronizationManager) : InterruptibleContext { var count = latch.count val latchWaiters = mutableMapOf() + val latchReference = WeakReference(latch) fun await(canInterrupt: Boolean, thread: ThreadContext): Boolean { if (count > 0) { @@ -59,12 +61,14 @@ class CountDownLatchContext(val latch: CountDownLatch, val syncManager: Synchron if (type == InterruptionType.FORCE) { while (count != 0L) { val unblockedThreads = countDown() - if (unblockedThreads > 0) { - syncManager.createWait(latch, unblockedThreads) - latch.countDown() - syncManager.wait(latch) - } else { - latch.countDown() + latchReference.get()?.let { latch -> + if (unblockedThreads > 0) { + syncManager.createWait(latch, unblockedThreads) + latch.countDown() + syncManager.wait(latch) + } else { + latch.countDown() + } } } return false diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/ObjectNotifyContext.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/ObjectNotifyContext.kt index e0a775b..db1c584 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/ObjectNotifyContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/primitives/ObjectNotifyContext.kt @@ -1,13 +1,16 @@ package org.pastalab.fray.core.concurrency.primitives +import java.lang.ref.WeakReference import org.pastalab.fray.core.ThreadContext import org.pastalab.fray.core.concurrency.operations.ObjectWaitBlock import org.pastalab.fray.core.concurrency.operations.ObjectWakeBlocked import org.pastalab.fray.rmi.ThreadState -class ObjectNotifyContext(lockContext: LockContext, val obj: Any) : SignalContext(lockContext) { +class ObjectNotifyContext(lockContext: LockContext, obj: Any) : SignalContext(lockContext) { + val objReference = WeakReference(obj) + override fun sendSignalToObject() { - synchronized(obj) { (obj as Object).notifyAll() } + objReference.get()?.let { obj -> synchronized(obj) { (obj as Object).notifyAll() } } } override fun updatedThreadContextDueToUnblock(