Skip to content

Commit

Permalink
Improve the UI and implement timeline view of the intellij plugin (#135)
Browse files Browse the repository at this point in the history
* Visualize timeline view of thread schedules.

* Visualize timeline.

* update changelog.

* Reduce the saturation of thread colors.

* format.

* Rename thread states.

* fmt.
  • Loading branch information
aoli-al authored Feb 26, 2025
1 parent 8bf26c3 commit 806e548
Show file tree
Hide file tree
Showing 26 changed files with 771 additions and 265 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@

### Added

- Add a new timeline view in the Intellij Plugin.

### Changed

- Assign different colors to different threads in the Intellij Plugin.
- Rename the thread state to `Runnabled` and `Blocked`.

### Deprecated

### Removed
Expand Down
67 changes: 34 additions & 33 deletions core/src/main/kotlin/org/pastalab/fray/core/RunContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class RunContext(val config: Configuration) {
sw.append("Error: ${e}\n")
if (e is org.pastalab.fray.runtime.DeadlockException) {
for (registeredThread in registeredThreads.values) {
if (registeredThread.state == ThreadState.Paused) {
if (registeredThread.state == ThreadState.Blocked) {
sw.append("Thread: ${registeredThread.thread}\n")
sw.append("Stacktrace: \n")
for (stackTraceElement in registeredThread.thread.stackTrace) {
Expand Down Expand Up @@ -189,13 +189,13 @@ class RunContext(val config: Configuration) {
it.value != context
}) {
try {
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
scheduleNextOperation(true)
} catch (e: org.pastalab.fray.runtime.TargetTerminateException) {
// If deadlock detected let's try to unblock one thread and continue.
if (e is org.pastalab.fray.runtime.DeadlockException) {
for (thread in registeredThreads.values) {
if (thread.state == ThreadState.Paused) {
if (thread.state == ThreadState.Blocked) {
val pendingOperation = thread.pendingOperation
if (pendingOperation is Interruptible) {
pendingOperation.unblockThread(thread.thread.id, InterruptionType.FORCE)
Expand All @@ -219,7 +219,7 @@ class RunContext(val config: Configuration) {
currentThreadId = t.id
mainThreadId = t.id
registeredThreads[t.id] = ThreadContext(t, registeredThreads.size, this)
registeredThreads[t.id]?.state = ThreadState.Enabled
registeredThreads[t.id]?.state = ThreadState.Runnable
scheduleNextOperation(true)
}

Expand Down Expand Up @@ -265,7 +265,7 @@ class RunContext(val config: Configuration) {
val t = Thread.currentThread()
val context = registeredThreads[t.id]!!

context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
context.pendingOperation = ParkBlocking()
scheduleNextOperation(true)

Expand All @@ -282,10 +282,10 @@ class RunContext(val config: Configuration) {
val supriousWakeup = config.randomnessProvider.nextInt() % 2 == 0
if (supriousWakeup) {
context.pendingOperation = ThreadResumeOperation(true)
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
} else {
context.pendingOperation = ParkBlocked(timed, context)
context.state = ThreadState.Paused
context.state = ThreadState.Blocked
scheduleNextOperation(true)
}
} else if (context.unparkSignaled) {
Expand All @@ -295,10 +295,10 @@ class RunContext(val config: Configuration) {

fun threadUnpark(t: Thread) {
val context = registeredThreads[t.id]
if (context?.state == ThreadState.Paused && context?.pendingOperation is ParkBlocked) {
context.state = ThreadState.Enabled
if (context?.state == ThreadState.Blocked && context?.pendingOperation is ParkBlocked) {
context.state = ThreadState.Runnable
context.pendingOperation = ThreadResumeOperation(true)
} else if (context?.state == ThreadState.Enabled || context?.state == ThreadState.Running) {
} else if (context?.state == ThreadState.Runnable || context?.state == ThreadState.Running) {
context.unparkSignaled = true
}
}
Expand All @@ -308,7 +308,7 @@ class RunContext(val config: Configuration) {
fun threadRun() {
var t = Thread.currentThread()
registeredThreads[t.id]?.pendingOperation = ThreadStartOperation()
registeredThreads[t.id]?.state = ThreadState.Enabled
registeredThreads[t.id]?.state = ThreadState.Runnable
syncManager.signal(t)
registeredThreads[t.id]?.block()
}
Expand All @@ -322,7 +322,7 @@ class RunContext(val config: Configuration) {
state == Thread.State.TIMED_WAITING ||
state == Thread.State.BLOCKED) {
val context = registeredThreads[t.id]
if (context?.state == ThreadState.Running || context?.state == ThreadState.Enabled) {
if (context?.state == ThreadState.Running || context?.state == ThreadState.Runnable) {
return Thread.State.RUNNABLE
}
}
Expand All @@ -342,7 +342,7 @@ class RunContext(val config: Configuration) {
val lockContext = lockManager.getContext(t)
lockContext.wakingThreads.let {
for (thread in it) {
thread.value.state = ThreadState.Enabled
thread.value.state = ThreadState.Runnable
}
size = it.size
}
Expand All @@ -366,7 +366,7 @@ class RunContext(val config: Configuration) {
val lockContext = signalContext.lockContext
val context = registeredThreads[t]!!
context.pendingOperation = ObjectWaitOperation(objId)
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
scheduleNextOperation(true)

context.pendingOperation = ThreadResumeOperation(true)
Expand Down Expand Up @@ -543,7 +543,7 @@ class RunContext(val config: Configuration) {
val lockContext = lockManager.getContext(lock)

context.pendingOperation = LockLockOperation(lock)
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
scheduleNextOperation(true)

if (canInterrupt) {
Expand All @@ -570,7 +570,7 @@ class RunContext(val config: Configuration) {
// lock.unlock();
// }
while (!lockContext.lock(context, blockingWait, false, canInterrupt) && blockingWait) {
context.state = ThreadState.Paused
context.state = ThreadState.Blocked
context.pendingOperation = LockBlocking(timed, lockContext)
// We want to block current thread because we do
// not want to rely on ReentrantLock. This allows
Expand Down Expand Up @@ -668,14 +668,14 @@ class RunContext(val config: Configuration) {
val t = Thread.currentThread().id
val context = registeredThreads[t]!!
context.pendingOperation = LockLockOperation(lock)
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
scheduleNextOperation(true)

val stampedLockContext = stampedLockManager.getContext(lock)
val lockFun = if (isReadLock) stampedLockContext::readLock else stampedLockContext::writeLock

while (!lockFun(context, shouldBlock, canInterrupt) && shouldBlock) {
context.state = ThreadState.Paused
context.state = ThreadState.Blocked
context.pendingOperation = LockBlocking(timed, stampedLockContext)

scheduleNextOperation(true)
Expand Down Expand Up @@ -734,13 +734,13 @@ class RunContext(val config: Configuration) {
val t = Thread.currentThread().id
val context = registeredThreads[t]!!
context.pendingOperation = LockLockOperation(sem)
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
scheduleNextOperation(true)

while (!semaphoreManager.getContext(sem).acquire(permits, shouldBlock, canInterrupt, context) &&
shouldBlock) {
context.pendingOperation = LockBlocking(timed, semaphoreManager.getContext(sem))
context.state = ThreadState.Paused
context.state = ThreadState.Blocked

scheduleNextOperation(true)
if (canInterrupt) {
Expand Down Expand Up @@ -802,7 +802,7 @@ class RunContext(val config: Configuration) {
fun memoryOperation(obj: Int, type: org.pastalab.fray.runtime.MemoryOpType) {
val t = Thread.currentThread().id
registeredThreads[t]?.pendingOperation = MemoryOperation(obj, type)
registeredThreads[t]?.state = ThreadState.Enabled
registeredThreads[t]?.state = ThreadState.Runnable
scheduleNextOperation(true)
}

Expand All @@ -813,12 +813,12 @@ class RunContext(val config: Configuration) {
val latchContext = latchManager.getContext(latch)

context.pendingOperation = ObjectWaitOperation(objId)
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
scheduleNextOperation(true)

if (latchContext.await(true, context)) {
context.pendingOperation = CountDownLatchAwaitBlocking(timed, latchContext)
context.state = ThreadState.Paused
context.state = ThreadState.Blocked
checkDeadlock {
// We should not use [InterruptionType.FORCE] here because
// The thread is not blocked by the latch yet.
Expand Down Expand Up @@ -884,11 +884,11 @@ class RunContext(val config: Configuration) {
if (false) {
context.checkInterrupt()
context.pendingOperation = ThreadSleepBlocking(context)
context.state = ThreadState.Paused
context.state = ThreadState.Blocked
scheduleNextOperation(true)
} else {
context.pendingOperation = ThreadResumeOperation(true)
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
scheduleNextOperation(true)
}
}
Expand All @@ -897,14 +897,15 @@ class RunContext(val config: Configuration) {
val context = registeredThreads[Thread.currentThread().id]!!
while (!condition.satisfied()) {
context.pendingOperation = SyncurityWaitOperation(condition, context)
context.state = ThreadState.Paused
context.state = ThreadState.Blocked
scheduleNextOperation(true)
}
}

fun checkAndUnblockSyncurityOperations() {
for (thread in registeredThreads.values) {
if (thread.state == ThreadState.Paused && thread.pendingOperation is SyncurityWaitOperation) {
if (thread.state == ThreadState.Blocked &&
thread.pendingOperation is SyncurityWaitOperation) {
val condition = (thread.pendingOperation as SyncurityWaitOperation).condition
val currentRuntimeDelegate = Runtime.DELEGATE
val result =
Expand All @@ -919,7 +920,7 @@ class RunContext(val config: Configuration) {
}
if (result) {
thread.pendingOperation = ThreadResumeOperation(true)
thread.state = ThreadState.Enabled
thread.state = ThreadState.Runnable
}
}
}
Expand All @@ -930,7 +931,7 @@ class RunContext(val config: Configuration) {
scheduleNextOperation(shouldBlockCurrentThread)
} catch (e: DeadlockException) {
for (thread in registeredThreads.values) {
if (thread.state == ThreadState.Paused) {
if (thread.state == ThreadState.Blocked) {
val pendingOperation = thread.pendingOperation
if (pendingOperation is Interruptible) {
pendingOperation.unblockThread(thread.thread.id, InterruptionType.FORCE)
Expand Down Expand Up @@ -959,7 +960,7 @@ class RunContext(val config: Configuration) {
}

fun yield() {
registeredThreads[Thread.currentThread().id]!!.state = ThreadState.Enabled
registeredThreads[Thread.currentThread().id]!!.state = ThreadState.Runnable
scheduleNextOperation(true)
}

Expand All @@ -979,7 +980,7 @@ class RunContext(val config: Configuration) {
verifyOrReport(
Thread.currentThread() is HelperThread ||
currentThreadId == Thread.currentThread().id ||
currentThread.state == ThreadState.Enabled ||
currentThread.state == ThreadState.Runnable ||
currentThread.state == ThreadState.Completed)
verifyOrReport(registeredThreads.none { it.value.state == ThreadState.Running })

Expand All @@ -996,7 +997,7 @@ class RunContext(val config: Configuration) {
var enabledOperations =
registeredThreads.values
.toList()
.filter { it.state == ThreadState.Enabled }
.filter { it.state == ThreadState.Runnable }
.sortedBy { it.thread.id }
if (mainExiting && (currentThreadId == mainThreadId || enabledOperations.size > 1)) {
enabledOperations = enabledOperations.filter { it.thread.id != mainThreadId }
Expand All @@ -1007,7 +1008,7 @@ class RunContext(val config: Configuration) {
enabledOperations =
registeredThreads.values
.toList()
.filter { it.state == ThreadState.Enabled }
.filter { it.state == ThreadState.Runnable }
.sortedBy { it.thread.id }
if (mainExiting && (currentThreadId == mainThreadId || enabledOperations.size > 1)) {
enabledOperations = enabledOperations.filter { it.thread.id != mainThreadId }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ThreadContext(val thread: Thread, val index: Int, context: RunContext) {
sync.block()
}

fun schedulable() = state == ThreadState.Enabled || state == ThreadState.Running
fun schedulable() = state == ThreadState.Runnable || state == ThreadState.Running

fun unblock() {
sync.unblock()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ParkBlocked(timed: Boolean, val threadContext: ThreadContext) :
override fun unblockThread(tid: Long, type: InterruptionType): Any? {
verifyOrReport(tid == threadContext.thread.id) { "Thread id mismatch" }
threadContext.pendingOperation = ThreadResumeOperation(type != InterruptionType.TIMEOUT)
threadContext.state = ThreadState.Enabled
threadContext.state = ThreadState.Runnable
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class SyncurityWaitOperation(val condition: SyncurityCondition, val threadContex
NonRacingOperation(), Interruptible {
override fun unblockThread(tid: Long, type: InterruptionType): Any? {
threadContext.pendingOperation = ThreadResumeOperation(true)
threadContext.state = ThreadState.Enabled
threadContext.state = ThreadState.Runnable
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.pastalab.fray.rmi.ThreadState
class ThreadSleepBlocking(val context: ThreadContext) : TimedBlockingOperation(true) {
override fun unblockThread(tid: Long, type: InterruptionType): Any? {
context.pendingOperation = ThreadResumeOperation(type != InterruptionType.TIMEOUT)
context.state = ThreadState.Enabled
context.state = ThreadState.Runnable
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ConditionSignalContext(lockContext: LockContext, lock: Lock, val condition
canInterrupt: Boolean
) {
threadContext.pendingOperation = ConditionAwaitBlocked(this, canInterrupt, timed)
threadContext.state = ThreadState.Paused
threadContext.state = ThreadState.Blocked
}

override fun getSyncObject(): Any {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class CountDownLatchContext(latch: CountDownLatch, val syncManager: Synchronizat
val pendingOperation = lockWaiter.thread.pendingOperation
verifyOrReport(pendingOperation is CountDownLatchAwaitBlocking)
lockWaiter.thread.pendingOperation = ThreadResumeOperation(type != InterruptionType.TIMEOUT)
lockWaiter.thread.state = ThreadState.Enabled
lockWaiter.thread.state = ThreadState.Runnable
latchWaiters.remove(tid)
return if ((pendingOperation as CountDownLatchAwaitBlocking).timed) {
false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ObjectNotifyContext(lockContext: LockContext, obj: Any) : SignalContext(lo
canInterrupt: Boolean
) {
threadContext.pendingOperation = ObjectWaitBlock(this, timedOperation)
threadContext.state = ThreadState.Paused
threadContext.state = ThreadState.Blocked
}

override fun getSyncObject(): Any {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class ReadLockContext : LockContext {
fun unlockWaiters() {
for (readLockWaiter in lockWaiters.values) {
readLockWaiter.thread.pendingOperation = ThreadResumeOperation(true)
readLockWaiter.thread.state = ThreadState.Enabled
readLockWaiter.thread.state = ThreadState.Runnable
}
}

Expand Down Expand Up @@ -108,7 +108,7 @@ class ReadLockContext : LockContext {
(type == InterruptionType.FORCE) ||
(type == InterruptionType.TIMEOUT)) {
lockWaiter.thread.pendingOperation = ThreadResumeOperation(noTimeout)
lockWaiter.thread.state = ThreadState.Enabled
lockWaiter.thread.state = ThreadState.Runnable
lockWaiters.remove(tid)
}
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ReentrantLockContext : LockContext {
wakingThreads.remove(tid)

for (thread in wakingThreads.values) {
thread.state = ThreadState.Paused
thread.state = ThreadState.Blocked
}
return true
} else {
Expand Down Expand Up @@ -75,13 +75,13 @@ class ReentrantLockContext : LockContext {
lockHolder = null
for (thread in wakingThreads.values) {
if (thread.state != ThreadState.Completed) {
thread.state = ThreadState.Enabled
thread.state = ThreadState.Runnable
}
}
for (lockWaiter in lockWaiters.values) {
if (lockWaiter.thread.state != ThreadState.Completed) {
lockWaiter.thread.pendingOperation = ThreadResumeOperation(true)
lockWaiter.thread.state = ThreadState.Enabled
lockWaiter.thread.state = ThreadState.Runnable
}
}
lockWaiters.clear()
Expand All @@ -100,7 +100,7 @@ class ReentrantLockContext : LockContext {
(type == InterruptionType.FORCE) ||
(type == InterruptionType.TIMEOUT)) {
lockWaiter.thread.pendingOperation = ThreadResumeOperation(type != InterruptionType.TIMEOUT)
lockWaiter.thread.state = ThreadState.Enabled
lockWaiter.thread.state = ThreadState.Runnable
lockWaiters.remove(tid)
}
return false
Expand Down
Loading

0 comments on commit 806e548

Please sign in to comment.