< prev index next >

src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c

Print this page

        

*** 1,7 **** /* ! * Copyright (c) 1998, 2007, 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this --- 1,7 ---- /* ! * Copyright (c) 1998, 2019, 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this
*** 37,46 **** --- 37,48 ---- /* * Collection of info for properly handling co-located events. * If the ei field is non-zero, then one of the possible * co-located events has been posted and the other fields describe * the event's location. + * + * See comment above deferEventReport() for an explanation of co-located events. */ typedef struct CoLocatedEventInfo_ { EventIndex ei; jclass clazz; jmethodID method;
*** 61,95 **** * from the debugger. suspends from the app itself are * not included in this count. */ typedef struct ThreadNode { jthread thread; ! unsigned int toBeResumed : 1; ! unsigned int pendingInterrupt : 1; ! unsigned int isDebugThread : 1; ! unsigned int suspendOnStart : 1; ! unsigned int isStarted : 1; unsigned int popFrameEvent : 1; unsigned int popFrameProceed : 1; unsigned int popFrameThread : 1; ! EventIndex current_ei; ! jobject pendingStop; jint suspendCount; jint resumeFrameDepth; /* !=0 => This thread is in a call to Thread.resume() */ jvmtiEventMode instructionStepMode; StepRequest currentStep; InvokeRequest currentInvoke; ! struct bag *eventBag; ! CoLocatedEventInfo cleInfo; struct ThreadNode *next; struct ThreadNode *prev; ! jlong frameGeneration; ! struct ThreadList *list; /* Tells us what list this thread is in */ } ThreadNode; static jint suspendAllCount; typedef struct ThreadList { ThreadNode *first; } ThreadList; /* --- 63,109 ---- * from the debugger. suspends from the app itself are * not included in this count. */ typedef struct ThreadNode { jthread thread; ! unsigned int toBeResumed : 1; /* true if this thread was successfully suspended. */ ! unsigned int pendingInterrupt : 1; /* true if thread is interrupted while handling an event. */ ! unsigned int isDebugThread : 1; /* true if this is one of our debug agent threads. */ ! unsigned int suspendOnStart : 1; /* true for new threads if we are currently in a VM.suspend(). */ ! unsigned int isStarted : 1; /* THREAD_START or FIBER_SCHEDULED event received. */ ! unsigned int is_fiber : 1; unsigned int popFrameEvent : 1; unsigned int popFrameProceed : 1; unsigned int popFrameThread : 1; ! EventIndex current_ei; /* Used to determine if we are currently handling an event on this thread. */ ! jobject pendingStop; /* Object we are throwing to stop the thread (ThreadReferenceImpl.stop). */ jint suspendCount; jint resumeFrameDepth; /* !=0 => This thread is in a call to Thread.resume() */ jvmtiEventMode instructionStepMode; StepRequest currentStep; InvokeRequest currentInvoke; ! struct bag *eventBag; /* Accumulation of JDWP events to be sent as a reply. */ ! CoLocatedEventInfo cleInfo; /* See comment above deferEventReport() for an explanation. */ ! jthread fiberHelperThread; /* Temporary thread created for mounting fiber on to get stack trace ! * or to support suspending an unmounted fiber. */ ! jboolean isTrackedSuspendedFiber; /* true if we are tracking the suspendCount of this fiber. */ ! struct ThreadNode *nextTrackedSuspendedFiber; ! struct ThreadNode *prevTrackedSuspendedFiber; struct ThreadNode *next; struct ThreadNode *prev; ! jlong frameGeneration; /* used to generate a unique frameID. Incremented whenever existing frameID ! needs to be invalidated, such as when the thread is resumed. */ ! struct ThreadList *list; /* Tells us what list this thread is in. */ ! #ifdef DEBUG_THREADNAME ! char name[256]; ! #endif } ThreadNode; static jint suspendAllCount; + struct ThreadNode *trackedSuspendedFibers = NULL; + typedef struct ThreadList { ThreadNode *first; } ThreadList; /*
*** 116,125 **** --- 130,140 ---- * end events are maintained in the "runningThreads" list. All other threads known * to this module are kept in the "otherThreads" list. */ static ThreadList runningThreads; static ThreadList otherThreads; + static ThreadList runningFibers; /* Fibers we have seen. */ #define MAX_DEBUG_THREADS 10 static int debugThreadCount; static jthread debugThreads[MAX_DEBUG_THREADS];
*** 216,244 **** } return node; } /* ! * These functions maintain the linked list of currently running threads. * All assume that the threadLock is held before calling. ! * If list==NULL, search both lists. */ static ThreadNode * findThread(ThreadList *list, jthread thread) { ThreadNode *node; /* Get thread local storage for quick thread -> node access */ node = getThreadLocalStorage(thread); /* In some rare cases we might get NULL, so we check the list manually for * any threads that we could match. */ if ( node == NULL ) { - JNIEnv *env; - - env = getEnv(); if ( list != NULL ) { node = nonTlsSearch(env, list, thread); } else { node = nonTlsSearch(env, &runningThreads, thread); if ( node == NULL ) { --- 231,274 ---- } return node; } /* ! * These functions maintain the linked list of currently running threads and fibers. * All assume that the threadLock is held before calling. ! */ ! ! ! /* ! * Search for a thread on the list. If list==NULL, search all lists. */ static ThreadNode * findThread(ThreadList *list, jthread thread) { ThreadNode *node; + JNIEnv *env = getEnv(); + + if (list == NULL || list == &runningFibers) { + /* + * Search for a fiber. + * fiber fixme: this needs to be done a lot faster. Maybe some sort of TLS for fibers is needed. + * Otherwise we'll need something like a hashlist front end to the runningFibers list so + * we can do quick lookups. + */ + ThreadNode *node = nonTlsSearch(env, &runningFibers, thread); + if (node != NULL || list == &runningFibers) { + return node; + } + } /* Get thread local storage for quick thread -> node access */ node = getThreadLocalStorage(thread); /* In some rare cases we might get NULL, so we check the list manually for * any threads that we could match. */ if ( node == NULL ) { if ( list != NULL ) { node = nonTlsSearch(env, list, thread); } else { node = nonTlsSearch(env, &runningThreads, thread); if ( node == NULL ) {
*** 301,310 **** --- 331,341 ---- static ThreadNode * insertThread(JNIEnv *env, ThreadList *list, jthread thread) { ThreadNode *node; struct bag *eventBag; + jboolean is_fiber = (list == &runningFibers); node = findThread(list, thread); if (node == NULL) { node = jvmtiAllocate(sizeof(*node)); if (node == NULL) {
*** 331,341 **** return NULL; } /* * Remember if it is a debug thread */ ! if (threadControl_isDebugThread(node->thread)) { node->isDebugThread = JNI_TRUE; } else if (suspendAllCount > 0){ /* * If there is a pending suspendAll, all new threads should * be initialized as if they were suspended by the suspendAll, --- 362,372 ---- return NULL; } /* * Remember if it is a debug thread */ ! if (!is_fiber && threadControl_isDebugThread(node->thread)) { node->isDebugThread = JNI_TRUE; } else if (suspendAllCount > 0){ /* * If there is a pending suspendAll, all new threads should * be initialized as if they were suspended by the suspendAll,
*** 343,361 **** */ node->suspendCount = suspendAllCount; node->suspendOnStart = JNI_TRUE; } node->current_ei = 0; node->instructionStepMode = JVMTI_DISABLE; node->eventBag = eventBag; addNode(list, node); /* Set thread local storage for quick thread -> node access. * Some threads may not be in a state that allows setting of TLS, * which is ok, see findThread, it deals with threads without TLS set. */ ! setThreadLocalStorage(node->thread, (void*)node); } return node; } --- 374,399 ---- */ node->suspendCount = suspendAllCount; node->suspendOnStart = JNI_TRUE; } node->current_ei = 0; + node->is_fiber = is_fiber; node->instructionStepMode = JVMTI_DISABLE; node->eventBag = eventBag; addNode(list, node); /* Set thread local storage for quick thread -> node access. * Some threads may not be in a state that allows setting of TLS, * which is ok, see findThread, it deals with threads without TLS set. */ ! if (!is_fiber) { ! setThreadLocalStorage(node->thread, (void*)node); ! } ! ! if (is_fiber) { ! node->isStarted = JNI_TRUE; /* Fibers are considered started by default. */ ! } } return node; }
*** 368,378 **** stepControl_clearRequest(node->thread, &node->currentStep); if (node->isDebugThread) { (void)threadControl_removeDebugThread(node->thread); } /* Clear out TLS on this thread (just a cleanup action) */ ! setThreadLocalStorage(node->thread, NULL); tossGlobalRef(env, &(node->thread)); bagDestroyBag(node->eventBag); jvmtiDeallocate(node); } --- 406,418 ---- stepControl_clearRequest(node->thread, &node->currentStep); if (node->isDebugThread) { (void)threadControl_removeDebugThread(node->thread); } /* Clear out TLS on this thread (just a cleanup action) */ ! if (!node->is_fiber) { ! setThreadLocalStorage(node->thread, NULL); ! } tossGlobalRef(env, &(node->thread)); bagDestroyBag(node->eventBag); jvmtiDeallocate(node); }
*** 568,577 **** --- 608,618 ---- jvmtiError error; suspendAllCount = 0; runningThreads.first = NULL; otherThreads.first = NULL; + runningFibers.first = NULL; debugThreadCount = 0; threadLock = debugMonitorCreate("JDWP Thread Lock"); if (gdata->threadClass==NULL) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER, "no java.lang.thread class"); }
*** 654,663 **** --- 695,707 ---- struct bag *eventBag) { ThreadNode *node; jthread thread; + /* fiber fixme: it's unclear how this is used and if anything special needs to be done for fibers. */ + JDI_ASSERT(!evinfo->matchesFiber); + thread = evinfo->thread; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread);
*** 735,744 **** --- 779,791 ---- static void handleAppResumeBreakpoint(JNIEnv *env, EventInfo *evinfo, HandlerNode *handlerNode, struct bag *eventBag) { + /* fiber fixme: it's unclear how this is used and if anything special needs to be done for fibers. */ + JDI_ASSERT(!evinfo->matchesFiber); + jthread resumer = evinfo->thread; jthread resumee = getResumee(resumer); debugMonitorEnter(threadLock); if (resumee != NULL) {
*** 841,850 **** --- 888,1001 ---- } END_WITH_LOCAL_REFS(env) debugMonitorExit(threadLock); } + + static jvmtiError + resumeFiberHelperThread(JNIEnv *env, ThreadNode *node, void *ignored) + { + jvmtiError error = JVMTI_ERROR_NONE; + if (node->fiberHelperThread != NULL) { + error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeThread) + (gdata->jvmti, node->fiberHelperThread); + tossGlobalRef(env, &node->fiberHelperThread); + } + return error; + } + + static void + startTrackingSuspendedFiber(ThreadNode *fiberNode) + { + /* Add fiberNode to the start of the list. */ + fiberNode->prevTrackedSuspendedFiber = NULL; + fiberNode->nextTrackedSuspendedFiber = trackedSuspendedFibers; + trackedSuspendedFibers = fiberNode; + + /* Since we didn't previously increment suspendCount for each suspendAll(), do that now. */ + fiberNode->suspendCount = suspendAllCount; + + fiberNode->isTrackedSuspendedFiber = JNI_TRUE; + } + + + static void + stopTrackingSuspendedFiber(ThreadNode *fiberNode) + { + /* Remove fiberNode from the list. */ + if (fiberNode->prevTrackedSuspendedFiber == NULL) { + /* Node is at the start of the list. */ + trackedSuspendedFibers = fiberNode->nextTrackedSuspendedFiber; + } else { + fiberNode->prevTrackedSuspendedFiber->nextTrackedSuspendedFiber = + fiberNode->nextTrackedSuspendedFiber; + } + if (fiberNode->nextTrackedSuspendedFiber != NULL) { + fiberNode->nextTrackedSuspendedFiber->prevTrackedSuspendedFiber = + fiberNode->prevTrackedSuspendedFiber; + } + + /* If this fiber has a helper thread, we no longer need or want it. */ + if (fiberNode->fiberHelperThread != NULL) { + resumeFiberHelperThread(getEnv(), fiberNode, NULL); + } + + fiberNode->isTrackedSuspendedFiber = JNI_FALSE; + } + + static jthread + getFiberHelperThread(jthread fiber) + { + JNIEnv *env; + ThreadNode *fiberNode; + jthread helperThread; + + fiberNode = findThread(&runningFibers, fiber); + if (fiberNode->fiberHelperThread != NULL) { + return fiberNode->fiberHelperThread; + } + + env = getEnv(); + + /* + * We need to mount the fiber on a helper thread. This is done by calling + * Fiber.tryMountAndSuspend(), which will create a helper thread for us, + * mount the fiber on the thread, suspend the thread, and then return the thread. + * + * This helper thread is disposed of by resumeFiberHelperThread() when it is + * determined that the helper thread is no longer need (the fiber was resumed, + * and we are no longer tracking it). + * + * Disable all event handling while doing this, since we don't want to deal + * with any incoming THREAD_START event. + * + * Also release the threadLock, or a deadlock will occur when the + * CONTINUATION_RUN event arrives on the helper thread. + * fiber fixme: this might not be safe to do. + */ + debugMonitorExit(threadLock); + gdata->ignoreEvents = JNI_TRUE; + helperThread = JNI_FUNC_PTR(env,CallObjectMethod) + (env, fiber, gdata->fiberTryMountAndSuspend); + gdata->ignoreEvents = JNI_FALSE; + debugMonitorEnter(threadLock); + + + if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { + JNI_FUNC_PTR(env,ExceptionClear)(env); + helperThread = NULL; + } + + if (helperThread != NULL) { + saveGlobalRef(env, helperThread, &(fiberNode->fiberHelperThread)); + /* Start tracking this fiber as a suspended one. */ + startTrackingSuspendedFiber(fiberNode); + } + + return fiberNode->fiberHelperThread; + } + static jvmtiError commonSuspendByNode(ThreadNode *node) { jvmtiError error;
*** 947,956 **** --- 1098,1121 ---- } } if (error == JVMTI_ERROR_NONE) { node->suspendCount++; + if (gdata->fibersSupported) { + /* + * If this is a carrier thread with a mounted fiber, and the fiber + * is being tracked, bump the fiber's suspendCount also. + */ + jthread fiber = getThreadFiber(node->thread); + if (fiber != NULL) { + ThreadNode *fiberNode = findThread(&runningFibers, fiber); + if (fiberNode != NULL && fiberNode->isTrackedSuspendedFiber) { + /* If tracking, bump the fiber suspendCount also. */ + fiberNode->suspendCount++; + } + } + } } debugMonitorNotifyAll(threadLock); return error;
*** 964,973 **** --- 1129,1154 ---- if (node->isDebugThread) { /* never suspended by debugger => don't ever try to resume */ return JVMTI_ERROR_NONE; } if (node->suspendCount > 0) { + if (gdata->fibersSupported) { + /* + * If this is a carrier thread with a mounted fiber, and the fiber + * is being tracked, decrement the fiber's suspendCount also. + */ + jthread fiber = getThreadFiber(node->thread); + if (fiber != NULL) { + ThreadNode *fiberNode = findThread(&runningFibers, fiber); + if (fiberNode != NULL && fiberNode->isTrackedSuspendedFiber) { + /* If tracking, decrement the fiber suspendCount also. */ + if (fiberNode->suspendCount > 0) { + fiberNode->suspendCount--; + } + } + } + } node->suspendCount--; debugMonitorNotifyAll(threadLock); if ((node->suspendCount == 0) && node->toBeResumed && !node->suspendOnStart) { LOG_MISC(("thread=%p resumed", node->thread));
*** 1047,1062 **** --- 1228,1281 ---- static jvmtiError commonSuspend(JNIEnv *env, jthread thread, jboolean deferred) { ThreadNode *node; + if (isFiber(thread)) { + jvmtiError error = JVMTI_ERROR_NONE; + while (JNI_TRUE) { + jthread carrier_thread = getFiberThread(thread); + if (carrier_thread != NULL) { + /* Fiber is mounted. Suspend the carrier thread. */ + node = findThread(&runningThreads, carrier_thread); + error = suspendThreadByNode(node); + if (error != JVMTI_ERROR_NONE) { + LOG_MISC(("commonSuspend: failed to suspend carrier thread(%p)", carrier_thread)); + return error; + } + if (isSameObject(env, carrier_thread, getFiberThread(thread))) { + /* Successfully suspended and still mounted on same carrier thread. */ + break; + } + /* Fiber moved to new carrier thread before it was suspended. Undo and retry. */ + resumeThreadByNode(node); + LOG_MISC(("commonSuspend: fiber mounted on different carrier thread(%p)", carrier_thread)); + } else { + /* Fiber is not mounted. Get a suspended helper thread for it. */ + ThreadNode *fiberNode = findThread(&runningFibers, thread); + if (getFiberHelperThread(thread) == NULL) { + /* fiber fixme: Sometimes the fiber is in a bad state and we can't create a + * helper thread for it. For now we just fail. */ + LOG_MISC(("commonSuspend: failed to get fiber helper thread.")); + return JVMTI_ERROR_INTERNAL; + } + fiberNode->suspendCount++; + break; + } + } + return error; + } + /* * If the thread is not between its start and end events, we should * still suspend it. To keep track of things, add the thread * to a separate list of threads so that we'll resume it later. */ node = findThread(&runningThreads, thread); + #if 0 + tty_message("commonSuspend: node(%p) suspendCount(%d) %s", node, node->suspendCount, node->name); + #endif if (node == NULL) { node = insertThread(env, &otherThreads, thread); } if ( deferred ) {
*** 1360,1390 **** debugMonitorNotifyAll(threadLock); return error; } - static jvmtiError commonResume(jthread thread) { jvmtiError error; ThreadNode *node; /* * The thread is normally between its start and end events, but if * not, check the auxiliary list used by threadControl_suspendThread. */ node = findThread(NULL, thread); /* * If the node is in neither list, the debugger never suspended * this thread, so do nothing. */ error = JVMTI_ERROR_NONE; if (node != NULL) { error = resumeThreadByNode(node); } return error; } jvmtiError --- 1579,1662 ---- debugMonitorNotifyAll(threadLock); return error; } static jvmtiError commonResume(jthread thread) { jvmtiError error; ThreadNode *node; + if (isFiber(thread)) { + jthread carrier_thread = getFiberThread(thread); + ThreadNode *fiberNode = findThread(&runningFibers, thread); + if (carrier_thread == NULL) { + /* + * Fiber is not mounted on a carrier thread. We may already be tracking this fiber as a + * suspended fiber at this point. We would not be if a suspendAll was done, and there was + * no suspend of just this fiber. If we are not tracking it, then we need to. + */ + if (fiberNode->isTrackedSuspendedFiber) { + if (fiberNode->suspendCount > 0) { + fiberNode->suspendCount--; + /* + * Note, if suspendCount == 0 but suspendAllCount does not, eventually + * threadControl_resumeAll() will be responsible for calling + * stopTrackingSuspendedFiber() + */ + if (fiberNode->suspendCount == 0 && suspendAllCount == 0) { + stopTrackingSuspendedFiber(fiberNode); + } + } + } else { + if (suspendAllCount > 0) { + startTrackingSuspendedFiber(fiberNode); + fiberNode->suspendCount--; + } + } + return JVMTI_ERROR_NONE; + } else { + /* + * This is a mounted fiber. If the fiber is being tracked, and the suspendCount + * of the carrier thread is 0, then decrement the fiber's suspendCount here + * since it cannot be done by resumeThreadByNode because we'll have no way to + * get the fiber if the carrier thread is not suspended (getThreadFiber() will + * produce a fatal error). + */ + if (fiberNode->isTrackedSuspendedFiber) { + if (fiberNode->suspendCount > 0) { + ThreadNode *threadNode = findThread(NULL, thread); + if (threadNode->suspendCount == 0) { + fiberNode->suspendCount--; + } + } + } + /* Fiber is mounted on a carrier thread. Fall through to code below to resume + * the carrier thread. */ + thread = carrier_thread; + } + } + /* * The thread is normally between its start and end events, but if * not, check the auxiliary list used by threadControl_suspendThread. */ node = findThread(NULL, thread); + #if 0 + tty_message("commonResume: node(%p) suspendCount(%d) %s", node, node->suspendCount, node->name); + #endif /* * If the node is in neither list, the debugger never suspended * this thread, so do nothing. */ error = JVMTI_ERROR_NONE; if (node != NULL) { error = resumeThreadByNode(node); } + return error; } jvmtiError
*** 1432,1452 **** jvmtiError threadControl_suspendCount(jthread thread, jint *count) { jvmtiError error; ThreadNode *node; debugMonitorEnter(threadLock); ! node = findThread(&runningThreads, thread); ! if (node == NULL) { ! node = findThread(&otherThreads, thread); } error = JVMTI_ERROR_NONE; if (node != NULL) { ! *count = node->suspendCount; } else { /* * If the node is in neither list, the debugger never suspended * this thread, so the suspend count is 0. */ --- 1704,1747 ---- jvmtiError threadControl_suspendCount(jthread thread, jint *count) { jvmtiError error; ThreadNode *node; + jboolean is_fiber = isFiber(thread); debugMonitorEnter(threadLock); ! if (is_fiber) { ! node = findThread(&runningFibers, thread); ! } else { ! node = findThread(&runningThreads, thread); ! if (node == NULL) { ! node = findThread(&otherThreads, thread); ! } } error = JVMTI_ERROR_NONE; if (node != NULL) { ! if (!is_fiber) { ! *count = node->suspendCount; ! } else { ! jthread carrier_thread = getFiberThread(thread); ! if (carrier_thread == NULL) { ! if (node->isTrackedSuspendedFiber) { ! /* Already tracking this fiber, so fiber node owns its suspendCount. */ ! *count = node->suspendCount; ! } else { ! /* Not tacking this fiber yet, so use suspendAllCount. */ ! *count = suspendAllCount; ! } ! } else { ! /* It's a mounted fiber, so the carrier thread tracks the suspend count. */ ! node = findThread(&runningThreads, carrier_thread); ! JDI_ASSERT(node != NULL); ! *count = node->suspendCount; ! } ! } } else { /* * If the node is in neither list, the debugger never suspended * this thread, so the suspend count is 0. */
*** 1493,1502 **** --- 1788,1800 ---- jvmtiError threadControl_suspendAll(void) { jvmtiError error; JNIEnv *env; + #if 0 + tty_message("threadControl_suspendAll: suspendAllCount(%d)", suspendAllCount); + #endif env = getEnv(); log_debugee_location("threadControl_suspendAll()", NULL, NULL, 0);
*** 1519,1531 **** error = commonSuspendList(env, count, threads); if (error != JVMTI_ERROR_NONE) { goto err; } } else { - int i; - for (i = 0; i < count; i++) { error = commonSuspend(env, threads[i], JNI_FALSE); if (error != JVMTI_ERROR_NONE) { goto err; --- 1817,1827 ----
*** 1543,1552 **** --- 1839,1863 ---- arg.count = count; error = enumerateOverThreadList(env, &otherThreads, suspendAllHelper, &arg); } + /* + * Update the suspend count of any fiber that was explicitly suspended + * and had a helper thread created for that purpose. These are known + * as "tracked" suspended fibers. + */ + debugMonitorEnter(threadLock); + { + ThreadNode *trackedSuspendedFiber = trackedSuspendedFibers; + while (trackedSuspendedFiber != NULL) { + trackedSuspendedFiber->suspendCount++; + trackedSuspendedFiber = trackedSuspendedFiber->nextTrackedSuspendedFiber; + } + } + debugMonitorExit(threadLock); + if (error == JVMTI_ERROR_NONE) { suspendAllCount++; } err: ;
*** 1572,1581 **** --- 1883,1895 ---- jvmtiError threadControl_resumeAll(void) { jvmtiError error; JNIEnv *env; + #if 0 + tty_message("threadControl_resumeAll: suspendAllCount(%d)", suspendAllCount); + #endif env = getEnv(); log_debugee_location("threadControl_resumeAll()", NULL, NULL, 0);
*** 1602,1611 **** --- 1916,1945 ---- if (suspendAllCount > 0) { suspendAllCount--; } + /* + * Update the suspend count of any fiber that is being tracked. If it is being + * tracked, that means that either it was explicitly suspended and had a helper + * thread created for helping to suspend it, or it had helper thread created for + * the purpose of getting its stack. If the count reaches zero, then stop tracking the fiber. + */ + { + ThreadNode *trackedSuspendedFiber = trackedSuspendedFibers; + while (trackedSuspendedFiber != NULL) { + ThreadNode *fiberNode = trackedSuspendedFiber; + trackedSuspendedFiber = trackedSuspendedFiber->nextTrackedSuspendedFiber; + if (fiberNode->suspendCount > 0) { + fiberNode->suspendCount--; + } + if (fiberNode->suspendCount == 0 && suspendAllCount == 0) { + stopTrackingSuspendedFiber(fiberNode); + } + } + } + debugMonitorExit(threadLock); eventHandler_unlock(); /* let eventHelper.c: commandLoop() know we are resuming */ unblockCommandLoop();
*** 2029,2045 **** /* Pretend we were never called */ return JNI_FALSE; } struct bag * ! threadControl_onEventHandlerEntry(jbyte sessionID, EventIndex ei, jthread thread, jobject currentException) { ThreadNode *node; JNIEnv *env; struct bag *eventBag; jthread threadToSuspend; jboolean consumed; env = getEnv(); threadToSuspend = NULL; log_debugee_location("threadControl_onEventHandlerEntry()", thread, NULL, 0); --- 2363,2381 ---- /* Pretend we were never called */ return JNI_FALSE; } struct bag * ! threadControl_onEventHandlerEntry(jbyte sessionID, EventInfo *evinfo, jobject currentException) { ThreadNode *node; JNIEnv *env; struct bag *eventBag; jthread threadToSuspend; jboolean consumed; + EventIndex ei = evinfo->ei; + jthread thread = evinfo->thread; env = getEnv(); threadToSuspend = NULL; log_debugee_location("threadControl_onEventHandlerEntry()", thread, NULL, 0);
*** 2174,2213 **** if (ei == EI_THREAD_END) { eventHandler_unlock(); } } /* Returns JDWP flavored status and status flags. */ jvmtiError threadControl_applicationThreadStatus(jthread thread, jdwpThreadStatus *pstatus, jint *statusFlags) { ! ThreadNode *node; jvmtiError error; jint state; log_debugee_location("threadControl_applicationThreadStatus()", thread, NULL, 0); debugMonitorEnter(threadLock); ! error = threadState(thread, &state); ! *pstatus = map2jdwpThreadStatus(state); ! *statusFlags = map2jdwpSuspendStatus(state); ! ! if (error == JVMTI_ERROR_NONE) { node = findThread(&runningThreads, thread); ! if ((node != NULL) && HANDLING_EVENT(node)) { ! /* ! * While processing an event, an application thread is always ! * considered to be running even if its handler happens to be ! * cond waiting on an internal debugger monitor, etc. ! * ! * Leave suspend status untouched since it is not possible ! * to distinguish debugger suspends from app suspends. ! */ ! *pstatus = JDWP_THREAD_STATUS(RUNNING); } } debugMonitorExit(threadLock); return error; --- 2510,2596 ---- if (ei == EI_THREAD_END) { eventHandler_unlock(); } } + void + threadControl_setName(jthread thread, const char *name) + { + #ifdef DEBUG_THREADNAME + ThreadNode *node = findThread(NULL, thread); + if (node != NULL) { + strncpy(node->name, name, sizeof(node->name) - 1); + } + #endif + } + /* Returns JDWP flavored status and status flags. */ jvmtiError threadControl_applicationThreadStatus(jthread thread, jdwpThreadStatus *pstatus, jint *statusFlags) { ! ThreadNode *node = NULL; jvmtiError error; jint state; + jboolean is_fiber = isFiber(thread); log_debugee_location("threadControl_applicationThreadStatus()", thread, NULL, 0); debugMonitorEnter(threadLock); ! if (!is_fiber) { ! error = threadState(thread, &state); ! *pstatus = map2jdwpThreadStatus(state); ! *statusFlags = map2jdwpSuspendStatus(state); node = findThread(&runningThreads, thread); ! ! if (error == JVMTI_ERROR_NONE) { ! if ((node != NULL) && HANDLING_EVENT(node)) { ! /* ! * While processing an event, an application thread is always ! * considered to be running even if its handler happens to be ! * cond waiting on an internal debugger monitor, etc. ! * ! * Leave suspend status untouched since it is not possible ! * to distinguish debugger suspends from app suspends. ! */ ! *pstatus = JDWP_THREAD_STATUS(RUNNING); ! } ! } ! #if 0 ! tty_message("status thread: node(%p) suspendCount(%d) %d %d %s", ! node, node->suspendCount, *pstatus, *statusFlags, node->name); ! #endif ! } else { /* It's a fiber */ ! int suspendCount; ! error = JVMTI_ERROR_NONE; ! *pstatus = JDWP_THREAD_STATUS(RUNNING); ! *statusFlags = 0; ! node = findThread(&runningFibers, thread); ! if (node->isTrackedSuspendedFiber) { ! /* Already tracking this fiber, so fiber node owns its suspendCount. */ ! suspendCount = node->suspendCount; ! } else { ! /* Not tacking this fiber yet, so use suspendAllCount. */ ! suspendCount = suspendAllCount; ! } ! if (suspendCount > 0) { ! *statusFlags = JDWP_SUSPEND_STATUS(SUSPENDED); ! } else { ! /* If the fiber was not suspended, maybe it's carrier thread was. */ ! thread = getFiberThread(thread); ! if (thread != NULL) { ! node = findThread(&runningThreads, thread); ! if (node->suspendCount > 0) { ! *statusFlags = JDWP_SUSPEND_STATUS(SUSPENDED); ! } ! } } + #if 0 + tty_message("status thread: fiber(%p) suspendCount(%d) %d %d %s", + node, node->suspendCount, *pstatus, *statusFlags, node->name); + #endif } debugMonitorExit(threadLock); return error;
*** 2395,2404 **** --- 2778,2788 ---- env = getEnv(); eventHandler_lock(); /* for proper lock order */ debugMonitorEnter(threadLock); (void)enumerateOverThreadList(env, &runningThreads, resetHelper, NULL); (void)enumerateOverThreadList(env, &otherThreads, resetHelper, NULL); + (void)enumerateOverThreadList(env, &runningFibers, resetHelper, NULL); removeResumed(env, &otherThreads); freeDeferredEventModes(env);
*** 2441,2450 **** --- 2825,2883 ---- /* Thread event */ ThreadNode *node; debugMonitorEnter(threadLock); { + if (isFiber(thread)) { + /* fiber fixme: Getting the carrier thread here is just a hack. It does not work if + * the fiber is not mounted, and even if mounted, does not result in the correct + * behaviour if the fiber changes carrier threads. If the carrier thread is + * NULL we need to defer all the code below, most notably + * threadSetEventNotificationMode(), until after the fiber is mounted. We also need + * to call threadSetEventNotificationMode() each time there is an unmount or mount + * since the thread that needs notifications will change as the fiber moves + * between carrier threads. The best way to manage this might be to move + * HandlerNodes for unmounted fibers onto a linked list hanging off the fiber's + * ThreadNode. But that also complicates finding HandlerNodes. For example, + * when a breakpoint is cleared, we call eventHandler_freeByID(), which would + * need to also search every fiber for the handler. The other choice is to + * keep handlers where they are now (off the array of handler chains), but + * for every mount/unmount, search all the handlers in all the chains for + * ones that are for the mounting/unmounting fiber. This could be slow, + * although generally speaking we don't have many HandlerNodes because + * they are generated indirectly by the debugger as users do things + * like set breakpoints. + * A hybrid approach might be best. Keep the handler chains as they are now, + * but also have each fiber maintain a list of its handler nodes for faster + * handling during mount/unmount. + * + * And it should also be noted here that if the carrier thread is null, the + * findThread() call ends up returning the current thread, and then + * threadSetEventNotificationMode() is called with a NULL thread, resulting + * in the event being enabled on all threads. This bug actually has the + * desireable affect of making breakpoints that are filtered on an unmounted + * fiber work as expected, because all the carrier threads get the breakpoint + * event enabled. However, for some odd reason it also works as expected if + * the fiber is already mounted. I expected that the breakpoint event would only + * be enabled on the carrier thread in that case, and therefore if the fiber + * was moved to a different carrier thread, you would stop getting breakpoints + * until it moved back to the original carrier thread. That's not the case for some + * reason, and I'm see the breakpoints no matter what carrier thread the fiber + * runs on. It turns out that the agent installs a global breakpoint for + * Thread.resume(), so global breakpoints are always enabled. + * See handleAppResumeBreakpoint. + * + * It also should be noted that this does not cause a problem for single stepping + * because: + * - There is at most one single step HandlerNode per thread. + * - Fiber mount/unmount events result explicitly dooing the proper + * enabling/disabling of the JVMTI single step event on the carrier thread. + * There is a potential issue with initiating a StepRequest on and unmounted + * fiber. See the fixme comment in stepControl_beginStep. + */ + thread = getFiberThread(thread); + } node = findThread(&runningThreads, thread); if ((node == NULL) || (!node->isStarted)) { JNIEnv *env; env = getEnv();
*** 2462,2472 **** /* * Returns the current thread, if the thread has generated at least * one event, and has not generated a thread end event. */ ! jthread threadControl_currentThread(void) { jthread thread; debugMonitorEnter(threadLock); { --- 2895,2906 ---- /* * Returns the current thread, if the thread has generated at least * one event, and has not generated a thread end event. */ ! jthread ! threadControl_currentThread(void) { jthread thread; debugMonitorEnter(threadLock); {
*** 2497,2501 **** --- 2931,3340 ---- } debugMonitorExit(threadLock); return frameGeneration; } + + jthread + threadControl_getFiberCarrierOrHelperThread(jthread fiber) + { + /* Get the carrier thread that the fiber is running on */ + jthread carrier_thread = getFiberThread(fiber); + if (carrier_thread != NULL) { + return carrier_thread; + } else { + jthread helperThread; + debugMonitorEnter(threadLock); + helperThread = getFiberHelperThread(fiber); + debugMonitorExit(threadLock); + if (helperThread == NULL) { + /* fiber fixme: we failed to get the helper thread, probably because the fiber + * is currently in the PARKING state. Still need a solution for this. Fix + * all callers too. + */ + LOG_MISC(("threadControl_getFiberCarrierOrHelperThread: getFiberHelperThread() failed")); + } + return helperThread; + } + } + + jthread * + threadControl_allFibers(jint *numFibers) + { + JNIEnv *env; + ThreadNode *node; + jthread* fibers; + + env = getEnv(); + debugMonitorEnter(threadLock); + + /* Count the number of fibers */ + /* fiber fixme: we should keep a running total so no counting is needed. */ + *numFibers = 0; + for (node = runningFibers.first; node != NULL; node = node->next) { + (*numFibers)++; + } + + /* Allocate and fill in the fibers array. */ + fibers = jvmtiAllocate(*numFibers * sizeof(jthread*)); + if (fibers != NULL) { + int i = 0; + for (node = runningFibers.first; node != NULL; node = node->next) { + fibers[i++] = node->thread; + } + } + + debugMonitorExit(threadLock); + + return fibers; + } + + jboolean threadControl_isKnownFiber(jthread fiber) { + ThreadNode *fiberNode; + debugMonitorEnter(threadLock); + fiberNode = findThread(&runningFibers, fiber); + debugMonitorExit(threadLock); + return fiberNode != NULL; + } + + void + threadControl_addFiber(jthread fiber) + { + ThreadNode *fiberNode; + debugMonitorEnter(threadLock); + fiberNode = insertThread(getEnv(), &runningFibers, fiber); + debugMonitorExit(threadLock); + } + + void + threadControl_mountFiber(jthread fiber, jthread thread, jbyte sessionID) { + /* fiber fixme: this funciton no longer serves any purpose now that we rely on + * continuation events instead. remove. + */ + } + + + void + threadControl_unmountFiber(jthread fiber, jthread thread) + { + /* fiber fixme: this funciton no longer serves any purpose now that we rely on + * continuation events instead. remove. + */ + } + + void + threadControl_continuationRun(jthread thread, jint continuation_frame_count) + { + debugMonitorEnter(threadLock); + { + JNIEnv *env = getEnv(); + ThreadNode *threadNode; + ThreadNode *fiberNode; + jthread fiber; + + threadNode = findThread(&runningThreads, thread); + + /* + * fiber fixme: For now, NULL implies that this is a helper thread created by + * getFiberHelperThread(). We should actually verify that, but for now just + * assume it is the case and ignore the event. The need for helper threads will + * hopefully go away, in which case the assert can be re-added. + */ + //JDI_ASSERT(threadNode != NULL); + if (threadNode == NULL) { + debugMonitorExit(threadLock); + return; + } + + JDI_ASSERT(threadNode->isStarted); + JDI_ASSERT(bagSize(threadNode->eventBag) == 0); + + if (threadNode->currentStep.pending) { + /* + * If we are doing a STEP_INTO and are doing class filtering (usually library + * classes), we are relying on METHOD_ENTRY events to tell us if we've stepped + * back into user code. We won't get this event if when we resume the + * continuation, so we need to let the stepControl now that we got a + * CONTINUATION_RUN event so it can do the right thing in absense of + * the METHOD_ENTRY event. There's also a FramePop setup situation that + * stepControl needs to deal with, which is another reason it needs to + * know about CONTINUATION_RUN events. + */ + stepControl_handleContinuationRun(env, thread, &threadNode->currentStep); + } + + fiber = getThreadFiber(threadNode->thread); + if (fiber == NULL) { + debugMonitorExit(threadLock); + return; /* Nothing more to do if thread is not executing a fiber. */ + } + + fiberNode = findThread(&runningFibers, fiber); + if (!gdata->notifyDebuggerOfAllFibers && fiberNode == NULL) { + /* This is not a fiber we are tracking, so nothing to do. */ + debugMonitorExit(threadLock); + return; + } + + JDI_ASSERT(fiberNode != NULL); + JDI_ASSERT(fiberNode->isStarted); + JDI_ASSERT(bagSize(fiberNode->eventBag) == 0); + + /* If we are not single stepping in this fiber then there is nothing to do. */ + if (!fiberNode->currentStep.pending) { + debugMonitorExit(threadLock); + return; + } + JDI_ASSERT(fiberNode->currentStep.is_fiber); + + /* + * Move the single step state from the fiberNode to threadNode, but only if we aren't + * already single stepping on the carrier thread. + */ + if (!threadNode->currentStep.pending) { + /* Copy fiber currentStep struct to carrier thread. */ + memcpy(&threadNode->currentStep, &fiberNode->currentStep, sizeof(fiberNode->currentStep)); + + /* Enable JVMTI single step on the carrier thread if necessary. */ + if (fiberNode->instructionStepMode == JVMTI_ENABLE) { + stepControl_enableStepping(thread); + threadNode->instructionStepMode = JVMTI_ENABLE; + } + + /* Restore the NotifyFramePop that was in place when this Fiber yielded. */ + { + jvmtiError error; + jint depth; + /* NotifyFramePop was originally called with a depth of 0 to indicate the current + * frame. However, frames have been pushed since then, so we need to adjust the + * depth to get to the right frame. + * + * fromStackDepth represents the number of frames on the stack when the STEP_OVER + * was started. NotifyFramePop was called on the method that was entered, which is + * one frame below (fromStackDepth + 1). To account for new frames pushed since + * then, we subtract fromStackDepth from the current number of frames. This + * represents the frame where the STEP_OVER was done, but since we want one + * frame below this point, we also subtract one. + */ + depth = getThreadFrameCount(thread) - fiberNode->currentStep.fromStackDepth; + depth--; /* We actually want the frame one below the adjusted fromStackDepth. */ + if (depth >= 0) { + error = JVMTI_FUNC_PTR(gdata->jvmti,NotifyFramePop)(gdata->jvmti, thread, depth); + if (error == JVMTI_ERROR_DUPLICATE) { + error = JVMTI_ERROR_NONE; + /* Already being notified, continue without error */ + } else if (error != JVMTI_ERROR_NONE) { + EXIT_ERROR(error, "NotifyFramePop failed during mountFiber"); + } + } else { + /* + * If the less than 0, then that means we were single stepping over + * the Continuation.doYield() call. In this case NotifyFramePop is not going to work + * since there was never one setup (doYield() was never actually entered). So + * all that needs to be done is to restore single stepping, and we'll stop + * on the next bytecode after the doYield() call. + */ + JDI_ASSERT(depth == -1); + if (fiberNode->instructionStepMode == JVMTI_DISABLE) { + stepControl_enableStepping(thread); + threadNode->instructionStepMode = JVMTI_ENABLE; + } + } + } + + /* Enable events */ + threadControl_setEventMode(JVMTI_ENABLE, EI_EXCEPTION_CATCH, thread); + threadControl_setEventMode(JVMTI_ENABLE, EI_FRAME_POP, thread); + if (threadNode->currentStep.methodEnterHandlerNode != NULL) { + threadControl_setEventMode(JVMTI_ENABLE, EI_METHOD_ENTRY, thread); + } + } + + /* Always clear the fiber single step state, regardless of what we've done above. */ + fiberNode->instructionStepMode = JVMTI_DISABLE; + memset(&fiberNode->currentStep, 0, sizeof(fiberNode->currentStep)); + + /* + * If for any reason we are tracking this fiber, then that must mean during a + * suspendAll there was a resume done on this fiber. So we started tracking it + * and decremented its suspendCount (which normally would put it at 0). + */ + if (fiberNode->isTrackedSuspendedFiber) { + JDI_ASSERT(suspendAllCount > 0 && fiberNode->suspendCount == 0); + } + if (suspendAllCount > 0) { + /* + * If there is an outstanding suspendAll, then we suspend the carrier thread. The + * way this typically ends up happening is if initially all threads were suspended + * (perhaps when a breakpoing was hit), and then the debugger user decides to resume + * the fiber or carrier thread. This could allow a new fiber to be mounted on the + * carrier thread, but the fiber is implied to be suspended because suspendAllCount + * is >0. In order to keep the fiber from running we must suspened the carrier thread. + */ + /* fiber fixme XXX: disable this feature for now. */ + //eventHelper_suspendThread(sessionID, thread); + } + } + debugMonitorExit(threadLock); + } + + void + threadControl_continuationYield(jthread thread, jint continuation_frame_count) + { + /* fiber fixme: need to figure out what to do with these 4 ThreadNode fields: + unsigned int popFrameEvent : 1; + unsigned int popFrameProceed : 1; + unsigned int popFrameThread : 1; + InvokeRequest currentInvoke; + */ + debugMonitorEnter(threadLock); + { + JNIEnv *env = getEnv(); + ThreadNode *threadNode; + jint total_frame_count; + jint fromDepth; + + threadNode = findThread(&runningThreads, thread); + + /* + * fiber fixme: For now, NULL implies that this is a helper thread created by + * getFiberHelperThread(). We should actually verify that, but for now just + * assume it is the case and ignore the event. The need for helper threads will + * hopefully go away, in which case the assert can be re-added. + */ + //JDI_ASSERT(threadNode != NULL); + if (threadNode == NULL) { + debugMonitorExit(threadLock); + return; /* Nothing to do if thread is not known */ + } + + JDI_ASSERT(threadNode->isStarted); + JDI_ASSERT(bagSize(threadNode->eventBag) == 0); + + /* + * If we are not single stepping in this thread, then there is nothing to do. + */ + if (!threadNode->currentStep.pending) { + debugMonitorExit(threadLock); + return; /* Nothing to do. */ + } + + /* At what depth were we single stepping. */ + fromDepth = threadNode->currentStep.fromStackDepth; + + /* + * Note the continuation has already been unmounted, so total_frame_count will not + * include the continuation frames. + */ + total_frame_count = getThreadFrameCount(thread); + + if (threadNode->currentStep.depth == JDWP_STEP_DEPTH(OVER) && + total_frame_count == fromDepth) { + /* + * We were stepping over Continuation.doContinue() in Continuation.run(). This + * is a special case. Before the continuation was unmounted do to the yield, the + * stack looked like: + * java.lang.Continuation.yield0 + * java.lang.Continuation.yield + * <fiber frames> <-- if Fiber, otherwise just additional continuation frames + * java.lang.Continuation.enter <-- bottommost continuation frame + * java.lang.Continuation.run <-- doContinue() call jumps into continuation + * java.lang.Fiber.runContinuation <-- if Fiber, otherwise will be different + * <scheduler frames> + * All frames above run(), starting with enter(), are continuation frames. The + * correct thing to do here is just enable single stepping. This will resume single + * stepping in Continuation.run() right after the Continuation.doContinue() call. + */ + JDI_ASSERT(threadNode->instructionStepMode == JVMTI_DISABLE); + { + stepControl_enableStepping(thread); + threadNode->instructionStepMode = JVMTI_ENABLE; + } + } else if (!threadNode->currentStep.is_fiber) { + /* We were single stepping, but not in a fiber. */ + if (total_frame_count < fromDepth) { /* Check if fromDepth is in the continuation. */ + /* + * This means the frame we were single stepping in was part of the set of + * frames that will were frozen when this continuation yielded. Because of that + * we need to re-enable single stepping because we won't ever be getting + * the FRAME_POP event for returning to that frame. This will resume single + * stepping in Continuation.run() right after the Continuation.enter() call. + */ + if (threadNode->instructionStepMode == JVMTI_DISABLE) { + stepControl_enableStepping(thread); + threadNode->instructionStepMode = JVMTI_ENABLE; + } + } else { + /* + * We are not single stepping in the continuation, and from the earlier check we + * know we are not single stepping in Continuation.run(), because that would imply + * we were single stepping over the doContinue() call, and we already checked + * for that. There is nothing to do in this case. A NotifyFramePop is already setup + * for a frame further up the stack. + */ + } + } else { + /* + * We are single stepping the fiber, not the carrier thread. Move the single step + * state to the fiberNode. + */ + jthread fiber = getThreadFiber(thread); + ThreadNode *fiberNode; + JDI_ASSERT(fiber != NULL); + + fiberNode = findThread(&runningFibers, fiber); + if (!gdata->notifyDebuggerOfAllFibers && fiberNode == NULL) { + /* This is not a fiber we are tracking. */ + debugMonitorExit(threadLock); + return; + } + + JDI_ASSERT(fiberNode != NULL); + JDI_ASSERT(fiberNode->isStarted); + JDI_ASSERT(bagSize(fiberNode->eventBag) == 0); + + if (threadNode->currentStep.depth == JDWP_STEP_DEPTH(INTO) && + (total_frame_count + continuation_frame_count == fromDepth)) { + /* We are stepping into Continuation.doYield(), so leave single stepping enabled. + * This will resume single stepping in Continuation.run() right after the + * Continuation.enter() call. + */ + } else if (total_frame_count >= fromDepth) { /* Check if fromDepth is NOT in the continuation. */ + /* + * This means the single stepping was initiated stepping in a fiber, but in that small + * window after Thread.setFiber(this) has been called, and before the fiber's + * continuation was actually mounted. An example of this is stepping over the cont.run() + * call in Fiber.runContinuation(). In this case we just leave the carrier thread's + * single step state in place. We should eventually get a FramePop event to + * enable single stepping again. + */ + JDI_ASSERT(threadNode->currentStep.depth == JDWP_STEP_DEPTH(OVER)); + } else { + /* + * We were single stepping in the fiber, and now we need to stop doing that since + * we are leaving the fiber. We will copy our single step state from the carrier + * thread to the fiber so we can later restore it when the fiber is mounted again + * and we get a CONTINUATION_RUN event. + */ + + /* Clean up JVMTI SINGLE_STEP state. */ + if (threadNode->instructionStepMode == JVMTI_ENABLE) { + stepControl_disableStepping(thread); + threadNode->instructionStepMode = JVMTI_DISABLE; + fiberNode->instructionStepMode = JVMTI_ENABLE; + } + + /* Disable events */ + threadControl_setEventMode(JVMTI_DISABLE, EI_EXCEPTION_CATCH, thread); + threadControl_setEventMode(JVMTI_DISABLE, EI_FRAME_POP, thread); + if (threadNode->currentStep.methodEnterHandlerNode != NULL) { + threadControl_setEventMode(JVMTI_DISABLE, EI_METHOD_ENTRY, thread); + } + + /* Copy currentStep struct from the threadNode to the fiberNode and then zero out the threadNode. */ + memcpy(&fiberNode->currentStep, &threadNode->currentStep, sizeof(threadNode->currentStep)); + memset(&threadNode->currentStep, 0, sizeof(threadNode->currentStep)); + } + } + } + debugMonitorExit(threadLock); + }
< prev index next >