JDK-8322237: Heap dump contains duplicate thread records for mounted virtual threads#17134
JDK-8322237: Heap dump contains duplicate thread records for mounted virtual threads#17134alexmenkov wants to merge 5 commits intoopenjdk:masterfrom
Conversation
|
👋 Welcome back amenkov! A progress list of the required criteria for merging this PR into |
|
@alexmenkov The following labels will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
| static bool is_vthread_mounted(oop vt) { | ||
| return JvmtiEnvBase::get_JavaThread_or_null(vt) != nullptr; | ||
| } |
There was a problem hiding this comment.
It doesn't seem appropriate to couple this to the JVMTI code (can this code be present if JVMTI is not part of the build?). Doesn't the VT state give you a good enough approximation of whether it is mounted i.e. RUNNABLE?
There was a problem hiding this comment.
Good point. I'll remove dependency on JVMTI.
I don't think approximation would be good here (comparing state to RUNNABLE/PINNED/TIMED_PINNED or comparing carrierThread with null).
It's racy and we have a chance to not dump unmounted vthread or dump mounted vthread twice.
Maybe is_vthread_mounted should check if the virtual thread continuation has non-empty chunk.
There was a problem hiding this comment.
If that is racy then any solution is going to be racy. I assumed this was all happening at a global safepoint, otherwise threads could be mounting/unmounting at any time.
I said "approximation" only because I'm unsure exactly when the thread state gets updated in the mounting/unmounting process.
There was a problem hiding this comment.
I mean race between virtual thread state change and the thread stack switch (to/from carrier).
Correct condition here is "dump the virtual thread if it was not dumped as mounted virtual thread".
Most likely for RUNNABLE/PINNED/TIMED_PINNED we can be sure the thread is mounted, but we can get vthread in transition (PARKING, YIELDING, etc). Virtual threads may be in transition at safepoints (but they can't change mounted/unmounted state).
So I think is_vthread_mounted can be implemented in 2 ways:
- copy logic of JvmtiEnvBase::get_JavaThread_or_null:
get JavaThread for java_lang_VirtualThread::carrier_thread(vt);
if not null, check Continuation::is_continuation_mounted(java_thread, java_lang_VirtualThread::continuation(vt)) - this is to handle transition, when vthread is already unmounted, but carrierThread is not yet set to null; - check that java_lang_VirtualThread::continuation(vt) doesn't have non-empty chunk.
AFAIU this is true for mounted vthreads. If we get it for unmounted vt, its stack trace of the thread is empty anyway, so it's ok to skip it (most likely it can happen only if thread state is NEW or TERMINATED, we already skip such vthreads).
@AlanBateman could you please comment if my understanding is correct
There was a problem hiding this comment.
I mean race between virtual thread state change and the thread stack switch (to/from carrier).
I'm not sure if I understand you correctly or if we can call it a race. Alan will correct me if I'm wrong.
You are talking about thread state change. At least, mount state transition happens on the same JavaThread (it seems, you call it thread state switch). Mount state transition can go over a safepoint. But it should not progress while in a safepoint. David pointed out, "this was all happening at a global safepoint". My understanding is this assumption is correct. Then your approach to identify that a virtual thread is mounted or not should work in general. The condition java_lang_VirtualThread::carrier_thread(vt) != nullptr should indicate that the vt is mounted or is being in mount or unmount transition.
If the vt is in mount or unmount transition then (it is a gray zone) the way we identify mounted state should match the way we did it when dumped mounted virtual threads.
It is done this way: oop mounted_vt = thread->is_vthread_mounted() ? thread->vthread() : nullptr;
So, it seems any of yous suggestion should work here. Though, it would be nice to simplify it a little if possible. Again, to be consistent, a vt in mount state transition just have to be identified as mounted or unmounted in both fragments in a similar way .
There was a problem hiding this comment.
Looks like "race" is wrong word here. There is no race between different threads, we just cannot rely on vt state or carrierThread value when the thread in mount/unmount transition.
Sorry for the confusion.
Serguei, thank you for the analysis. I agree, the code for mounted and unmounted vthreads should be consistent.
For unmounted threads we have to get JavaThread of the carrier thread and if it's not null, check java_thread->is_vthread_mounted().
We don't need to check is_continuation_mounted as we are at safepoint.
dholmes-ora
left a comment
There was a problem hiding this comment.
This seems good to me know - thanks for the updates. One minor suggestion below.
|
@alexmenkov This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be: You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 71 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
sspitsyn
left a comment
There was a problem hiding this comment.
The fix looks good. Thank you for the update.
Also, added a minor comment.
| // create a HPROF_GC_INSTANCE record for each object | ||
| DumperSupport::dump_instance(writer(), o, &_class_cache); | ||
| if (java_lang_VirtualThread::is_instance(o) && ThreadDumper::should_dump_vthread(o)) { | ||
| // If we encounter an unmounted virtual thread it needs to be dumped explicitly. |
There was a problem hiding this comment.
Nit: It is nice to have a comment here.
I'm thinking if it'd make sens to shortly explain why mounted threads are not dumped here.
sspitsyn
left a comment
There was a problem hiding this comment.
Thank you for the update!
|
/integrate |
|
Going to push as commit dd8ae61.
Your commit was automatically rebased without conflicts. |
|
@alexmenkov Pushed as commit dd8ae61. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
|
/backport jdk22 |
|
@alexmenkov the backport was successfully created on the branch backport-alexmenkov-dd8ae616 in my personal fork of openjdk/jdk22. To create a pull request with this backport targeting openjdk/jdk22:master, just click the following link: The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:
If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk22: |
HeapDumper dumps virtual threads in 2 places:
java.lang.VirtualThread.In the 2nd case mounted virtual threads should be skipped (as they are already dumped with correct stack traces/stack references)
Check that a virtual thread is mounted is non-trivial, method from JvmtiEnvBase was used for this.
Testing: tier1..3, heapdump-related tests: open/test/hotspot/jtreg/serviceability,open/test/hotspot/jtreg/runtime/ErrorHandling,open/test/hotspot/jtreg/gc/epsilon,open/test/jdk/sun/tools/jhsdb
Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/17134/head:pull/17134$ git checkout pull/17134Update a local copy of the PR:
$ git checkout pull/17134$ git pull https://git.openjdk.org/jdk.git pull/17134/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 17134View PR using the GUI difftool:
$ git pr show -t 17134Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/17134.diff
Webrev
Link to Webrev Comment