Thanks for the re-review, Dan!
Coleen

On 11/22/19 5:53 PM, Daniel D. Daugherty wrote:
http://cr.openjdk.java.net/~coleenp/2019/8173361.04.incr/webrev

src/hotspot/share/prims/jvmtiImpl.cpp
    No comments.

src/hotspot/share/prims/jvmtiImpl.hpp
    No comments.

src/hotspot/share/runtime/serviceThread.cpp
    No comments.

Thumbs up.

Dan


On 11/22/19 2:15 PM, [email protected] wrote:

Dan, Thank you for reviewing this!

On 11/22/19 12:49 PM, Daniel D. Daugherty wrote:
Hi Coleen,

Sorry for the delay in getting back to this re-review.


On 11/21/19 9:12 AM, [email protected] wrote:

Please review a new version of this change that keeps the nmethod from being unloaded, after it is added to the deferred event queue:

http://cr.openjdk.java.net/~coleenp/2019/8173361.03/webrev/index.html

src/hotspot/share/code/nmethod.cpp
    No comments.

src/hotspot/share/oops/instanceKlass.cpp
    No comments.

src/hotspot/share/prims/jvmtiExport.cpp
    No comments.

src/hotspot/share/prims/jvmtiImpl.cpp
    Nice solution with the new oops_do() and nmethods_do() functions!
