-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8277444: Data race between JvmtiClassFileReconstituter::copy_bytecodes and class linking #26863
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
Conversation
|
👋 Welcome back eastigeevich! A progress list of the required criteria for merging this PR into |
|
@eastig 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 136 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 |
|
Hi @coleenp, |
Webrevs
|
|
@eastig I am not sure about this one. Can you clarify please how you can be transforming a class that has not yet been linked? If this is possible then it seems to me we are missing a call to ensure linkage. |
Hi @dholmes-ora, I checked what was happening. The reproducer from JDK-8277444 simplified:
So we can have the linking process and the retransforming process running in parallel. There is no synchronization between them. We get a race condition. I think I hope I've got everything correctly from logs and sources. |
|
@eastig Thank you very much for that detailed analysis. An interesting scenario. I still find it somewhat suspect that we can transform an unlinked class and wonder if we should instead ensure it is linked first, rather than trying to coordinate the two pieces of code via the use of the init_lock. ? |
|
According to
they allow flexibility in an implementation of the linking process. If I am correct, we have a "lazy" linkage strategy in Hotspot. I don't think we want to change anything here.
As we don't keep the initial bytecodes (do we?), If we use What other options do we have which don't require many changes? We have two mutually exclusive processes. |
|
This seems like the correct fix. Forcing these APIs to link the class first would be a change in behavior and I assume it could cause linking-related exceptions to be thrown that the client might not expect. |
@dean-long |
Just to be clear The retransformation API's are somewhat vague about the exact state of a class to be retransformed - the class must be "modifiable" but that seems to be treated as a static rather than dynamic property ie. primitives, arrays, hidden classes are not modifiable - any other type of class instance is. Bottom line is that I don't think any of the specifications help us here, so we need to look at implementation practicalities. The linking code, uses the I don't like JVM TI having to know anything about the I'd like to hear from others more knowledgeable than myself in this area, unfortunately @coleenp is on vacation and won't be back till next week. |
| // Method bytecodes can be rewritten during linking. | ||
| // Whilst the linking process rewriting bytescodes, | ||
| // is_rewritten() returns false. So we won't restore the original bytecodes. | ||
| // We hold a lock to guarantee we are not getting bytecodes | ||
| // at the same time the linking process are rewriting them. |
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.
| // Method bytecodes can be rewritten during linking. | |
| // Whilst the linking process rewriting bytescodes, | |
| // is_rewritten() returns false. So we won't restore the original bytecodes. | |
| // We hold a lock to guarantee we are not getting bytecodes | |
| // at the same time the linking process are rewriting them. | |
| // We acquire the init_lock monitor to serialize with class linking so we are not getting | |
| // bytecodes at the same time the linking process is rewriting them. |
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.
Done
dholmes-ora
left a comment
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've heard from Coleen and she seems okay with this approach; and Patricio doesn't think it will have an impact on the virtual thread work - so that eases my concerns. I have some minor requested changes whilst we wait for a serviceability review.
Thanks
| Handle h_init_lock(Thread::current(), mh->method_holder()->init_lock()); | ||
| ObjectLocker ol(h_init_lock, JavaThread::current()); |
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.
| Handle h_init_lock(Thread::current(), mh->method_holder()->init_lock()); | |
| ObjectLocker ol(h_init_lock, JavaThread::current()); | |
| JavaThread* current = JavaThread::current(); | |
| Handle h_init_lock(current, mh->method_holder()->init_lock()); | |
| ObjectLocker ol(h_init_lock, current); |
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.
Done
I see that in java.lang.instrument, but the JVMTI spec says "In response to this call, no events other than the ClassFileLoadHook event will be sent." Normally during linking we send the ClassPrepare event. |
To have a Class object it must have already undergone the "preparation" part of linking (it is done at the end of loading when we create the class mirror). |
|
@dholmes-ora Thank you for review and the suggestions. |
|
(I realize this is a tangent, but maybe there is a separate bug here...)
If that's what "preparation" means, and Class.forName(name, false, loader) does not link the class, then posting the event here in InstanceKlass::link_class_impl() instead of earlier seems misplaced: jdk/src/hotspot/share/oops/instanceKlass.cpp Line 1064 in c755345
Class.forName(name, false, loader) would result in a prepared class but without the event being sent. |
alexmenkov
left a comment
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 from serviceability perspective
@dean-long it seems the JVM TI "prepare" event is somewhat misnamed. It states "At this point, class fields, methods, and implemented interfaces are available, and no code from the class has been executed." but that neither describes preparation, nor the rest of linking. You can only access fields and methods after a class has been initialized! But lets take this elsewhere if needed. |
|
MacOS test failures are known: |
|
Hi @dholmes-ora, |
The virtual thread test that is failing in this PR's GitHub actions job is It looks like it's taking long to complete and that's causing the test timeout. I recollect that this test failure was addressed some time back, so this appears to be a new occurrence. In any case, this failure doesn't look related to the changes in this PR because I see some other PRs having failed with this same issue. I'll check and file an issue later today/tomorrow (unless anyone else gets to it first). |
|
Does it fail with the patch? Sorry for the delay. @dholmes-ora and I have been discussing how all this works offline, but with time-zone differences, we won't have any agreement until next week. I wrote a more limited version of the patch I sent and am testing it now. |
|
Okay I ran the test case in the issue and I see why it wouldn't be reliable. I verified it with the new more minimalistic patch here: #26971 |
Thank you for the minimalistic patch. I now have a version of the jtreg test which fails more reliably. BTW I have updated the title of JDK-8277444. |
|
I'll add the test to the PR soon. |
|
I have pulled your changes. I don't use the macros because they rely on |
I've filed https://bugs.openjdk.org/browse/JDK-8366669 to track this failure. |
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 had a couple of minor comments but otherwise looks good. Is the test now reliable? Thank you for adding a test.
| #include "prims/jvmtiClassFileReconstituter.hpp" | ||
| #include "runtime/handles.inline.hpp" | ||
| #include "runtime/signature.hpp" | ||
| #include "runtime/synchronizer.hpp" |
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.
You don't need this include anymore.
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.
Removed.
| if (current_thread->has_pending_exception()) { | ||
| current_thread->clear_pending_exception(); | ||
| return JVMTI_ERROR_INVALID_CLASS; | ||
| } |
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.
Can you use the pattern:
JavaThread* THREAD = current_thread;
... link_class(THREAD);
if (HAS_PENDING_EXCEPTION)
etc.
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.
Switched to macros.
coleenp
left a comment
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 looks good now. Thank you for your patience while we found the right solution.
dholmes-ora
left a comment
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.
Generally looks good. There are a few minor nits/typos.
I'm not sure about the test, in particular the attempt to calculate MIN_LINK_TIME_MS. It is very hard to see/know that you will actually induce the desired race. But as the test doesn't actually have any explicit failure conditions, it at least won't generate false reports.
Thanks
src/hotspot/share/prims/jvmtiEnv.cpp
Outdated
| InstanceKlass* ik = InstanceKlass::cast(klass); | ||
| if (ik->get_cached_class_file_bytes() == nullptr) { | ||
| // Link the class to avoid races with the rewriter. This will call the verifier also | ||
| // on the class. Linking is done already below in VM_RedefineClasses below, but we need |
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.
| // on the class. Linking is done already below in VM_RedefineClasses below, but we need | |
| // on the class. Linking is also done in VM_RedefineClasses below, but we need |
There are two "below"s
| * This test puts the linking process in one thread and the retransforming process | ||
| * in another thread. The test uses Class.forName("BigClass", false, classLoader) | ||
| * which does not link the class. When the class is used, the linking process starts. | ||
| * In another thread retransforming of the class is happening, |
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.
| * In another thread retransforming of the class is happening, | |
| * In another thread retransforming of the class is happening. |
| * in another thread. The test uses Class.forName("BigClass", false, classLoader) | ||
| * which does not link the class. When the class is used, the linking process starts. | ||
| * In another thread retransforming of the class is happening, | ||
| * We generate a class with big methods. A number of methods and thier size are |
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.
| * We generate a class with big methods. A number of methods and thier size are | |
| * We generate a class with big methods. A number of methods and their size are |
| * which does not link the class. When the class is used, the linking process starts. | ||
| * In another thread retransforming of the class is happening, | ||
| * We generate a class with big methods. A number of methods and thier size are | ||
| * chosen to make the linking and retransforming processes running concurrently. |
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.
| * chosen to make the linking and retransforming processes running concurrently. | |
| * chosen to make the linking and retransforming processes run concurrently. |
|
|
||
| private static final Object LOCK = new Object(); | ||
| private static final int COUNTER_INC_COUNT = 2000; // A number of 'c+=1;' statements in methods of a class. | ||
| private static final int MIN_LINK_TIME_MS = 60; // This time is chosen to be big enough the linking and retransforming processes are running in parallel. |
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.
| private static final int MIN_LINK_TIME_MS = 60; // This time is chosen to be big enough the linking and retransforming processes are running in parallel. | |
| private static final int MIN_LINK_TIME_MS = 60; // Large enough so linking and retransforming run in parallel. |
| return InMemoryJavaCompiler.compile(className, classSrc); | ||
| } | ||
|
|
||
| // We calculate a number of methods the linking time to exceed MIN_LINK_TIME_MS. |
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 can't quite parse this sentence.
|
@dholmes-ora, Thank you for review.
Making a reliable regression test was a problem. |
|
If the test becomes problematic because of timing, we could have another change to make it /manual. |
dholmes-ora
left a comment
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.
Nothing further from me. If there are issues with the test we will address them as needed.
Thanks
|
Thank you, everyone, for reviewing. |
|
/integrate |
|
Going to push as commit 46ae1ee.
Your commit was automatically rebased without conflicts. |
There is a race between
JvmtiClassFileReconstituter::copy_bytecodesandInstanceKlass::link_class_impl.InstanceKlass::link_class_implcan be rewriting bytecodes.JvmtiClassFileReconstituter::copy_bytecodeswill not restore them to the original ones because the flagrewrittenisfalse. This will result in invalid bytecode.This PR adds linking a class before the
copy_bytecodesmethod is called.The PR also adds a regression test.
Tested fastdebug and release builds: Linux x86_64 and arm64
Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/26863/head:pull/26863$ git checkout pull/26863Update a local copy of the PR:
$ git checkout pull/26863$ git pull https://git.openjdk.org/jdk.git pull/26863/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 26863View PR using the GUI difftool:
$ git pr show -t 26863Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/26863.diff
Using Webrev
Link to Webrev Comment