While a thread is suspended, some JDI objects can be created which contain info
about the suspended state of the thread. Currently, these objects are StackFrameImpl
and MonitorInfoImpl. When the thread is resumed, the info in these objects can
no longer be considered to be valid. Methods which access these objects must
throw InvalidStackFrameException if called when the thread is not suspended.
When these objects are created, the associated ThreadReference is passed to their
ctors. The ctors register themselves as ThreadListeners with the associated
ThreadReference object. When a ThreadReference.resume is performed, ThreadReferenceImpl
notifies its listeners that the thread is resuming. Each listener marks itself as invalid
and removes itself as a listener. Subsequent calls to an object marked as invalid
cause the InvalidStackFrameException to be thrown.
However, what happens when a VM-wide resume occurs, eg, VirtualMachine.resume?
In this case, ThreadReference.resume is not called so how do the ThreadReference
listeners get notified that their thread is resuming? Read on ...
The above talked about info about a particular suspended thread that is contained
in StackFrameImpl and MonitorInfoImpl objects and is only valid while the thread
is suspended. But other objects can hold info that is only valid while ALL threads
are suspended. Most (if not all) of this info is info that is cached in a JDI object
so it can be accessed without having to get it from JDWP for each access. An example
is that ThreadGroupReferenceImpl caches info about its child threads and threadgroups.
It can only do this caching while all threads are suspended, otherwise, a new
child could be added to the thread group by a running thread. Thus, ThreadGroupReferenceImpl
and other objects that do caching must be notified when VM-wide suspend occurs, and when
ANY thread is resumed.
To do this, each VirtualMachineImpl has an associated VMState object. Methods in this
object are called to 'freeze' and 'thaw' the target VM. While frozen, info can be cached.
When a thaw occurs, caches must be discarded and no new caching may be performed until
the next freeze.
Objects which are affected by the VMState register themselves as VMListeners with the VMState
object and are notified when freezes and thaws occur.
So, we have two types of listeners and two actions they listen for:
- ThreadListeners listen for an individual thread being resumed
- VMListeners listen for a VM-wide suspension, and ANY thread being resumed.
So we have the questions:
a) if a Thread.resume is done, how are VMListeners informed?
b) if a VirtualMachine.resume is done, how are ThreadListeners informed?
For a), VirtualMachineImpl registers itself as a ThreadListener for each ThreadReferenceImpl
that is created. When VirtualMachineImpl is notified that a Thread.resume has occured,
it calls VMState.thaw().
For b), when a ThreadReferenceImpl is created, it adds itself as a VMListener
to the VMState. Thus, when a thaw occurs, each ThreadReferenceImpl object is notified
and it in turn notifies each of its ThreadListeners.
However, this has a flaw. Suppose threads T1 and T2 are suspended. Now suppose T2
is resumed by ThreadReference.resume. The following will happen:
- ThreadReferenceImpl for T2 will notify each of its ThreadListeners that T2 is resuming
- Associated StackFrameImpls and MonitorImpls will invalidate themselves
- VMState.thaw will be called
- VMState will notify Each VMListener of the thaw and each listener will purge their caches
- One of these listeners is the ThreadReferenceImpl for T1.
It then notifies each of its listeners of a resume, EVEN THOUGH T1
IS NOT RESUMING.
(Note that this means that VMState.thaw will be called recursively,
but it is coded to handle that.)
Another flaw is that ThreadReferenceImpl itself caches info about the suspended state of
the thread, eg, framecount, frames, ... As it happens, MOST of this info is valid as long
as the thread is suspended, even if other threads are running. Thus, this cached info
doesn't have to be wiped out when some other thread resumes, but it currently is.
This doesn't cause a failure, it is just wasted time to reload the cache if another
method call comes into the suspended thread.
The one exception to this is the thread's name. This can be changed by another thread.
--- So, what to fix?
- In VirtualMachineImpl's ThreadListener method, threadResumable(ThreadAction), get
the thread from the ThreadAction and pass it to thaw. thaw will put it into the
VMState object that it creates and which is passed to the vmNotSuspended methods
in the listeners.
- In ThreadReferenceImpl's VMState vmNotSuspended listener, only notify the thread's listeners
if ALL threads are being resumed, based on the contents of the new thread field
- Create a new cache in ThreadReferenceImpl that is not wiped out when a different thread
is resumed. Move all the cached fields into that cache except for 'name' which continues
to reside in the ObjectReferenceImpl subcache, that IS wiped out when any thread resumes.
NOTE that ThreadReference.isSuspended() can be called on a thread that isn't suspended.
In this case, we can't cache the 'status' value because when the thread really is suspended,
this status value is no longer valid. And we don't reset the cache when a thread is suspended,
just when it is resumed.
- Associated bug fixed is an oversight in the original code that forgot to add MonitorInfoImpl
as a ThreadListener.