Hi Sergei,

> The fix looks pretty clean now.
> I also like new name of the lock.:)

Thank you!

> Just one comment below.
> 
> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.06/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c.frames.html
> 
> 110 if (tag != 0l) {
> 111 return; // Already added
>  112     }
> 
>  It is better to use a named constant or macro instead.
>  Also, it'd be nice to add a short comment about this value is.

As I replied to Chris earlier, this whole block can be turned into an
assert. I also made a constant for the value 0, which should be pretty
much self-explaining.

http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.07/

> How do you test the fix?

I am using a manual test that is provided in this bug report:
https://bugzilla.redhat.com/show_bug.cgi?id=1751985

"Script to compare performance of GC with and without debugger, when
many classes are loaded and classes are being unloaded":

https://bugzilla.redhat.com/attachment.cgi?id=1640688

I am also using this test and manually attach/detach jdb a couple of
times in a row to check that disconnecting and reconnecting works well
(this tended to deadlock or crash with an earlier version of the patch,
and is now looking good).

I am also running tier1 and tier2 tests locally, and as soon as we all
agree that the fix is reasonable, I will push it to the submit repo. I
am not sure if any of those tests actually exercise that code, though.
Let me know if you want me to run any specific tests.

Thank you,
Roman



> Thanks,
> Serguei
> 
> 
> On 3/20/20 08:30, Roman Kennke wrote:
>> I believe I came up with a much simpler solution that also solves the
>> problems of the existing one, and the ones I proposed earlier.
>>
>> It turns out that we can take advantage of the fact that we can use
>> *anything* as tags in JVMTI, even pointers to stuff (this is explicitely
>> mentioned in the JVMTI spec). This means we can simply stick a pointer
>> to the signature of a class into the tag, and pull it out again when we
>> get notified that the class gets unloaded.
>>
>> This means we don't need an extra data-structure to keep track of
>> classes and signatures, and it also makes the story around locking
>> *much* simpler. Performance-wise this is O(1), i.e. no scanning of all
>> classes needed (as in the current implementation) and no searching of
>> table needed (like in my previous attempts).
>>
>> Please review this new revision:
>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.06/
>>
>> (Notice that there still appears to be a performance bottleneck with
>> class-unloading when an actual debugger is attached. This doesn't seem
>> to be related to the classTrack.c implementation though, but looks like
>> a consequence of getting all those class-unload notifications over the
>> wire. My testcase generates 1000s of them, and it's clogging up the
>> buffers.)
>>
>> I am not sure why jdb needs to enable class-unload listener always. A
>> simple hack disables it, and performance is brilliant, even when jdb is
>> attached:
>> http://cr.openjdk.java.net/~rkennke/disable-jdk-class-unload.patch
>>
>> But this is not in the scope of this bug.)
>>
>> Roman
>>
>>
>> On 3/16/20 8:05 AM, serguei.spit...@oracle.com wrote:
>>> Sorry, forgot to complete my comments at the end (see below).
>>>
>>>
>>> On 3/15/20 23:57, serguei.spit...@oracle.com wrote:
>>>> Hi Roman,
>>>>
>>>> Thank you for the update and sorry for the latency in review.
>>>>
>>>> Some comments are below.
>>>>
>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.05/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c.frames.html
>>>>
>>>> 87 cbTrackingObjectFree(jvmtiEnv* jvmti_env, jlong tag)
>>>>   88 {
>>>> 89 debugMonitorEnter(deletedSignatureLock);
>>>> 90 if (currentClassTag == -1) {
>>>> 91 // Class tracking not initialized, nobody's interested
>>>> 92 debugMonitorExit(deletedSignatureLock);
>>>> 93 return;
>>>>   94     }
>>>> Just a question:
>>>>   Q1: Should the ObjectFree events be disabled for the jvmtiEnv that does
>>>>       the class tracking if class tracking has not been initialized?
>>>>
>>>> 70 static jlong currentClassTag; I'm thinking if the name is better to
>>>> be something like: lastClassTag or highestClassTag.
>>>>
>>>> 99 KlassNode* klass = *klass_ptr;
>>>> 100 102 while (klass != NULL && klass->klass_tag != tag) { 103
>>>> klass_ptr = &klass->next; 104 klass = *klass_ptr;
>>>> 105 } 106 if (klass != NULL || klass->klass_tag != tag) { // klass not
>>>> found - ignore.
>>>> 107 debugMonitorExit(deletedSignatureLock);
>>>> 108 return;
>>>>  109     }
>>>>  It seems to me, something is wrong in the condition at L106 above.
>>>>  Should it be? :
>>>>     if (klass == NULL || klass->klass_tag != tag)
>>>>
>>>>  Otherwise, how can the second check ever work correctly as the return
>>>> will always happen when (klass != NULL)?
>>>>
>>>>  
>>>> There are several places in this file with the the indent:
>>>> 90 if (currentClassTag == -1) {
>>>> 91 // Class tracking not initialized, nobody's interested
>>>> 92 debugMonitorExit(deletedSignatureLock);
>>>> 93 return;
>>>>   94     }
>>>>  ...
>>>> 152 if (currentClassTag == -1) {
>>>> 153 // Class tracking not initialized yet, nobody's interested
>>>> 154 debugMonitorExit(deletedSignatureLock);
>>>> 155 return;
>>>>  156     }
>>>>  ...
>>>> 161 if (error != JVMTI_ERROR_NONE) {
>>>> 162 EXIT_ERROR(error, "Unable to GetTag with class trackingEnv");
>>>>  163     }
>>>> 164 if (tag != 0l) {
>>>> 165 debugMonitorExit(deletedSignatureLock);
>>>> 166 return; // Already added
>>>>  167     }
>>>>  ...
>>>> 281 cleanDeleted(void *signatureVoid, void *arg)
>>>> 282 {
>>>> 283 char* sig = (char*)signatureVoid;
>>>> 284 jvmtiDeallocate(sig);
>>>> 285 return JNI_TRUE;
>>>>  286 }
>>>>  ...
>>>>  291 void
>>>>  292 classTrack_reset(void)
>>>>  293 {
>>>> 294 int idx;
>>>> 295 debugMonitorEnter(deletedSignatureLock);
>>>> 296
>>>> 297 for (idx = 0; idx < CT_SLOT_COUNT; ++idx) {
>>>> 298 KlassNode* node = table[idx];
>>>> 299 while (node != NULL) {
>>>> 300 KlassNode* next = node->next;
>>>> 301 jvmtiDeallocate(node->signature);
>>>> 302 jvmtiDeallocate(node);
>>>> 303 node = next;
>>>> 304 }
>>>> 305 }
>>>> 306 jvmtiDeallocate(table);
>>>> 307
>>>> 308 bagEnumerateOver(deletedSignatureBag, cleanDeleted, NULL);
>>>> 309 bagDestroyBag(deletedSignatureBag);
>>>> 310
>>>> 311 currentClassTag = -1;
>>>> 312
>>>> 313 (void)JVMTI_FUNC_PTR(trackingEnv,DisposeEnvironment)(trackingEnv);
>>>> 314 trackingEnv = NULL;
>>>> 315
>>>> 316 debugMonitorExit(deletedSignatureLock);
>>>>
>>>> Could you, please, fix several comments below?
>>>> 63 * The JVMTI tracking env to keep track of klass tags, for class-unloads
>>>>  The comma is not needed.
>>>>  Would it better to replace: klass tags => klass_tag's ?
>>>>
>>>>
>>>> 73 * Lock to keep table, currentClassTag and deletedSignatureBag
>>>> consistent
>>>>  Maybe: Lock to guard ... or lock to keep integrity of ...
>>>>
>>>> 84 * Callback when classes are freed, Finds the signature and
>>>> remembers it in deletedSignatureBag. Would be better to use words like
>>>> "store" or "record", "Find" should not start from capital letter:
>>>> Invoke the callback when classes are freed, find and record the
>>>> signature in deletedSignatureBag.
>>>>
>>>> 96 // Find deleted KlassNode 133 // Class tracking not initialized,
>>>> nobody's interested 153 // Class tracking not initialized yet,
>>>> nobody's interested 158 /* Check this is not a duplicate */ Missed dot
>>>> at the end. 106 if (klass != NULL || klass->klass_tag != tag) { //
>>>> klass not found - ignore. In opposite, dot is not needed as the
>>>> comment does not start from a capital letter. 111 // At this point we
>>>> have the KlassNode corresponding to the tag
>>>> 112 // in klass, and the pointer to it in klass_node.
>>>  The comment above can be better. Maybe, something like:
>>>    " At this point, we found the KlassNode matching the klass tag(and it is
>>> linked).
>>>
>>>> 113 // Remember the unloaded signature.
>>>  Better: Record the signature of the unloaded class and unlink it.
>>>
>>> Thanks,
>>> Serguei
>>>
>>>> Thanks,
>>>> Serguei 
>>>>
>>>> On 3/9/20 05:39, Roman Kennke wrote:
>>>>> Hello all,
>>>>>
>>>>> Can I please get reviews of this change? In the meantime, we've done
>>>>> more testing and also field-/torture-testing by a customer who is happy
>>>>> now. :-)
>>>>>
>>>>> Thanks,
>>>>> Roman
>>>>>
>>>>>
>>>>>> Hi Serguei,
>>>>>>
>>>>>> Thanks for reviewing!
>>>>>>
>>>>>> I updated the patch to reflect your suggestions, very good!
>>>>>> It also includes a fix to allow re-connecting an agent after disconnect,
>>>>>> namely move setup of the trackingEnv and deletedSignatureBag to
>>>>>> _activate() to ensure have those structures after re-connect.
>>>>>>
>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.05/
>>>>>>
>>>>>> Let me know what you think!
>>>>>> Roman
>>>>>>
>>>>>>> Hi Roman,
>>>>>>>
>>>>>>> Thank you for taking care about this scalability issue!
>>>>>>>
>>>>>>> I have a couple of quick comments.
>>>>>>>
>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.04/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c.frames.html
>>>>>>>
>>>>>>> 72 /*
>>>>>>> 73 * Lock to protect deletedSignatureBag
>>>>>>> 74 */
>>>>>>> 75 static jrawMonitorID deletedSignatureLock; 76 77 /*
>>>>>>> 78 * A bag containing all the deleted classes' signatures. Must be
>>>>>>> accessed under
>>>>>>> 79 * deletedTagLock,
>>>>>>>   80  */
>>>>>>> 81 struct bag* deletedSignatureBag;
>>>>>>>
>>>>>>>   The comments contradict to each other.
>>>>>>>   I guess, the lock name at line 79 has to be deletedSignatureLock
>>>>>>> instead of deletedTagLock.
>>>>>>>   Also, comma at the end must be replaced with dot.
>>>>>>>
>>>>>>>
>>>>>>> 101 // Tag not found? Ignore.
>>>>>>> 102 if (klass == NULL) {
>>>>>>> 103 debugMonitorExit(deletedSignatureLock);
>>>>>>> 104 return;
>>>>>>> 105 }
>>>>>>>  106 
>>>>>>> 107 // Scan linked-list.
>>>>>>> 108 jlong found_tag = klass->klass_tag;
>>>>>>> 109 while (klass != NULL && found_tag != tag) {
>>>>>>> 110 klass_ptr = &klass->next;
>>>>>>> 111 klass = *klass_ptr;
>>>>>>> 112 found_tag = klass->klass_tag;
>>>>>>>  113     }
>>>>>>> 114
>>>>>>> 115 // Tag not found? Ignore.
>>>>>>> 116 if (found_tag != tag) {
>>>>>>> 117 debugMonitorExit(deletedSignatureLock);
>>>>>>> 118 return;
>>>>>>>  119     }
>>>>>>>
>>>>>>>
>>>>>>>  The code above can be simplified, so that the lines 101-105 are not
>>>>>>> needed anymore.
>>>>>>>  It can be something like this:
>>>>>>>
>>>>>>> // Scan linked-list.
>>>>>>> while (klass != NULL && klass->klass_tag != tag) {
>>>>>>> klass_ptr = &klass->next;
>>>>>>> klass = *klass_ptr;
>>>>>>>      }
>>>>>>> if (klass == NULL || klass->klass_tag != tag) { // klass not found - 
>>>>>>> ignore.
>>>>>>> debugMonitorExit(deletedSignatureLock);
>>>>>>> return;
>>>>>>>      }
>>>>>>>
>>>>>>> It will take more time when I get a chance to look at the rest.
>>>>>>>
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Serguei
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On 12/21/19 13:24, Roman Kennke wrote:
>>>>>>>> Here comes an update that resolves some races that happen when
>>>>>>>> disconnecting an agent. In particular, we need to take the lock on
>>>>>>>> basically every operation, and also need to check whether or not
>>>>>>>> class-tracking is active and return an appropriate result (e.g. an 
>>>>>>>> empty
>>>>>>>> list) when we're not.
>>>>>>>>
>>>>>>>> Updated webrev:
>>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.04/
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>>> Roman
>>>>>>>>
>>>>>>>>
>>>>>>>>> So, here comes the O(1) implementation:
>>>>>>>>>
>>>>>>>>> - Whenever a class is 'prepared', it is registered with a tag, and we
>>>>>>>>> set-up a listener to get notified when it is unloaded.
>>>>>>>>> - Prepared classes are kept in a datastructure that is a table, which
>>>>>>>>> each entry being the head of a linked-list of KlassNode*. The table is
>>>>>>>>> indexed by tag % slot-count, and then simply prepend the new 
>>>>>>>>> KlassNode*.
>>>>>>>>> This is O(1) operation.
>>>>>>>>> - When we get notified of unloading a class, we look up the signature 
>>>>>>>>> of
>>>>>>>>> the reported tag in that table, and remember it in a bag. The 
>>>>>>>>> KlassNode*
>>>>>>>>> is then unlinked from the table and deallocated. This is ~O(1) 
>>>>>>>>> operation
>>>>>>>>> too, depending on the depth of the table. In my testcase which 
>>>>>>>>> hammered
>>>>>>>>> the code with class-loads and unloads, I usually see depths of like 
>>>>>>>>> 2-3,
>>>>>>>>> but not usually more. It should be ok.
>>>>>>>>> - when processUnloads() gets called, we simply hand out that bag, and
>>>>>>>>> allocate a new one.
>>>>>>>>> - I also added cleanup-code in classTrack_reset() to avoid leaking the
>>>>>>>>> signatures and KlassNode* etc when debug agent gets detached and/or
>>>>>>>>> re-attached (was missing before).
>>>>>>>>> - I also added locks around data-structure-manipulation (was missing
>>>>>>>>> before).
>>>>>>>>> - Also, I only activate this whole process when an actual listener 
>>>>>>>>> gets
>>>>>>>>> registered on EI_GC_FINISH. This seems to happen right when attaching 
>>>>>>>>> a
>>>>>>>>> jdb, not sure why jdb does that though. This may be something to 
>>>>>>>>> improve
>>>>>>>>> in the future?
>>>>>>>>>
>>>>>>>>> In my tests, the performance of class-tracking itself looks really 
>>>>>>>>> good.
>>>>>>>>> The bottleneck now is clearly actual synthesizing the class-unload
>>>>>>>>> events. I don't see how this can be helped when the debug agent asks 
>>>>>>>>> for it?
>>>>>>>>>
>>>>>>>>> Updated webrev:
>>>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.03/
>>>>>>>>>
>>>>>>>>> Please let me know what you think of it.
>>>>>>>>>
>>>>>>>>> Thanks,
>>>>>>>>> Roman
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> Alright, the perfectionist in me got me. I am implementing the even 
>>>>>>>>>> more
>>>>>>>>>> efficient ~O(1) class tracking. Please hold off reviewing for now.
>>>>>>>>>>
>>>>>>>>>> Thanks,Roman
>>>>>>>>>>
>>>>>>>>>>  Hi Chris,
>>>>>>>>>>>> I'll have a look at this, although it might not be for a few days. 
>>>>>>>>>>>> In
>>>>>>>>>>>> the meantime, maybe you can describe your new implementation in
>>>>>>>>>>>> classTrack.c so it's easier to look through the changes.
>>>>>>>>>>> Sure.
>>>>>>>>>>>
>>>>>>>>>>> The purpose of this class-tracking is to be able to determine the
>>>>>>>>>>> signatures of unloaded classes when GC/class-unloading happened, so 
>>>>>>>>>>> that
>>>>>>>>>>> we can generate the appropriate JDWP event.
>>>>>>>>>>>
>>>>>>>>>>> The current implementation does so by maintaining a table of 
>>>>>>>>>>> currently
>>>>>>>>>>> prepared classes by building that table when classTrack is 
>>>>>>>>>>> initialized,
>>>>>>>>>>> and then add new classes whenever a class gets loaded. When 
>>>>>>>>>>> unloading
>>>>>>>>>>> occurs, that cache is rebuilt into a new table, and compared with 
>>>>>>>>>>> the
>>>>>>>>>>> old table, and whatever is in the old, but not in the new table gets
>>>>>>>>>>> returned. The problem is that when GCs happen frequently and/or many
>>>>>>>>>>> classes get loaded+unloaded, this amounts to O(classCount*gcCount)
>>>>>>>>>>> complexity.
>>>>>>>>>>>
>>>>>>>>>>> The new implementation keeps a linked-list of prepared classes, and 
>>>>>>>>>>> also
>>>>>>>>>>> tracks unloads via the listener cbTrackingObjectFree(). Whenever an
>>>>>>>>>>> unload/GC occurs, the list of prepared classes is scanned, and 
>>>>>>>>>>> classes
>>>>>>>>>>> that are also in the deletedTagBag are unlinked (thus maintaining 
>>>>>>>>>>> the
>>>>>>>>>>> prepared-classes-list) and its signature put in the list that gets 
>>>>>>>>>>> returned.
>>>>>>>>>>>
>>>>>>>>>>> The implementation is not perfect. In order to determine whether or 
>>>>>>>>>>> not
>>>>>>>>>>> a class is unloaded, it needs to scan the deletedTagBag. That 
>>>>>>>>>>> process is
>>>>>>>>>>> therefore still O(unloadedClassCount). The assumption here is that
>>>>>>>>>>> unloadedClassCount << classCount. In my experiments this seems to be
>>>>>>>>>>> true, and also reasonable to expect.
>>>>>>>>>>>
>>>>>>>>>>> (I have some ideas how to improve the implementation to ~O(1) but it
>>>>>>>>>>> would be considerably more complex: have to maintain a (hash)table 
>>>>>>>>>>> that
>>>>>>>>>>> maps tags -> KlassNode*, unlink them directly upon unload, and 
>>>>>>>>>>> build the
>>>>>>>>>>> unloaded-signatures list there, but I don't currently see that it's
>>>>>>>>>>> worth the effort).
>>>>>>>>>>>
>>>>>>>>>>> In addition to all that, this process is only activated when 
>>>>>>>>>>> there's an
>>>>>>>>>>> actual listener registered for EI_GC_FINISH.
>>>>>>>>>>>
>>>>>>>>>>> Thanks,
>>>>>>>>>>> Roman
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> Chris
>>>>>>>>>>>>
>>>>>>>>>>>> On 12/18/19 5:05 AM, Roman Kennke wrote:
>>>>>>>>>>>>> Hello all,
>>>>>>>>>>>>>
>>>>>>>>>>>>> Issue:
>>>>>>>>>>>>> https://bugs.openjdk.java.net/browse/JDK-8227269
>>>>>>>>>>>>>
>>>>>>>>>>>>> I am proposing what amounts to a rewrite of classTrack.c. It 
>>>>>>>>>>>>> avoids
>>>>>>>>>>>>> throwing away the class cache on GC, and instead keeps track of
>>>>>>>>>>>>> loaded/unloaded classes one-by-one.
>>>>>>>>>>>>>
>>>>>>>>>>>>> In addition to that, it avoids this whole dance until an agent
>>>>>>>>>>>>> registers interest in EI_GC_FINISH.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Webrev:
>>>>>>>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.01/
>>>>>>>>>>>>>
>>>>>>>>>>>>> Testing: manual testing of provided test scenarios and timing.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Eg with the testcase provided here:
>>>>>>>>>>>>> https://bugzilla.redhat.com/show_bug.cgi?id=1751985
>>>>>>>>>>>>>
>>>>>>>>>>>>> I am getting those numbers:
>>>>>>>>>>>>> unpatched: no debug: 84s with debug: 225s
>>>>>>>>>>>>> patched:   no debug: 85s with debug: 95s
>>>>>>>>>>>>>
>>>>>>>>>>>>> I also tested successfully through jdk/submit repo
>>>>>>>>>>>>>
>>>>>>>>>>>>> Can I please get a review?
>>>>>>>>>>>>>
>>>>>>>>>>>>> Thanks,
>>>>>>>>>>>>> Roman
>>>>>>>>>>>>>
> 

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to