Erik's insistance!

    old L988: void JvmtiDeferredEventQueue::enqueue(const JvmtiDeferredEvent& event) {     new L998: void JvmtiDeferredEventQueue::enqueue(JvmtiDeferredEvent event) {
        Not sure why this was changed.

        Update: Looks like Serguei raised the issue and Coleen has already
        resolved it.

Yes.

src/hotspot/share/prims/jvmtiImpl.hpp
    old L494:     QueueNode(const JvmtiDeferredEvent& event)
    new L498:     QueueNode(JvmtiDeferredEvent& event)
        Why was this changed?

        Update: Not clear if this was covered by Coleen's reply to Serguei.

    old L497:     const JvmtiDeferredEvent& event() const { return _event; }
    new L501:     JvmtiDeferredEvent& event() { return _event; }
        Why was this changed?

        Update: Coleen's reply to Serguei explained this. Perhaps add:
                  // Not const because of oops_do() and nmethods_do().

    old L509:   static void enqueue(const JvmtiDeferredEvent& event) NOT_JVMTI_RETURN;     new L513:   static void enqueue(JvmtiDeferredEvent event) NOT_JVMTI_RETURN;
        Why was this changed?

        Update: Looks like Serguei raised the issue and Coleen has already
        resolved it.

Yes, I fixed these.

src/hotspot/share/runtime/mutexLocker.cpp
    This change is going to require some testing to make sure we don't
    have any new deadlock scenarios.

Luckily, I've previously added an implicit NoSafepointVerifier to locks that are _allow_vm_block = true, like this one. + def(JmethodIdCreation_lock , PaddedMutex , leaf, true, _safepoint_check_never); // used for creating jmethodIDs. which prevents one class of deadlock. If we take out another lock with a higher rank, we'll get the ranking assert.

This lock prevents insertion into an array, and has little outside calls.

I'm running tests in tier 1-6 but any code that travels through this should get these assertion checks, rather than deadlocking.


src/hotspot/share/runtime/serviceThread.cpp
    L50 - nit - why the extra blank line?

To separate static data member definitions from functions.  I removed it.

src/hotspot/share/runtime/serviceThread.hpp
    Thanks for cleaning up the static:

      ServiceThread::is_service_thread(Thread* thread)

    stuff. Having it be different than the other threads was
    a bit jarring.

src/hotspot/share/runtime/thread.hpp
    No comments.

Thumbs up. My only comments are nits so I don't need to see a
new webrev if you decide to fix them.

So it turns out that in stress testing my fix forhttps://bugs.openjdk.java.net/browse/JDK-8212160

Because I was in the area and thought this was a duplicate of that bug (it is not).   I found that calling oops_do and nmethods_do the ServiceThread  needs to hold the Service_lock, because other threads can be adding things to the global queue while the sweeper thread is calling this in a handshake.

I am now retesting this change with the changes above, and with the Service_lock.   So far my stress tests for JDK-81212160 and the stress test for this bug pass, but I'm going to run through all the tiers 1-6 over the weekend.

Please have a look at the changes in the meantime.

http://cr.openjdk.java.net/~coleenp/2019/8173361.04.incr/webrev
http://cr.openjdk.java.net/~coleenp/2019/8173361.04/webrev

Thanks,
Coleen

Dan


Ran the test that failed 100 times without failure, tier1 on Oracle supported platforms, and tier2-3 including jvmti and jdi tests locally.

See bug for more details about the crash.

https://bugs.openjdk.java.net/browse/JDK-8173361

Thanks,
Coleen

On 11/18/19 10:09 PM, [email protected] wrote:

Hi Serguei,

Sorry for not sending an update.  I talked to Erik and am working on a version that keeps the nmethod from being unloaded while it's in the deferred event queue, with a version that the GC people will like, and I like.  I'm testing it out now.

Thanks!
Coleen


On 11/18/19 10:03 PM, [email protected] wrote:
Hi Coleen,

Sorry for the latency, I had to investigate it a little bit.
I still have some doubt your fix is right thing to do.


On 11/16/19 04:55, [email protected] wrote:


On 11/15/19 11:17 PM, [email protected] wrote:
Hi Coleen,

On 11/15/19 2:12 PM, [email protected] wrote:

Hi, I've been working on answers to these questions, so I'll start with this one.

The nmethodLocker keeps the nmethod from being reclaimed (made_zombie or memory released) by the sweeper, but the nmethod could be unloaded. Unloading the nmethod clears the Method* _method field.

Yes, I see it is done in the nmethod::make_unloaded().

The post_compiled_method_load event needs the _method field to look at things like inlining and ScopeDesc fields.   If the nmethod is unloaded, some of the oops are dead.  There are "holder" oops that correspond to the metadata in the nmethod.  If these oops are dead, causing the nmethod to get unloaded, then the metadata may not be valid.

So my change 02 looks for a NULL nmethod._method field to tell whether we can post information about the nmethod.

There's code in nmethod.cpp like:

jmethodID nmethod::get_and_cache_jmethod_id() {
  if (_jmethod_id == NULL) {
    // Cache the jmethod_id since it can no longer be looked up once the
    // method itself has been marked for unloading.
    _jmethod_id = method()->jmethod_id();
  }
  return _jmethod_id;
}

Which was added when post_method_load and unload were turned into deferred events.

Could we cache the jmethodID in the JvmtiDeferredEvent::compiled_method_load_event similarly as we do in the JvmtiDeferredEvent::compiled_method_unload_event? This would help to get rid of the dependency on the nmethod::_method.
Do we depend on any other nmethod fields?

Yes, there are other nmethod metadata that we rely on to print inline information, and this function JvmtiCodeBlobEvents::build_jvmti_addr_location_map because it uses the ScopeDesc data in the nmethod.

One possible approach is to prepare and cache all this information
in the nmethod::post_compiled_method_load_event() before the
JvmtiDeferredEvent::compiled_method_load_event() is called.
The event parameters are:
typedef struct {
     const void* start_address;
     jlocation location;
} jvmtiAddrLocationMap;
CompiledMethodLoad(jvmtiEnv *jvmti_env,
             jmethodID method,
             jint code_size,
             const void* code_addr,
             jint map_length,
             const jvmtiAddrLocationMap* map,
             const void* compile_info)
Some of these addresses above could be not accessible when an event is posted.
Not sure yet if it is Okay.
The question is if this kind of refactoring is worth and right thing to do.


We do cache the jmethodID but that's not good enough. See my last comment in the bug report.  The jmethodID can point to an unloaded method.

This looks like it is done a little bit late.
It'd better to do it before the event is deferred (see above).

I tried a version of keeping the nmethod alive, but the GC folks will hate it.  And it doesn't work and I hate it.

From serviceability point of view this is the best and most consistent approach.
I seems to me, it was initially designed this way.
The downside is it adds some extra complexity to the GC.

My version 01 is the best, with the caveat that maybe it should check for _method == NULL instead of nmethod->is_alive().  I have to talk to Erik to see if there's a race with concurrent class unloading.

Any application that depends on a compiled method loading event on a class that could be unloaded is a buggy application.  Applications should not rely on when the JIT compiler decides to compile a method! This happens to us for a stress test.  Most applications will get most of their compiled method loading events as they normally do.

It is not an application that relies on the compiled method loading event. It is about profiling tools to be able to get correct information about what is going on with compilations. My concern is that if we skip such compiled method load events then profilers have no way to find out there many unneeded compilations that are thrown away without any real use. Also, it is not clear what happens with the subsequent compiled method unload events. Are they going to be skipped as well or they can appear and confuse profilers?


Thanks,
Serguei

Thanks,
Coleen



Thanks,
Serguei

I put more debugging in the bug to show this crash was from an unloaded nmethod.

Coleen


On 11/15/19 4:45 PM, [email protected] wrote:
Hi Coleen,

I have some questions.

Both the compiler method load and unload are posted as deferred events. Both events keep the nmethod alive until the ServiceThread processes the event.

The implementation is:

JvmtiDeferredEvent JvmtiDeferredEvent::compiled_method_load_event(nmethod* nm) {
  . . .
  // Keep the nmethod alive until the ServiceThread can process
  // this deferred event.
  nmethodLocker::lock_nmethod(nm);
  return event;
}

JvmtiDeferredEvent JvmtiDeferredEvent::compiled_method_unload_event(nmethod* nm, jmethodID id, const void* code) {
  . . .
  // Keep the nmethod alive until the ServiceThread can process
  // this deferred event. This will keep the memory for the
  // generated code from being reused too early. We pass
  // zombie_ok == true here so that our nmethod that was just
  // made into a zombie can be locked.
  nmethodLocker::lock_nmethod(nm, true /* zombie_ok */);
  return event;
}

void JvmtiDeferredEvent::post() {
assert(ServiceThread::is_service_thread(Thread::current()),
         "Service thread must post enqueued events");
  switch(_type) {
    case TYPE_COMPILED_METHOD_LOAD: {
      nmethod* nm = _event_data.compiled_method_load;
JvmtiExport::post_compiled_method_load(nm);
      // done with the deferred event so unlock the nmethod
      nmethodLocker::unlock_nmethod(nm);
      break;
    }
    case TYPE_COMPILED_METHOD_UNLOAD: {
      nmethod* nm = _event_data.compiled_method_unload.nm;
      JvmtiExport::post_compiled_method_unload(
_event_data.compiled_method_unload.method_id,
_event_data.compiled_method_unload.code_begin);
      // done with the deferred event so unlock the nmethod
      nmethodLocker::unlock_nmethod(nm);
      break;
    }
    . . .
  }
}

Then I wonder how is it possible for the nmethod to be not alive here?:
2168 void JvmtiExport::post_compiled_method_load(nmethod *nm) {
. . .
2173 // It's not safe to look at metadata for unloaded methods.
2174 if (!nm->is_alive()) {
2175 return;
2176 }
At least, it lokks like something else is broken.
Do I miss something important here?

Thanks,
Serguei


On 11/14/19 5:15 PM, [email protected] wrote:
Summary: Don't post information which uses metadata from unloaded nmethods

Tested tier1-3 and 100 times with test that failed (reproduced failure without the fix).

open webrev at http://cr.openjdk.java.net/~coleenp/2019/8173361.01/webrev
bug link https://bugs.openjdk.java.net/browse/JDK-8173361

Thanks,
Coleen











Reply via email to