blob: 4cce9f81c1e71d3052d7b237f68eaa59832c496c [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Thread support.
*/
#include "Dalvik.h"
#include "utils/threads.h" // need Android thread priorities
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#if defined(HAVE_PRCTL)
#include <sys/prctl.h>
#endif
/* desktop Linux needs a little help with gettid() */
#if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS)
#define __KERNEL__
# include <linux/unistd.h>
#ifdef _syscall0
_syscall0(pid_t,gettid)
#else
pid_t gettid() { return syscall(__NR_gettid);}
#endif
#undef __KERNEL__
#endif
// Change this to enable logging on cgroup errors
#define ENABLE_CGROUP_ERR_LOGGING 0
// change this to LOGV/LOGD to debug thread activity
#define LOG_THREAD LOGVV
/*
Notes on Threading
All threads are native pthreads. All threads, except the JDWP debugger
thread, are visible to code running in the VM and to the debugger. (We
don't want the debugger to try to manipulate the thread that listens for
instructions from the debugger.) Internal VM threads are in the "system"
ThreadGroup, all others are in the "main" ThreadGroup, per convention.
The GC only runs when all threads have been suspended. Threads are
expected to suspend themselves, using a "safe point" mechanism. We check
for a suspend request at certain points in the main interpreter loop,
and on requests coming in from native code (e.g. all JNI functions).
Certain debugger events may inspire threads to self-suspend.
Native methods must use JNI calls to modify object references to avoid
clashes with the GC. JNI doesn't provide a way for native code to access
arrays of objects as such -- code must always get/set individual entries --
so it should be possible to fully control access through JNI.
Internal native VM threads, such as the finalizer thread, must explicitly
check for suspension periodically. In most cases they will be sound
asleep on a condition variable, and won't notice the suspension anyway.
Threads may be suspended by the GC, debugger, or the SIGQUIT listener
thread. The debugger may suspend or resume individual threads, while the
GC always suspends all threads. Each thread has a "suspend count" that
is incremented on suspend requests and decremented on resume requests.
When the count is zero, the thread is runnable. This allows us to fulfill
a debugger requirement: if the debugger suspends a thread, the thread is
not allowed to run again until the debugger resumes it (or disconnects,
in which case we must resume all debugger-suspended threads).
Paused threads sleep on a condition variable, and are awoken en masse.
Certain "slow" VM operations, such as starting up a new thread, will be
done in a separate "VMWAIT" state, so that the rest of the VM doesn't
freeze up waiting for the operation to finish. Threads must check for
pending suspension when leaving VMWAIT.
Because threads suspend themselves while interpreting code or when native
code makes JNI calls, there is no risk of suspending while holding internal
VM locks. All threads can enter a suspended (or native-code-only) state.
Also, we don't have to worry about object references existing solely
in hardware registers.
We do, however, have to worry about objects that were allocated internally
and aren't yet visible to anything else in the VM. If we allocate an
object, and then go to sleep on a mutex after changing to a non-RUNNING
state (e.g. while trying to allocate a second object), the first object
could be garbage-collected out from under us while we sleep. To manage
this, we automatically add all allocated objects to an internal object
tracking list, and only remove them when we know we won't be suspended
before the object appears in the GC root set.
The debugger may choose to suspend or resume a single thread, which can
lead to application-level deadlocks; this is expected behavior. The VM
will only check for suspension of single threads when the debugger is
active (the java.lang.Thread calls for this are deprecated and hence are
not supported). Resumption of a single thread is handled by decrementing
the thread's suspend count and sending a broadcast signal to the condition
variable. (This will cause all threads to wake up and immediately go back
to sleep, which isn't tremendously efficient, but neither is having the
debugger attached.)
The debugger is not allowed to resume threads suspended by the GC. This
is trivially enforced by ignoring debugger requests while the GC is running
(the JDWP thread is suspended during GC).
The VM maintains a Thread struct for every pthread known to the VM. There
is a java/lang/Thread object associated with every Thread. At present,
there is no safe way to go from a Thread object to a Thread struct except by
locking and scanning the list; this is necessary because the lifetimes of
the two are not closely coupled. We may want to change this behavior,
though at present the only performance impact is on the debugger (see
threadObjToThread()). See also notes about dvmDetachCurrentThread().
*/
/*
Alternate implementation (signal-based):
Threads run without safe points -- zero overhead. The VM uses a signal
(e.g. pthread_kill(SIGUSR1)) to notify threads of suspension or resumption.
The trouble with using signals to suspend threads is that it means a thread
can be in the middle of an operation when garbage collection starts.
To prevent some sticky situations, we have to introduce critical sections
to the VM code.
Critical sections temporarily block suspension for a given thread.
The thread must move to a non-blocked state (and self-suspend) after
finishing its current task. If the thread blocks on a resource held
by a suspended thread, we're hosed.
One approach is to require that no blocking operations, notably
acquisition of mutexes, can be performed within a critical section.
This is too limiting. For example, if thread A gets suspended while
holding the thread list lock, it will prevent the GC or debugger from
being able to safely access the thread list. We need to wrap the critical
section around the entire operation (enter critical, get lock, do stuff,
release lock, exit critical).
A better approach is to declare that certain resources can only be held
within critical sections. A thread that enters a critical section and
then gets blocked on the thread list lock knows that the thread it is
waiting for is also in a critical section, and will release the lock
before suspending itself. Eventually all threads will complete their
operations and self-suspend. For this to work, the VM must:
(1) Determine the set of resources that may be accessed from the GC or
debugger threads. The mutexes guarding those go into the "critical
resource set" (CRS).
(2) Ensure that no resource in the CRS can be acquired outside of a
critical section. This can be verified with an assert().
(3) Ensure that only resources in the CRS can be held while in a critical
section. This is harder to enforce.
If any of these conditions are not met, deadlock can ensue when grabbing
resources in the GC or debugger (#1) or waiting for threads to suspend
(#2,#3). (You won't actually deadlock in the GC, because if the semantics
above are followed you don't need to lock anything in the GC. The risk is
rather that the GC will access data structures in an intermediate state.)
This approach requires more care and awareness in the VM than
safe-pointing. Because the GC and debugger are fairly intrusive, there
really aren't any internal VM resources that aren't shared. Thus, the
enter/exit critical calls can be added to internal mutex wrappers, which
makes it easy to get #1 and #2 right.
An ordering should be established for all locks to avoid deadlocks.
Monitor locks, which are also implemented with pthread calls, should not
cause any problems here. Threads fighting over such locks will not be in
critical sections and can be suspended freely.
This can get tricky if we ever need exclusive access to VM and non-VM
resources at the same time. It's not clear if this is a real concern.
There are (at least) two ways to handle the incoming signals:
(a) Always accept signals. If we're in a critical section, the signal
handler just returns without doing anything (the "suspend level"
should have been incremented before the signal was sent). Otherwise,
if the "suspend level" is nonzero, we go to sleep.
(b) Block signals in critical sections. This ensures that we can't be
interrupted in a critical section, but requires pthread_sigmask()
calls on entry and exit.
This is a choice between blocking the message and blocking the messenger.
Because UNIX signals are unreliable (you can only know that you have been
signaled, not whether you were signaled once or 10 times), the choice is
not significant for correctness. The choice depends on the efficiency
of pthread_sigmask() and the desire to actually block signals. Either way,
it is best to ensure that there is only one indication of "blocked";
having two (i.e. block signals and set a flag, then only send a signal
if the flag isn't set) can lead to race conditions.
The signal handler must take care to copy registers onto the stack (via
setjmp), so that stack scans find all references. Because we have to scan
native stacks, "exact" GC is not possible with this approach.
Some other concerns with flinging signals around:
- Odd interactions with some debuggers (e.g. gdb on the Mac)
- Restrictions on some standard library calls during GC (e.g. don't
use printf on stdout to print GC debug messages)
*/
#define kMaxThreadId ((1<<15) - 1)
#define kMainThreadId ((1<<1) | 1)
static Thread* allocThread(int interpStackSize);
static bool prepareThread(Thread* thread);
static void setThreadSelf(Thread* thread);
static void unlinkThread(Thread* thread);
static void freeThread(Thread* thread);
static void assignThreadId(Thread* thread);
static bool createFakeEntryFrame(Thread* thread);
static bool createFakeRunFrame(Thread* thread);
static void* interpThreadStart(void* arg);
static void* internalThreadStart(void* arg);
static void threadExitUncaughtException(Thread* thread, Object* group);
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
* up some basic stuff so that dvmThreadSelf() will work when we start
* loading classes (e.g. to check for exceptions).
*/
bool dvmThreadStartup(void)
{
Thread* thread;
/* allocate a TLS slot */
if (pthread_key_create(&gDvm.pthreadKeySelf, threadExitCheck) != 0) {
LOGE("ERROR: pthread_key_create failed\n");
return false;
}
/* test our pthread lib */
if (pthread_getspecific(gDvm.pthreadKeySelf) != NULL)
LOGW("WARNING: newly-created pthread TLS slot is not NULL\n");
/* prep thread-related locks and conditions */
dvmInitMutex(&gDvm.threadListLock);
pthread_cond_init(&gDvm.threadStartCond, NULL);
//dvmInitMutex(&gDvm.vmExitLock);
pthread_cond_init(&gDvm.vmExitCond, NULL);
dvmInitMutex(&gDvm._threadSuspendLock);
dvmInitMutex(&gDvm.threadSuspendCountLock);
pthread_cond_init(&gDvm.threadSuspendCountCond, NULL);
#ifdef WITH_DEADLOCK_PREDICTION
dvmInitMutex(&gDvm.deadlockHistoryLock);
#endif
/*
* Dedicated monitor for Thread.sleep().
* TODO: change this to an Object* so we don't have to expose this
* call, and we interact better with JDWP monitor calls. Requires
* deferring the object creation to much later (e.g. final "main"
* thread prep) or until first use.
*/
gDvm.threadSleepMon = dvmCreateMonitor(NULL);
gDvm.threadIdMap = dvmAllocBitVector(kMaxThreadId, false);
thread = allocThread(gDvm.stackSize);
if (thread == NULL)
return false;
/* switch mode for when we run initializers */
thread->status = THREAD_RUNNING;
/*
* We need to assign the threadId early so we can lock/notify
* object monitors. We'll set the "threadObj" field later.
*/
prepareThread(thread);
gDvm.threadList = thread;
#ifdef COUNT_PRECISE_METHODS
gDvm.preciseMethods = dvmPointerSetAlloc(200);
#endif
return true;
}
/*
* We're a little farther up now, and can load some basic classes.
*
* We're far enough along that we can poke at java.lang.Thread and friends,
* but should not assume that static initializers have run (or cause them
* to do so). That means no object allocations yet.
*/
bool dvmThreadObjStartup(void)
{
/*
* Cache the locations of these classes. It's likely that we're the
* first to reference them, so they're being loaded now.
*/
gDvm.classJavaLangThread =
dvmFindSystemClassNoInit("Ljava/lang/Thread;");
gDvm.classJavaLangVMThread =
dvmFindSystemClassNoInit("Ljava/lang/VMThread;");
gDvm.classJavaLangThreadGroup =
dvmFindSystemClassNoInit("Ljava/lang/ThreadGroup;");
if (gDvm.classJavaLangThread == NULL ||
gDvm.classJavaLangThreadGroup == NULL ||
gDvm.classJavaLangThreadGroup == NULL)
{
LOGE("Could not find one or more essential thread classes\n");
return false;
}
/*
* Cache field offsets. This makes things a little faster, at the
* expense of hard-coding non-public field names into the VM.
*/
gDvm.offJavaLangThread_vmThread =
dvmFindFieldOffset(gDvm.classJavaLangThread,
"vmThread", "Ljava/lang/VMThread;");
gDvm.offJavaLangThread_group =
dvmFindFieldOffset(gDvm.classJavaLangThread,
"group", "Ljava/lang/ThreadGroup;");
gDvm.offJavaLangThread_daemon =
dvmFindFieldOffset(gDvm.classJavaLangThread, "daemon", "Z");
gDvm.offJavaLangThread_name =
dvmFindFieldOffset(gDvm.classJavaLangThread,
"name", "Ljava/lang/String;");
gDvm.offJavaLangThread_priority =
dvmFindFieldOffset(gDvm.classJavaLangThread, "priority", "I");
if (gDvm.offJavaLangThread_vmThread < 0 ||
gDvm.offJavaLangThread_group < 0 ||
gDvm.offJavaLangThread_daemon < 0 ||
gDvm.offJavaLangThread_name < 0 ||
gDvm.offJavaLangThread_priority < 0)
{
LOGE("Unable to find all fields in java.lang.Thread\n");
return false;
}
gDvm.offJavaLangVMThread_thread =
dvmFindFieldOffset(gDvm.classJavaLangVMThread,
"thread", "Ljava/lang/Thread;");
gDvm.offJavaLangVMThread_vmData =
dvmFindFieldOffset(gDvm.classJavaLangVMThread, "vmData", "I");
if (gDvm.offJavaLangVMThread_thread < 0 ||
gDvm.offJavaLangVMThread_vmData < 0)
{
LOGE("Unable to find all fields in java.lang.VMThread\n");
return false;
}
/*
* Cache the vtable offset for "run()".
*
* We don't want to keep the Method* because then we won't find see
* methods defined in subclasses.
*/
Method* meth;
meth = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangThread, "run", "()V");
if (meth == NULL) {
LOGE("Unable to find run() in java.lang.Thread\n");
return false;
}
gDvm.voffJavaLangThread_run = meth->methodIndex;
/*
* Cache vtable offsets for ThreadGroup methods.
*/
meth = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangThreadGroup,
"removeThread", "(Ljava/lang/Thread;)V");
if (meth == NULL) {
LOGE("Unable to find removeThread(Thread) in java.lang.ThreadGroup\n");
return false;
}
gDvm.voffJavaLangThreadGroup_removeThread = meth->methodIndex;
return true;
}
/*
* All threads should be stopped by now. Clean up some thread globals.
*/
void dvmThreadShutdown(void)
{
if (gDvm.threadList != 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;
}
dvmFreeBitVector(gDvm.threadIdMap);
dvmFreeMonitorList();
pthread_key_delete(gDvm.pthreadKeySelf);
}
/*
* Grab the suspend count global lock.
*/
static inline void lockThreadSuspendCount(void)
{
/*
* Don't try to change to VMWAIT here. When we change back to RUNNING
* we have to check for a pending suspend, which results in grabbing
* this lock recursively. Doesn't work with "fast" pthread mutexes.
*
* This lock is always held for very brief periods, so as long as
* mutex ordering is respected we shouldn't stall.
*/
int cc = pthread_mutex_lock(&gDvm.threadSuspendCountLock);
assert(cc == 0);
}
/*
* Release the suspend count global lock.
*/
static inline void unlockThreadSuspendCount(void)
{
dvmUnlockMutex(&gDvm.threadSuspendCountLock);
}
/*
* Grab the thread list global lock.
*
* This is held while "suspend all" is trying to make everybody stop. If
* the shutdown is in progress, and somebody tries to grab the lock, they'll
* have to wait for the GC to finish. Therefore it's important that the
* thread not be in RUNNING mode.
*
* 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)
{
ThreadStatus oldStatus;
if (self == NULL) /* try to get it from TLS */
self = dvmThreadSelf();
if (self != NULL) {
oldStatus = self->status;
self->status = THREAD_VMWAIT;
} else {
/* happens during VM shutdown */
//LOGW("NULL self in dvmLockThreadList\n");
oldStatus = -1; // shut up gcc
}
int cc = pthread_mutex_lock(&gDvm.threadListLock);
assert(cc == 0);
if (self != NULL)
self->status = oldStatus;
}
/*
* Release the thread list global lock.
*/
void dvmUnlockThreadList(void)
{
int cc = pthread_mutex_unlock(&gDvm.threadListLock);
assert(cc == 0);
}
/*
* Convert SuspendCause to a string.
*/
static const char* getSuspendCauseStr(SuspendCause why)
{
switch (why) {
case SUSPEND_NOT: return "NOT?";
case SUSPEND_FOR_GC: return "gc";
case SUSPEND_FOR_DEBUG: return "debug";
case SUSPEND_FOR_DEBUG_EVENT: return "debug-event";
case SUSPEND_FOR_STACK_DUMP: return "stack-dump";
default: return "UNKNOWN";
}
}
/*
* Grab the "thread suspend" lock. This is required to prevent the
* GC and the debugger from simultaneously suspending all threads.
*
* If we fail to get the lock, somebody else is trying to suspend all
* threads -- including us. If we go to sleep on the lock we'll deadlock
* the VM. Loop until we get it or somebody puts us to sleep.
*/
static void lockThreadSuspend(const char* who, SuspendCause why)
{
const int kSpinSleepTime = 3*1000*1000; /* 3s */
u8 startWhen = 0; // init req'd to placate gcc
int sleepIter = 0;
int cc;
do {
cc = pthread_mutex_trylock(&gDvm._threadSuspendLock);
if (cc != 0) {
if (!dvmCheckSuspendPending(NULL)) {
/*
* 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. (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
* haven't been incremented yet.
*
* Could be an unusual JNI thread-attach thing.
*
* Could be the debugger telling us to resume at roughly
* the same time we're posting an event.
*/
LOGI("threadid=%d ODD: want thread-suspend lock (%s:%s),"
" it's held, no suspend pending\n",
dvmThreadSelf()->threadId, who, getSuspendCauseStr(why));
} else {
/* we suspended; reset timeout */
sleepIter = 0;
}
/* give the lock-holder a chance to do some work */
if (sleepIter == 0)
startWhen = dvmGetRelativeTimeUsec();
if (!dvmIterativeSleep(sleepIter++, kSpinSleepTime, startWhen)) {
LOGE("threadid=%d: couldn't get thread-suspend lock (%s:%s),"
" bailing\n",
dvmThreadSelf()->threadId, who, getSuspendCauseStr(why));
/* threads are not suspended, thread dump could crash */
dvmDumpAllThreads(false);
dvmAbort();
}
}
} while (cc != 0);
assert(cc == 0);
}
/*
* Release the "thread suspend" lock.
*/
static inline void unlockThreadSuspend(void)
{
int cc = pthread_mutex_unlock(&gDvm._threadSuspendLock);
assert(cc == 0);
}
/*
* Kill any daemon threads that still exist. All of ours should be
* stopped, so these should be Thread objects or JNI-attached threads
* started by the application. Actively-running threads are likely
* to crash the process if they continue to execute while the VM
* shuts down, so we really need to kill or suspend them. (If we want
* the VM to restart within this process, we need to kill them, but that
* leaves open the possibility of orphaned resources.)
*
* Waiting for the thread to suspend may be unwise at this point, but
* if one of these is wedged in a critical section then we probably
* would've locked up on the last GC attempt.
*
* 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(); // may be null
Thread* target;
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) {
target = target->next;
continue;
}
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",
threadId, target->threadId);
}
char* threadName = dvmGetThreadName(target);
LOGD("threadid=%d: suspending daemon id=%d name='%s'\n",
threadId, target->threadId, threadName);
free(threadName);
/* 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
//dvmDumpAllThreads(true);
}
/*
* Finish preparing the parts of the Thread struct required to support
* JNI registration.
*/
bool dvmPrepMainForJni(JNIEnv* pEnv)
{
Thread* self;
/* main thread is always first in list at this point */
self = gDvm.threadList;
assert(self->threadId == kMainThreadId);
/* create a "fake" JNI frame at the top of the main thread interp stack */
if (!createFakeEntryFrame(self))
return false;
/* fill these in, since they weren't ready at dvmCreateJNIEnv time */
dvmSetJniEnvThreadId(pEnv, self);
dvmSetThreadJNIEnv(self, (JNIEnv*) pEnv);
return true;
}
/*
* Finish preparing the main thread, allocating some objects to represent
* it. As part of doing so, we finish initializing Thread and ThreadGroup.
* This will execute some interpreted code (e.g. class initializers).
*/
bool dvmPrepMainThread(void)
{
Thread* thread;
Object* groupObj;
Object* threadObj;
Object* vmThreadObj;
StringObject* threadNameStr;
Method* init;
JValue unused;
LOGV("+++ finishing prep on main VM thread\n");
/* main thread is always first in list at this point */
thread = gDvm.threadList;
assert(thread->threadId == kMainThreadId);
/*
* Make sure the classes are initialized. We have to do this before
* we create an instance of them.
*/
if (!dvmInitClass(gDvm.classJavaLangClass)) {
LOGE("'Class' class failed to initialize\n");
return false;
}
if (!dvmInitClass(gDvm.classJavaLangThreadGroup) ||
!dvmInitClass(gDvm.classJavaLangThread) ||
!dvmInitClass(gDvm.classJavaLangVMThread))
{
LOGE("thread classes failed to initialize\n");
return false;
}
groupObj = dvmGetMainThreadGroup();
if (groupObj == NULL)
return false;
/*
* Allocate and construct a Thread with the internal-creation
* constructor.
*/
threadObj = dvmAllocObject(gDvm.classJavaLangThread, ALLOC_DEFAULT);
if (threadObj == NULL) {
LOGE("unable to allocate main thread object\n");
return false;
}
dvmReleaseTrackedAlloc(threadObj, NULL);
threadNameStr = dvmCreateStringFromCstr("main", ALLOC_DEFAULT);
if (threadNameStr == NULL)
return false;
dvmReleaseTrackedAlloc((Object*)threadNameStr, NULL);
init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangThread, "<init>",
"(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V");
assert(init != NULL);
dvmCallMethod(thread, init, threadObj, &unused, groupObj, threadNameStr,
THREAD_NORM_PRIORITY, false);
if (dvmCheckException(thread)) {
LOGE("exception thrown while constructing main thread object\n");
return false;
}
/*
* Allocate and construct a VMThread.
*/
vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT);
if (vmThreadObj == NULL) {
LOGE("unable to allocate main vmthread object\n");
return false;
}
dvmReleaseTrackedAlloc(vmThreadObj, NULL);
init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangVMThread, "<init>",
"(Ljava/lang/Thread;)V");
dvmCallMethod(thread, init, vmThreadObj, &unused, threadObj);
if (dvmCheckException(thread)) {
LOGE("exception thrown while constructing main vmthread object\n");
return false;
}
/* set the VMThread.vmData field to our Thread struct */
assert(gDvm.offJavaLangVMThread_vmData != 0);
dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)thread);
/*
* Stuff the VMThread back into the Thread. From this point on, other
* Threads will see that this Thread is running (at least, they would,
* if there were any).
*/
dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread,
vmThreadObj);
thread->threadObj = threadObj;
/*
* Set the context class loader. This invokes a ClassLoader method,
* which could conceivably call Thread.currentThread(), so we want the
* Thread to be fully configured before we do this.
*/
Object* systemLoader = dvmGetSystemClassLoader();
if (systemLoader == NULL) {
LOGW("WARNING: system class loader is NULL (setting main ctxt)\n");
/* keep going */
}
int ctxtClassLoaderOffset = dvmFindFieldOffset(gDvm.classJavaLangThread,
"contextClassLoader", "Ljava/lang/ClassLoader;");
if (ctxtClassLoaderOffset < 0) {
LOGE("Unable to find contextClassLoader field in Thread\n");
return false;
}
dvmSetFieldObject(threadObj, ctxtClassLoaderOffset, systemLoader);
/*
* Finish our thread prep.
*/
/* include self in non-daemon threads (mainly for AttachCurrentThread) */
gDvm.nonDaemonThreadCount++;
return true;
}
/*
* Alloc and initialize a Thread struct.
*
* "threadObj" is the java.lang.Thread object. It will be NULL for the
* main VM thread, but non-NULL for everything else.
*
* Does not create any objects, just stuff on the system (malloc) heap. (If
* this changes, we need to use ALLOC_NO_GC. And also verify that we're
* ready to load classes at the time this is called.)
*/
static Thread* allocThread(int interpStackSize)
{
Thread* thread;
u1* stackBottom;
thread = (Thread*) calloc(1, sizeof(Thread));
if (thread == NULL)
return NULL;
assert(interpStackSize >= kMinStackSize && interpStackSize <=kMaxStackSize);
thread->status = THREAD_INITIALIZING;
thread->suspendCount = 0;
#ifdef WITH_ALLOC_LIMITS
thread->allocLimit = -1;
#endif
/*
* Allocate and initialize the interpreted code stack. We essentially
* "lose" the alloc pointer, which points at the bottom of the stack,
* but we can get it back later because we know how big the stack is.
*
* The stack must be aligned on a 4-byte boundary.
*/
#ifdef MALLOC_INTERP_STACK
stackBottom = (u1*) malloc(interpStackSize);
if (stackBottom == NULL) {
free(thread);
return NULL;
}
memset(stackBottom, 0xc5, interpStackSize); // stop valgrind complaints
#else
stackBottom = mmap(NULL, interpStackSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON, -1, 0);
if (stackBottom == MAP_FAILED) {
free(thread);
return NULL;
}
#endif
assert(((u4)stackBottom & 0x03) == 0); // looks like our malloc ensures this
thread->interpStackSize = interpStackSize;
thread->interpStackStart = stackBottom + interpStackSize;
thread->interpStackEnd = stackBottom + STACK_OVERFLOW_RESERVE;
/* give the thread code a chance to set things up */
dvmInitInterpStack(thread, interpStackSize);
return thread;
}
/*
* Get a meaningful thread ID. At present this only has meaning under Linux,
* where getpid() and gettid() sometimes agree and sometimes don't depending
* on your thread model (try "export LD_ASSUME_KERNEL=2.4.19").
*/
pid_t dvmGetSysThreadId(void)
{
#ifdef HAVE_GETTID
return gettid();
#else
return getpid();
#endif
}
/*
* Finish initialization of a Thread struct.
*
* This must be called while executing in the new thread, but before the
* thread is added to the thread list.
*
* *** NOTE: The threadListLock must be held by the caller (needed for
* assignThreadId()).
*/
static bool prepareThread(Thread* thread)
{
assignThreadId(thread);
thread->handle = pthread_self();
thread->systemTid = dvmGetSysThreadId();
//LOGI("SYSTEM TID IS %d (pid is %d)\n", (int) thread->systemTid,
// (int) getpid());
setThreadSelf(thread);
LOGV("threadid=%d: interp stack at %p\n",
thread->threadId, thread->interpStackStart - thread->interpStackSize);
/*
* Initialize invokeReq.
*/
pthread_mutex_init(&thread->invokeReq.lock, NULL);
pthread_cond_init(&thread->invokeReq.cv, NULL);
/*
* Initialize our reference tracking tables.
*
* 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;
memset(&thread->jniMonitorRefTable, 0, sizeof(thread->jniMonitorRefTable));
return true;
}
/*
* Remove a thread from the internal list.
* Clear out the links to make it obvious that the thread is
* no longer on the list. Caller must hold gDvm.threadListLock.
*/
static void unlinkThread(Thread* thread)
{
LOG_THREAD("threadid=%d: removing from list\n", thread->threadId);
if (thread == gDvm.threadList) {
assert(thread->prev == NULL);
gDvm.threadList = thread->next;
} else {
assert(thread->prev != NULL);
thread->prev->next = thread->next;
}
if (thread->next != NULL)
thread->next->prev = thread->prev;
thread->prev = thread->next = NULL;
}
/*
* Free a Thread struct, and all the stuff allocated within.
*/
static void freeThread(Thread* thread)
{
if (thread == NULL)
return;
/* thread->threadId is zero at this point */
LOGVV("threadid=%d: freeing\n", thread->threadId);
if (thread->interpStackStart != NULL) {
u1* interpStackBottom;
interpStackBottom = thread->interpStackStart;
interpStackBottom -= thread->interpStackSize;
#ifdef MALLOC_INTERP_STACK
free(interpStackBottom);
#else
if (munmap(interpStackBottom, thread->interpStackSize) != 0)
LOGW("munmap(thread stack) failed\n");
#endif
}
#ifdef USE_INDIRECT_REF
dvmClearIndirectRefTable(&thread->jniLocalRefTable);
#else
dvmClearReferenceTable(&thread->jniLocalRefTable);
#endif
dvmClearReferenceTable(&thread->internalLocalRefTable);
if (&thread->jniMonitorRefTable.table != NULL)
dvmClearReferenceTable(&thread->jniMonitorRefTable);
free(thread);
}
/*
* Like pthread_self(), but on a Thread*.
*/
Thread* dvmThreadSelf(void)
{
return (Thread*) pthread_getspecific(gDvm.pthreadKeySelf);
}
/*
* Explore our sense of self. Stuffs the thread pointer into TLS.
*/
static void setThreadSelf(Thread* thread)
{
int cc;
cc = pthread_setspecific(gDvm.pthreadKeySelf, thread);
if (cc != 0) {
/*
* Sometimes this fails under Bionic with EINVAL during shutdown.
* This can happen if the timing is just right, e.g. a thread
* fails to attach during shutdown, but the "fail" path calls
* here to ensure we clean up after ourselves.
*/
if (thread != NULL) {
LOGE("pthread_setspecific(%p) failed, err=%d\n", thread, cc);
dvmAbort(); /* the world is fundamentally hosed */
}
}
}
/*
* This is associated with the pthreadKeySelf key. It's called by the
* pthread library when a thread is exiting and the "self" pointer in TLS
* is non-NULL, meaning the VM hasn't had a chance to clean up. In normal
* operation this should never be called.
*
* 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)
{
Thread* thread = (Thread*) arg;
LOGI("In threadExitCheck %p\n", arg);
assert(thread != NULL);
if (thread->status != THREAD_ZOMBIE) {
LOGE("Native thread exited without telling us\n");
dvmAbort();
}
}
/*
* Assign the threadId. This needs to be a small integer so that our
* "thin" locks fit in a small number of bits.
*
* We reserve zero for use as an invalid ID.
*
* This must be called with threadListLock held (unless we're still
* initializing the system).
*/
static void assignThreadId(Thread* thread)
{
/* Find a small unique integer. threadIdMap is a vector of
* kMaxThreadId bits; dvmAllocBit() returns the index of a
* bit, meaning that it will always be < kMaxThreadId.
*
* The thin locking magic requires that the low bit is always
* set, so we do it once, here.
*/
int num = dvmAllocBit(gDvm.threadIdMap);
if (num < 0) {
LOGE("Ran out of thread IDs\n");
dvmAbort(); // TODO: make this a non-fatal error result
}
thread->threadId = ((num + 1) << 1) | 1;
assert(thread->threadId != 0);
assert(thread->threadId != DVM_LOCK_INITIAL_THIN_VALUE);
}
/*
* Give back the thread ID.
*/
static void releaseThreadId(Thread* thread)
{
assert(thread->threadId > 0);
dvmClearBit(gDvm.threadIdMap, (thread->threadId >> 1) - 1);
thread->threadId = 0;
}
/*
* Add a stack frame that makes it look like the native code in the main
* thread was originally invoked from interpreted code. This gives us a
* place to hang JNI local references. The VM spec says (v2 5.2) that the
* VM begins by executing "main" in a class, so in a way this brings us
* closer to the spec.
*/
static bool createFakeEntryFrame(Thread* thread)
{
assert(thread->threadId == kMainThreadId); // main thread only
/* find the method on first use */
if (gDvm.methFakeNativeEntry == NULL) {
ClassObject* nativeStart;
Method* mainMeth;
nativeStart = dvmFindSystemClassNoInit(
"Ldalvik/system/NativeStart;");
if (nativeStart == NULL) {
LOGE("Unable to find dalvik.system.NativeStart class\n");
return false;
}
/*
* Because we are creating a frame that represents application code, we
* want to stuff the application class loader into the method's class
* loader field, even though we're using the system class loader to
* load it. This makes life easier over in JNI FindClass (though it
* could bite us in other ways).
*
* Unfortunately this is occurring too early in the initialization,
* of necessity coming before JNI is initialized, and we're not quite
* ready to set up the application class loader.
*
* So we save a pointer to the method in gDvm.methFakeNativeEntry
* and check it in FindClass. The method is private so nobody else
* can call it.
*/
//nativeStart->classLoader = dvmGetSystemClassLoader();
mainMeth = dvmFindDirectMethodByDescriptor(nativeStart,
"main", "([Ljava/lang/String;)V");
if (mainMeth == NULL) {
LOGE("Unable to find 'main' in dalvik.system.NativeStart\n");
return false;
}
gDvm.methFakeNativeEntry = mainMeth;
}
return dvmPushJNIFrame(thread, gDvm.methFakeNativeEntry);
}
/*
* Add a stack frame that makes it look like the native thread has been
* executing interpreted code. This gives us a place to hang JNI local
* references.
*/
static bool createFakeRunFrame(Thread* thread)
{
ClassObject* nativeStart;
Method* runMeth;
assert(thread->threadId != 1); // not for main thread
nativeStart =
dvmFindSystemClassNoInit("Ldalvik/system/NativeStart;");
if (nativeStart == NULL) {
LOGE("Unable to find dalvik.system.NativeStart class\n");
return false;
}
runMeth = dvmFindVirtualMethodByDescriptor(nativeStart, "run", "()V");
if (runMeth == NULL) {
LOGE("Unable to find 'run' in dalvik.system.NativeStart\n");
return false;
}
return dvmPushJNIFrame(thread, runMeth);
}
/*
* Helper function to set the name of the current thread
*/
static void setThreadName(const char *threadName)
{
#if defined(HAVE_PRCTL)
int hasAt = 0;
int hasDot = 0;
const char *s = threadName;
while (*s) {
if (*s == '.') hasDot = 1;
else if (*s == '@') hasAt = 1;
s++;
}
int len = s - threadName;
if (len < 15 || hasAt || !hasDot) {
s = threadName;
} else {
s = threadName + len - 15;
}
prctl(PR_SET_NAME, (unsigned long) s, 0, 0, 0);
#endif
}
/*
* Create a thread as a result of java.lang.Thread.start().
*
* We do have to worry about some concurrency problems, e.g. programs
* that try to call Thread.start() on the same object from multiple threads.
* (This will fail for all but one, but we have to make sure that it succeeds
* for exactly one.)
*
* Some of the complexity here arises from our desire to mimic the
* Thread vs. VMThread class decomposition we inherited. We've been given
* a Thread, and now we need to create a VMThread and then populate both
* objects. We also need to create one of our internal Thread objects.
*
* Pass in a stack size of 0 to get the default.
*/
bool dvmCreateInterpThread(Object* threadObj, int reqStackSize)
{
pthread_attr_t threadAttr;
pthread_t threadHandle;
Thread* self;
Thread* newThread = NULL;
Object* vmThreadObj = NULL;
int stackSize;
assert(threadObj != NULL);
if(gDvm.zygote) {
// 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;
}
}
self = dvmThreadSelf();
if (reqStackSize == 0)
stackSize = gDvm.stackSize;
else if (reqStackSize < kMinStackSize)
stackSize = kMinStackSize;
else if (reqStackSize > kMaxStackSize)
stackSize = kMaxStackSize;
else
stackSize = reqStackSize;
pthread_attr_init(&threadAttr);
pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
/*
* To minimize the time spent in the critical section, we allocate the
* vmThread object here.
*/
vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT);
if (vmThreadObj == NULL)
goto fail;
newThread = allocThread(stackSize);
if (newThread == NULL)
goto fail;
newThread->threadObj = threadObj;
assert(newThread->status == THREAD_INITIALIZING);
/*
* We need to lock out other threads while we test and set the
* "vmThread" field in java.lang.Thread, because we use that to determine
* if this thread has been started before. We use the thread list lock
* because it's handy and we're going to need to grab it again soon
* anyway.
*/
dvmLockThreadList(self);
if (dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread) != NULL) {
dvmUnlockThreadList();
dvmThrowException("Ljava/lang/IllegalThreadStateException;",
"thread has already been started");
goto fail;
}
/*
* There are actually three data structures: Thread (object), VMThread
* (object), and Thread (C struct). All of them point to at least one
* other.
*
* As soon as "VMThread.vmData" is assigned, other threads can start
* making calls into us (e.g. setPriority).
*/
dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)newThread);
dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj);
/*
* Thread creation might take a while, so release the lock.
*/
dvmUnlockThreadList();
int cc, oldStatus;
oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart,
newThread);
oldStatus = dvmChangeStatus(self, oldStatus);
if (cc != 0) {
/*
* Failure generally indicates that we have exceeded system
* resource limits. VirtualMachineError is probably too severe,
* so use OutOfMemoryError.
*/
LOGE("Thread creation failed (err=%s)\n", strerror(errno));
dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, NULL);
dvmThrowException("Ljava/lang/OutOfMemoryError;",
"thread creation failed");
goto fail;
}
/*
* We need to wait for the thread to start. Otherwise, depending on
* the whims of the OS scheduler, we could return and the code in our
* thread could try to do operations on the new thread before it had
* finished starting.
*
* The new thread will lock the thread list, change its state to
* THREAD_STARTING, broadcast to gDvm.threadStartCond, and then sleep
* on gDvm.threadStartCond (which uses the thread list lock). This
* thread (the parent) will either see that the thread is already ready
* after we grab the thread list lock, or will be awakened from the
* condition variable on the broadcast.
*
* We don't want to stall the rest of the VM while the new thread
* starts, which can happen if the GC wakes up at the wrong moment.
* So, we change our own status to VMWAIT, and self-suspend if
* necessary after we finish adding the new thread.
*
*
* We have to deal with an odd race with the GC/debugger suspension
* mechanism when creating a new thread. The information about whether
* or not a thread should be suspended is contained entirely within
* the Thread struct; this is usually cleaner to deal with than having
* one or more globally-visible suspension flags. The trouble is that
* we could create the thread while the VM is trying to suspend all
* threads. The suspend-count won't be nonzero for the new thread,
* so dvmChangeStatus(THREAD_RUNNING) won't cause a suspension.
*
* The easiest way to deal with this is to prevent the new thread from
* running until the parent says it's okay. This results in the
* following (correct) sequence of events for a "badly timed" GC
* (where '-' is us, 'o' is the child, and '+' is some other thread):
*
* - call pthread_create()
* - lock thread list
* - put self into THREAD_VMWAIT so GC doesn't wait for us
* - sleep on condition var (mutex = thread list lock) until child starts
* + GC triggered by another thread
* + thread list locked; suspend counts updated; thread list unlocked
* + loop waiting for all runnable threads to suspend
* + success, start GC
* o child thread wakes, signals condition var to wake parent
* o child waits for parent ack on condition variable
* - we wake up, locking thread list
* - add child to thread list
* - unlock thread list
* - change our state back to THREAD_RUNNING; GC causes us to suspend
* + GC finishes; all threads in thread list are resumed
* - lock thread list
* - set child to THREAD_VMWAIT, and signal it to start
* - unlock thread list
* o child resumes
* o child changes state to THREAD_RUNNING
*
* The above shows the GC starting up during thread creation, but if
* it starts anywhere after VMThread.create() is called it will
* produce the same series of events.
*
* Once the child is in the thread list, it will be suspended and
* resumed like any other thread. In the above scenario the resume-all
* code will try to resume the new thread, which was never actually
* suspended, and try to decrement the child's thread suspend count to -1.
* We can catch this in the resume-all code.
*
* Bouncing back and forth between threads like this adds a small amount
* of scheduler overhead to thread startup.
*
* One alternative to having the child wait for the parent would be
* to have the child inherit the parents' suspension count. This
* would work for a GC, since we can safely assume that the parent
* thread didn't cause it, but we must only do so if the parent suspension
* was caused by a suspend-all. If the parent was being asked to
* suspend singly by the debugger, the child should not inherit the value.
*
* We could also have a global "new thread suspend count" that gets
* picked up by new threads before changing state to THREAD_RUNNING.
* This would be protected by the thread list lock and set by a
* suspend-all.
*/
dvmLockThreadList(self);
assert(self->status == THREAD_RUNNING);
self->status = THREAD_VMWAIT;
while (newThread->status != THREAD_STARTING)
pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock);
LOG_THREAD("threadid=%d: adding to list\n", newThread->threadId);
newThread->next = gDvm.threadList->next;
if (newThread->next != NULL)
newThread->next->prev = newThread;
newThread->prev = gDvm.threadList;
gDvm.threadList->next = newThread;
if (!dvmGetFieldBoolean(threadObj, gDvm.offJavaLangThread_daemon))
gDvm.nonDaemonThreadCount++; // guarded by thread list lock
dvmUnlockThreadList();
/* change status back to RUNNING, self-suspending if necessary */
dvmChangeStatus(self, THREAD_RUNNING);
/*
* Tell the new thread to start.
*
* We must hold the thread list lock before messing with another thread.
* In the general case we would also need to verify that newThread was
* still in the thread list, but in our case the thread has not started
* executing user code and therefore has not had a chance to exit.
*
* We move it to VMWAIT, and it then shifts itself to RUNNING, which
* comes with a suspend-pending check.
*/
dvmLockThreadList(self);
assert(newThread->status == THREAD_STARTING);
newThread->status = THREAD_VMWAIT;
pthread_cond_broadcast(&gDvm.threadStartCond);
dvmUnlockThreadList();
dvmReleaseTrackedAlloc(vmThreadObj, NULL);
return true;
fail:
freeThread(newThread);
dvmReleaseTrackedAlloc(vmThreadObj, NULL);
return false;
}
/*
* pthread entry function for threads started from interpreted code.
*/
static void* interpThreadStart(void* arg)
{
Thread* self = (Thread*) arg;
char *threadName = dvmGetThreadName(self);
setThreadName(threadName);
free(threadName);
/*
* Finish initializing the Thread struct.
*/
prepareThread(self);
LOG_THREAD("threadid=%d: created from interp\n", self->threadId);
/*
* Change our status and wake our parent, who will add us to the
* thread list and advance our state to VMWAIT.
*/
dvmLockThreadList(self);
self->status = THREAD_STARTING;
pthread_cond_broadcast(&gDvm.threadStartCond);
/*
* Wait until the parent says we can go. Assuming there wasn't a
* suspend pending, this will happen immediately. When it completes,
* we're full-fledged citizens of the VM.
*
* We have to use THREAD_VMWAIT here rather than THREAD_RUNNING
* because the pthread_cond_wait below needs to reacquire a lock that
* suspend-all is also interested in. If we get unlucky, the parent could
* change us to THREAD_RUNNING, then a GC could start before we get
* signaled, and suspend-all will grab the thread list lock and then
* wait for us to suspend. We'll be in the tail end of pthread_cond_wait
* trying to get the lock.
*/
while (self->status != THREAD_VMWAIT)
pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock);
dvmUnlockThreadList();
/*
* Add a JNI context.
*/
self->jniEnv = dvmCreateJNIEnv(self);
/*
* Change our state so the GC will wait for us from now on. If a GC is
* in progress this call will suspend us.
*/
dvmChangeStatus(self, THREAD_RUNNING);
/*
* Notify the debugger & DDM. The debugger notification may cause
* us to suspend ourselves (and others).
*/
if (gDvm.debuggerConnected)
dvmDbgPostThreadStart(self);
/*
* Set the system thread priority according to the Thread object's
* priority level. We don't usually need to do this, because both the
* Thread object and system thread priorities inherit from parents. The
* tricky case is when somebody creates a Thread object, calls
* setPriority(), and then starts the thread. We could manage this with
* a "needs priority update" flag to avoid the redundant call.
*/
int priority = dvmGetFieldInt(self->threadObj,
gDvm.offJavaLangThread_priority);
dvmChangeThreadPriority(self, priority);
/*
* Execute the "run" method.
*
* At this point our stack is empty, so somebody who comes looking for
* stack traces right now won't have much to look at. This is normal.
*/
Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run];
JValue unused;
LOGV("threadid=%d: calling run()\n", self->threadId);
assert(strcmp(run->name, "run") == 0);
dvmCallMethod(self, run, self->threadObj, &unused);
LOGV("threadid=%d: exiting\n", self->threadId);
/*
* Remove the thread from various lists, report its death, and free
* its resources.
*/
dvmDetachCurrentThread();
return NULL;
}
/*
* The current thread is exiting with an uncaught exception. The
* Java programming language allows the application to provide a
* thread-exit-uncaught-exception handler for the VM, for a specific
* Thread, and for all threads in a ThreadGroup.
*
* Version 1.5 added the per-thread handler. We need to call
* "uncaughtException" in the handler object, which is either the
* ThreadGroup object or the Thread-specific handler.
*/
static void threadExitUncaughtException(Thread* self, Object* group)
{
Object* exception;
Object* handlerObj;
ClassObject* throwable;
Method* uncaughtHandler = NULL;
InstField* threadHandler;
LOGW("threadid=%d: thread exiting with uncaught exception (group=%p)\n",
self->threadId, group);
assert(group != NULL);
/*
* Get a pointer to the exception, then clear out the one in the
* thread. We don't want to have it set when executing interpreted code.
*/
exception = dvmGetException(self);
dvmAddTrackedAlloc(exception, self);
dvmClearException(self);
/*
* Get the Thread's "uncaughtHandler" object. Use it if non-NULL;
* else use "group" (which is an instance of UncaughtExceptionHandler).
*/
threadHandler = dvmFindInstanceField(gDvm.classJavaLangThread,
"uncaughtHandler", "Ljava/lang/Thread$UncaughtExceptionHandler;");
if (threadHandler == NULL) {
LOGW("WARNING: no 'uncaughtHandler' field in java/lang/Thread\n");
goto bail;
}
handlerObj = dvmGetFieldObject(self->threadObj, threadHandler->byteOffset);
if (handlerObj == NULL)
handlerObj = group;
/*
* Find the "uncaughtHandler" field in this object.
*/
uncaughtHandler = dvmFindVirtualMethodHierByDescriptor(handlerObj->clazz,
"uncaughtException", "(Ljava/lang/Thread;Ljava/lang/Throwable;)V");
if (uncaughtHandler != NULL) {
//LOGI("+++ calling %s.uncaughtException\n",
// handlerObj->clazz->descriptor);
JValue unused;
dvmCallMethod(self, uncaughtHandler, handlerObj, &unused,
self->threadObj, exception);
} else {
/* restore it and dump a stack trace */
LOGW("WARNING: no 'uncaughtException' method in class %s\n",
handlerObj->clazz->descriptor);
dvmSetException(self, exception);
dvmLogExceptionStackTrace();
}
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);
}
/*
* Create an internal VM thread, for things like JDWP and finalizers.
*
* The easiest way to do this is create a new thread and then use the
* JNI AttachCurrentThread implementation.
*
* This does not return until after the new thread has begun executing.
*/
bool dvmCreateInternalThread(pthread_t* pHandle, const char* name,
InternalThreadStart func, void* funcArg)
{
InternalStartArgs* pArgs;
Object* systemGroup;
pthread_attr_t threadAttr;
volatile Thread* newThread = NULL;
volatile int createStatus = 0;
systemGroup = dvmGetSystemThreadGroup();
if (systemGroup == NULL)
return false;
pArgs = (InternalStartArgs*) malloc(sizeof(*pArgs));
pArgs->func = func;
pArgs->funcArg = funcArg;
pArgs->name = strdup(name); // storage will be owned by new thread
pArgs->group = systemGroup;
pArgs->isDaemon = true;
pArgs->pThread = &newThread;
pArgs->pCreateStatus = &createStatus;
pthread_attr_init(&threadAttr);
//pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
if (pthread_create(pHandle, &threadAttr, internalThreadStart,
pArgs) != 0)
{
LOGE("internal thread creation failed\n");
free(pArgs->name);
free(pArgs);
return false;
}
/*
* Wait for the child to start. This gives us an opportunity to make
* sure that the thread started correctly, and allows our caller to
* assume that the thread has started running.
*
* Because we aren't holding a lock across the thread creation, it's
* possible that the child will already have completed its
* initialization. Because the child only adjusts "createStatus" while
* holding the thread list lock, the initial condition on the "while"
* loop will correctly avoid the wait if this occurs.
*
* It's also possible that we'll have to wait for the thread to finish
* being created, and as part of allocating a Thread object it might
* need to initiate a GC. We switch to VMWAIT while we pause.
*/
Thread* self = dvmThreadSelf();
int oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
dvmLockThreadList(self);
while (createStatus == 0)
pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock);
if (newThread == NULL) {
LOGW("internal thread create failed (createStatus=%d)\n", createStatus);
assert(createStatus < 0);
/* don't free pArgs -- if pthread_create succeeded, child owns it */
dvmUnlockThreadList();
dvmChangeStatus(self, oldStatus);
return false;
}
/* thread could be in any state now (except early init states) */
//assert(newThread->status == THREAD_RUNNING);
dvmUnlockThreadList();
dvmChangeStatus(self, oldStatus);
return true;
}
/*
* pthread entry function for internally-created threads.
*
* We are expected to free "arg" and its contents. If we're a daemon
* thread, and we get cancelled abruptly when the VM shuts down, the
* storage won't be freed. If this becomes a concern we can make a copy
* on the stack.
*/
static void* internalThreadStart(void* arg)
{
InternalStartArgs* pArgs = (InternalStartArgs*) arg;
JavaVMAttachArgs jniArgs;
jniArgs.version = JNI_VERSION_1_2;
jniArgs.name = pArgs->name;
jniArgs.group = pArgs->group;
setThreadName(pArgs->name);
/* use local jniArgs as stack top */
if (dvmAttachCurrentThread(&jniArgs, pArgs->isDaemon)) {
/*
* Tell the parent of our success.
*
* threadListLock is the mutex for threadStartCond.
*/
dvmLockThreadList(dvmThreadSelf());
*pArgs->pCreateStatus = 1;
*pArgs->pThread = dvmThreadSelf();
pthread_cond_broadcast(&gDvm.threadStartCond);
dvmUnlockThreadList();
LOG_THREAD("threadid=%d: internal '%s'\n",
dvmThreadSelf()->threadId, pArgs->name);
/* execute */
(*pArgs->func)(pArgs->funcArg);
/* detach ourselves */
dvmDetachCurrentThread();
} else {
/*
* Tell the parent of our failure. We don't have a Thread struct,
* so we can't be suspended, so we don't need to enter a critical
* section.
*/
dvmLockThreadList(dvmThreadSelf());
*pArgs->pCreateStatus = -1;
assert(*pArgs->pThread == NULL);
pthread_cond_broadcast(&gDvm.threadStartCond);
dvmUnlockThreadList();
assert(*pArgs->pThread == NULL);
}
free(pArgs->name);
free(pArgs);
return NULL;
}
/*
* Attach the current thread to the VM.
*
* Used for internally-created threads and JNI's AttachCurrentThread.
*/
bool dvmAttachCurrentThread(const JavaVMAttachArgs* pArgs, bool isDaemon)
{
Thread* self = NULL;
Object* threadObj = NULL;
Object* vmThreadObj = NULL;
StringObject* threadNameStr = NULL;
Method* init;
bool ok, ret;
/* establish a basic sense of self */
self = allocThread(gDvm.stackSize);
if (self == NULL)
goto fail;
setThreadSelf(self);
/*
* Create Thread and VMThread objects. We have to use ALLOC_NO_GC
* because this thread is not yet visible to the VM. We could also
* just grab the GC lock earlier, but that leaves us executing
* interpreted code with the lock held, which is not prudent.
*
* The alloc calls will block if a GC is in progress, so we don't need
* to check for global suspension here.
*
* It's also possible for the allocation calls to *cause* a GC.
*/
//BUG: deadlock if a GC happens here during HeapWorker creation
threadObj = dvmAllocObject(gDvm.classJavaLangThread, ALLOC_NO_GC);
if (threadObj == NULL)
goto fail;
vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_NO_GC);
if (vmThreadObj == NULL)
goto fail;
self->threadObj = threadObj;
dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)self);
/*
* Do some java.lang.Thread constructor prep before we lock stuff down.
*/
if (pArgs->name != NULL) {
threadNameStr = dvmCreateStringFromCstr(pArgs->name, ALLOC_NO_GC);
if (threadNameStr == NULL) {
assert(dvmCheckException(dvmThreadSelf()));
goto fail;
}
}
init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangThread, "<init>",
"(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V");
if (init == NULL) {
assert(dvmCheckException(dvmThreadSelf()));
goto fail;
}
/*
* Finish our thread prep. We need to do this before invoking any
* interpreted code. prepareThread() requires that we hold the thread
* list lock.
*/
dvmLockThreadList(self);
ok = prepareThread(self);
dvmUnlockThreadList();
if (!ok)
goto fail;
self->jniEnv = dvmCreateJNIEnv(self);
if (self->jniEnv == NULL)
goto fail;
/*
* Create a "fake" JNI frame at the top of the main thread interp stack.
* It isn't really necessary for the internal threads, but it gives
* the debugger something to show. It is essential for the JNI-attached
* threads.
*/
if (!createFakeRunFrame(self))
goto fail;
/*
* The native side of the thread is ready; add it to the list.
*/
LOG_THREAD("threadid=%d: adding to list (attached)\n", self->threadId);
/* Start off in VMWAIT, because we may be about to block
* on the heap lock, and we don't want any suspensions
* to wait for us.
*/
self->status = THREAD_VMWAIT;
/*
* Add ourselves to the thread list. Once we finish here we are
* visible to the debugger and the GC.
*/
dvmLockThreadList(self);
self->next = gDvm.threadList->next;
if (self->next != NULL)
self->next->prev = self;
self->prev = gDvm.threadList;
gDvm.threadList->next = self;
if (!isDaemon)
gDvm.nonDaemonThreadCount++;
dvmUnlockThreadList();
/*
* It's possible that a GC is currently running. Our thread
* wasn't in the list when the GC started, so it's not properly
* suspended in that case. Synchronize on the heap lock (held
* when a GC is happening) to guarantee that any GCs from here
* on will see this thread in the list.
*/
dvmLockMutex(&gDvm.gcHeapLock);
dvmUnlockMutex(&gDvm.gcHeapLock);
/*
* Switch to the running state now that we're ready for
* suspensions. This call may suspend.
*/
dvmChangeStatus(self, THREAD_RUNNING);
/*
* Now we're ready to run some interpreted code.
*
* We need to construct the Thread object and set the VMThread field.
* Setting VMThread tells interpreted code that we're alive.
*
* Call the (group, name, priority, daemon) constructor on the Thread.
* This sets the thread's name and adds it to the specified group, and
* provides values for priority and daemon (which are normally inherited
* from the current thread).
*/
JValue unused;
dvmCallMethod(self, init, threadObj, &unused, (Object*)pArgs->group,
threadNameStr, getThreadPriorityFromSystem(), isDaemon);
if (dvmCheckException(self)) {
LOGE("exception thrown while constructing attached thread object\n");
goto fail_unlink;
}
//if (isDaemon)
// dvmSetFieldBoolean(threadObj, gDvm.offJavaLangThread_daemon, true);
/*
* Set the VMThread field, which tells interpreted code that we're alive.
*
* The risk of a thread start collision here is very low; somebody
* would have to be deliberately polling the ThreadGroup list and
* trying to start threads against anything it sees, which would
* generally cause problems for all thread creation. However, for
* correctness we test "vmThread" before setting it.
*/
if (dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread) != NULL) {
dvmThrowException("Ljava/lang/IllegalThreadStateException;",
"thread has already been started");
/* We don't want to free anything associated with the thread
* because someone is obviously interested in it. Just let
* it go and hope it will clean itself up when its finished.
* This case should never happen anyway.
*
* Since we're letting it live, we need to finish setting it up.
* We just have to let the caller know that the intended operation
* has failed.
*
* [ This seems strange -- stepping on the vmThread object that's
* already present seems like a bad idea. TODO: figure this out. ]
*/
ret = false;
} else
ret = true;
dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj);
/* These are now reachable from the thread groups. */
dvmClearAllocFlags(threadObj, ALLOC_NO_GC);
dvmClearAllocFlags(vmThreadObj, ALLOC_NO_GC);
/*
* The thread is ready to go; let the debugger see it.
*/
self->threadObj = threadObj;
LOG_THREAD("threadid=%d: attached from native, name=%s\n",
self->threadId, pArgs->name);
/* tell the debugger & DDM */
if (gDvm.debuggerConnected)
dvmDbgPostThreadStart(self);
return ret;
fail_unlink:
dvmLockThreadList(self);
unlinkThread(self);
if (!isDaemon)
gDvm.nonDaemonThreadCount--;
dvmUnlockThreadList();
/* fall through to "fail" */
fail:
dvmClearAllocFlags(threadObj, ALLOC_NO_GC);
dvmClearAllocFlags(vmThreadObj, ALLOC_NO_GC);
if (self != NULL) {
if (self->jniEnv != NULL) {
dvmDestroyJNIEnv(self->jniEnv);
self->jniEnv = NULL;
}
freeThread(self);
}
setThreadSelf(NULL);
return false;
}
/*
* Detach the thread from the various data structures, notify other threads
* that are waiting to "join" it, and free up all heap-allocated storage.
*
* Used for all threads.
*
* When we get here the interpreted stack should be empty. The JNI 1.6 spec
* requires us to enforce this for the DetachCurrentThread call, probably
* because it also says that DetachCurrentThread causes all monitors
* associated with the thread to be released. (Because the stack is empty,
* we only have to worry about explicit JNI calls to MonitorEnter.)
*
* THOUGHT:
* We might want to avoid freeing our internal Thread structure until the
* associated Thread/VMThread objects get GCed. Our Thread is impossible to
* get to once the thread shuts down, but there is a small possibility of
* an operation starting in another thread before this thread halts, and
* finishing much later (perhaps the thread got stalled by a weird OS bug).
* We don't want something like Thread.isInterrupted() crawling through
* freed storage. Can do with a Thread finalizer, or by creating a
* dedicated ThreadObject class for java/lang/Thread and moving all of our
* state into that.
*/
void dvmDetachCurrentThread(void)
{
Thread* self = dvmThreadSelf();
Object* vmThread;
Object* group;
/*
* Make sure we're not detaching a thread that's still running. (This
* could happen with an explicit JNI detach call.)
*
* A thread created by interpreted code will finish with a depth of
* zero, while a JNI-attached thread will have the synthetic "stack
* starter" native method at the top.
*/
int curDepth = dvmComputeExactFrameDepth(self->curFrame);
if (curDepth != 0) {
bool topIsNative = false;
if (curDepth == 1) {
/* not expecting a lingering break frame; just look at curFrame */
assert(!dvmIsBreakFrame(self->curFrame));
StackSaveArea* ssa = SAVEAREA_FROM_FP(self->curFrame);
if (dvmIsNativeMethod(ssa->method))
topIsNative = true;
}
if (!topIsNative) {
LOGE("ERROR: detaching thread with interp frames (count=%d)\n",
curDepth);
dvmDumpThread(self, false);
dvmAbort();
}
}
group = dvmGetFieldObject(self->threadObj, gDvm.offJavaLangThread_group);
LOG_THREAD("threadid=%d: detach (group=%p)\n", self->threadId, group);
/*
* Release any held monitors. Since there are no interpreted stack
* frames, the only thing left are the monitors held by JNI MonitorEnter
* calls.
*/
dvmReleaseJniMonitors(self);
/*
* Do some thread-exit uncaught exception processing if necessary.
*/
if (dvmCheckException(self))
threadExitUncaughtException(self, group);
/*
* Remove the thread from the thread group.
*/
if (group != NULL) {
Method* removeThread =
group->clazz->vtable[gDvm.voffJavaLangThreadGroup_removeThread];
JValue unused;
dvmCallMethod(self, removeThread, group, &unused, self->threadObj);
}
/*
* Clear the vmThread reference in the Thread object. Interpreted code
* will now see that this Thread is not running. As this may be the
* only reference to the VMThread object that the VM knows about, we
* have to create an internal reference to it first.
*/
vmThread = dvmGetFieldObject(self->threadObj,
gDvm.offJavaLangThread_vmThread);
dvmAddTrackedAlloc(vmThread, self);
dvmSetFieldObject(self->threadObj, gDvm.offJavaLangThread_vmThread, NULL);
/* clear out our struct Thread pointer, since it's going away */
dvmSetFieldObject(vmThread, gDvm.offJavaLangVMThread_vmData, NULL);
/*
* Tell the debugger & DDM. This may cause the current thread or all
* threads to suspend.
*
* The JDWP spec is somewhat vague about when this happens, other than
* that it's issued by the dying thread, which may still appear in
* an "all threads" listing.
*/
if (gDvm.debuggerConnected)
dvmDbgPostThreadDeath(self);
/*
* Thread.join() is implemented as an Object.wait() on the VMThread
* object. Signal anyone who is waiting.
*/
dvmLockObject(self, vmThread);
dvmObjectNotifyAll(self, vmThread);
dvmUnlockObject(self, vmThread);
dvmReleaseTrackedAlloc(vmThread, self);
vmThread = NULL;
/*
* We're done manipulating objects, so it's okay if the GC runs in
* parallel with us from here out. It's important to do this if
* profiling is enabled, since we can wait indefinitely.
*/
self->status = THREAD_VMWAIT;
#ifdef WITH_PROFILER
/*
* If we're doing method trace profiling, we don't want threads to exit,
* because if they do we'll end up reusing thread IDs. This complicates
* analysis and makes it impossible to have reasonable output in the
* "threads" section of the "key" file.
*
* We need to do this after Thread.join() completes, or other threads
* could get wedged. Since self->threadObj is still valid, the Thread
* object will not get GCed even though we're no longer in the ThreadGroup
* list (which is important since the profiling thread needs to get
* the thread's name).
*/
MethodTraceState* traceState = &gDvm.methodTrace;
dvmLockMutex(&traceState->startStopLock);
if (traceState->traceEnabled) {
LOGI("threadid=%d: waiting for method trace to finish\n",
self->threadId);
while (traceState->traceEnabled) {
int cc;
cc = pthread_cond_wait(&traceState->threadExitCond,
&traceState->startStopLock);
assert(cc == 0);
}
}
dvmUnlockMutex(&traceState->startStopLock);
#endif
dvmLockThreadList(self);
/*
* Lose the JNI context.
*/
dvmDestroyJNIEnv(self->jniEnv);
self->jniEnv = NULL;
self->status = THREAD_ZOMBIE;
/*
* Remove ourselves from the internal thread list.
*/
unlinkThread(self);
/*
* If we're the last one standing, signal anybody waiting in
* DestroyJavaVM that it's okay to exit.
*/
if (!dvmGetFieldBoolean(self->threadObj, gDvm.offJavaLangThread_daemon)) {
gDvm.nonDaemonThreadCount--; // guarded by thread list lock
if (gDvm.nonDaemonThreadCount == 0) {
int cc;
LOGV("threadid=%d: last non-daemon thread\n", self->threadId);
//dvmDumpAllThreads(false);
// cond var guarded by threadListLock, which we already hold
cc = pthread_cond_signal(&gDvm.vmExitCond);
assert(cc == 0);
}
}
LOGV("threadid=%d: bye!\n", self->threadId);
releaseThreadId(self);
dvmUnlockThreadList();
setThreadSelf(NULL);
if (self->statFile > 0) {
close(self->statFile);
}
freeThread(self);
}
/*
* Suspend a single thread. Do not use to suspend yourself.
*
* This is used primarily for debugger/DDMS activity. Does not return
* until the thread has suspended or is in a "safe" state (e.g. executing
* native code outside the VM).
*
* The thread list lock should be held before calling here -- it's not
* entirely safe to hang on to a Thread* from another thread otherwise.
* (We'd need to grab it here anyway to avoid clashing with a suspend-all.)
*/
void dvmSuspendThread(Thread* thread)
{
assert(thread != NULL);
assert(thread != dvmThreadSelf());
//assert(thread->handle != dvmJdwpGetDebugThread(gDvm.jdwpState));
lockThreadSuspendCount();
dvmAddToThreadSuspendCount(&thread->suspendCount, 1);
thread->dbgSuspendCount++;
LOG_THREAD("threadid=%d: suspend++, now=%d\n",
thread->threadId, thread->suspendCount);
unlockThreadSuspendCount();
waitForThreadSuspend(dvmThreadSelf(), thread);
}
/*
* Reduce the suspend count of a thread. If it hits zero, tell it to
* resume.
*
* Used primarily for debugger/DDMS activity. The thread in question
* might have been suspended singly or as part of a suspend-all operation.
*
* The thread list lock should be held before calling here -- it's not
* entirely safe to hang on to a Thread* from another thread otherwise.
* (We'd need to grab it here anyway to avoid clashing with a suspend-all.)
*/
void dvmResumeThread(Thread* thread)
{
assert(thread != NULL);
assert(thread != dvmThreadSelf());
//assert(thread->handle != dvmJdwpGetDebugThread(gDvm.jdwpState));
lockThreadSuspendCount();
if (thread->suspendCount > 0) {
dvmAddToThreadSuspendCount(&thread->suspendCount, -1);
thread->dbgSuspendCount--;
} else {
LOG_THREAD("threadid=%d: suspendCount already zero\n",
thread->threadId);
}
LOG_THREAD("threadid=%d: suspend--, now=%d\n",
thread->threadId, thread->suspendCount);
if (thread->suspendCount == 0) {
int cc = pthread_cond_broadcast(&gDvm.threadSuspendCountCond);
assert(cc == 0);
}
unlockThreadSuspendCount();
}
/*
* Suspend yourself, as a result of debugger activity.
*/
void dvmSuspendSelf(bool jdwpActivity)
{
Thread* self = dvmThreadSelf();
/* debugger thread may not suspend itself due to debugger activity! */
assert(gDvm.jdwpState != NULL);
if (self->handle == dvmJdwpGetDebugThread(gDvm.jdwpState)) {
assert(false);
return;
}
/*
* Collisions with other suspends aren't really interesting. We want
* to ensure that we're the only one fiddling with the suspend count
* though.
*/
lockThreadSuspendCount();
dvmAddToThreadSuspendCount(&self->suspendCount, 1);
self->dbgSuspendCount++;
/*
* Suspend ourselves.
*/
assert(self->suspendCount > 0);
self->isSuspended = true;
LOG_THREAD("threadid=%d: self-suspending (dbg)\n", self->threadId);
/*
* Tell JDWP that we've completed suspension. The JDWP thread can't
* tell us to resume before we're fully asleep because we hold the
* suspend count lock.
*
* If we got here via waitForDebugger(), don't do this part.
*/
if (jdwpActivity) {
//LOGI("threadid=%d: clearing wait-for-event (my handle=%08x)\n",
// self->threadId, (int) self->handle);
dvmJdwpClearWaitForEventThread(gDvm.jdwpState);
}
while (self->suspendCount != 0) {
int cc;
cc = pthread_cond_wait(&gDvm.threadSuspendCountCond,
&gDvm.threadSuspendCountLock);
assert(cc == 0);
if (self->suspendCount != 0) {
/*
* The condition was signaled but we're still suspended. This
* can happen if the debugger lets go while a SIGQUIT thread
* dump event is pending (assuming SignalCatcher was resumed for
* just long enough to try to grab the thread-suspend lock).
*/
LOGD("threadid=%d: still suspended after undo (sc=%d dc=%d s=%c)\n",
self->threadId, self->suspendCount, self->dbgSuspendCount,
self->isSuspended ? 'Y' : 'N');
}
}
assert(self->suspendCount == 0 && self->dbgSuspendCount == 0);
self->isSuspended = false;
LOG_THREAD("threadid=%d: self-reviving (dbg), status=%d\n",
self->threadId, self->status);
unlockThreadSuspendCount();
}
#ifdef HAVE_GLIBC
# define NUM_FRAMES 20
# include <execinfo.h>
/*
* glibc-only stack dump function. Requires link with "--export-dynamic".
*
* TODO: move this into libs/cutils and make it work for all platforms.
*/
static void printBackTrace(void)
{
void* array[NUM_FRAMES];
size_t size;
char** strings;
size_t i;
size = backtrace(array, NUM_FRAMES);
strings = backtrace_symbols(array, size);
LOGW("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
LOGW("%s\n", strings[i]);
free(strings);
}
#else
static void printBackTrace(void) {}
#endif
/*
* Dump the state of the current thread and that of another thread that
* we think is wedged.
*/
static void dumpWedgedThread(Thread* thread)
{
char exePath[1024];
/*
* The "executablepath" function in libutils is host-side only.
*/
strcpy(exePath, "-");
#ifdef HAVE_GLIBC
{
char proc[100];
sprintf(proc, "/proc/%d/exe", getpid());
int len;
len = readlink(proc, exePath, sizeof(exePath)-1);
exePath[len] = '\0';
}
#endif
LOGW("dumping state: process %s %d\n", exePath, getpid());
dvmDumpThread(dvmThreadSelf(), false);
printBackTrace();
// dumping a running thread is risky, but could be useful
dvmDumpThread(thread, true);
// stop now and get a core dump
//abort();
}
/*
* Wait for another thread to see the pending suspension and stop running.
* It can either suspend itself or go into a non-running state such as
* VMWAIT or NATIVE in which it cannot interact with the GC.
*
* If we're running at a higher priority, sched_yield() may not do anything,
* so we need to sleep for "long enough" to guarantee that the other
* thread has a chance to finish what it's doing. Sleeping for too short
* a period (e.g. less than the resolution of the sleep clock) might cause
* the scheduler to return immediately, so we want to start with a
* "reasonable" value and expand.
*
* This does not return until the other thread has stopped running.
* Eventually we time out and the VM aborts.
*
* This does not try to detect the situation where two threads are
* waiting for each other to suspend. In normal use this is part of a
* suspend-all, which implies that the suspend-all lock is held, or as
* part of a debugger action in which the JDWP thread is always the one
* doing the suspending. (We may need to re-evaluate this now that
* getThreadStackTrace is implemented as suspend-snapshot-resume.)
*
* 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;
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
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 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",
self->threadId, thread->threadId);
dvmDumpAllThreads(false);
dvmAbort();
}
}
}
if (complained) {
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);
}
}
}
/*
* Suspend all threads except the current one. This is used by the GC,
* the debugger, and by any thread that hits a "suspend all threads"
* debugger event (e.g. breakpoint or exception).
*
* If thread N hits a "suspend all threads" breakpoint, we don't want it
* to suspend the JDWP thread. For the GC, we do, because the debugger can
* create objects and even execute arbitrary code. The "why" argument
* allows the caller to say why the suspension is taking place.
*
* This can be called when a global suspend has already happened, due to
* various debugger gymnastics, so keeping an "everybody is suspended" flag
* doesn't work.
*
* DO NOT grab any locks before calling here. We grab & release the thread
* lock and suspend lock here (and we're not using recursive threads), and
* we might have to self-suspend if somebody else beats us here.
*
* The current thread may not be attached to the VM. This can happen if
* we happen to GC as the result of an allocation of a Thread object.
*/
void dvmSuspendAllThreads(SuspendCause why)
{
Thread* self = dvmThreadSelf();
Thread* thread;
assert(why != 0);
/*
* Start by grabbing the thread suspend lock. If we can't get it, most
* likely somebody else is in the process of performing a suspend or
* resume, so lockThreadSuspend() will cause us to self-suspend.
*
* We keep the lock until all other threads are suspended.
*/
lockThreadSuspend("susp-all", why);
LOG_THREAD("threadid=%d: SuspendAll starting\n", self->threadId);
/*
* This is possible if the current thread was in VMWAIT mode when a
* suspend-all happened, and then decided to do its own suspend-all.
* This can happen when a couple of threads have simultaneous events
* of interest to the debugger.
*/
//assert(self->suspendCount == 0);
/*
* Increment everybody's suspend count (except our own).
*/
dvmLockThreadList(self);
lockThreadSuspendCount();
for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
if (thread == self)
continue;
/* debugger events don't suspend JDWP thread */
if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) &&
thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
continue;
dvmAddToThreadSuspendCount(&thread->suspendCount, 1);
if (why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT)
thread->dbgSuspendCount++;
}
unlockThreadSuspendCount();
/*
* Wait for everybody in THREAD_RUNNING state to stop. Other states
* indicate the code is either running natively or sleeping quietly.
* Any attempt to transition back to THREAD_RUNNING will cause a check
* for suspension, so it should be impossible for anything to execute
* interpreted code or modify objects (assuming native code plays nicely).
*
* It's also okay if the thread transitions to a non-RUNNING state.
*
* Note we released the threadSuspendCountLock before getting here,
* so if another thread is fiddling with its suspend count (perhaps
* self-suspending for the debugger) it won't block while we're waiting
* in here.
*/
for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
if (thread == self)
continue;
/* debugger events don't suspend JDWP thread */
if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) &&
thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
continue;
/* 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",
self->threadId,
thread->threadId, thread->status, thread->suspendCount,
thread->dbgSuspendCount, thread->isSuspended);
}
dvmUnlockThreadList();
unlockThreadSuspend();
LOG_THREAD("threadid=%d: SuspendAll complete\n", self->threadId);
}
/*
* Resume all threads that are currently suspended.
*
* The "why" must match with the previous suspend.
*/
void dvmResumeAllThreads(SuspendCause why)
{
Thread* self = dvmThreadSelf();
Thread* thread;
int cc;
lockThreadSuspend("res-all", why); /* one suspend/resume at a time */
LOG_THREAD("threadid=%d: ResumeAll starting\n", self->threadId);
/*
* Decrement the suspend counts for all threads. No need for atomic
* writes, since nobody should be moving until we decrement the count.
* We do need to hold the thread list because of JNI attaches.
*/
dvmLockThreadList(self);
lockThreadSuspendCount();
for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
if (thread == self)
continue;
/* debugger events don't suspend JDWP thread */
if ((why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT) &&
thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
{
continue;
}
if (thread->suspendCount > 0) {
dvmAddToThreadSuspendCount(&thread->suspendCount, -1);
if (why == SUSPEND_FOR_DEBUG || why == SUSPEND_FOR_DEBUG_EVENT)
thread->dbgSuspendCount--;
} else {
LOG_THREAD("threadid=%d: suspendCount already zero\n",
thread->threadId);
}
}
unlockThreadSuspendCount();
dvmUnlockThreadList();
/*
* In some ways it makes sense to continue to hold the thread-suspend
* lock while we issue the wakeup broadcast. It allows us to complete
* one operation before moving on to the next, which simplifies the
* thread activity debug traces.
*
* This approach caused us some difficulty under Linux, because the
* condition variable broadcast not only made the threads runnable,
* but actually caused them to execute, and it was a while before
* the thread performing the wakeup had an opportunity to release the
* thread-suspend lock.
*
* This is a problem because, when a thread tries to acquire that
* lock, it times out after 3 seconds. If at some point the thread
* is told to suspend, the clock resets; but since the VM is still
* theoretically mid-resume, there's no suspend pending. If, for
* example, the GC was waking threads up while the SIGQUIT handler
* was trying to acquire the lock, we would occasionally time out on
* a busy system and SignalCatcher would abort.
*
* We now perform the unlock before the wakeup broadcast. The next
* suspend can't actually start until the broadcast completes and
* returns, because we're holding the thread-suspend-count lock, but the
* suspending thread is now able to make progress and we avoid the abort.
*
* (Technically there is a narrow window between when we release
* the thread-suspend lock and grab the thread-suspend-count lock.
* This could cause us to send a broadcast to threads with nonzero
* suspend counts, but this is expected and they'll all just fall
* right back to sleep. It's probably safe to grab the suspend-count
* lock before releasing thread-suspend, since we're still following
* the correct order of acquisition, but it feels weird.)
*/
LOG_THREAD("threadid=%d: ResumeAll waking others\n", self->threadId);
unlockThreadSuspend();
/*
* Broadcast a notification to all suspended threads, some or all of
* which may choose to wake up. No need to wait for them.
*/
lockThreadSuspendCount();
cc = pthread_cond_broadcast(&gDvm.threadSuspendCountCond);
assert(cc == 0);
unlockThreadSuspendCount();
LOG_THREAD("threadid=%d: ResumeAll complete\n", self->threadId);
}
/*
* Undo any debugger suspensions. This is called when the debugger
* disconnects.
*/
void dvmUndoDebuggerSuspensions(void)
{
Thread* self = dvmThreadSelf();
Thread* thread;
int cc;
lockThreadSuspend("undo", SUSPEND_FOR_DEBUG);
LOG_THREAD("threadid=%d: UndoDebuggerSusp starting\n", self->threadId);
/*
* Decrement the suspend counts for all threads. No need for atomic
* writes, since nobody should be moving until we decrement the count.
* We do need to hold the thread list because of JNI attaches.
*/
dvmLockThreadList(self);
lockThreadSuspendCount();
for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
if (thread == self)
continue;
/* debugger events don't suspend JDWP thread */
if (thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState)) {
assert(thread->dbgSuspendCount == 0);
continue;
}
assert(thread->suspendCount >= thread->dbgSuspendCount);
dvmAddToThreadSuspendCount(&thread->suspendCount,
-thread->dbgSuspendCount);
thread->dbgSuspendCount = 0;
}
unlockThreadSuspendCount();
dvmUnlockThreadList();
/*
* Broadcast a notification to all suspended threads, some or all of
* which may choose to wake up. No need to wait for them.
*/
lockThreadSuspendCount();
cc = pthread_cond_broadcast(&gDvm.threadSuspendCountCond);
assert(cc == 0);
unlockThreadSuspendCount();
unlockThreadSuspend();
LOG_THREAD("threadid=%d: UndoDebuggerSusp complete\n", self->threadId);
}
/*
* Determine if a thread is suspended.
*
* As with all operations on foreign threads, the caller should hold
* the thread list lock before calling.
*/
bool dvmIsSuspended(Thread* thread)
{
/*
* The thread could be:
* (1) Running happily. status is RUNNING, isSuspended is false,
* suspendCount is zero. Return "false".
* (2) Pending suspend. status is RUNNING, isSuspended is false,
* suspendCount is nonzero. Return "false".
* (3) Suspended. suspendCount is nonzero, and either (status is
* RUNNING and isSuspended is true) OR (status is !RUNNING).
* Return "true".
* (4) Waking up. suspendCount is zero, status is RUNNING and
* isSuspended is true. Return "false" (since it could change
* out from under us, unless we hold suspendCountLock).
*/
return (thread->suspendCount != 0 &&
((thread->status == THREAD_RUNNING && thread->isSuspended) ||
(thread->status != THREAD_RUNNING)));
}
/*
* Wait until another thread self-suspends. This is specifically for
* synchronization between the JDWP thread and a thread that has decided
* to suspend itself after sending an event to the debugger.
*
* Threads that encounter "suspend all" events work as well -- the thread
* in question suspends everybody else and then itself.
*
* We can't hold a thread lock here or in the caller, because we could
* get here just before the to-be-waited-for-thread issues a "suspend all".
* There's an opportunity for badness if the thread we're waiting for exits
* and gets cleaned up, but since the thread in question is processing a
* debugger event, that's not really a possibility. (To avoid deadlock,
* it's important that we not be in THREAD_RUNNING while we wait.)
*/
void dvmWaitForSuspend(Thread* thread)
{
Thread* self = dvmThreadSelf();
LOG_THREAD("threadid=%d: waiting for threadid=%d to sleep\n",
self->threadId, thread->threadId);
assert(thread->handle != dvmJdwpGetDebugThread(gDvm.jdwpState));
assert(thread != self);
assert(self->status != THREAD_RUNNING);
waitForThreadSuspend(self, thread);
LOG_THREAD("threadid=%d: threadid=%d is now asleep\n",
self->threadId, thread->threadId);
}
/*
* Check to see if we need to suspend ourselves. If so, go to sleep on
* a condition variable.
*
* Takes "self" as an argument as an optimization. Pass in NULL to have
* it do the lookup.
*
* Returns "true" if we suspended ourselves.
*/
bool dvmCheckSuspendPending(Thread* self)
{
bool didSuspend;
if (self == NULL)
self = dvmThreadSelf();
/* fast path: if count is zero, bail immediately */
if (self->suspendCount == 0)
return false;
lockThreadSuspendCount(); /* grab gDvm.threadSuspendCountLock */
assert(self->suspendCount >= 0); /* XXX: valid? useful? */
didSuspend = (self->suspendCount != 0);
self->isSuspended = true;
LOG_THREAD("threadid=%d: self-suspending\n", self->threadId);
while (self->suspendCount != 0) {
/* wait for wakeup signal; releases lock */
int cc;
cc = pthread_cond_wait(&gDvm.threadSuspendCountCond,
&gDvm.threadSuspendCountLock);
assert(cc == 0);
}
assert(self->suspendCount == 0 && self->dbgSuspendCount == 0);
self->isSuspended = false;
LOG_THREAD("threadid=%d: self-reviving, status=%d\n",
self->threadId, self->status);
unlockThreadSuspendCount();
return didSuspend;
}
/*
* Update our status.
*
* The "self" argument, which may be NULL, is accepted as an optimization.
*
* Returns the old status.
*/
ThreadStatus dvmChangeStatus(Thread* self, ThreadStatus newStatus)
{
ThreadStatus oldStatus;
if (self == NULL)
self = dvmThreadSelf();
LOGVV("threadid=%d: (status %d -> %d)\n",
self->threadId, self->status, newStatus);
oldStatus = self->status;
if (newStatus == THREAD_RUNNING) {
/*
* Change our status to THREAD_RUNNING. The transition requires
* that we check for pending suspension, because the VM considers
* us to be "asleep" in all other states.
*
* We need to do the "suspend pending" check FIRST, because it grabs
* a lock that could be held by something that wants us to suspend.
* If we're in RUNNING it will wait for us, and we'll be waiting
* for the lock it holds.
*/
assert(self->status != THREAD_RUNNING);
dvmCheckSuspendPending(self);
self->status = THREAD_RUNNING;
} else {
/*
* Change from one state to another, neither of which is
* THREAD_RUNNING. This is most common during system or thread
* initialization.
*/
self->status = newStatus;
}
return oldStatus;
}
/*
* Get a statically defined thread group from a field in the ThreadGroup
* Class object. Expected arguments are "mMain" and "mSystem".
*/
static Object* getStaticThreadGroup(const char* fieldName)
{
StaticField* groupField;
Object* groupObj;
groupField = dvmFindStaticField(gDvm.classJavaLangThreadGroup,
fieldName, "Ljava/lang/ThreadGroup;");
if (groupField == NULL) {
LOGE("java.lang.ThreadGroup does not have an '%s' field\n", fieldName);
dvmThrowException("Ljava/lang/IncompatibleClassChangeError;", NULL);
return NULL;
}
groupObj = dvmGetStaticFieldObject(groupField);
if (groupObj == NULL) {
LOGE("java.lang.ThreadGroup.%s not initialized\n", fieldName);
dvmThrowException("Ljava/lang/InternalError;", NULL);
return NULL;
}
return groupObj;
}
Object* dvmGetSystemThreadGroup(void)
{
return getStaticThreadGroup("mSystem");
}
Object* dvmGetMainThreadGroup(void)
{
return getStaticThreadGroup("mMain");
}
/*
* Given a VMThread object, return the associated Thread*.
*
* NOTE: if the thread detaches, the struct Thread will disappear, and
* we will be touching invalid data. For safety, lock the thread list
* before calling this.
*/
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;
}
/*
* Conversion map for "nice" values.
*
* We use Android thread priority constants to be consistent with the rest
* of the system. In some cases adjacent entries may overlap.
*/
static const int kNiceValues[10] = {
ANDROID_PRIORITY_LOWEST, /* 1 (MIN_PRIORITY) */
ANDROID_PRIORITY_BACKGROUND + 6,
ANDROID_PRIORITY_BACKGROUND + 3,
ANDROID_PRIORITY_BACKGROUND,
ANDROID_PRIORITY_NORMAL, /* 5 (NORM_PRIORITY) */
ANDROID_PRIORITY_NORMAL - 2,
ANDROID_PRIORITY_NORMAL - 4,
ANDROID_PRIORITY_URGENT_DISPLAY + 3,
ANDROID_PRIORITY_URGENT_DISPLAY + 2,
ANDROID_PRIORITY_URGENT_DISPLAY /* 10 (MAX_PRIORITY) */
};
/*
* Change the scheduler cgroup of the current thread.
*
* Returns 0 on success.
*/
int dvmChangeThreadSchedulerGroup(const char *cgroup)
{
#ifdef HAVE_ANDROID_OS
int fd;
char path[255];
snprintf(path, sizeof(path), "/dev/cpuctl/%s/tasks", (cgroup ? cgroup :""));
if ((fd = open(path, O_WRONLY)) < 0) {
int err = errno;
#if ENABLE_CGROUP_ERR_LOGGING
LOGW("Unable to open %s (%s)\n", path, strerror(err));
#endif
return -err;
}
if (write(fd, "0", 1) < 0) {
int err = errno;
#if ENABLE_CGROUP_ERR_LOGGING
LOGW("Unable to move tid %d to cgroup %s (%s)\n",
dvmThreadSelf()->systemTid,
(cgroup ? cgroup : "<default>"), strerror(err));
#endif
close(fd);
return -err;
}
close(fd);
return 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
* numbers indicate higher priority.
*/
void dvmChangeThreadPriority(Thread* thread, int newPriority)
{
pid_t pid = thread->systemTid;
int newNice;
if (newPriority < 1 || newPriority > 10) {
LOGW("bad priority %d\n", newPriority);
newPriority = 5;
}
newNice = kNiceValues[newPriority-1];
if (newNice >= ANDROID_PRIORITY_BACKGROUND) {
dvmChangeThreadSchedulerGroup("bg_non_interactive");
} else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) {
dvmChangeThreadSchedulerGroup(NULL);
}
if (setpriority(PRIO_PROCESS, pid, newNice) != 0) {
char* str = dvmGetThreadName(thread);
LOGI("setPriority(%d) '%s' to prio=%d(n=%d) failed: %s\n",
pid, str, newPriority, newNice, strerror(errno));
free(str);
} else {
LOGV("setPriority(%d) to prio=%d(n=%d)\n",
pid, newPriority, newNice);
}
}
/*
* Get the thread priority for the current thread by querying the system.
* This is useful when attaching a thread through JNI.
*
* Returns a value from 1 to 10 (compatible with java.lang.Thread values).
*/
static int getThreadPriorityFromSystem(void)
{
int i, sysprio, jprio;
errno = 0;
sysprio = getpriority(PRIO_PROCESS, 0);
if (sysprio == -1 && errno != 0) {
LOGW("getpriority() failed: %s\n", strerror(errno));
return THREAD_NORM_PRIORITY;
}
jprio = THREAD_MIN_PRIORITY;
for (i = 0; i < NELEM(kNiceValues); i++) {
if (sysprio >= kNiceValues[i])
break;
jprio++;
}
if (jprio > THREAD_MAX_PRIORITY)
jprio = THREAD_MAX_PRIORITY;
return jprio;
}
/*
* Return true if the thread is on gDvm.threadList.
* Caller should not hold gDvm.threadListLock.
*/
bool dvmIsOnThreadList(const Thread* thread)
{
bool ret = false;
dvmLockThreadList(NULL);
if (thread == gDvm.threadList) {
ret = true;
} else {
ret = thread->prev != NULL || thread->next != NULL;
}
dvmUnlockThreadList();
return ret;
}
/*
* Dump a thread to the log file -- just calls dvmDumpThreadEx() with an
* output target.
*/
void dvmDumpThread(Thread* thread, bool isRunning)
{
DebugOutputTarget target;
dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG);
dvmDumpThreadEx(&target, thread, 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.
* When dumping a separate thread that's still running, set "isRunning" to
* use a more cautious thread dump function.
*/
void dvmDumpThreadEx(const DebugOutputTarget* target, Thread* thread,
bool isRunning)
{
/* tied to ThreadStatus enum */
static const char* kStatusNames[] = {
"ZOMBIE", "RUNNABLE", "TIMED_WAIT", "MONITOR", "WAIT",
"INITIALIZING", "STARTING", "NATIVE", "VMWAIT"
};
Object* threadObj;
Object* groupObj;
StringObject* nameStr;
char* threadName = NULL;
char* groupName = NULL;
char schedulerGroupBuf[32];
bool isDaemon;
int priority; // java.lang.Thread priority
int policy; // pthread policy
struct sched_param sp; // pthread scheduling parameters
threadObj = thread->threadObj;
if (threadObj == NULL) {
LOGW("Can't dump thread %d: threadObj not set\n", thread->threadId);
return;
}
nameStr = (StringObject*) dvmGetFieldObject(threadObj,
gDvm.offJavaLangThread_name);
threadName = dvmCreateCstrFromString(nameStr);
priority = dvmGetFieldInt(threadObj, gDvm.offJavaLangThread_priority);
isDaemon = dvmGetFieldBoolean(threadObj, gDvm.offJavaLangThread_daemon);
if (pthread_getschedparam(pthread_self(), &policy, &sp) != 0) {
LOGW("Warning: pthread_getschedparam failed\n");
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,
gDvm.offJavaLangThread_group);
if (groupObj != NULL) {
int offset = dvmFindFieldOffset(gDvm.classJavaLangThreadGroup,
"name", "Ljava/lang/String;");
if (offset < 0) {
LOGW("Unable to find 'name' field in ThreadGroup\n");
} else {
nameStr = (StringObject*) dvmGetFieldObject(groupObj, offset);
groupName = dvmCreateCstrFromString(nameStr);
}
}
if (groupName == NULL)
groupName = strdup("(BOGUS GROUP)");
assert(thread->status < NELEM(kStatusNames));
dvmPrintDebugMessage(target,
"\"%s\"%s prio=%d tid=%d %s\n",
threadName, isDaemon ? " daemon" : "",
priority, thread->threadId, kStatusNames[thread->status]);
dvmPrintDebugMessage(target,
" | group=\"%s\" sCount=%d dsCount=%d s=%c obj=%p self=%p\n",
groupName, thread->suspendCount, thread->dbgSuspendCount,
thread->isSuspended ? 'Y' : 'N', thread->threadObj, thread);
dvmPrintDebugMessage(target,
" | sysTid=%d nice=%d sched=%d/%d cgrp=%s handle=%d\n",
thread->systemTid, getpriority(PRIO_PROCESS, thread->systemTid),
policy, sp.sched_priority, schedulerGroupBuf, (int)thread->handle);
#ifdef WITH_MONITOR_TRACKING
if (!isRunning) {
LockedObjectData* lod = thread->pLockedObjects;
if (lod != NULL)
dvmPrintDebugMessage(target, " | monitors held:\n");
else
dvmPrintDebugMessage(target, " | monitors held: <none>\n");
while (lod != NULL) {
dvmPrintDebugMessage(target, " > %p[%d] (%s)\n",
lod->obj, lod->recursionCount, lod->obj->clazz->descriptor);
lod = lod->next;
}
}
#endif
if (isRunning)
dvmDumpRunningThreadStack(target, thread);
else
dvmDumpThreadStack(target, thread);
free(threadName);
free(groupName);
}
/*
* Get the name of a thread.
*
* For correctness, the caller should hold the thread list lock to ensure
* that the thread doesn't go away mid-call.
*
* Returns a newly-allocated string, or NULL if the Thread doesn't have a name.
*/
char* dvmGetThreadName(Thread* thread)
{
StringObject* nameObj;
if (thread->threadObj == NULL) {
LOGW("threadObj is NULL, name not available\n");
return strdup("-unknown-");
}
nameObj = (StringObject*)
dvmGetFieldObject(thread->threadObj, gDvm.offJavaLangThread_name);
return dvmCreateCstrFromString(nameObj);
}
/*
* Dump all threads to the log file -- just calls dvmDumpAllThreadsEx() with
* an output target.
*/
void dvmDumpAllThreads(bool grabLock)
{
DebugOutputTarget target;
dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG);
dvmDumpAllThreadsEx(&target, grabLock);
}
/*
* Print information about all known threads. Assumes they have been
* suspended (or are in a non-interpreting state, e.g. WAIT or NATIVE).
*
* If "grabLock" is true, we grab the thread lock list. This is important
* to do unless the caller already holds the lock.
*/
void dvmDumpAllThreadsEx(const DebugOutputTarget* target, bool grabLock)
{
Thread* thread;
dvmPrintDebugMessage(target, "DALVIK THREADS:\n");
if (grabLock)
dvmLockThreadList(dvmThreadSelf());
thread = gDvm.threadList;
while (thread != NULL) {
dvmDumpThreadEx(target, thread, false);
/* verify link */
assert(thread->next == NULL || thread->next->prev == thread);
thread = thread->next;
}
if (grabLock)
dvmUnlockThreadList();
}
#ifdef WITH_MONITOR_TRACKING
/*
* Count up the #of locked objects in the current thread.
*/
static int getThreadObjectCount(const Thread* self)
{
LockedObjectData* lod;
int count = 0;
lod = self->pLockedObjects;
while (lod != NULL) {
count++;
lod = lod->next;
}
return count;
}
/*
* Add the object to the thread's locked object list if it doesn't already
* exist. The most recently added object is the most likely to be released
* next, so we insert at the head of the list.
*
* If it already exists, we increase the recursive lock count.
*
* The object's lock may be thin or fat.
*/
void dvmAddToMonitorList(Thread* self, Object* obj, bool withTrace)
{
LockedObjectData* newLod;
LockedObjectData* lod;
int* trace;
int depth;
lod = self->pLockedObjects;
while (lod != NULL) {
if (lod->obj == obj) {
lod->recursionCount++;
LOGV("+++ +recursive lock %p -> %d\n", obj, lod->recursionCount);
return;
}
lod = lod->next;
}
newLod = (LockedObjectData*) calloc(1, sizeof(LockedObjectData));
if (newLod == NULL) {
LOGE("malloc failed on %d bytes\n", sizeof(LockedObjectData));
return;
}
newLod->obj = obj;
newLod->recursionCount = 0;
if (withTrace) {
trace = dvmFillInStackTraceRaw(self, &depth);
newLod->rawStackTrace = trace;
newLod->stackDepth = depth;
}
newLod->next = self->pLockedObjects;
self->pLockedObjects = newLod;
LOGV("+++ threadid=%d: added %p, now %d\n",
self->threadId, newLod, getThreadObjectCount(self));
}
/*
* Remove the object from the thread's locked object list. If the entry
* has a nonzero recursion count, we just decrement the count instead.
*/
void dvmRemoveFromMonitorList(Thread* self, Object* obj)
{
LockedObjectData* lod;
LockedObjectData* prevLod;
lod = self->pLockedObjects;
prevLod = NULL;
while (lod != NULL) {
if (lod->obj == obj) {
if (lod->recursionCount > 0) {
lod->recursionCount--;
LOGV("+++ -recursive lock %p -> %d\n",
obj, lod->recursionCount);
return;
} else {
break;
}
}
prevLod = lod;
lod = lod->next;
}
if (lod == NULL) {
LOGW("BUG: object %p not found in thread's lock list\n", obj);
return;
}
if (prevLod == NULL) {
/* first item in list */
assert(self->pLockedObjects == lod);
self->pLockedObjects = lod->next;
} else {
/* middle/end of list */
prevLod->next = lod->next;
}
LOGV("+++ threadid=%d: removed %p, now %d\n",
self->threadId, lod, getThreadObjectCount(self));
free(lod->rawStackTrace);
free(lod);
}
/*
* If the specified object is already in the thread's locked object list,
* return the LockedObjectData struct. Otherwise return NULL.
*/
LockedObjectData* dvmFindInMonitorList(const Thread* self, const Object* obj)
{
LockedObjectData* lod;
lod = self->pLockedObjects;
while (lod != NULL) {
if (lod->obj == obj)
return lod;
lod = lod->next;
}
return NULL;
}
#endif /*WITH_MONITOR_TRACKING*/
/*
* 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) {
const StackSaveArea *saveArea;
const Method *method;
saveArea = SAVEAREA_FROM_FP(framePtr);
method = saveArea->method;
if (method != NULL && !dvmIsNativeMethod(method)) {
#ifdef COUNT_PRECISE_METHODS
/* the GC is running, so no lock required */
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;
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 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 ||
saveArea->prevFrame == NULL);
framePtr = saveArea->prevFrame;
}
}
static void gcScanReferenceTable(ReferenceTable *refTable)
{
Object **op;
//TODO: these asserts are overkill; turn them off when things stablize.
assert(refTable != NULL);
assert(refTable->table != NULL);
assert(refTable->nextEntry != NULL);
assert((uintptr_t)refTable->nextEntry >= (uintptr_t)refTable->table);
assert(refTable->nextEntry - refTable->table <= refTable->maxEntries);
op = refTable->table;
while ((uintptr_t)op < (uintptr_t)refTable->nextEntry) {
dvmMarkObjectNonNull(*(op++));
}
}
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.
*/
static void gcScanThread(Thread *thread)
{
assert(thread != NULL);
/*
* The target thread must be suspended or in a state where it can't do
* any harm (e.g. in Object.wait()). The only exception is the current
* thread, which will still be active and in the "running" state.
*
* (Newly-created threads shouldn't be able to shift themselves to
* RUNNING without a suspend-pending check, so this shouldn't cause
* a false-positive.)
*/
assert(thread->status != THREAD_RUNNING || thread->isSuspended ||
thread == dvmThreadSelf());
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_THREAD_OBJECT, thread->threadId);
dvmMarkObject(thread->threadObj); // could be NULL, when constructing
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_NATIVE_STACK, thread->threadId);
dvmMarkObject(thread->exception); // usually NULL
gcScanReferenceTable(&thread->internalLocalRefTable);
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);
gcScanReferenceTable(&thread->jniMonitorRefTable);
}
HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_JAVA_FRAME, thread->threadId);
gcScanInterpStackReferences(thread);
HPROF_CLEAR_GC_SCAN_STATE();
}
static void gcScanAllThreads()
{
Thread *thread;
/* Lock the thread list so we can safely use the
* next/prev pointers.
*/
dvmLockThreadList(dvmThreadSelf());
for (thread = gDvm.threadList; thread != NULL;
thread = thread->next)
{
/* We need to scan our own stack, so don't special-case
* the current thread.
*/
gcScanThread(thread);
}
dvmUnlockThreadList();
}
void dvmGcScanRootThreadGroups()
{
/* We scan the VM's list of threads instead of going
* through the actual ThreadGroups, but it should be
* equivalent.
*
* 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();
}
/* Converts a Linux thread state to a ThreadStatus. */
static ThreadStatus stateToStatus(char state) {
switch (state) {
case 'R': return THREAD_RUNNING; // running
case 'S': return THREAD_WAIT; // sleeping in interruptible wait
case 'D': return THREAD_WAIT; // uninterruptible disk sleep
case 'Z': return THREAD_ZOMBIE; // zombie
case 'T': return THREAD_WAIT; // traced or stopped on a signal
case 'W': return THREAD_PAGING; // paging memory
default:
LOGE("Unexpected state: %c", state);
return THREAD_NATIVE;
}
}
/* Reads the state char starting from the beginning of the file. */
static char readStateFromBeginning(Thread* thread) {
char buffer[256];
int size = read(thread->statFile, buffer, sizeof(buffer));
if (size == 0) {
LOGE("EOF trying to read stat file.");
return 0;
}
char* endOfName = (char*) memchr(buffer, ')', sizeof(buffer));
if (endOfName == NULL) {
LOGE("End of executable name not found.");
return 0;
}
char* state = endOfName + 2;
thread->stateOffset = state - buffer;
if (*state == 0) {
LOGE("State char is 0.");
}
return *state;
}
/*
* Looks for the state char at the last place we found it. Read from the
* beginning if necessary.
*/
static char readStateRelatively(Thread* thread) {
char buffer[3];
// Position file offset at end of executable name.
int result = lseek(thread->statFile, thread->stateOffset - 2, SEEK_SET);
if (result < 0) {
LOGE("lseek() error.");
return 0;
}
int size = read(thread->statFile, buffer, sizeof(buffer));
if (size < (int) sizeof(buffer)) {
LOGE("EOF trying to read stat file.");
return 0;
}
if (buffer[0] != ')') {
// The executable name must have changed.
result = lseek(thread->statFile, 0, SEEK_SET);
if (result < 0) {
LOGE("lseek() error.");
return 0;
}
return readStateFromBeginning(thread);
}
char state = buffer[2];
if (state == 0) {
LOGE("State char is 0.");
}
return state;
}
ThreadStatus dvmGetNativeThreadStatus(Thread* thread) {
// TODO: Add a custom hook so this just reads a char from memory.
ThreadStatus status = thread->status;
if (status != THREAD_NATIVE) {
// Return cached status so we don't accidentally return THREAD_NATIVE.
return status;
}
if (thread->statFile == -1) {
// We tried and failed to open the file earlier. Return current status.
return thread->status;
}
// Note: see "man proc" for the format of stat.
char state;
if (thread->statFile == 0) {
// We haven't tried to open the file yet. Do so.
char fileName[256];
sprintf(fileName, "/proc/self/task/%d/stat", thread->systemTid);
thread->statFile = open(fileName, O_RDONLY);
if (thread->statFile == NULL) {
LOGE("Error opening %s: %s", fileName, strerror(errno));
thread->statFile = -1;
return thread->status;
}
state = readStateFromBeginning(thread);
} else {
state = readStateRelatively(thread);
}
if (state == 0) {
close(thread->statFile);
thread->statFile = -1;
return thread->status;
}
ThreadStatus nativeStatus = stateToStatus(state);
// The thread status could have changed from NATIVE.
status = thread->status;
return status == THREAD_NATIVE ? nativeStatus : status;
}