Add fairly complete, and apparently working, support for condition
variables.
git-svn-id: svn://svn.valgrind.org/valgrind/trunk@102 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/vg_scheduler.c b/vg_scheduler.c
index 194c231..da8143c 100644
--- a/vg_scheduler.c
+++ b/vg_scheduler.c
@@ -188,9 +188,12 @@
case VgTs_WaitJoinee: VG_(printf)("WaitJoinee"); break;
case VgTs_Sleeping: VG_(printf)("Sleeping"); break;
case VgTs_WaitMX: VG_(printf)("WaitMX"); break;
+ case VgTs_WaitCV: VG_(printf)("WaitCV"); break;
default: VG_(printf)("???"); break;
}
- VG_(printf)(", waited_on_mx = %p\n", vg_threads[i].waited_on_mx );
+ VG_(printf)(", associated_mx = %p, associated_cv = %p\n",
+ vg_threads[i].associated_mx,
+ vg_threads[i].associated_cv );
VG_(pp_ExeContext)(
VG_(get_ExeContext)( False, vg_threads[i].m_eip,
vg_threads[i].m_ebp ));
@@ -513,10 +516,11 @@
tid_main = vg_alloc_ThreadState();
vg_assert(tid_main == 1);
- vg_threads[tid_main].status = VgTs_Runnable;
- vg_threads[tid_main].joiner = VG_INVALID_THREADID;
- vg_threads[tid_main].waited_on_mx = NULL;
- vg_threads[tid_main].retval = NULL; /* not important */
+ vg_threads[tid_main].status = VgTs_Runnable;
+ vg_threads[tid_main].joiner = VG_INVALID_THREADID;
+ vg_threads[tid_main].associated_mx = NULL;
+ vg_threads[tid_main].associated_cv = NULL;
+ vg_threads[tid_main].retval = NULL; /* not important */
vg_threads[tid_main].stack_highest_word
= vg_threads[tid_main].m_esp /* -4 ??? */;
@@ -1140,7 +1144,7 @@
aim is not to do too many of Phase 1 since it is expensive. */
if (0)
- VG_(printf)("SCHED: tid %d, used %d\n", tid, VG_N_THREADS);
+ VG_(printf)("SCHED: tid %d\n", tid);
/* Figure out how many bbs to ask vg_run_innerloop to do. Note
that it decrements the counter before testing it for zero, so
@@ -1363,6 +1367,23 @@
}
+static
+void do_pthread_exit ( ThreadId tid, void* retval )
+{
+ Char msg_buf[100];
+ /* We want make is appear that this thread has returned to
+ do_pthread_create_bogusRA with retval as the
+ return value. So: simple: put retval into %EAX
+ and &do_pthread_create_bogusRA into %EIP and keep going! */
+ if (VG_(clo_trace_sched)) {
+ VG_(sprintf)(msg_buf, "exiting with %p", retval);
+ print_sched_event(tid, msg_buf);
+ }
+ vg_threads[tid].m_eax = (UInt)retval;
+ vg_threads[tid].m_eip = (UInt)&VG_(pthreadreturn_bogusRA);
+ vg_threads[tid].status = VgTs_Runnable;
+}
+
/* Thread tid is exiting, by returning from the function it was
created with. Or possibly due to pthread_exit or cancellation.
@@ -1581,9 +1602,10 @@
// ***** CHECK *thread is writable
*thread = (pthread_t)tid;
- vg_threads[tid].waited_on_mx = NULL;
- vg_threads[tid].joiner = VG_INVALID_THREADID;
- vg_threads[tid].status = VgTs_Runnable;
+ vg_threads[tid].associated_mx = NULL;
+ vg_threads[tid].associated_cv = NULL;
+ vg_threads[tid].joiner = VG_INVALID_THREADID;
+ vg_threads[tid].status = VgTs_Runnable;
/* return zero */
vg_threads[tid].m_edx = 0; /* success */
@@ -1638,6 +1660,47 @@
deals with that for us.
*/
+/* Helper fns ... */
+static
+void release_one_thread_waiting_on_mutex ( pthread_mutex_t* mutex,
+ Char* caller )
+{
+ Int i;
+ Char msg_buf[100];
+
+ /* Find some arbitrary thread waiting on this mutex, and make it
+ runnable. If none are waiting, mark the mutex as not held. */
+ for (i = 1; i < VG_N_THREADS; i++) {
+ if (vg_threads[i].status == VgTs_Empty)
+ continue;
+ if (vg_threads[i].status == VgTs_WaitMX
+ && vg_threads[i].associated_mx == mutex)
+ break;
+ }
+
+ vg_assert(i <= VG_N_THREADS);
+ if (i == VG_N_THREADS) {
+ /* Nobody else is waiting on it. */
+ mutex->__m_count = 0;
+ mutex->__m_owner = VG_INVALID_THREADID;
+ } else {
+ /* Notionally transfer the hold to thread i, whose
+ pthread_mutex_lock() call now returns with 0 (success). */
+ /* The .count is already == 1. */
+ vg_assert(vg_threads[i].associated_mx == mutex);
+ mutex->__m_owner = (_pthread_descr)i;
+ vg_threads[i].status = VgTs_Runnable;
+ vg_threads[i].associated_mx = NULL;
+ vg_threads[i].m_edx = 0; /* pth_lock() success */
+
+ if (VG_(clo_trace_pthread_level) >= 1) {
+ VG_(sprintf)(msg_buf, "%s mx %p: RESUME",
+ caller, mutex );
+ print_pthread_event(i, msg_buf);
+ }
+ }
+}
+
static
void do_pthread_mutex_lock( ThreadId tid, pthread_mutex_t *mutex )
@@ -1645,7 +1708,7 @@
Char msg_buf[100];
if (VG_(clo_trace_pthread_level) >= 2) {
- VG_(sprintf)(msg_buf, "pthread_mutex_lock %p", mutex );
+ VG_(sprintf)(msg_buf, "pthread_mutex_lock mx %p ...", mutex );
print_pthread_event(tid, msg_buf);
}
@@ -1683,7 +1746,7 @@
/* return 0 (success). */
mutex->__m_count++;
vg_threads[tid].m_edx = 0;
- VG_(printf)("!!!!!! tid %d, mutex %p -> locked %d\n",
+ VG_(printf)("!!!!!! tid %d, mx %p -> locked %d\n",
tid, mutex, mutex->__m_count);
return;
} else {
@@ -1693,11 +1756,11 @@
} else {
/* Someone else has it; we have to wait. Mark ourselves
thusly. */
- vg_threads[tid].status = VgTs_WaitMX;
- vg_threads[tid].waited_on_mx = mutex;
+ vg_threads[tid].status = VgTs_WaitMX;
+ vg_threads[tid].associated_mx = mutex;
/* No assignment to %EDX, since we're blocking. */
if (VG_(clo_trace_pthread_level) >= 1) {
- VG_(sprintf)(msg_buf, "pthread_mutex_lock %p: BLOCK",
+ VG_(sprintf)(msg_buf, "pthread_mutex_lock mx %p: BLOCK",
mutex );
print_pthread_event(tid, msg_buf);
}
@@ -1710,7 +1773,7 @@
/* We get it! [for the first time]. */
mutex->__m_count = 1;
mutex->__m_owner = (_pthread_descr)tid;
- vg_assert(vg_threads[tid].waited_on_mx == NULL);
+ vg_assert(vg_threads[tid].associated_mx == NULL);
/* return 0 (success). */
vg_threads[tid].m_edx = 0;
}
@@ -1722,11 +1785,10 @@
void do_pthread_mutex_unlock ( ThreadId tid,
pthread_mutex_t *mutex )
{
- Int i;
- Char msg_buf[100];
+ Char msg_buf[100];
if (VG_(clo_trace_pthread_level) >= 2) {
- VG_(sprintf)(msg_buf, "pthread_mutex_unlock %p", mutex );
+ VG_(sprintf)(msg_buf, "pthread_mutex_unlock mx %p ...", mutex );
print_pthread_event(tid, msg_buf);
}
@@ -1773,40 +1835,10 @@
vg_assert(mutex->__m_count == 1);
vg_assert((ThreadId)mutex->__m_owner == tid);
- /* Find some arbitrary thread waiting on this mutex, and make it
- runnable. If none are waiting, mark the mutex as not held. */
- for (i = 1; i < VG_N_THREADS; i++) {
- if (vg_threads[i].status == VgTs_Empty)
- continue;
- if (vg_threads[i].status == VgTs_WaitMX
- && vg_threads[i].waited_on_mx == mutex)
- break;
- }
+ /* Release at max one thread waiting on this mutex. */
+ release_one_thread_waiting_on_mutex ( mutex, "pthread_mutex_lock" );
- vg_assert(i <= VG_N_THREADS);
- if (i == VG_N_THREADS) {
- /* Nobody else is waiting on it. */
- mutex->__m_count = 0;
- mutex->__m_owner = VG_INVALID_THREADID;
- } else {
- /* Notionally transfer the hold to thread i, whose
- pthread_mutex_lock() call now returns with 0 (success). */
- /* The .count is already == 1. */
- vg_assert(vg_threads[i].waited_on_mx == mutex);
- mutex->__m_owner = (_pthread_descr)i;
- vg_threads[i].status = VgTs_Runnable;
- vg_threads[i].waited_on_mx = NULL;
- vg_threads[i].m_edx = 0; /* pth_lock() success */
-
- if (VG_(clo_trace_pthread_level) >= 1) {
- VG_(sprintf)(msg_buf, "pthread_mutex_lock %p: RESUME",
- mutex );
- print_pthread_event(i, msg_buf);
- }
- }
-
- /* In either case, our (tid's) pth_unlock() returns with 0
- (success). */
+ /* Our (tid's) pth_unlock() returns with 0 (success). */
vg_threads[tid].m_edx = 0; /* Success. */
}
@@ -1833,15 +1865,173 @@
#define PTHREAD_COND_INITIALIZER {__LOCK_INITIALIZER, 0}
- We'll just use the __c_waiting field to point to the head of the
- list of threads waiting on this condition. Note how the static
- initialiser has __c_waiting == 0 == VG_INVALID_THREADID.
+ We don't use any fields of pthread_cond_t for anything at all.
+ Only the identity of the CVs is important.
Linux pthreads supports no attributes on condition variables, so we
- don't need to think too hard there.
-*/
+ don't need to think too hard there. */
+static
+void release_N_threads_waiting_on_cond ( pthread_cond_t* cond,
+ Int n_to_release,
+ Char* caller )
+{
+ Int i;
+ Char msg_buf[100];
+ pthread_mutex_t* mx;
+
+ while (True) {
+ if (n_to_release == 0)
+ return;
+
+ /* Find a thread waiting on this CV. */
+ for (i = 1; i < VG_N_THREADS; i++) {
+ if (vg_threads[i].status == VgTs_Empty)
+ continue;
+ if (vg_threads[i].status == VgTs_WaitCV
+ && vg_threads[i].associated_cv == cond)
+ break;
+ }
+ vg_assert(i <= VG_N_THREADS);
+
+ if (i == VG_N_THREADS) {
+ /* Nobody else is waiting on it. */
+ return;
+ }
+
+ mx = vg_threads[i].associated_mx;
+ vg_assert(mx != NULL);
+
+ if (mx->__m_owner == VG_INVALID_THREADID) {
+ /* Currently unheld; hand it out to thread i. */
+ vg_assert(mx->__m_count == 0);
+ vg_threads[i].status = VgTs_Runnable;
+ vg_threads[i].associated_cv = NULL;
+ vg_threads[i].associated_mx = NULL;
+ mx->__m_owner = (_pthread_descr)i;
+ mx->__m_count = 1;
+ vg_threads[i].m_edx = 0; /* pthread_cond_wait returns success */
+
+ if (VG_(clo_trace_pthread_level) >= 1) {
+ VG_(sprintf)(msg_buf, "%s cv %p: RESUME with mx %p",
+ caller, cond, mx );
+ print_pthread_event(i, msg_buf);
+ }
+
+ } else {
+ /* Currently held. Make thread i be blocked on it. */
+ vg_threads[i].status = VgTs_WaitMX;
+ vg_threads[i].associated_cv = NULL;
+ vg_threads[i].associated_mx = mx;
+
+ if (VG_(clo_trace_pthread_level) >= 1) {
+ VG_(sprintf)(msg_buf, "%s cv %p: BLOCK for mx %p",
+ caller, cond, mx );
+ print_pthread_event(i, msg_buf);
+ }
+
+ }
+
+ n_to_release--;
+ }
+}
+
+
+static
+void do_pthread_cond_wait ( ThreadId tid,
+ pthread_cond_t *cond,
+ pthread_mutex_t *mutex )
+{
+ Char msg_buf[100];
+
+ /* pre: mutex should be a valid mutex and owned by tid. */
+ if (VG_(clo_trace_pthread_level) >= 2) {
+ VG_(sprintf)(msg_buf, "pthread_cond_wait cv %p, mx %p ...",
+ cond, mutex );
+ print_pthread_event(tid, msg_buf);
+ }
+
+ /* Paranoia ... */
+ vg_assert(is_valid_tid(tid)
+ && vg_threads[tid].status == VgTs_Runnable);
+
+ if (mutex == NULL || cond == NULL) {
+ vg_threads[tid].m_edx = EINVAL;
+ return;
+ }
+
+ /* More paranoia ... */
+ switch (mutex->__m_kind) {
+ case PTHREAD_MUTEX_TIMED_NP:
+ case PTHREAD_MUTEX_RECURSIVE_NP:
+ case PTHREAD_MUTEX_ERRORCHECK_NP:
+ case PTHREAD_MUTEX_ADAPTIVE_NP:
+ if (mutex->__m_count >= 0) break;
+ /* else fall thru */
+ default:
+ vg_threads[tid].m_edx = EINVAL;
+ return;
+ }
+
+ /* Barf if we don't currently hold the mutex. */
+ if (mutex->__m_count == 0 /* nobody holds it */
+ || (ThreadId)mutex->__m_owner != tid /* we don't hold it */) {
+ vg_threads[tid].m_edx = EINVAL;
+ return;
+ }
+
+ /* Queue ourselves on the condition. */
+ vg_threads[tid].status = VgTs_WaitCV;
+ vg_threads[tid].associated_cv = cond;
+ vg_threads[tid].associated_mx = mutex;
+
+ if (VG_(clo_trace_pthread_level) >= 1) {
+ VG_(sprintf)(msg_buf,
+ "pthread_cond_wait cv %p, mx %p: BLOCK",
+ cond, mutex );
+ print_pthread_event(tid, msg_buf);
+ }
+
+ /* Release the mutex. */
+ release_one_thread_waiting_on_mutex ( mutex, "pthread_cond_wait " );
+}
+
+
+static
+void do_pthread_cond_signal_or_broadcast ( ThreadId tid,
+ Bool broadcast,
+ pthread_cond_t *cond )
+{
+ Char msg_buf[100];
+ Char* caller
+ = broadcast ? "pthread_cond_broadcast"
+ : "pthread_cond_signal ";
+
+ if (VG_(clo_trace_pthread_level) >= 2) {
+ VG_(sprintf)(msg_buf, "%s cv %p ...",
+ caller, cond );
+ print_pthread_event(tid, msg_buf);
+ }
+
+ /* Paranoia ... */
+ vg_assert(is_valid_tid(tid)
+ && vg_threads[tid].status == VgTs_Runnable);
+
+ if (cond == NULL) {
+ vg_threads[tid].m_edx = EINVAL;
+ return;
+ }
+
+ release_N_threads_waiting_on_cond (
+ cond,
+ broadcast ? VG_N_THREADS : 1,
+ caller
+ );
+
+ vg_threads[tid].m_edx = 0; /* success */
+}
+
/* ---------------------------------------------------------------------
Handle non-trivial client requests.
@@ -1882,6 +2072,30 @@
do_pthread_cancel( tid, (pthread_t)(arg[1]) );
break;
+ case VG_USERREQ__PTHREAD_EXIT:
+ do_pthread_exit( tid, (void*)(arg[1]) );
+ break;
+
+ case VG_USERREQ__PTHREAD_COND_WAIT:
+ do_pthread_cond_wait( tid,
+ (pthread_cond_t *)(arg[1]),
+ (pthread_mutex_t *)(arg[2]) );
+ break;
+
+ case VG_USERREQ__PTHREAD_COND_SIGNAL:
+ do_pthread_cond_signal_or_broadcast(
+ tid,
+ False, /* signal, not broadcast */
+ (pthread_cond_t *)(arg[1]) );
+ break;
+
+ case VG_USERREQ__PTHREAD_COND_BROADCAST:
+ do_pthread_cond_signal_or_broadcast(
+ tid,
+ True, /* broadcast, not signal */
+ (pthread_cond_t *)(arg[1]) );
+ break;
+
case VG_USERREQ__MAKE_NOACCESS:
case VG_USERREQ__MAKE_WRITABLE:
case VG_USERREQ__MAKE_READABLE:
@@ -1917,17 +2131,27 @@
static
void scheduler_sanity ( void )
{
- pthread_mutex_t* mutex;
+ pthread_mutex_t* mx;
+ pthread_cond_t* cv;
Int i;
/* VG_(printf)("scheduler_sanity\n"); */
for (i = 1; i < VG_N_THREADS; i++) {
+ mx = vg_threads[i].associated_mx;
+ cv = vg_threads[i].associated_cv;
if (vg_threads[i].status == VgTs_WaitMX) {
- mutex = vg_threads[i].waited_on_mx;
- vg_assert(mutex != NULL);
- vg_assert(mutex->__m_count > 0);
- vg_assert(is_valid_tid((ThreadId)mutex->__m_owner));
+ vg_assert(cv == NULL);
+ vg_assert(mx != NULL);
+ vg_assert(mx->__m_count > 0);
+ vg_assert(is_valid_tid((ThreadId)mx->__m_owner));
+ vg_assert(i != (ThreadId)mx->__m_owner);
+ /* otherwise thread i would be deadlocked. */
+ } else
+ if (vg_threads[i].status == VgTs_WaitCV) {
+ vg_assert(cv != NULL);
+ vg_assert(mx != NULL);
} else {
- vg_assert(vg_threads[i].waited_on_mx == NULL);
+ vg_assert(cv == NULL);
+ vg_assert(mx == NULL);
}
}
}