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 956d51af..ec71cf07 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/RunContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/RunContext.kt @@ -122,20 +122,20 @@ class RunContext(val config: Configuration) { val pendingOperation = thread.pendingOperation thread.pendingOperation = when (pendingOperation) { - is ObjectWaitBlocking -> { - ObjectWakeBlocking(pendingOperation.o) + is ObjectWaitBlock -> { + ObjectWakeBlocked(pendingOperation.o, false) } - is ConditionAwaitBlocking -> { - ConditionWakeBlocking(pendingOperation.condition) + is ConditionAwaitBlocked -> { + ConditionWakeBlocked(pendingOperation.condition, false) } - is ObjectWakeBlocking -> { + is ObjectWakeBlocked -> { pendingOperation } - is ConditionWakeBlocking -> { + is ConditionWakeBlocked -> { pendingOperation } else -> { - ThreadResumeOperation() + ThreadResumeOperation(true) } } lockManager.threadUnblockedDueToDeadlock(thread.thread) @@ -214,14 +214,20 @@ class RunContext(val config: Configuration) { LockSupport.unpark(t) } - fun threadParkDone() { + fun threadParkDone(timed: Boolean) { val t = Thread.currentThread() val context = registeredThreads[t.id]!! if (!context.unparkSignaled && !context.interruptSignaled) { - context.pendingOperation = ParkBlocking() - context.state = ThreadState.Paused - scheduleNextOperation(true) + val supriousWakeup = config.randomnessProvider.nextInt() % 2 == 0 + if (supriousWakeup) { + context.pendingOperation = ThreadResumeOperation(true) + context.state = ThreadState.Enabled + } else { + context.pendingOperation = ParkBlocked(timed) + context.state = ThreadState.Paused + scheduleNextOperation(true) + } } else if (context.unparkSignaled) { context.unparkSignaled = false } @@ -229,9 +235,9 @@ class RunContext(val config: Configuration) { fun threadUnpark(t: Thread) { val context = registeredThreads[t.id] - if (context?.state == ThreadState.Paused && context?.pendingOperation is ParkBlocking) { + if (context?.state == ThreadState.Paused && context?.pendingOperation is ParkBlocked) { context.state = ThreadState.Enabled - context.pendingOperation = ThreadResumeOperation() + context.pendingOperation = ThreadResumeOperation(true) } else if (context?.state == ThreadState.Enabled || context?.state == ThreadState.Running) { context.unparkSignaled = true } @@ -292,7 +298,12 @@ class RunContext(val config: Configuration) { } } - private fun objectWaitImpl(waitingObject: Any, lockObject: Any, canInterrupt: Boolean) { + private fun objectWaitImpl( + waitingObject: Any, + lockObject: Any, + canInterrupt: Boolean, + timed: Boolean + ) { val t = Thread.currentThread().id val objId = System.identityHashCode(waitingObject) val context = registeredThreads[t]!! @@ -312,32 +323,28 @@ class RunContext(val config: Configuration) { return } - // This is a spurious wakeup. - // https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#wait(long,int) - val spuriousWakeup = config.randomnessProvider.nextInt() % 2 == 0 - - if (!spuriousWakeup) { - if (lockObject == waitingObject) { - context.pendingOperation = ObjectWaitBlocking(waitingObject) - } else { - context.pendingOperation = ConditionAwaitBlocking(waitingObject as Condition, canInterrupt) - } - lockManager.addWaitingThread(waitingObject, Thread.currentThread()) + if (lockObject == waitingObject) { + context.pendingOperation = ObjectWaitBlock(waitingObject, timed) } else { - if (waitingObject == lockObject) { - context.pendingOperation = ObjectWakeBlocking(waitingObject) - } else { - context.pendingOperation = ConditionWakeBlocking(waitingObject as Condition) - } - lockManager.addWakingThread(lockObject, context) + context.pendingOperation = + ConditionAwaitBlocked(waitingObject as Condition, canInterrupt, timed) } + lockManager.addWaitingThread(waitingObject, Thread.currentThread()) context.state = ThreadState.Paused unlockImpl(lockObject, t, true, true, lockObject == waitingObject) + // This is a spurious wakeup. + // https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#wait(long,int) + val spuriousWakeup = config.randomnessProvider.nextInt() % 2 == 0 + + if (spuriousWakeup) { + lockManager.objectWaitUnblockedWithoutNotify(waitingObject, lockObject, context, true) + } + if (!spuriousWakeup) { checkDeadlock { - context.pendingOperation = ThreadResumeOperation() + context.pendingOperation = ThreadResumeOperation(true) assert(lockManager.lock(lockObject, context, false, true, false)) syncManager.removeWait(lockObject) context.state = ThreadState.Running @@ -357,16 +364,16 @@ class RunContext(val config: Configuration) { } } - fun objectWait(o: Any) { - objectWaitImpl(o, o, true) + fun objectWait(o: Any, timed: Boolean) { + objectWaitImpl(o, o, true, timed) } - fun conditionAwait(o: Condition, canInterrupt: Boolean) { + fun conditionAwait(o: Condition, canInterrupt: Boolean, timed: Boolean) { val lock = lockManager.lockFromCondition(o) - objectWaitImpl(o, lock, canInterrupt) + objectWaitImpl(o, lock, canInterrupt, timed) } - fun objectWaitDoneImpl(waitingObject: Any, lockObject: Any, canInterrupt: Boolean) { + fun objectWaitDoneImpl(waitingObject: Any, lockObject: Any, canInterrupt: Boolean): Boolean { val t = Thread.currentThread() val context = registeredThreads[t.id]!! // We will unblock here only if the scheduler @@ -378,6 +385,7 @@ class RunContext(val config: Configuration) { Thread.interrupted() } if (waitingObject is Condition) { + // TODO(aoli): Is this necessary? if (canInterrupt) { waitingObject.await() } else { @@ -395,6 +403,9 @@ class RunContext(val config: Configuration) { if (canInterrupt) { context.checkInterrupt() } + val pendingOperation = context.pendingOperation + assert(pendingOperation is ThreadResumeOperation) + return (pendingOperation as ThreadResumeOperation).noTimeout } fun threadInterrupt(t: Thread) { @@ -408,31 +419,31 @@ class RunContext(val config: Configuration) { val pendingOperation = context.pendingOperation var waitingObject: Any? = null when (pendingOperation) { - is ObjectWaitBlocking -> { - if (lockManager.threadInterruptDuringObjectWait( - pendingOperation.o, pendingOperation.o, context)) { + is ObjectWaitBlock -> { + if (lockManager.objectWaitUnblockedWithoutNotify( + pendingOperation.o, pendingOperation.o, context, false)) { syncManager.createWait(pendingOperation.o, 1) waitingObject = pendingOperation.o } } - is ConditionAwaitBlocking -> { + is ConditionAwaitBlocked -> { if (pendingOperation.canInterrupt) { val lock = lockManager.lockFromCondition(pendingOperation.condition) - if (lockManager.threadInterruptDuringObjectWait( - pendingOperation.condition, lock, context)) { + if (lockManager.objectWaitUnblockedWithoutNotify( + pendingOperation.condition, lock, context, false)) { syncManager.createWait(lock, 1) waitingObject = lock } } } is CountDownLatchAwaitBlocking -> { - context.pendingOperation = ThreadResumeOperation() + context.pendingOperation = ThreadResumeOperation(true) context.state = ThreadState.Enabled syncManager.createWait(pendingOperation.latch, 1) waitingObject = pendingOperation.latch } - is ParkBlocking -> { - context.pendingOperation = ThreadResumeOperation() + is ParkBlocked -> { + context.pendingOperation = ThreadResumeOperation(true) context.state = ThreadState.Enabled } is LockBlocking -> { @@ -452,7 +463,7 @@ class RunContext(val config: Configuration) { if (pendingOperation is InterruptPendingOperation) { syncManager.wait(pendingOperation.waitingObject) } - context.pendingOperation = ThreadResumeOperation() + context.pendingOperation = ThreadResumeOperation(true) } fun threadClearInterrupt(t: Thread): Boolean { @@ -466,8 +477,41 @@ class RunContext(val config: Configuration) { objectWaitDoneImpl(o, o, true) } - fun conditionAwaitDone(o: Condition, canInterrupt: Boolean) { - objectWaitDoneImpl(o, lockManager.lockFromCondition(o), canInterrupt) + fun conditionAwaitDone(o: Condition, canInterrupt: Boolean): Boolean { + return objectWaitDoneImpl(o, lockManager.lockFromCondition(o), canInterrupt) + } + + fun timedOperationUnblocked(context: ThreadContext) { + val pendingOperation = context.pendingOperation + assert(pendingOperation is TimedBlockingOperation && pendingOperation.timed) + when (pendingOperation) { + is ObjectWaitBlock -> { + lockManager.objectWaitUnblockedWithoutNotify( + pendingOperation.o, pendingOperation.o, context, true) + } + is ConditionAwaitBlocked -> { + lockManager.objectWaitUnblockedWithoutNotify( + pendingOperation.condition, + lockManager.lockFromCondition(pendingOperation.condition), + context, + true) + } + is ParkBlocked -> { + context.pendingOperation = ThreadResumeOperation(false) + context.state = ThreadState.Enabled + } + is CountDownLatchAwaitBlocking -> { + context.pendingOperation = ThreadResumeOperation(false) + context.state = ThreadState.Enabled + if (context.thread != Thread.currentThread()) { + syncManager.createWait(pendingOperation.latch, 1) + context.thread.interrupt() + syncManager.wait(pendingOperation.latch) + } else { + context.thread.interrupt() + } + } + } } fun objectNotifyImpl(waitingObject: Any, lockObject: Any) { @@ -480,9 +524,9 @@ class RunContext(val config: Configuration) { val context = registeredThreads[t]!! lockManager.addWakingThread(lockObject, context) if (waitingObject == lockObject) { - context.pendingOperation = ObjectWakeBlocking(waitingObject) + context.pendingOperation = ObjectWakeBlocked(waitingObject, false) } else { - context.pendingOperation = ConditionWakeBlocking(waitingObject as Condition) + context.pendingOperation = ConditionWakeBlocked(waitingObject as Condition, false) } it.remove(t) if (it.size == 0) { @@ -510,9 +554,9 @@ class RunContext(val config: Configuration) { // We cannot enable the thread immediately because // the thread is still waiting for the monitor lock. if (waitingObject == lockObject) { - context.pendingOperation = ObjectWakeBlocking(waitingObject) + context.pendingOperation = ObjectWakeBlocked(waitingObject, false) } else { - context.pendingOperation = ConditionWakeBlocking(waitingObject as Condition) + context.pendingOperation = ConditionWakeBlocked(waitingObject as Condition, false) } lockManager.addWakingThread(lockObject, context) } @@ -721,15 +765,15 @@ class RunContext(val config: Configuration) { scheduleNextOperation(true) } - fun latchAwait(latch: CountDownLatch) { + fun latchAwait(latch: CountDownLatch, timed: Boolean) { val t = Thread.currentThread().id val context = registeredThreads[t]!! if (latchManager.await(latch, true, context)) { - context.pendingOperation = CountDownLatchAwaitBlocking(latch) + context.pendingOperation = CountDownLatchAwaitBlocking(latch, timed) context.state = ThreadState.Paused checkDeadlock { context.state = ThreadState.Running - context.pendingOperation = ThreadResumeOperation() + context.pendingOperation = ThreadResumeOperation(true) } executor.submit { while (registeredThreads[t]!!.thread.state == Thread.State.RUNNABLE) { @@ -737,6 +781,8 @@ class RunContext(val config: Configuration) { } scheduleNextOperationAndCheckDeadlock(false) } + } else { + context.pendingOperation = ThreadResumeOperation(true) } } @@ -748,13 +794,16 @@ class RunContext(val config: Configuration) { return lockManager.hasQueuedThread(lock, thread) } - fun latchAwaitDone(latch: CountDownLatch) { + fun latchAwaitDone(latch: CountDownLatch): Boolean { val t = Thread.currentThread().id val context = registeredThreads[t]!! if (context.state != ThreadState.Running) { syncManager.signal(latch) context.block() } + val pendingOperation = context.pendingOperation + assert(pendingOperation is ThreadResumeOperation) + return (pendingOperation as ThreadResumeOperation).noTimeout } fun latchCountDown(latch: CountDownLatch) { @@ -778,11 +827,11 @@ class RunContext(val config: Configuration) { lockManager.threadUnblockedDueToDeadlock(thread.thread) val pendingOperation = thread.pendingOperation when (pendingOperation) { - is ObjectWaitBlocking -> { - thread.pendingOperation = ObjectWakeBlocking(pendingOperation.o) + is ObjectWaitBlock -> { + thread.pendingOperation = ObjectWakeBlocked(pendingOperation.o, false) } - is ConditionAwaitBlocking -> { - thread.pendingOperation = ConditionWakeBlocking(pendingOperation.condition) + is ConditionAwaitBlocked -> { + thread.pendingOperation = ConditionWakeBlocked(pendingOperation.condition, false) } is CountDownLatchAwaitBlocking -> { val releasedThreads = latchManager.release(pendingOperation.latch) @@ -801,7 +850,14 @@ class RunContext(val config: Configuration) { } fun checkDeadlock(cleanUp: () -> Unit) { - val deadLock = registeredThreads.values.toList().none { it.schedulable() } + val deadLock = + if (registeredThreads.values.toList().none { it.schedulable() }) { + unblockTimedOperations() + registeredThreads.values.toList().none { it.schedulable() } + } else { + false + } + if (deadLock) { val e = org.pastalab.fray.runtime.DeadlockException() reportError(e) @@ -817,6 +873,15 @@ class RunContext(val config: Configuration) { scheduleNextOperation(true) } + fun unblockTimedOperations() { + registeredThreads.values.forEach { + val op = it.pendingOperation + if (op is TimedBlockingOperation && op.timed) { + timedOperationUnblocked(it) + } + } + } + fun scheduleNextOperation(shouldBlockCurrentThread: Boolean) { // Our current design makes sure that reschedule is only called // by scheduled thread. @@ -845,6 +910,15 @@ class RunContext(val config: Configuration) { enabledOperations = enabledOperations.filter { it.thread.id != mainThreadId } } + if (enabledOperations.isEmpty()) { + unblockTimedOperations() + enabledOperations = + registeredThreads.values + .toList() + .filter { it.state == ThreadState.Enabled } + .sortedBy { it.thread.id } + } + if (enabledOperations.isEmpty()) { if (registeredThreads.all { it.value.state == ThreadState.Completed }) { // We are done here, we should go back to the main thread. @@ -884,21 +958,21 @@ class RunContext(val config: Configuration) { fun unblockThread(currentThread: ThreadContext, nextThread: ThreadContext) { val pendingOperation = nextThread.pendingOperation when (pendingOperation) { - is ConditionWakeBlocking -> { - nextThread.pendingOperation = ThreadResumeOperation() + is ConditionWakeBlocked -> { + nextThread.pendingOperation = ThreadResumeOperation(pendingOperation.noTimeout) val lock = lockManager.lockFromCondition(pendingOperation.condition) lock.lock() pendingOperation.condition.signalAll() lock.unlock() return } - is ObjectWakeBlocking -> { - nextThread.pendingOperation = ThreadResumeOperation() + is ObjectWakeBlocked -> { + nextThread.pendingOperation = ThreadResumeOperation(pendingOperation.noTimeout) synchronized(pendingOperation.o) { (pendingOperation.o as Object).notifyAll() } return } } - if (currentThread != nextThread) { + if (currentThread != nextThread || Thread.currentThread() is HelperThread) { nextThread.unblock() } } 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 2d84c75e..ac29af0a 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/RuntimeDelegate.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/RuntimeDelegate.kt @@ -82,10 +82,10 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg return result } - override fun onObjectWait(o: Any) { + override fun onObjectWait(o: Any, timeout: Long) { if (checkEntered()) return try { - context.objectWait(o) + context.objectWait(o, timeout != 0L) } finally { entered.set(false) } @@ -254,53 +254,45 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg } override fun onConditionAwait(o: Condition) { - if (checkEntered()) { - onSkipMethod("Condition.await") - return - } - try { - context.conditionAwait(o, true) - } finally { - entered.set(false) - onSkipMethod("Condition.await") - } + onConditionAwaitImpl(o, true, false) } - override fun onConditionAwaitUninterruptibly(o: Condition) { + fun onConditionAwaitImpl(o: Condition, canInterrupt: Boolean, timed: Boolean): Boolean { if (checkEntered()) { onSkipMethod("Condition.await") - return + return true } try { - context.conditionAwait(o, false) + context.conditionAwait(o, canInterrupt, timed) } finally { entered.set(false) onSkipMethod("Condition.await") } + return false } - override fun onConditionAwaitDone(o: Condition) { + fun onConditionAwaitDoneImpl(o: Condition, canInterrupt: Boolean): Boolean { if (!onSkipMethodDone("Condition.await")) { - return + return true } - if (checkEntered()) return + if (checkEntered()) return true try { - context.conditionAwaitDone(o, true) + return context.conditionAwaitDone(o, canInterrupt) } finally { entered.set(false) } } + override fun onConditionAwaitUninterruptibly(o: Condition) { + onConditionAwaitImpl(o, false, false) + } + + override fun onConditionAwaitDone(o: Condition) { + onConditionAwaitDoneImpl(o, true) + } + override fun onConditionAwaitUninterruptiblyDone(o: Condition) { - if (!onSkipMethodDone("Condition.await")) { - return - } - if (checkEntered()) return - try { - context.conditionAwaitDone(o, false) - } finally { - entered.set(false) - } + onConditionAwaitDoneImpl(o, false) } override fun onConditionSignal(o: Condition) { @@ -425,24 +417,40 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg return true } - override fun onThreadPark() { - if (checkEntered()) return + fun onThreadParkImpl(): Boolean { + if (checkEntered()) { + onSkipMethod("Thread.park") + return true + } try { context.threadPark() } finally { entered.set(false) + onSkipMethod("Thread.park") } + return false } - override fun onThreadParkDone() { + override fun onThreadPark() { + onThreadParkImpl() + } + + fun onThreadParkDoneImpl(timed: Boolean) { + if (!onSkipMethodDone("Thread.park")) { + return + } if (checkEntered()) return try { - context.threadParkDone() + context.threadParkDone(timed) } finally { entered.set(false) } } + override fun onThreadParkDone() { + onThreadParkDoneImpl(false) + } + override fun onThreadUnpark(t: Thread?) { if (t == null) return if (checkEntered()) { @@ -578,34 +586,49 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg onSkipMethodDone("Semaphore.reducePermits") } - override fun onLatchAwait(latch: CountDownLatch) { + fun onLatchAwaitImpl(latch: CountDownLatch, timed: Boolean): Boolean { if (checkEntered()) { onSkipMethod("Latch.await") - return + return true } try { - context.latchAwait(latch) + context.latchAwait(latch, timed) } finally { entered.set(false) onSkipMethod("Latch.await") } + return false + } + + override fun onLatchAwait(latch: CountDownLatch) { + onLatchAwaitImpl(latch, false) + } + + fun onLatchAwaitDoneImpl(latch: CountDownLatch): Boolean { + onSkipMethodDone("Latch.await") + if (checkEntered()) return true + try { + return context.latchAwaitDone(latch) + } finally { + entered.set(false) + } } override fun onLatchAwaitTimeout(latch: CountDownLatch, timeout: Long, unit: TimeUnit): Boolean { - if (context.config.executionInfo.timedOpAsYield) { - onYield() - return false - } else { + if (onLatchAwaitImpl(latch, true)) { + onSkipMethodDone("Latch.await") + return latch.await(timeout, unit) + } + try { latch.await() - return true + } catch (e: InterruptedException) { + // Do nothing } + return onLatchAwaitDoneImpl(latch) } override fun onLatchAwaitDone(latch: CountDownLatch) { - onSkipMethodDone("Latch.await") - if (checkEntered()) return - context.latchAwaitDone(latch) - entered.set(false) + onLatchAwaitDoneImpl(latch) } override fun onLatchCountDown(latch: CountDownLatch) { @@ -664,81 +687,77 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg } override fun onThreadParkNanos(nanos: Long) { - if (context.config.executionInfo.timedOpAsYield) { - onYield() - } else { - LockSupport.park() + if (onThreadParkImpl()) { + onSkipMethodDone("Thread.park") + LockSupport.parkNanos(nanos) + return } + LockSupport.park() + onThreadParkDoneImpl(true) } - override fun onThreadParkUntil(nanos: Long) { - if (context.config.executionInfo.timedOpAsYield) { - onYield() - } else { - LockSupport.park() + override fun onThreadParkUntil(deadline: Long) { + if (onThreadParkImpl()) { + onSkipMethodDone("Thread.park") + LockSupport.parkUntil(deadline) + return } + LockSupport.park() + onThreadParkDoneImpl(true) } override fun onThreadParkNanosWithBlocker(blocker: Any?, nanos: Long) { - if (checkEntered()) { - try { - LockSupport.parkNanos(nanos) - } finally { - entered.set(false) - } - } - entered.set(false) - if (context.config.executionInfo.timedOpAsYield) { - onYield() - } else { - LockSupport.park(blocker) + if (onThreadParkImpl()) { + onSkipMethodDone("Thread.park") + LockSupport.parkNanos(blocker, nanos) + return } + LockSupport.park() + onThreadParkDoneImpl(true) } - override fun onThreadParkUntilWithBlocker(blocker: Any?, nanos: Long) { - if (context.config.executionInfo.timedOpAsYield) { - onYield() - } else { - LockSupport.park(blocker) + override fun onThreadParkUntilWithBlocker(blocker: Any?, deadline: Long) { + if (onThreadParkImpl()) { + onSkipMethodDone("Thread.park") + LockSupport.parkUntil(blocker, deadline) + return } + LockSupport.park() + onThreadParkDoneImpl(true) } override fun onConditionAwaitTime(o: Condition, time: Long, unit: TimeUnit): Boolean { - if (context.config.executionInfo.timedOpAsYield) { - onYield() - return false - } else { - o.await() - return true + if (onConditionAwaitImpl(o, true, true)) { + val result = o.await(time, unit) + onSkipMethodDone("Condition.await") + return result } + o.await() + return onConditionAwaitDoneImpl(o, true) } override fun onConditionAwaitNanos(o: Condition, nanos: Long): Long { - if (checkEntered()) { - try { - return o.awaitNanos(nanos) - } finally { - entered.set(false) - } + if (onConditionAwaitImpl(o, true, true)) { + val result = o.awaitNanos(nanos) + onSkipMethodDone("Condition.await") + return result } - entered.set(false) - if (context.config.executionInfo.timedOpAsYield) { - onYield() + o.await() + if (onConditionAwaitDoneImpl(o, true)) { return 0 } else { - o.await() - return nanos + return nanos - 1 } } override fun onConditionAwaitUntil(o: Condition, deadline: Date): Boolean { - if (context.config.executionInfo.timedOpAsYield) { - onYield() - return false - } else { - o.await() - return true + if (onConditionAwaitImpl(o, true, true)) { + val result = o.awaitUntil(deadline) + onSkipMethodDone("Condition.await") + return result } + o.await() + return onConditionAwaitDoneImpl(o, true) } override fun onThreadIsInterrupted(result: Boolean, t: Thread): Boolean { diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/Sync.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/Sync.kt index 5b2b30b5..bfd0d00d 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/Sync.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/Sync.kt @@ -36,9 +36,6 @@ class Sync(val goal: Int) : Any() { fun unblock() { count += 1 signaler.add(Thread.currentThread().name) - if (count > goal) { - println("?") - } assert(count <= goal) if (count == goal) { (this as Object).notify() diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/CountDownLatchContext.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/CountDownLatchContext.kt index 4730bc76..a041f2c6 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/CountDownLatchContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/CountDownLatchContext.kt @@ -22,7 +22,7 @@ class CountDownLatchContext(var count: Long) : Interruptible { override fun interrupt(tid: Long) { val lockWaiter = latchWaiters[tid] ?: return if (lockWaiter.canInterrupt) { - lockWaiter.thread.pendingOperation = ThreadResumeOperation() + lockWaiter.thread.pendingOperation = ThreadResumeOperation(false) lockWaiter.thread.state = ThreadState.Enabled latchWaiters.remove(tid) } @@ -35,7 +35,7 @@ class CountDownLatchContext(var count: Long) : Interruptible { count = 0 var threads = 0 for (lockWaiter in latchWaiters.values) { - lockWaiter.thread.pendingOperation = ThreadResumeOperation() + lockWaiter.thread.pendingOperation = ThreadResumeOperation(true) lockWaiter.thread.state = ThreadState.Enabled threads += 1 } @@ -55,7 +55,7 @@ class CountDownLatchContext(var count: Long) : Interruptible { if (count == 0L) { var threads = 0 for (lockWaiter in latchWaiters.values) { - lockWaiter.thread.pendingOperation = ThreadResumeOperation() + lockWaiter.thread.pendingOperation = ThreadResumeOperation(true) lockWaiter.thread.state = ThreadState.Enabled threads += 1 } diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/LockManager.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/LockManager.kt index a0c0b614..653a9dda 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/LockManager.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/LockManager.kt @@ -6,8 +6,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock import org.pastalab.fray.core.ThreadContext import org.pastalab.fray.core.ThreadState -import org.pastalab.fray.core.concurrency.operations.ConditionWakeBlocking -import org.pastalab.fray.core.concurrency.operations.ObjectWakeBlocking +import org.pastalab.fray.core.concurrency.operations.ConditionWakeBlocked +import org.pastalab.fray.core.concurrency.operations.ObjectWakeBlocked class LockManager { val lockContextManager = ReferencedContextManager { ReentrantLockContext() } @@ -70,10 +70,11 @@ class LockManager { } // TODO(aoli): can we merge this logic with `objectNotifyImply`? - fun threadInterruptDuringObjectWait( + fun objectWaitUnblockedWithoutNotify( waitingObject: Any, lockObject: Any, - context: ThreadContext + context: ThreadContext, + isTimeout: Boolean ): Boolean { val id = System.identityHashCode(waitingObject) val lockContext = getLockContext(lockObject) @@ -84,9 +85,9 @@ class LockManager { } addWakingThread(lockObject, context) if (waitingObject == lockObject) { - context.pendingOperation = ObjectWakeBlocking(waitingObject) + context.pendingOperation = ObjectWakeBlocked(waitingObject, !isTimeout) } else { - context.pendingOperation = ConditionWakeBlocking(waitingObject as Condition) + context.pendingOperation = ConditionWakeBlocked(waitingObject as Condition, !isTimeout) } if (lockContext.canLock(context.thread.id)) { context.state = ThreadState.Enabled diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/ReentrantLockContext.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/ReentrantLockContext.kt index 3be8e73e..594af58c 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/ReentrantLockContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/ReentrantLockContext.kt @@ -84,7 +84,7 @@ class ReentrantLockContext : LockContext { } for (lockWaiter in lockWaiters.values) { if (lockWaiter.thread.state != ThreadState.Completed) { - lockWaiter.thread.pendingOperation = ThreadResumeOperation() + lockWaiter.thread.pendingOperation = ThreadResumeOperation(true) lockWaiter.thread.state = ThreadState.Enabled } } @@ -97,7 +97,7 @@ class ReentrantLockContext : LockContext { override fun interrupt(tid: Long) { val lockWaiter = lockWaiters[tid] ?: return if (lockWaiter.canInterrupt) { - lockWaiter.thread.pendingOperation = ThreadResumeOperation() + lockWaiter.thread.pendingOperation = ThreadResumeOperation(false) lockWaiter.thread.state = ThreadState.Enabled lockWaiters.remove(tid) } diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/ReentrantReadWriteLockContext.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/ReentrantReadWriteLockContext.kt index 72cc2a84..78f24abe 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/ReentrantReadWriteLockContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/ReentrantReadWriteLockContext.kt @@ -58,13 +58,13 @@ class ReentrantReadWriteLockContext : LockContext { override fun interrupt(tid: Long) { val writeLockWaiter = writeLockWaiters[tid] if (writeLockWaiter != null && writeLockWaiter.canInterrupt) { - writeLockWaiter.thread.pendingOperation = ThreadResumeOperation() + writeLockWaiter.thread.pendingOperation = ThreadResumeOperation(false) writeLockWaiter.thread.state = ThreadState.Enabled writeLockWaiters.remove(tid) } val readLockWaiter = readLockWaiters[tid] if (readLockWaiter != null && readLockWaiter.canInterrupt) { - readLockWaiter.thread.pendingOperation = ThreadResumeOperation() + readLockWaiter.thread.pendingOperation = ThreadResumeOperation(false) readLockWaiter.thread.state = ThreadState.Enabled readLockWaiters.remove(tid) } @@ -164,19 +164,19 @@ class ReentrantReadWriteLockContext : LockContext { fun unlockWriteWaiters() { for (writeLockWaiter in writeLockWaiters.values) { - writeLockWaiter.thread.pendingOperation = ThreadResumeOperation() + writeLockWaiter.thread.pendingOperation = ThreadResumeOperation(true) writeLockWaiter.thread.state = ThreadState.Enabled } // Waking threads are write waiters as well. for (thread in wakingThreads.values) { - thread.pendingOperation = ThreadResumeOperation() + thread.pendingOperation = ThreadResumeOperation(true) thread.state = ThreadState.Enabled } } fun unlockReadWaiters() { for (readLockWaiter in readLockWaiters.values) { - readLockWaiter.thread.pendingOperation = ThreadResumeOperation() + readLockWaiter.thread.pendingOperation = ThreadResumeOperation(true) readLockWaiter.thread.state = ThreadState.Enabled } } diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/SemaphoreContext.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/SemaphoreContext.kt index da0aa0af..6b9ed5f8 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/SemaphoreContext.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/locks/SemaphoreContext.kt @@ -40,7 +40,7 @@ class SemaphoreContext(var totalPermits: Int) : Interruptible { while (it.hasNext()) { val (tid, p) = it.next() if (totalPermits >= p.first) { - p.second.thread.pendingOperation = ThreadResumeOperation() + p.second.thread.pendingOperation = ThreadResumeOperation(true) p.second.thread.state = ThreadState.Enabled lockWaiters.remove(tid) } @@ -55,7 +55,7 @@ class SemaphoreContext(var totalPermits: Int) : Interruptible { override fun interrupt(tid: Long) { val lockWaiter = lockWaiters[tid] ?: return if (lockWaiter.second.canInterrupt) { - lockWaiter.second.thread.pendingOperation = ThreadResumeOperation() + lockWaiter.second.thread.pendingOperation = ThreadResumeOperation(false) lockWaiter.second.thread.state = ThreadState.Enabled lockWaiters.remove(tid) } diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionAwaitBlocked.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionAwaitBlocked.kt new file mode 100644 index 00000000..802d9c43 --- /dev/null +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionAwaitBlocked.kt @@ -0,0 +1,6 @@ +package org.pastalab.fray.core.concurrency.operations + +import java.util.concurrent.locks.Condition + +class ConditionAwaitBlocked(val condition: Condition, val canInterrupt: Boolean, timed: Boolean) : + TimedBlockingOperation(timed) {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionAwaitBlocking.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionWakeBlocked.kt similarity index 60% rename from core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionAwaitBlocking.kt rename to core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionWakeBlocked.kt index 2a5cee21..b0bcd43a 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionAwaitBlocking.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionWakeBlocked.kt @@ -2,5 +2,5 @@ package org.pastalab.fray.core.concurrency.operations import java.util.concurrent.locks.Condition -class ConditionAwaitBlocking(val condition: Condition, val canInterrupt: Boolean) : +class ConditionWakeBlocked(val condition: Condition, val noTimeout: Boolean) : NonRacingOperation() {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionWakeBlocking.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionWakeBlocking.kt deleted file mode 100644 index db35e491..00000000 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ConditionWakeBlocking.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.pastalab.fray.core.concurrency.operations - -import java.util.concurrent.locks.Condition - -class ConditionWakeBlocking(val condition: Condition) : NonRacingOperation() {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/CountDownLatchWaitOperation.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/CountDownLatchWaitOperation.kt index df9ca2db..e417521b 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/CountDownLatchWaitOperation.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/CountDownLatchWaitOperation.kt @@ -2,4 +2,5 @@ package org.pastalab.fray.core.concurrency.operations import java.util.concurrent.CountDownLatch -class CountDownLatchAwaitBlocking(val latch: CountDownLatch) : NonRacingOperation() {} +class CountDownLatchAwaitBlocking(val latch: CountDownLatch, timed: Boolean) : + TimedBlockingOperation(timed) {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWaitBlock.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWaitBlock.kt new file mode 100644 index 00000000..360142c7 --- /dev/null +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWaitBlock.kt @@ -0,0 +1,3 @@ +package org.pastalab.fray.core.concurrency.operations + +class ObjectWaitBlock(val o: Any, timed: Boolean) : TimedBlockingOperation(timed) {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWaitBlocking.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWaitBlocking.kt deleted file mode 100644 index 1e60c129..00000000 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWaitBlocking.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.pastalab.fray.core.concurrency.operations - -class ObjectWaitBlocking(val o: Any) : NonRacingOperation() {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWakeBlocked.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWakeBlocked.kt new file mode 100644 index 00000000..73daf3f0 --- /dev/null +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWakeBlocked.kt @@ -0,0 +1,3 @@ +package org.pastalab.fray.core.concurrency.operations + +class ObjectWakeBlocked(val o: Any, val noTimeout: Boolean) : NonRacingOperation() {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWakeBlocking.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWakeBlocking.kt deleted file mode 100644 index 2e842187..00000000 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ObjectWakeBlocking.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.pastalab.fray.core.concurrency.operations - -class ObjectWakeBlocking(val o: Any) : NonRacingOperation() {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ParkBlocked.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ParkBlocked.kt new file mode 100644 index 00000000..ec799002 --- /dev/null +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ParkBlocked.kt @@ -0,0 +1,3 @@ +package org.pastalab.fray.core.concurrency.operations + +class ParkBlocked(timed: Boolean) : TimedBlockingOperation(timed) {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ThreadResumeOperation.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ThreadResumeOperation.kt index 97e1f960..4d6f2acf 100644 --- a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ThreadResumeOperation.kt +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/ThreadResumeOperation.kt @@ -1,3 +1,3 @@ package org.pastalab.fray.core.concurrency.operations -class ThreadResumeOperation : NonRacingOperation() {} +class ThreadResumeOperation(val noTimeout: Boolean) : NonRacingOperation() {} diff --git a/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/TimedBlockingOperation.kt b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/TimedBlockingOperation.kt new file mode 100644 index 00000000..91d7afc2 --- /dev/null +++ b/core/src/main/kotlin/org/pastalab/fray/core/concurrency/operations/TimedBlockingOperation.kt @@ -0,0 +1,3 @@ +package org.pastalab.fray.core.concurrency.operations + +open class TimedBlockingOperation(val timed: Boolean) : NonRacingOperation() {} diff --git a/instrumentation/base/src/main/kotlin/org/pastalab/fray/instrumentation/base/visitors/ObjectInstrumenter.kt b/instrumentation/base/src/main/kotlin/org/pastalab/fray/instrumentation/base/visitors/ObjectInstrumenter.kt index d08902ea..41c9e531 100644 --- a/instrumentation/base/src/main/kotlin/org/pastalab/fray/instrumentation/base/visitors/ObjectInstrumenter.kt +++ b/instrumentation/base/src/main/kotlin/org/pastalab/fray/instrumentation/base/visitors/ObjectInstrumenter.kt @@ -25,7 +25,7 @@ class ObjectInstrumenter(cv: ClassVisitor) : ClassVisitorBase(cv, Object::class. name, descriptor, true, - false) + true) // We cannot use MethodExitVisitor here because `onObjectWaitDone` may throw an // `InterruptedException` // So we cannot catch that exception twice. diff --git a/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/ParkDeadlock.java b/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/ParkDeadlock.java new file mode 100644 index 00000000..c74b2737 --- /dev/null +++ b/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/ParkDeadlock.java @@ -0,0 +1,9 @@ +package org.pastalab.fray.test.fail.wait; + +import java.util.concurrent.locks.LockSupport; + +public class ParkDeadlock { + public static void main(String[] args) { + LockSupport.park(); + } +} diff --git a/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/ParkSpuriousWakeup.java b/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/ParkSpuriousWakeup.java new file mode 100644 index 00000000..8c65c845 --- /dev/null +++ b/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/ParkSpuriousWakeup.java @@ -0,0 +1,29 @@ +package org.pastalab.fray.test.fail.wait; + +import org.pastalab.fray.test.ExpectedException; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; + +public class ParkSpuriousWakeup { + public static void main(String[] args) { + CountDownLatch latch = new CountDownLatch(1); + AtomicBoolean flag = new AtomicBoolean(false); + Thread t = new Thread(() -> { + latch.countDown(); + LockSupport.park(); + flag.set(true); + }); + t.start(); + try { + latch.await(); // Wait for the thread to start + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (flag.get()) { + throw new ExpectedException("Spurious wakeup bug found"); + } + LockSupport.unpark(t); + } +} diff --git a/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/WaitWithoutNotify.java b/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/WaitWithoutNotifyDeadlock.java similarity index 88% rename from integration-test/src/main/java/org/pastalab/fray/test/fail/wait/WaitWithoutNotify.java rename to integration-test/src/main/java/org/pastalab/fray/test/fail/wait/WaitWithoutNotifyDeadlock.java index 1d74d863..429dc2d6 100644 --- a/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/WaitWithoutNotify.java +++ b/integration-test/src/main/java/org/pastalab/fray/test/fail/wait/WaitWithoutNotifyDeadlock.java @@ -1,6 +1,6 @@ package org.pastalab.fray.test.fail.wait; -public class WaitWithoutNotify { +public class WaitWithoutNotifyDeadlock { public static void main(String[] args) { Object o = new Object(); try { diff --git a/integration-test/src/main/java/org/pastalab/fray/test/success/wait/ConditionAwaitTimeoutNoDeadlock.java b/integration-test/src/main/java/org/pastalab/fray/test/success/wait/ConditionAwaitTimeoutNoDeadlock.java new file mode 100644 index 00000000..4506db4d --- /dev/null +++ b/integration-test/src/main/java/org/pastalab/fray/test/success/wait/ConditionAwaitTimeoutNoDeadlock.java @@ -0,0 +1,27 @@ +package org.pastalab.fray.test.success.wait; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class ConditionAwaitTimeoutNoDeadlock { + public static void main(String[] args) { + Lock l = new ReentrantLock(); + Condition c = l.newCondition(); + l.lock(); + try { + boolean result = c.await(1000, java.util.concurrent.TimeUnit.MILLISECONDS); + assert(!result); + + boolean result2 = c.awaitUntil(new java.util.Date(System.currentTimeMillis() + 1000)); + assert(!result2); + + long result3 = c.awaitNanos(1000000000); + assert(result3 < 1000000000); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + l.unlock(); + } + } +} diff --git a/integration-test/src/main/java/org/pastalab/fray/test/success/wait/CountDownLatchAwaitTimeoutNoDeadlock.java b/integration-test/src/main/java/org/pastalab/fray/test/success/wait/CountDownLatchAwaitTimeoutNoDeadlock.java new file mode 100644 index 00000000..182f4cc4 --- /dev/null +++ b/integration-test/src/main/java/org/pastalab/fray/test/success/wait/CountDownLatchAwaitTimeoutNoDeadlock.java @@ -0,0 +1,14 @@ +package org.pastalab.fray.test.success.wait; + +import java.util.concurrent.CountDownLatch; + +public class CountDownLatchAwaitTimeoutNoDeadlock { + public static void main(String[] args) { + CountDownLatch cdl = new CountDownLatch(1); + try { + cdl.await(1000, java.util.concurrent.TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/integration-test/src/main/java/org/pastalab/fray/test/success/wait/ParkWithTimeoutNoDeadlock.java b/integration-test/src/main/java/org/pastalab/fray/test/success/wait/ParkWithTimeoutNoDeadlock.java new file mode 100644 index 00000000..cd974bc2 --- /dev/null +++ b/integration-test/src/main/java/org/pastalab/fray/test/success/wait/ParkWithTimeoutNoDeadlock.java @@ -0,0 +1,12 @@ +package org.pastalab.fray.test.success.wait; + +import java.util.concurrent.locks.LockSupport; + +public class ParkWithTimeoutNoDeadlock { + public static void main(String[] args) { + LockSupport.parkNanos(1000000000); + LockSupport.parkUntil(System.currentTimeMillis() + 1000); + LockSupport.parkUntil(null, System.currentTimeMillis() + 1000); + LockSupport.parkNanos(null, 1000000000); + } +} diff --git a/integration-test/src/main/java/org/pastalab/fray/test/success/wait/WaitWithTimeoutNoDeadlock.java b/integration-test/src/main/java/org/pastalab/fray/test/success/wait/WaitWithTimeoutNoDeadlock.java new file mode 100644 index 00000000..22691fc7 --- /dev/null +++ b/integration-test/src/main/java/org/pastalab/fray/test/success/wait/WaitWithTimeoutNoDeadlock.java @@ -0,0 +1,14 @@ +package org.pastalab.fray.test.success.wait; + +public class WaitWithTimeoutNoDeadlock { + public static void main(String[] args) { + Object o = new Object(); + synchronized (o) { + try { + o.wait(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/runtime/src/main/java/org/pastalab/fray/runtime/Delegate.java b/runtime/src/main/java/org/pastalab/fray/runtime/Delegate.java index 56aa2701..bf1438ec 100644 --- a/runtime/src/main/java/org/pastalab/fray/runtime/Delegate.java +++ b/runtime/src/main/java/org/pastalab/fray/runtime/Delegate.java @@ -22,7 +22,7 @@ public void onThreadRun() { public void onThreadEnd() { } - public void onObjectWait(Object o) { + public void onObjectWait(Object o, long timeout) { } public void onObjectWaitDone(Object o) { diff --git a/runtime/src/main/java/org/pastalab/fray/runtime/Runtime.java b/runtime/src/main/java/org/pastalab/fray/runtime/Runtime.java index d6aba60d..210459ad 100644 --- a/runtime/src/main/java/org/pastalab/fray/runtime/Runtime.java +++ b/runtime/src/main/java/org/pastalab/fray/runtime/Runtime.java @@ -73,8 +73,8 @@ public static void onLockNewCondition(Condition c, Lock l) { DELEGATE.onLockNewCondition(c, l); } - public static void onObjectWait(Object o) { - DELEGATE.onObjectWait(o); + public static void onObjectWait(Object o, long timeout) { + DELEGATE.onObjectWait(o, timeout); } public static void onObjectWaitDone(Object o) {