-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JDK-8322237: Heap dump contains duplicate thread records for mounted virtual threads #17134
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix looks good. Thank you for the update.
Also, added a minor comment.
@@ -1918,7 +1931,9 @@ void HeapObjectDumper::do_object(oop o) { | |||
if (o->is_instance()) { | |||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/17134/head:pull/17134
$ git checkout pull/17134
Update a local copy of the PR:
$ git checkout pull/17134
$ git pull https://git.openjdk.org/jdk.git pull/17134/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 17134
View PR using the GUI difftool:
$ git pr show -t 17134
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/17134.diff
Webrev
Link to Webrev Comment