Fix some potential SMP issues.
Wandered through Thread.c looking for SMP trouble spots. Changed a
couple of stores to android_atomic_release_store, and added some
comments.
Also, wrapped the self-suspend stuff that's in the thread status change
code so it only happens if we're actually going to suspend the thread.
Change-Id: I2d3feae7ce8937eada9111bd31928b61875a86d3
diff --git a/vm/Thread.c b/vm/Thread.c
index 40c79c4..9024312 100644
--- a/vm/Thread.c
+++ b/vm/Thread.c
@@ -472,6 +472,11 @@
* 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.
*
+ * This function deliberately avoids the use of dvmChangeStatus(),
+ * which could grab threadSuspendCountLock. To avoid deadlock, threads
+ * are required to grab the thread list lock before the thread suspend
+ * count lock. (See comment in DvmGlobals.)
+ *
* 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
@@ -2262,7 +2267,7 @@
* 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;
+ android_atomic_release_store(THREAD_VMWAIT, &self->status);
#ifdef WITH_PROFILER
/*
@@ -2974,8 +2979,13 @@
*
* As with all operations on foreign threads, the caller should hold
* the thread list lock before calling.
+ *
+ * If the thread is suspending or waking, these fields could be changing
+ * out from under us (or the thread could change state right after we
+ * examine it), making this generally unreliable. This is chiefly
+ * intended for use by the debugger.
*/
-bool dvmIsSuspended(Thread* thread)
+bool dvmIsSuspended(const Thread* thread)
{
/*
* The thread could be:
@@ -3052,27 +3062,27 @@
lockThreadSuspendCount(); /* grab gDvm.threadSuspendCountLock */
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);
+ if (didSuspend) {
+ self->isSuspended = true;
+ LOG_THREAD("threadid=%d: self-suspending\n", self->threadId);
+ while (self->suspendCount != 0) {
+ /* wait for wakeup signal; releases lock */
+ dvmWaitCond(&gDvm.threadSuspendCountCond,
+ &gDvm.threadSuspendCountLock);
+ }
+ assert(self->suspendCount == 0 && self->dbgSuspendCount == 0);
+ self->isSuspended = false;
+ LOG_THREAD("threadid=%d: self-reviving, status=%d\n",
+ self->threadId, self->status);
}
- assert(self->suspendCount == 0 && self->dbgSuspendCount == 0);
- self->isSuspended = false;
- LOG_THREAD("threadid=%d: self-reviving, status=%d\n",
- self->threadId, self->status);
/*
* The status change needs to happen while the suspend count lock is
* held. Otherwise we could switch to RUNNING after another thread
* increases our suspend count, which isn't a "bad" state for us
* (we'll suspend on the next check) but could be a problem for the
- * other thread (which thinks we're safely in VMWAIT or NATIVE with
- * a nonzero suspend count, and proceeds to initate GC).
+ * other thread (which thinks we're still safely in VMWAIT or NATIVE
+ * with a nonzero suspend count, and proceeds to initate GC).
*/
if (newStatus != THREAD_UNDEFINED)
self->status = newStatus;
@@ -3128,8 +3138,12 @@
} else {
/*
* Not changing to THREAD_RUNNING. No additional work required.
+ *
+ * We use a releasing store to ensure that, if we were RUNNING,
+ * any updates we previously made to objects on the managed heap
+ * will be observed before the state change.
*/
- self->status = newStatus;
+ android_atomic_release_store(newStatus, &self->status);
}
return oldStatus;
@@ -4084,6 +4098,12 @@
* (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.)
+ *
+ * Since the thread is supposed to be suspended, we should have already
+ * observed all changes to thread state, and don't need to use a memory
+ * barrier here. It's possible we might miss a wayward unsuspended
+ * thread on SMP, but as this is primarily a sanity check it's not
+ * worth slowing the common case.
*/
if (thread->status == THREAD_RUNNING && !thread->isSuspended &&
thread != dvmThreadSelf())
diff --git a/vm/Thread.h b/vm/Thread.h
index 204135a..b01676c 100644
--- a/vm/Thread.h
+++ b/vm/Thread.h
@@ -319,7 +319,7 @@
/*
* Check suspend state. Grab threadListLock before calling.
*/
-bool dvmIsSuspended(Thread* thread);
+bool dvmIsSuspended(const Thread* thread);
/*
* Wait until a thread has suspended. (Used by debugger support.)