summaryrefslogtreecommitdiffstats
path: root/vm/Thread.c
diff options
context:
space:
mode:
authorJean-Baptiste Queru <jbq@google.com>2009-11-12 18:45:15 -0800
committerJean-Baptiste Queru <jbq@google.com>2009-11-12 18:45:15 -0800
commit72e93344b4d1ffc71e9c832ec23de0657e5b04a5 (patch)
tree1a08d1e43d54200ea737234d865c4668c5d3535b /vm/Thread.c
parentdfd0afbcb08b871e224a28ecb4ed427a7693545c (diff)
downloadandroid_dalvik-72e93344b4d1ffc71e9c832ec23de0657e5b04a5.tar.gz
android_dalvik-72e93344b4d1ffc71e9c832ec23de0657e5b04a5.tar.bz2
android_dalvik-72e93344b4d1ffc71e9c832ec23de0657e5b04a5.zip
eclair snapshot
Diffstat (limited to 'vm/Thread.c')
-rw-r--r--vm/Thread.c609
1 files changed, 511 insertions, 98 deletions
diff --git a/vm/Thread.c b/vm/Thread.c
index ee195eb6c..be3e95229 100644
--- a/vm/Thread.c
+++ b/vm/Thread.c
@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/*
* Thread support.
*/
#include "Dalvik.h"
+#include "native/SystemThread.h"
#include "utils/threads.h" // need Android thread priorities
@@ -26,6 +28,9 @@
#include <sys/resource.h>
#include <sys/mman.h>
#include <errno.h>
+#include <fcntl.h>
+
+#include <cutils/sched_policy.h>
#if defined(HAVE_PRCTL)
#include <sys/prctl.h>
@@ -233,6 +238,16 @@ static void threadExitCheck(void* arg);
static void waitForThreadSuspend(Thread* self, Thread* thread);
static int getThreadPriorityFromSystem(void);
+/*
+ * The JIT needs to know if any thread is suspended. We do this by
+ * maintaining a global sum of all threads' suspend counts. All suspendCount
+ * updates should go through this after aquiring threadSuspendCountLock.
+ */
+static inline void dvmAddToThreadSuspendCount(int *pSuspendCount, int delta)
+{
+ *pSuspendCount += delta;
+ gDvm.sumThreadSuspendCount += delta;
+}
/*
* Initialize thread list and main thread's environment. We need to set
@@ -398,8 +413,12 @@ bool dvmThreadObjStartup(void)
void dvmThreadShutdown(void)
{
if (gDvm.threadList != NULL) {
- assert(gDvm.threadList->next == NULL);
- assert(gDvm.threadList->prev == NULL);
+ /*
+ * If we walk through the thread list and try to free the
+ * lingering thread structures (which should only be for daemon
+ * threads), the daemon threads may crash if they execute before
+ * the process dies. Let them leak.
+ */
freeThread(gDvm.threadList);
gDvm.threadList = NULL;
}
@@ -448,6 +467,11 @@ static inline void unlockThreadSuspendCount(void)
* We don't have to check to see if we should be suspended once we have
* the lock. Nobody can suspend all threads without holding the thread list
* lock while they do it, so by definition there isn't a GC in progress.
+ *
+ * TODO: consider checking for suspend after acquiring the lock, and
+ * backing off if set. As stated above, it can't happen during normal
+ * execution, but it *can* happen during shutdown when daemon threads
+ * are being suspended.
*/
void dvmLockThreadList(Thread* self)
{
@@ -460,7 +484,7 @@ void dvmLockThreadList(Thread* self)
oldStatus = self->status;
self->status = THREAD_VMWAIT;
} else {
- /* happens for JNI AttachCurrentThread [not anymore?] */
+ /* happens during VM shutdown */
//LOGW("NULL self in dvmLockThreadList\n");
oldStatus = -1; // shut up gcc
}
@@ -510,7 +534,7 @@ static void lockThreadSuspend(const char* who, SuspendCause why)
u8 startWhen = 0; // init req'd to placate gcc
int sleepIter = 0;
int cc;
-
+
do {
cc = pthread_mutex_trylock(&gDvm._threadSuspendLock);
if (cc != 0) {
@@ -519,8 +543,8 @@ static void lockThreadSuspend(const char* who, SuspendCause why)
* Could be that a resume-all is in progress, and something
* grabbed the CPU when the wakeup was broadcast. The thread
* performing the resume hasn't had a chance to release the
- * thread suspend lock. (Should no longer be an issue --
- * we now release before broadcast.)
+ * thread suspend lock. (We release before the broadcast,
+ * so this should be a narrow window.)
*
* Could be we hit the window as a suspend was started,
* and the lock has been grabbed but the suspend counts
@@ -580,19 +604,24 @@ static inline void unlockThreadSuspend(void)
*
* It's possible for this function to get called after a failed
* initialization, so be careful with assumptions about the environment.
+ *
+ * This will be called from whatever thread calls DestroyJavaVM, usually
+ * but not necessarily the main thread. It's likely, but not guaranteed,
+ * that the current thread has already been cleaned up.
*/
void dvmSlayDaemons(void)
{
- Thread* self = dvmThreadSelf();
+ Thread* self = dvmThreadSelf(); // may be null
Thread* target;
- Thread* nextTarget;
-
- if (self == NULL)
- return;
+ int threadId = 0;
+ bool doWait = false;
//dvmEnterCritical(self);
dvmLockThreadList(self);
+ if (self != NULL)
+ threadId = self->threadId;
+
target = gDvm.threadList;
while (target != NULL) {
if (target == self) {
@@ -603,27 +632,95 @@ void dvmSlayDaemons(void)
if (!dvmGetFieldBoolean(target->threadObj,
gDvm.offJavaLangThread_daemon))
{
+ /* should never happen; suspend it with the rest */
LOGW("threadid=%d: non-daemon id=%d still running at shutdown?!\n",
- self->threadId, target->threadId);
- target = target->next;
- continue;
+ threadId, target->threadId);
}
- LOGI("threadid=%d: killing leftover daemon threadid=%d [TODO]\n",
- self->threadId, target->threadId);
- // TODO: suspend and/or kill the thread
- // (at the very least, we can "rescind their JNI privileges")
+ char* threadName = dvmGetThreadName(target);
+ LOGD("threadid=%d: suspending daemon id=%d name='%s'\n",
+ threadId, target->threadId, threadName);
+ free(threadName);
- /* remove from list */
- nextTarget = target->next;
- unlinkThread(target);
+ /* mark as suspended */
+ lockThreadSuspendCount();
+ dvmAddToThreadSuspendCount(&target->suspendCount, 1);
+ unlockThreadSuspendCount();
+ doWait = true;
+
+ target = target->next;
+ }
+
+ //dvmDumpAllThreads(false);
+
+ /*
+ * Unlock the thread list, relocking it later if necessary. It's
+ * possible a thread is in VMWAIT after calling dvmLockThreadList,
+ * and that function *doesn't* check for pending suspend after
+ * acquiring the lock. We want to let them finish their business
+ * and see the pending suspend before we continue here.
+ *
+ * There's no guarantee of mutex fairness, so this might not work.
+ * (The alternative is to have dvmLockThreadList check for suspend
+ * after acquiring the lock and back off, something we should consider.)
+ */
+ dvmUnlockThreadList();
+
+ if (doWait) {
+ usleep(200 * 1000);
+
+ dvmLockThreadList(self);
+
+ /*
+ * Sleep for a bit until the threads have suspended. We're trying
+ * to exit, so don't wait for too long.
+ */
+ int i;
+ for (i = 0; i < 10; i++) {
+ bool allSuspended = true;
+
+ target = gDvm.threadList;
+ while (target != NULL) {
+ if (target == self) {
+ target = target->next;
+ continue;
+ }
+
+ if (target->status == THREAD_RUNNING && !target->isSuspended) {
+ LOGD("threadid=%d not ready yet\n", target->threadId);
+ allSuspended = false;
+ break;
+ }
+
+ target = target->next;
+ }
+
+ if (allSuspended) {
+ LOGD("threadid=%d: all daemons have suspended\n", threadId);
+ break;
+ } else {
+ LOGD("threadid=%d: waiting for daemons to suspend\n", threadId);
+ }
+
+ usleep(200 * 1000);
+ }
+ dvmUnlockThreadList();
+ }
+#if 0 /* bad things happen if they come out of JNI or "spuriously" wake up */
+ /*
+ * Abandon the threads and recover their resources.
+ */
+ target = gDvm.threadList;
+ while (target != NULL) {
+ Thread* nextTarget = target->next;
+ unlinkThread(target);
freeThread(target);
target = nextTarget;
}
+#endif
- dvmUnlockThreadList();
- //dvmExitCritical(self);
+ //dvmDumpAllThreads(true);
}
@@ -886,15 +983,22 @@ static bool prepareThread(Thread* thread)
/*
* Initialize our reference tracking tables.
*
- * The JNI local ref table *must* be fixed-size because we keep pointers
- * into the table in our stack frames.
- *
* Most threads won't use jniMonitorRefTable, so we clear out the
* structure but don't call the init function (which allocs storage).
*/
+#ifdef USE_INDIRECT_REF
+ if (!dvmInitIndirectRefTable(&thread->jniLocalRefTable,
+ kJniLocalRefMin, kJniLocalRefMax, kIndirectKindLocal))
+ return false;
+#else
+ /*
+ * The JNI local ref table *must* be fixed-size because we keep pointers
+ * into the table in our stack frames.
+ */
if (!dvmInitReferenceTable(&thread->jniLocalRefTable,
kJniLocalRefMax, kJniLocalRefMax))
return false;
+#endif
if (!dvmInitReferenceTable(&thread->internalLocalRefTable,
kInternalRefDefault, kInternalRefMax))
return false;
@@ -948,7 +1052,11 @@ static void freeThread(Thread* thread)
#endif
}
+#ifdef USE_INDIRECT_REF
+ dvmClearIndirectRefTable(&thread->jniLocalRefTable);
+#else
dvmClearReferenceTable(&thread->jniLocalRefTable);
+#endif
dvmClearReferenceTable(&thread->internalLocalRefTable);
if (&thread->jniMonitorRefTable.table != NULL)
dvmClearReferenceTable(&thread->jniMonitorRefTable);
@@ -995,6 +1103,12 @@ static void setThreadSelf(Thread* thread)
* This is mainly of use to ensure that we don't leak resources if, for
* example, a thread attaches itself to us with AttachCurrentThread and
* then exits without notifying the VM.
+ *
+ * We could do the detach here instead of aborting, but this will lead to
+ * portability problems. Other implementations do not do this check and
+ * will simply be unaware that the thread has exited, leading to resource
+ * leaks (and, if this is a non-daemon thread, an infinite hang when the
+ * VM tries to shut down).
*/
static void threadExitCheck(void* arg)
{
@@ -1004,7 +1118,6 @@ static void threadExitCheck(void* arg)
assert(thread != NULL);
if (thread->status != THREAD_ZOMBIE) {
- /* TODO: instead of failing, we could call dvmDetachCurrentThread() */
LOGE("Native thread exited without telling us\n");
dvmAbort();
}
@@ -1185,10 +1298,18 @@ bool dvmCreateInterpThread(Object* threadObj, int reqStackSize)
assert(threadObj != NULL);
if(gDvm.zygote) {
- dvmThrowException("Ljava/lang/IllegalStateException;",
- "No new threads in -Xzygote mode");
+ // Allow the sampling profiler thread. We shut it down before forking.
+ StringObject* nameStr = (StringObject*) dvmGetFieldObject(threadObj,
+ gDvm.offJavaLangThread_name);
+ char* threadName = dvmCreateCstrFromString(nameStr);
+ bool profilerThread = strcmp(threadName, "SamplingProfiler") == 0;
+ free(threadName);
+ if (!profilerThread) {
+ dvmThrowException("Ljava/lang/IllegalStateException;",
+ "No new threads in -Xzygote mode");
- goto fail;
+ goto fail;
+ }
}
self = dvmThreadSelf();
@@ -1470,7 +1591,7 @@ static void* interpThreadStart(void* arg)
* setPriority(), and then starts the thread. We could manage this with
* a "needs priority update" flag to avoid the redundant call.
*/
- int priority = dvmGetFieldBoolean(self->threadObj,
+ int priority = dvmGetFieldInt(self->threadObj,
gDvm.offJavaLangThread_priority);
dvmChangeThreadPriority(self, priority);
@@ -1562,6 +1683,12 @@ static void threadExitUncaughtException(Thread* self, Object* group)
}
bail:
+#if defined(WITH_JIT)
+ /* Remove this thread's suspendCount from global suspendCount sum */
+ lockThreadSuspendCount();
+ dvmAddToThreadSuspendCount(&self->suspendCount, -self->suspendCount);
+ unlockThreadSuspendCount();
+#endif
dvmReleaseTrackedAlloc(exception, self);
}
@@ -2116,6 +2243,9 @@ void dvmDetachCurrentThread(void)
dvmUnlockThreadList();
setThreadSelf(NULL);
+
+ dvmDetachSystemThread(self);
+
freeThread(self);
}
@@ -2138,7 +2268,7 @@ void dvmSuspendThread(Thread* thread)
//assert(thread->handle != dvmJdwpGetDebugThread(gDvm.jdwpState));
lockThreadSuspendCount();
- thread->suspendCount++;
+ dvmAddToThreadSuspendCount(&thread->suspendCount, 1);
thread->dbgSuspendCount++;
LOG_THREAD("threadid=%d: suspend++, now=%d\n",
@@ -2167,7 +2297,7 @@ void dvmResumeThread(Thread* thread)
lockThreadSuspendCount();
if (thread->suspendCount > 0) {
- thread->suspendCount--;
+ dvmAddToThreadSuspendCount(&thread->suspendCount, -1);
thread->dbgSuspendCount--;
} else {
LOG_THREAD("threadid=%d: suspendCount already zero\n",
@@ -2205,7 +2335,7 @@ void dvmSuspendSelf(bool jdwpActivity)
* though.
*/
lockThreadSuspendCount();
- self->suspendCount++;
+ dvmAddToThreadSuspendCount(&self->suspendCount, 1);
self->dbgSuspendCount++;
/*
@@ -2300,7 +2430,7 @@ static void dumpWedgedThread(Thread* thread)
char proc[100];
sprintf(proc, "/proc/%d/exe", getpid());
int len;
-
+
len = readlink(proc, exePath, sizeof(exePath)-1);
exePath[len] = '\0';
}
@@ -2343,29 +2473,83 @@ static void dumpWedgedThread(Thread* thread)
*
* TODO: track basic stats about time required to suspend VM.
*/
+#define FIRST_SLEEP (250*1000) /* 0.25s */
+#define MORE_SLEEP (750*1000) /* 0.75s */
static void waitForThreadSuspend(Thread* self, Thread* thread)
{
const int kMaxRetries = 10;
- const int kSpinSleepTime = 750*1000; /* 0.75s */
+ int spinSleepTime = FIRST_SLEEP;
bool complained = false;
+ bool needPriorityReset = false;
+ int savedThreadPrio = -500;
int sleepIter = 0;
int retryCount = 0;
u8 startWhen = 0; // init req'd to placate gcc
+ u8 firstStartWhen = 0;
while (thread->status == THREAD_RUNNING && !thread->isSuspended) {
- if (sleepIter == 0) // get current time on first iteration
+ if (sleepIter == 0) { // get current time on first iteration
startWhen = dvmGetRelativeTimeUsec();
+ if (firstStartWhen == 0) // first iteration of first attempt
+ firstStartWhen = startWhen;
+
+ /*
+ * After waiting for a bit, check to see if the target thread is
+ * running at a reduced priority. If so, bump it up temporarily
+ * to give it more CPU time.
+ *
+ * getpriority() returns the "nice" value, so larger numbers
+ * indicate lower priority.
+ *
+ * (Not currently changing the cgroup. Wasn't necessary in some
+ * simple experiments.)
+ */
+ if (retryCount == 2) {
+ assert(thread->systemTid != 0);
+ errno = 0;
+ int threadPrio = getpriority(PRIO_PROCESS, thread->systemTid);
+ if (errno == 0 && threadPrio > 0) {
+ const int kHigher = 0;
+ if (setpriority(PRIO_PROCESS, thread->systemTid, kHigher) < 0)
+ {
+ LOGW("Couldn't raise priority on tid %d to %d\n",
+ thread->systemTid, kHigher);
+ } else {
+ savedThreadPrio = threadPrio;
+ needPriorityReset = true;
+ LOGD("Temporarily raising priority on tid %d (%d -> %d)\n",
+ thread->systemTid, threadPrio, kHigher);
+ }
+ }
+ }
+ }
- if (!dvmIterativeSleep(sleepIter++, kSpinSleepTime, startWhen)) {
- LOGW("threadid=%d (h=%d): spin on suspend threadid=%d (handle=%d)\n",
- self->threadId, (int)self->handle,
+#if defined (WITH_JIT)
+ /*
+ * If we're still waiting after the first timeout,
+ * unchain all translations.
+ */
+ if (gDvmJit.pJitEntryTable && retryCount > 0) {
+ LOGD("JIT unchain all attempt #%d",retryCount);
+ dvmJitUnchainAll();
+ }
+#endif
+
+ /*
+ * Sleep briefly. This returns false if we've exceeded the total
+ * time limit for this round of sleeping.
+ */
+ if (!dvmIterativeSleep(sleepIter++, spinSleepTime, startWhen)) {
+ LOGW("threadid=%d: spin on suspend #%d threadid=%d (h=%d)\n",
+ self->threadId, retryCount,
thread->threadId, (int)thread->handle);
dumpWedgedThread(thread);
complained = true;
// keep going; could be slow due to valgrind
sleepIter = 0;
+ spinSleepTime = MORE_SLEEP;
if (retryCount++ == kMaxRetries) {
LOGE("threadid=%d: stuck on threadid=%d, giving up\n",
@@ -2377,9 +2561,20 @@ static void waitForThreadSuspend(Thread* self, Thread* thread)
}
if (complained) {
- LOGW("threadid=%d: spin on suspend resolved\n", self->threadId);
+ LOGW("threadid=%d: spin on suspend resolved in %lld msec\n",
+ self->threadId,
+ (dvmGetRelativeTimeUsec() - firstStartWhen) / 1000);
//dvmDumpThread(thread, false); /* suspended, so dump is safe */
}
+ if (needPriorityReset) {
+ if (setpriority(PRIO_PROCESS, thread->systemTid, savedThreadPrio) < 0) {
+ LOGW("NOTE: couldn't reset priority on thread %d to %d\n",
+ thread->systemTid, savedThreadPrio);
+ } else {
+ LOGV("Restored priority on %d to %d\n",
+ thread->systemTid, savedThreadPrio);
+ }
+ }
}
/*
@@ -2444,7 +2639,7 @@ void dvmSuspendAllThreads(SuspendCause why)
thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
continue;
- thread->suspendCount++;
+ dvmAddToThreadSuspendCount(&thread->suspendCount, 1);
if (why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT)
thread->dbgSuspendCount++;
}
@@ -2476,7 +2671,7 @@ void dvmSuspendAllThreads(SuspendCause why)
/* wait for the other thread to see the pending suspend */
waitForThreadSuspend(self, thread);
- LOG_THREAD("threadid=%d: threadid=%d status=%d c=%d dc=%d isSusp=%d\n",
+ LOG_THREAD("threadid=%d: threadid=%d status=%d c=%d dc=%d isSusp=%d\n",
self->threadId,
thread->threadId, thread->status, thread->suspendCount,
thread->dbgSuspendCount, thread->isSuspended);
@@ -2521,7 +2716,7 @@ void dvmResumeAllThreads(SuspendCause why)
}
if (thread->suspendCount > 0) {
- thread->suspendCount--;
+ dvmAddToThreadSuspendCount(&thread->suspendCount, -1);
if (why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT)
thread->dbgSuspendCount--;
} else {
@@ -2612,7 +2807,8 @@ void dvmUndoDebuggerSuspensions(void)
}
assert(thread->suspendCount >= thread->dbgSuspendCount);
- thread->suspendCount -= thread->dbgSuspendCount;
+ dvmAddToThreadSuspendCount(&thread->suspendCount,
+ -thread->dbgSuspendCount);
thread->dbgSuspendCount = 0;
}
unlockThreadSuspendCount();
@@ -2827,6 +3023,23 @@ Thread* dvmGetThreadFromThreadObject(Object* vmThreadObj)
int vmData;
vmData = dvmGetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData);
+
+ if (false) {
+ Thread* thread = gDvm.threadList;
+ while (thread != NULL) {
+ if ((Thread*)vmData == thread)
+ break;
+
+ thread = thread->next;
+ }
+
+ if (thread == NULL) {
+ LOGW("WARNING: vmThreadObj=%p has thread=%p, not in thread list\n",
+ vmThreadObj, (Thread*)vmData);
+ vmData = 0;
+ }
+ }
+
return (Thread*) vmData;
}
@@ -2851,41 +3064,6 @@ static const int kNiceValues[10] = {
};
/*
- * Change the scheduler cgroup of a pid
- */
-int dvmChangeThreadSchedulerGroup(const char *cgroup)
-{
-#ifdef HAVE_ANDROID_OS
- FILE *fp;
- char path[255];
- int rc;
-
- sprintf(path, "/dev/cpuctl/%s/tasks", (cgroup ? cgroup : ""));
-
- if (!(fp = fopen(path, "w"))) {
-#if ENABLE_CGROUP_ERR_LOGGING
- LOGW("Unable to open %s (%s)\n", path, strerror(errno));
-#endif
- return -errno;
- }
-
- rc = fprintf(fp, "0");
- fclose(fp);
-
- if (rc < 0) {
-#if ENABLE_CGROUP_ERR_LOGGING
- LOGW("Unable to move pid %d to cgroup %s (%s)\n", getpid(),
- (cgroup ? cgroup : "<default>"), strerror(errno));
-#endif
- }
-
- return (rc < 0) ? errno : 0;
-#else // HAVE_ANDROID_OS
- return 0;
-#endif
-}
-
-/*
* Change the priority of a system thread to match that of the Thread object.
*
* We map a priority value from 1-10 to Linux "nice" values, where lower
@@ -2902,10 +3080,10 @@ void dvmChangeThreadPriority(Thread* thread, int newPriority)
}
newNice = kNiceValues[newPriority-1];
- if (newPriority >= ANDROID_PRIORITY_BACKGROUND) {
- dvmChangeThreadSchedulerGroup("bg_non_interactive");
+ if (newNice >= ANDROID_PRIORITY_BACKGROUND) {
+ set_sched_policy(dvmGetSysThreadId(), SP_BACKGROUND);
} else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) {
- dvmChangeThreadSchedulerGroup(NULL);
+ set_sched_policy(dvmGetSysThreadId(), SP_FOREGROUND);
}
if (setpriority(PRIO_PROCESS, pid, newNice) != 0) {
@@ -2981,6 +3159,56 @@ void dvmDumpThread(Thread* thread, bool isRunning)
}
/*
+ * Try to get the scheduler group.
+ *
+ * The data from /proc/<pid>/cgroup looks like:
+ * 2:cpu:/bg_non_interactive
+ *
+ * We return the part after the "/", which will be an empty string for
+ * the default cgroup. If the string is longer than "bufLen", the string
+ * will be truncated.
+ */
+static bool getSchedulerGroup(Thread* thread, char* buf, size_t bufLen)
+{
+#ifdef HAVE_ANDROID_OS
+ char pathBuf[32];
+ char readBuf[256];
+ ssize_t count;
+ int fd;
+
+ snprintf(pathBuf, sizeof(pathBuf), "/proc/%d/cgroup", thread->systemTid);
+ if ((fd = open(pathBuf, O_RDONLY)) < 0) {
+ LOGV("open(%s) failed: %s\n", pathBuf, strerror(errno));
+ return false;
+ }
+
+ count = read(fd, readBuf, sizeof(readBuf));
+ if (count <= 0) {
+ LOGV("read(%s) failed (%d): %s\n",
+ pathBuf, (int) count, strerror(errno));
+ close(fd);
+ return false;
+ }
+ close(fd);
+
+ readBuf[--count] = '\0'; /* remove the '\n', now count==strlen */
+
+ char* cp = strchr(readBuf, '/');
+ if (cp == NULL) {
+ readBuf[sizeof(readBuf)-1] = '\0';
+ LOGV("no '/' in '%s' (file=%s count=%d)\n",
+ readBuf, pathBuf, (int) count);
+ return false;
+ }
+
+ memcpy(buf, cp+1, count); /* count-1 for cp+1, count+1 for NUL */
+ return true;
+#else
+ return false;
+#endif
+}
+
+/*
* Print information about the specified thread.
*
* Works best when the thread in question is "self" or has been suspended.
@@ -3000,6 +3228,7 @@ void dvmDumpThreadEx(const DebugOutputTarget* target, Thread* thread,
StringObject* nameStr;
char* threadName = NULL;
char* groupName = NULL;
+ char schedulerGroupBuf[32];
bool isDaemon;
int priority; // java.lang.Thread priority
int policy; // pthread policy
@@ -3022,6 +3251,12 @@ void dvmDumpThreadEx(const DebugOutputTarget* target, Thread* thread,
policy = -1;
sp.sched_priority = -1;
}
+ if (!getSchedulerGroup(thread, schedulerGroupBuf,sizeof(schedulerGroupBuf)))
+ {
+ strcpy(schedulerGroupBuf, "unknown");
+ } else if (schedulerGroupBuf[0] == '\0') {
+ strcpy(schedulerGroupBuf, "default");
+ }
/* a null value for group is not expected, but deal with it anyway */
groupObj = (Object*) dvmGetFieldObject(threadObj,
@@ -3049,9 +3284,9 @@ void dvmDumpThreadEx(const DebugOutputTarget* target, Thread* thread,
groupName, thread->suspendCount, thread->dbgSuspendCount,
thread->isSuspended ? 'Y' : 'N', thread->threadObj, thread);
dvmPrintDebugMessage(target,
- " | sysTid=%d nice=%d sched=%d/%d handle=%d\n",
+ " | sysTid=%d nice=%d sched=%d/%d cgrp=%s handle=%d\n",
thread->systemTid, getpriority(PRIO_PROCESS, thread->systemTid),
- policy, sp.sched_priority, (int)thread->handle);
+ policy, sp.sched_priority, schedulerGroupBuf, (int)thread->handle);
#ifdef WITH_MONITOR_TRACKING
if (!isRunning) {
@@ -3274,9 +3509,15 @@ LockedObjectData* dvmFindInMonitorList(const Thread* self, const Object* obj)
* GC helper functions
*/
+/*
+ * Add the contents of the registers from the interpreted call stack.
+ */
static void gcScanInterpStackReferences(Thread *thread)
{
const u4 *framePtr;
+#if WITH_EXTRA_GC_CHECKS > 1
+ bool first = true;
+#endif
framePtr = (const u4 *)thread->curFrame;
while (framePtr != NULL) {
@@ -3285,27 +3526,182 @@ static void gcScanInterpStackReferences(Thread *thread)
saveArea = SAVEAREA_FROM_FP(framePtr);
method = saveArea->method;
- if (method != NULL) {
+ if (method != NULL && !dvmIsNativeMethod(method)) {
#ifdef COUNT_PRECISE_METHODS
/* the GC is running, so no lock required */
- if (!dvmIsNativeMethod(method)) {
- if (dvmPointerSetAddEntry(gDvm.preciseMethods, method))
- LOGI("Added %s.%s %p\n",
- method->clazz->descriptor, method->name, method);
+ if (dvmPointerSetAddEntry(gDvm.preciseMethods, method))
+ LOGI("PGC: added %s.%s %p\n",
+ method->clazz->descriptor, method->name, method);
+#endif
+#if WITH_EXTRA_GC_CHECKS > 1
+ /*
+ * May also want to enable the memset() in the "invokeMethod"
+ * goto target in the portable interpreter. That sets the stack
+ * to a pattern that makes referring to uninitialized data
+ * very obvious.
+ */
+
+ if (first) {
+ /*
+ * First frame, isn't native, check the "alternate" saved PC
+ * as a sanity check.
+ *
+ * It seems like we could check the second frame if the first
+ * is native, since the PCs should be the same. It turns out
+ * this doesn't always work. The problem is that we could
+ * have calls in the sequence:
+ * interp method #2
+ * native method
+ * interp method #1
+ *
+ * and then GC while in the native method after returning
+ * from interp method #2. The currentPc on the stack is
+ * for interp method #1, but thread->currentPc2 is still
+ * set for the last thing interp method #2 did.
+ *
+ * This can also happen in normal execution:
+ * - sget-object on not-yet-loaded class
+ * - class init updates currentPc2
+ * - static field init is handled by parsing annotations;
+ * static String init requires creation of a String object,
+ * which can cause a GC
+ *
+ * Essentially, any pattern that involves executing
+ * interpreted code and then causes an allocation without
+ * executing instructions in the original method will hit
+ * this. These are rare enough that the test still has
+ * some value.
+ */
+ if (saveArea->xtra.currentPc != thread->currentPc2) {
+ LOGW("PGC: savedPC(%p) != current PC(%p), %s.%s ins=%p\n",
+ saveArea->xtra.currentPc, thread->currentPc2,
+ method->clazz->descriptor, method->name, method->insns);
+ if (saveArea->xtra.currentPc != NULL)
+ LOGE(" pc inst = 0x%04x\n", *saveArea->xtra.currentPc);
+ if (thread->currentPc2 != NULL)
+ LOGE(" pc2 inst = 0x%04x\n", *thread->currentPc2);
+ dvmDumpThread(thread, false);
+ }
+ } else {
+ /*
+ * It's unusual, but not impossible, for a non-first frame
+ * to be at something other than a method invocation. For
+ * example, if we do a new-instance on a nonexistent class,
+ * we'll have a lot of class loader activity on the stack
+ * above the frame with the "new" operation. Could also
+ * happen while we initialize a Throwable when an instruction
+ * fails.
+ *
+ * So there's not much we can do here to verify the PC,
+ * except to verify that it's a GC point.
+ */
}
+ assert(saveArea->xtra.currentPc != NULL);
#endif
+
+ const RegisterMap* pMap;
+ const u1* regVector;
int i;
- for (i = method->registersSize - 1; i >= 0; i--) {
- u4 rval = *framePtr++;
-//TODO: wrap markifobject in a macro that does pointer checks
- if (rval != 0 && (rval & 0x3) == 0) {
- dvmMarkIfObject((Object *)rval);
+
+ Method* nonConstMethod = (Method*) method; // quiet gcc
+ pMap = dvmGetExpandedRegisterMap(nonConstMethod);
+ if (pMap != NULL) {
+ /* found map, get registers for this address */
+ int addr = saveArea->xtra.currentPc - method->insns;
+ regVector = dvmRegisterMapGetLine(pMap, addr);
+ if (regVector == NULL) {
+ LOGW("PGC: map but no entry for %s.%s addr=0x%04x\n",
+ method->clazz->descriptor, method->name, addr);
+ } else {
+ LOGV("PGC: found map for %s.%s 0x%04x (t=%d)\n",
+ method->clazz->descriptor, method->name, addr,
+ thread->threadId);
+ }
+ } else {
+ /*
+ * No map found. If precise GC is disabled this is
+ * expected -- we don't create pointers to the map data even
+ * if it's present -- but if it's enabled it means we're
+ * unexpectedly falling back on a conservative scan, so it's
+ * worth yelling a little.
+ */
+ if (gDvm.preciseGc) {
+ LOGVV("PGC: no map for %s.%s\n",
+ method->clazz->descriptor, method->name);
}
+ regVector = NULL;
+ }
+
+ if (regVector == NULL) {
+ /* conservative scan */
+ for (i = method->registersSize - 1; i >= 0; i--) {
+ u4 rval = *framePtr++;
+ if (rval != 0 && (rval & 0x3) == 0) {
+ dvmMarkIfObject((Object *)rval);
+ }
+ }
+ } else {
+ /*
+ * Precise scan. v0 is at the lowest address on the
+ * interpreted stack, and is the first bit in the register
+ * vector, so we can walk through the register map and
+ * memory in the same direction.
+ *
+ * A '1' bit indicates a live reference.
+ */
+ u2 bits = 1 << 1;
+ for (i = method->registersSize - 1; i >= 0; i--) {
+ u4 rval = *framePtr++;
+
+ bits >>= 1;
+ if (bits == 1) {
+ /* set bit 9 so we can tell when we're empty */
+ bits = *regVector++ | 0x0100;
+ LOGVV("loaded bits: 0x%02x\n", bits & 0xff);
+ }
+
+ if (rval != 0 && (bits & 0x01) != 0) {
+ /*
+ * Non-null, register marked as live reference. This
+ * should always be a valid object.
+ */
+#if WITH_EXTRA_GC_CHECKS > 0
+ if ((rval & 0x3) != 0 ||
+ !dvmIsValidObject((Object*) rval))
+ {
+ /* this is very bad */
+ LOGE("PGC: invalid ref in reg %d: 0x%08x\n",
+ method->registersSize-1 - i, rval);
+ } else
+#endif
+ {
+ dvmMarkObjectNonNull((Object *)rval);
+ }
+ } else {
+ /*
+ * Null or non-reference, do nothing at all.
+ */
+#if WITH_EXTRA_GC_CHECKS > 1
+ if (dvmIsValidObject((Object*) rval)) {
+ /* this is normal, but we feel chatty */
+ LOGD("PGC: ignoring valid ref in reg %d: 0x%08x\n",
+ method->registersSize-1 - i, rval);
+ }
+#endif
+ }
+ }
+ dvmReleaseRegisterMapLine(pMap, regVector);
}
}
- /* else this is a break frame; nothing to mark.
+ /* else this is a break frame and there is nothing to mark, or
+ * this is a native method and the registers are just the "ins",
+ * copied from various registers in the caller's set.
*/
+#if WITH_EXTRA_GC_CHECKS > 1
+ first = false;
+#endif
+
/* Don't fall into an infinite loop if things get corrupted.
*/
assert((uintptr_t)saveArea->prevFrame > (uintptr_t)framePtr ||
@@ -3331,6 +3727,20 @@ static void gcScanReferenceTable(ReferenceTable *refTable)
}
}
+static void gcScanIndirectRefTable(IndirectRefTable* pRefTable)
+{
+ Object** op = pRefTable->table;
+ int numEntries = dvmIndirectRefTableEntries(pRefTable);
+ int i;
+
+ for (i = 0; i < numEntries; i++) {
+ Object* obj = *op;
+ if (obj != NULL)
+ dvmMarkObjectNonNull(obj);
+ op++;
+ }
+}
+
/*
* Scan a Thread and mark any objects it references.
*/
@@ -3361,7 +3771,11 @@ static void gcScanThread(Thread *thread)
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_JNI_LOCAL, thread->threadId);
+#ifdef USE_INDIRECT_REF
+ gcScanIndirectRefTable(&thread->jniLocalRefTable);
+#else
gcScanReferenceTable(&thread->jniLocalRefTable);
+#endif
if (thread->jniMonitorRefTable.table != NULL) {
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_JNI_MONITOR, thread->threadId);
@@ -3403,11 +3817,10 @@ void dvmGcScanRootThreadGroups()
* through the actual ThreadGroups, but it should be
* equivalent.
*
- * This assumes that the ThreadGroup class object is in
+ * This assumes that the ThreadGroup class object is in
* the root set, which should always be true; it's
* loaded by the built-in class loader, which is part
* of the root set.
*/
gcScanAllThreads();
}
-