-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8255384: Remove special_runtime_exit_condition() check from SS::block() #913
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 pchilanomate! A progress list of the required criteria for merging this PR into |
Webrevs
|
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.
Hi Patricio,
Thanks for the detailed analysis on this. I agree with what your are doing in pricniple, but disagree with the means you've chosen to do it. Please see comments below.
Thanks,
David
if (self->is_obj_deopt_suspend()) { | ||
self->wait_for_object_deoptimization(); | ||
{ | ||
ThreadInVMfromJava tivm(self); |
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 doesn't look good to me. We are in low-level safepoint/handshake related code, but we call a higher-level abstraction for thread-state transition management, just to get the side-effect of processing the safepoint/handshake correctly. To me this suggests we're missing an appropriate API entry point to do what needs to be done. I would rather see something like:
SafepointMechanism::process_if_requested(self);
self->handle_special_runtime_exit_condition(true /* check asyncs */);
(assuming that is the correct action of course).
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.
Ok, initially I thought of doing that but then decided to use transition wrappers. I'll change it.
I think ideally we would like to use a transition wrapper in SafepointSynchronize::handle_polling_page_exception() but given the complexity of ThreadSafepointState::handle_polling_page_exception() we have to do things more manual unfortunately.
// code will notice the async and deoptimize and the exception will | ||
// be delivered. (Polling at a return point is ok though). Sure is | ||
// a lot of bother for a deprecated feature... | ||
ThreadInVMfromJavaNoAsyncException tivm(self); |
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.
Same comment as above.
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.
Ok, I'll change it.
/* zap freed handles rather than GC'ing them */ \ | ||
HandleMarkCleaner __hmc(THREAD); \ | ||
if (SafepointMechanism::should_process(THREAD)) { \ | ||
CALL_VM({ThreadInVMfromJava tiv(THREAD);}, handle_exception); \ |
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 initially thought this was an okay convenience technique to get the desired effects, but seeing it used elsewhere I no longer think that - see comments "above".
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 this case I thought using TIVFJ seemed better because when reading CALL_VM() one would expect a transition from _thread_in_Java to _thread_in_vm and then a call to the appropiate method (like with JRT_ENTRY). It's just that in this case the call that we want to make (SafepointMechanism::process_if_requested()) is already included in the TIVFJ. Anyways, doing it the other way is okay too.
Hi David, Please check the updated version. I added also a has_special_runtime_exit_condition() check before actually calling handle_special_runtime_exit_condition(). Patricio |
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 functionally fine, but it still seems to me that we are missing a Safepoint or SafepointMechanism API helper method that does:
SafepointMechanism::process_if_requested(THREAD);
if (THREAD->has_special_runtime_exit_condition()) {
THREAD->handle_special_runtime_exit_condition(checkAsyncs);
}
Lets see if others have an opinion.
Thanks.
if (self->is_obj_deopt_suspend()) { | ||
self->wait_for_object_deoptimization(); | ||
if (self->has_special_runtime_exit_condition()) { | ||
self->handle_special_runtime_exit_condition(); |
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.
Please add explicit arg: "(true /* check asyncs */)"
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.
Added.
if (SafepointMechanism::should_process(THREAD)) { \ | ||
CALL_VM(SafepointMechanism::process_if_requested(THREAD); \ | ||
if (THREAD->has_special_runtime_exit_condition()) { \ | ||
THREAD->handle_special_runtime_exit_condition(); \ |
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.
Please add explicit arg: "(true /* check asyncs */)"
Using a multi-statement code block for the CALL_VM macro looks odd to me, but I find these zero macros unpleasant to begin with.
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.
Yes, that's also why I liked better the TIVFJ wrapper. But if we can agree on the helper method then that would change to a single line.
How about SafepointMechanism::process_if_requested_with_exit_check(bool check_asyncs)? |
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.
Hi Patricio,
the change looks good to me.
In JavaThread::check_and_handle_async_exceptions()
the block depending on is_at_poll_safepoint() looks like dead code now. I wonder if ThreadSafepointState::_at_poll_safepoint
could even be DEBUG_ONLY?
Thanks, Richard.
Mailing list message from Patricio Chilano on hotspot-runtime-dev: Hi Richard, On 11/3/20 12:22 PM, Richard Reingruber wrote:
Thanks for looking at this.
Yes, I actually thought about doing that in the first version but then I Thanks, |
I'd think you can do it in another bug also. I'm ok either way actually. Thanks, Richard. |
Patricio |
Mailing list message from David Holmes on hotspot-runtime-dev: On 4/11/2020 3:02 am, Patricio Chilano Mateo wrote:
That works for me. Thanks, |
But _suspend_flag have nothing to do with safepoint polling, handling it with SafepointMechanism doesn't seem right? Thanks, Robbin
|
Hi Robbin, Thanks, for looking at this.
Patricio
|
Skimming a bit, as far as I can see void ThreadSafepointState::handle_polling_page_exception() should use ThreadInVMfromJava and in the other path ThreadInVMfromJavaNoAsyncException. |
Patricio |
Mailing list message from David Holmes on hotspot-runtime-dev: On 5/11/2020 12:26 am, Robbin Ehn wrote:
No :) I strongly object to using dummy thread-state transitions just to And when we have a recurring pattern like: SafepointMechanism::process_if_requested(THREAD); it says to me that we are missing an appropriate API abstraction to Whether there are things presently inside Cheers, |
@robehn are you okay with SafepointMechanism::process_if_requested_with_exit_check()? Patricio |
Yes, with a caveat; I may try to move it later.
Yes in this is what interfaceSupport.xxx gives us.
|
Ok, sounds good. I'll update. |
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.
Okay. It took me some time to be convinced of the equivalence,
but I'm on-board at this point. The new code is much more clear
and easy to reason about (once you convince yourself that the
new, simpler code is equivalent to the older code).
As @dholmes-ora likes to say: the proof is in the testing. I'll add
that the proof will be in the stress testing.
if (self->is_obj_deopt_suspend()) { | ||
self->wait_for_object_deoptimization(); | ||
} |
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's not clear to me why this if-block is deleted.
You change to call SafepointMechanism::process_if_requested_with_exit_check(),
but I don't see equivalent code there.
Update: I found it in JavaThread::handle_special_runtime_exit_condition().
if (self->is_obj_deopt_suspend()) { | ||
self->wait_for_object_deoptimization(); | ||
} |
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's not clear to me why this if-block is deleted.
You change to call SafepointMechanism::process_if_requested_with_exit_check(),
but I don't see equivalent code there.
Update: I found it in JavaThread::handle_special_runtime_exit_condition().
} | ||
// We never deliver an async exception at a polling point as the | ||
// compiler may not have an exception handler for it. The polling | ||
// code will notice the async and deoptimize and the exception will |
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.
Perhaps a little rewording:
code will notice the pending async exception, deoptimize and the exception will
and then reformat the block comment a bit.
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.
Fixed.
@@ -80,7 +80,7 @@ void SafepointMechanism::process(JavaThread *thread) { | |||
// Any load in ::block must not pass the global poll load. | |||
// Otherwise we might load an old safepoint counter (for example). | |||
OrderAccess::loadload(); | |||
SafepointSynchronize::block(thread); // Recursive |
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.
Wasn't that comment just added recently? :-)
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.
: ) Should have been there before that!
if (thread->is_obj_deopt_suspend()) { | ||
thread->wait_for_object_deoptimization(); | ||
} |
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's not clear to me why this if-block is deleted.
You change to call SafepointMechanism::process_if_requested_with_exit_check(),
but I don't see equivalent code there.
Update: I found it in JavaThread::handle_special_runtime_exit_condition().
thread->wait_for_object_deoptimization(); | ||
} | ||
|
||
JFR_ONLY(SUSPEND_THREAD_CONDITIONAL(thread);) |
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.
Where's the equivalent of this JFR code?
Update: I found it in JavaThread::handle_special_runtime_exit_condition().
if (thread->is_external_suspend()) { | ||
thread->java_suspend_self_with_safepoint_check(); |
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 found the equivalent of this part of the if-statement in
JavaThread::handle_special_runtime_exit_condition().
@pchilano 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 1 new commit pushed to the
Please see this link for an up-to-date comparison between the source branch of this pull request and the ➡️ To integrate this PR with the above commit message to the |
Thanks Dan! |
Mailing list message from David Holmes on hotspot-runtime-dev: On 6/11/2020 4:19 am, Robbin Ehn wrote:
I don't understand what you are referring to here with reference to { which transitions from Java to VM and back, for absolutely no reason David |
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.
Thanks for the updates! All LGTM.
David
Patricio |
As I see it, if you have an oop map and are executing in VM, you are technically in VM. If the thread is in Java it is not guaranteed that it will have an oop map and it's not certain that it can be made walkable. Polling page exception handling can thus been seen as a cheat avoiding setting in vm since it will actually have no effect on the safepoint synchronizer. So no we are not looking for side-effects, we need to be in VM (at minimum technically) to become blocked. { Would be the preferred way of doing it.
|
Mailing list message from David Holmes on hotspot-runtime-dev: Hi Robbin, On 6/11/2020 6:39 pm, Robbin Ehn wrote:
Sorry but I don't understand the point you are making here. The
But that is simply not true - we end up being blocked for
??? Why would you call SS::block explicitly there - the transition back David |
Hi David,
We have many such transition which only sets correct thread state.
Either you can look this as set of rules that we can follow.
The point being that we should be in VM when entering SS:block(). So you can either see this "special code". It is much easier to read/review the code as: When setting thread state without fence we may skip it, front edge of going Java->VM->Blocked can be optimized to:
Back edge can be optimized to:
This is what we do, but it is distributed several methods ~3-4 methods. So the reason for ending-up in SS:block with all kinds of state is that in some-cases the first part of the transition is in another methods e.g. the native wrapper. That case can be seen as native->vm->blocked->vm->java: Native->VM, oopmap, front edge polling/_suspend_flag, fence (safe>unsafe) The native wrapper does the front edge polling safe->unsafe + fence
When going back it looks the other case of blocked->vm->java, but native wrapper sets the final thread state of thread in Java.
You don't have to think of it like this, but it certainly helps me when writing/reviewing the code. Thanks, Robbin
|
/integrate |
@pchilano Since your change was applied there has been 1 commit pushed to the
Your commit was automatically rebased without conflicts. Pushed as commit 3675653. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Hi all,
Please review the following patch that removes the call to handle_special_runtime_exit_condition() from SS::block(). This avoids recursive calls when transitioning and stopping for safepoints and also makes the code simpler to read since it is not trivial to deduce why we need to execute the check for certain states but not others, i.e. what exact scenarios we are trying to guard against.
Method handle_special_runtime_exit_condition() checks for external suspends, object deoptimization, async exceptions and JFR suspends. All these need to be checked when transitioning to java and when transitioning out of native (except for async exceptions when transitioning to thread_in_vm). In SS::block() this check is executed for the _thread_new_trans, _thread_in_native_trans and thread_in_Java cases. For _thread_new_trans, we know the JT will have to go through JavaCallWrapper() the first time it transitions to Java and that already has a check for handle_special_runtime_exit_condition(). For _thread_in_native_trans, transitioning out of native already has checks for external suspends, object deoptimization and JFR suspends in check_safepoint_and_suspend_for_native_trans() which is called from ThreadStateTransition::transition_from_native()(called either directly or through the ThreadStateTransition wrappers) and check_special_condition_for_native_trans (for native wrappers case). So that leaves the thread_in_Java case.
Careful analysis shows the handle_special_runtime_exit_condition() call in SS::block() prevents JTs transitioning back to Java from escaping after being externally suspended. This can happen when calling SafepointMechanism::process_if_requested() while transitiong back to java without a later check for external suspend. Looking at the callers of SafepointMechanism::process_if_requested() we see that this can happen from handle_polling_page_exception(), java_suspend_self_with_safepoint_check() and check_safepoint_and_suspend_for_native_trans(). An example of this can be shown for the handle_polling_page_exception() case:
- JT hits a poll exception while executing nmethod.
- JT calls handle_polling_page_exception() ( which doesn't use ThreadStateTransition wrappers) and calls SafepointMechanism::process_if_requested()
- Stops for a safepoint due to a VM_ThreadSuspend request
- Upon unblocking from the safepoint, unless we have the check in SS::block() the JT will transition back to java without actually suspending
The "escape from suspend" scenarios for the other callers of SafepointMechanism::process_if_requested() are described in the comments of the bug as well as the proper fixes.
I have tested the patch several times in mach5 tiers1-7 and saw no issues. Let me know if you think I should run any other special tests.
Thanks,
Patricio
Progress
Testing
Issue
Reviewers
Download
$ git fetch https://git.openjdk.java.net/jdk pull/913/head:pull/913
$ git checkout pull/913