[15] RFR: 8246381: VM crashes with "Current BasicObjectLock* below than low_mark"

David Holmes david.holmes at oracle.com
Thu Jul 16 01:07:33 UTC 2020


Hi Jamsheed,

tl;dr version: fix looks good. Thanks for working through things with me 
on this one.

Long version ... for the sake of other reviewers (and myself) I'm going 
to walk through the problem scenario and how the fix addresses it, 
because the bug report is long and confusing and touches on a number of 
different issues with async exception handling.

We are dealing with the code generated for Java method entry, and in 
particular for a synchronized Java method. We do a lot of things in the 
entry code before we actually lock the monitor and jump to the Java 
method. Some of those things include method profiling and the counter 
overflow check for the JIT. If an exception is thrown at this point, the 
logic to remove the activation would unlock the monitor - which we 
haven't actually locked yet! So we have the 
do_not_unlock_if_synchronized flag which is stored in the current 
JavaThread. We set that flag true so that if any exceptions result in 
activation removal, the removal logic won't try to unlock the monitor. 
Once we're ready to lock the monitor we set the flag back to false (note 
there is an implicit assumption here that monitor locking can never 
raise an exception).

The problem arises with async exceptions, or more specifically the async 
exception that is raised due to an "unsafe access error". This is where 
a memory-mapped ByteBuffer causes an access violation (SEGV) due to a 
bad pointer. The signal handler simply sets a flag to indicate we 
encountered an "unsafe access error", adjusts the BCI to the next 
instruction and allows execution to proceed at the next instruction. It 
is then expected that the runtime will "soon" notice this pending unsafe 
access error and create and throw the InternalError instance that 
indicates the ByteBuffer operation failed. This requires executing Java 
code.

One of the places that checks for that pending unsafe access error is in 
the destructor of the JRT_ENTRY wrapper that is used for the method 
profiling and counter overflow checking. This occurs whilst the 
do_not_unlock_if_synchronized flag is true, so the resulting 
InternalError won't result in an attempt to unlock the not-locked monitor.

The problem is that creating the InternalError executes Java code - it 
calls constructors, which call methods etc. And some of those methods 
are synchronized. So the method entry logic for such a call will set 
do_not_unlock_if_synchronized to true, perform all the preamble related 
to the call, then set do_not_unlock_if_synchronized to false, lock the 
monitor and make the call. When construction completes the InternalError 
is thrown and we remove the activation for the method we had originally 
started to call. But now the do_not_unlock_if_synchronized flag has been 
reset to false by the nested Java method call, so we do in fact try to 
unlock a monitor that was never locked, and things break.

This nesting problem is well known and we have a mechanism for dealing 
with - the UnlockFlagSaver. The actual logic executed for profiling 
methods and doing the counter overflow check contains the requisite 
UnlockFlagSaver to avoid the problem just outlined. Unfortunately the 
async exception is processed in the JRT_ENTRY wrapper, which is outside 
the scope of those UnlockFlagSaver helpers and so they don't help in 
this case.

So the fix is to "simply" move the UnlockFlagSaver deeper into the call 
stack to the code that actually does the async exception processing:

  void JavaThread::check_and_handle_async_exceptions(bool 
check_unsafe_error) {
+   // May be we are at method entry and requires to save do not unlock 
flag.
+   UnlockFlagSaver fs(this);

so now after the InternalError has been created and thrown we will 
restore the original value of the do_not_unlock_if_synchronized flag 
(false) and so the InternalError will not cause activation removal to 
attempt to unlock the not-locked monitor.

The scope of the UnlockFlagSaver could be narrowed to the actual logic 
for processing the unsafe access error, but it seems fine at method scope.

A second fix is that the overflow counter check had an assertion that it 
was not executed with any pending exceptions. But that turned out to be 
false for reasons I can't fully explain, but it again appears to relate 
to a pending async exception being installed prior to the method call - 
and seems related to the two referenced JVM TI functions. The simple 
solution here is to delete the assertion and to check for pending 
exceptions on entry to the code and just return immediately. The 
JRT_ENTRY destructor will see the pending exception and propagate it.

Cheers,
David

On 16/07/2020 9:50 am, David Holmes wrote:
> Hi Jamsheed,
> 
> On 16/07/2020 8:16 am, Jamsheed C M wrote:
>> (Thank you Dean, adding serviceability team as this issue involves 
>> JVMTI features PopFrame, EarlyReturn features)
> 
> It is not at all obvious how your proposed fix impacts the JVM TI features.
> 
>> JBS entry: https://bugs.openjdk.java.net/browse/JDK-8246381
>>
>> (testing: mach5, tier1-5 links in JBS)
>>
>> Best regards,
>>
>> Jamsheed
>>
>> On 15/07/2020 21:25, Jamsheed C M wrote:
>>>
>>> Hi,
>>>
>>> Async handling at method entry requires it to be aware of 
>>> synchronization(like whether it is doing async handling before lock 
>>> acquire or after)
>>>
>>> This is required as exception handler rely on this info for 
>>> unlocking.  Async handling code never had this special condition 
>>> handled and it worked most of the time as we were using biased 
>>> locking which got disabled by [1]
>>>
>>> There was one other issue reported in similar time[2]. This issue got 
>>> triggered in test case by [3], back to back extra safepoint after 
>>> suspend and TLH for ThreadDeath. So in this setup both PopFrame 
>>> request and Thread.Stop request happened together for the test 
>>> scenario and it reached java method entry with pending_exception set.
>>>
>>> I have done a partial fix for the issue, mainly to handle production 
>>> mode crash failures(do not unlock flag related ones)
>>>
>>> Fix detail:
>>>
>>> 1) I save restore the "do not unlock" flag in async handling.
> 
> Sorry but you completely changed the fix compared to what we discussed 
> and what I pre-reviewed! What happened to changing from JRT_ENTRY to 
> JRT_ENTRY_NOASYNC? It is going to take me a lot of time and effort to 
> determine that this save/restore of the "do not unlock flag" is actually 
> correct and valid!
> 
>>>
>>> 2) Return for floating pending exception for some cases(PopFrame, 
>>> Early return related). This is debug(JVMTI) feature and floating 
>>> exception can get cleaned just like that in present compiler request 
>>> and deopt code.
> 
> What part of the change addresses this?
> 
> Thanks,
> David
> -----
> 
>>>
>>> webrev :http://cr.openjdk.java.net/~jcm/8246381/webrev.02/
>>>
>>> There are more problems in these code areas, like we clear all 
>>> exceptions in compilation request path(interpreter,c1), as well as 
>>> deoptimization path.
>>>
>>> All these un-handled cases will be separately handled by 
>>> https://bugs.openjdk.java.net/browse/JDK-8249451
>>>
>>> Request for review.
>>>
>>> Best regards,
>>>
>>> Jamsheed
>>>
>>> [1]https://bugs.openjdk.java.net/browse/JDK-8231264 
>>> <https://bugs.openjdk.java.net/browse/JDK-8231264>
>>>
>>> [2] https://bugs.openjdk.java.net/browse/JDK-8246727
>>>
>>> [3] https://bugs.openjdk.java.net/browse/JDK-8221207
>>>


More information about the hotspot-runtime-dev mailing list