8295376: Improve debug agent virtual thread performance when no debugger is attached

Reviewed-by: sspitsyn, kevinw
This commit is contained in:
Chris Plummer 2022-11-08 01:09:26 +00:00
parent 76790ad242
commit 47d2c7b4cf
6 changed files with 117 additions and 4 deletions

View File

@ -1028,6 +1028,7 @@ parseOptions(char *options)
/* Set vthread debugging level. */
gdata->vthreadsSupported = JNI_TRUE;
gdata->includeVThreads = JNI_FALSE;
gdata->rememberVThreadsWhenDisconnected = JNI_FALSE;
/* Options being NULL will end up being an error. */
if (options == NULL) {
@ -1142,6 +1143,8 @@ parseOptions(char *options)
} else {
goto syntax_error;
}
// These two flags always set the same for now.
gdata->rememberVThreadsWhenDisconnected = gdata->includeVThreads;
current += strlen(current) + 1;
} else if (strcmp(buf, "launch") == 0) {
/*LINTED*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -97,6 +97,7 @@ debugLoop_run(void)
standardHandlers_onConnect();
threadControl_onConnect();
eventHandler_onConnect();
/* Okay, start reading cmds! */
while (shouldListen) {

View File

@ -132,6 +132,10 @@ static jrawMonitorID callbackBlock;
debugMonitorEnter(callbackBlock); \
debugMonitorExit(callbackBlock); \
} else { \
/* Notify anyone waiting for callbacks to exit */ \
if (active_callbacks == 0) { \
debugMonitorNotifyAll(callbackLock); \
} \
debugMonitorExit(callbackLock); \
} \
} \
@ -1509,8 +1513,11 @@ eventHandler_initialize(jbyte sessionID)
if (error != JVMTI_ERROR_NONE) {
EXIT_ERROR(error,"Can't enable garbage collection finish events");
}
/* Only enable vthread events if vthread support is enabled. */
if (gdata->vthreadsSupported) {
/*
* Only enable vthread START and END events if we want to remember
* vthreads when no debugger is connected.
*/
if (gdata->vthreadsSupported && gdata->rememberVThreadsWhenDisconnected) {
error = threadControl_setEventMode(JVMTI_ENABLE,
EI_VIRTUAL_THREAD_START, NULL);
if (error != JVMTI_ERROR_NONE) {
@ -1582,6 +1589,33 @@ eventHandler_initialize(jbyte sessionID)
eventHelper_initialize(sessionID);
}
void
eventHandler_onConnect() {
debugMonitorEnter(handlerLock);
/*
* Enable vthread START and END events if they are not already always enabled.
* They are always enabled if we are remembering vthreads when no debugger is
* connected. Otherwise they are only enabled when connected because they can
* be very noisy and hurt performance a lot.
*/
if (gdata->vthreadsSupported && !gdata->rememberVThreadsWhenDisconnected) {
jvmtiError error;
error = threadControl_setEventMode(JVMTI_ENABLE,
EI_VIRTUAL_THREAD_START, NULL);
if (error != JVMTI_ERROR_NONE) {
EXIT_ERROR(error,"Can't enable vthread start events");
}
error = threadControl_setEventMode(JVMTI_ENABLE,
EI_VIRTUAL_THREAD_END, NULL);
if (error != JVMTI_ERROR_NONE) {
EXIT_ERROR(error,"Can't enable vthread end events");
}
}
debugMonitorExit(handlerLock);
}
void
eventHandler_reset(jbyte sessionID)
{
@ -1596,6 +1630,24 @@ eventHandler_reset(jbyte sessionID)
*/
threadControl_detachInvokes();
/* Disable vthread START and END events unless we are remembering vthreads
* when no debugger is connected. We do this because these events can
* be very noisy and hurt performance a lot.
*/
if (gdata->vthreadsSupported && !gdata->rememberVThreadsWhenDisconnected) {
jvmtiError error;
error = threadControl_setEventMode(JVMTI_DISABLE,
EI_VIRTUAL_THREAD_START, NULL);
if (error != JVMTI_ERROR_NONE) {
EXIT_ERROR(error,"Can't disable vthread start events");
}
error = threadControl_setEventMode(JVMTI_DISABLE,
EI_VIRTUAL_THREAD_END, NULL);
if (error != JVMTI_ERROR_NONE) {
EXIT_ERROR(error,"Can't disable vthread end events");
}
}
/* Reset the event helper thread, purging all queued and
* in-process commands.
*/
@ -1612,6 +1664,23 @@ eventHandler_reset(jbyte sessionID)
debugMonitorExit(handlerLock);
}
void
eventHandler_waitForActiveCallbacks()
{
/*
* Wait for active callbacks to complete. It is ok if more callbacks come in
* after this point. This is being done so threadControl_reset() can safely
* remove all vthreads without worry that they might be referenced in an active
* callback. The only callbacks enabled at this point are the permanent ones,
* and they never involve vthreads.
*/
debugMonitorEnter(callbackLock);
while (active_callbacks > 0) {
debugMonitorWait(callbackLock);
}
debugMonitorExit(callbackLock);
}
void
eventHandler_lock(void)
{

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -71,7 +71,9 @@ void eventHandler_freeClassBreakpoints(jclass clazz);
/***** HandlerNode manipulation *****/
void eventHandler_initialize(jbyte sessionID);
void eventHandler_onConnect();
void eventHandler_reset(jbyte sessionID);
void eventHandler_waitForActiveCallbacks();
void eventHandler_lock(void);
void eventHandler_unlock(void);

View File

@ -510,6 +510,19 @@ removeResumed(JNIEnv *env, ThreadList *list)
}
}
static void
removeVThreads(JNIEnv *env)
{
ThreadList *list = &runningVThreads;
ThreadNode *node = list->first;
while (node != NULL) {
ThreadNode *temp = node->next;
removeNode(list, node);
clearThread(env, node);
node = temp;
}
}
static void
moveNode(ThreadList *source, ThreadList *dest, ThreadNode *node)
{
@ -2754,6 +2767,30 @@ threadControl_reset(void)
debugMonitorNotifyAll(threadLock);
debugMonitorExit(threadLock);
eventHandler_unlock();
/*
* Unless we are remembering all vthreads when the debugger is not connected,
* we free them all up here.
*/
if (!gdata->rememberVThreadsWhenDisconnected) {
/*
* First we need to wait for all active callbacks to complete. They were resumed
* above by the resetHelper. We can't remove the vthreads until after they complete,
* because the vthread ThreadNodes might be referenced as the callbacks unwind.
* We do this outside of any locking, because the callbacks may need to acquire locks
* in order to complete. It's ok if there are more callbacks after this point because
* the only callbacks enabled are the permanent ones, and they never involve vthreads.
*/
eventHandler_waitForActiveCallbacks();
/*
* Now that event callbacks have exited, we can reacquire the threadLock, which
* is needed before before calling removeVThreads().
*/
debugMonitorEnter(threadLock);
removeVThreads(env);
debugMonitorExit(threadLock);
}
}
jvmtiEventMode

View File

@ -86,6 +86,7 @@ typedef struct {
jboolean assertFatal;
jboolean vthreadsSupported; /* If true, debugging support for vthreads is enabled. */
jboolean includeVThreads; /* If true, VM.AllThreads includes vthreads. */
jboolean rememberVThreadsWhenDisconnected;
jboolean doerrorexit;
jboolean modifiedUtf8;
jboolean quiet;