blob: fa3e20854bb693ec5bd08171b4a278d4868497f7 [file] [log] [blame]
/*--------------------------------------------------------------------*/
/*--- Thread modelling. m_threadmodel.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of Valgrind, a dynamic binary instrumentation
framework.
Copyright (C) 2005-2006 Jeremy Fitzhardinge
jeremy@goop.org
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307, USA.
The GNU General Public License is contained in the file COPYING.
*/
/*
This file implements an abstract thread model, by which a client's
thread usage is validated. The model the file implements is
intended to be enough to contain pthreads, or perhaps a superset,
but it is not pthread-specific; that's done in in vg_pthreadmodel.c
While the primary client of this file is vg_pthreadmodel.c, it is
also intended that clients can make direct use of this file for
home-grown threading libraries. It is therefore useful for both
validating the library itself as well as the users of the library.
A note on terminology:
The states referred to in this file ("blocked state", "zombie
state") are specific to this threads model, and have nothing do to
with the scheduler status for a thread. For example, a thread
could be "blocked" in a lock but be in VgTs_Runnable status,
because the lock is actually a spinlock.
"Fails" means "reports an error" and possibly means that the model
is getting out of sync with the actual implementation. This model
only reports problems to the user, and doesn't attempt to actually
change the behaviour of the implementation.
NB:
This file assumes there's a 1:1 relationship between application
threads and Valgrind threads, which means that 1:N and M:N thread
models are not (yet) supported. At some point we may need to
introduce a separate notion of a "thread" for modelling purposes.
*/
#include "pub_core_basics.h"
#include "pub_core_vki.h"
#include "pub_core_errormgr.h"
#include "pub_core_execontext.h"
#include "pub_core_libcassert.h"
#include "pub_core_libcbase.h"
#include "pub_core_libcprint.h"
#include "pub_core_mallocfree.h"
#include "pub_core_oset.h"
#include "pub_core_threadmodel.h"
#include "pub_core_threadstate.h"
#include "pub_core_tooliface.h"
struct thread;
struct mutex;
struct condvar;
static const Bool debug_thread = False;
static const Bool debug_mutex = False;
/* --------------------------------------------------
Thread lifetime
Threads are all expressed in terms of internal ThreadIds. The
thread library interface needs to map from the library's identifers
to ThreadIds.
-------------------------------------------------- */
/* Per-thread state. We maintain our own here rather than hanging it
off ThreadState, so that we have the option of not having a 1:1
relationship between modelled threads and Valgrind threads. */
struct thread
{
ThreadId tid;
ThreadId creator;
Bool detached; /* thread is detached */
enum thread_state {
TS_Alive, /* alive */
TS_Zombie, /* waiting to be joined on (detached is False) */
TS_Dead, /* all dead */
TS_Running, /* running */
TS_MutexBlocked, /* blocked on mutex */
TS_CVBlocked, /* blocked on condvar */
TS_JoinBlocked, /* blocked in join */
} state;
struct mutex *mx_blocked; /* mutex we're blocked on (state==TS_MutexBlocked) */
struct condvar *cv_blocked; /* condvar we're blocked on (state==TS_CVBlocked) */
struct thread *th_blocked; /* thread we're blocked on (state==TS_JoinBlocked) */
ExeContext *ec_created; /* where created */
ExeContext *ec_blocked; /* where blocked/unblocked */
};
enum thread_error
{
THE_NotExist, /* thread doesn't exist */
THE_NotAlive, /* thread isn't alive (use after death) */
THE_Rebirth, /* thread already alive */
THE_Blocked, /* thread not supposed to be blocked */
THE_NotBlocked, /* thread supposed to be blocked */
THE_Detached, /* thread is detached */
};
struct thread_error_data
{
enum thread_error err;
struct thread *th;
const Char *action;
};
static const Char *pp_threadstate(const struct thread *th)
{
if (th == NULL)
return "non-existent";
switch(th->state) {
case TS_Alive: return "alive";
case TS_Zombie: return "zombie";
case TS_Dead: return "dead";
case TS_Running: return "running";
case TS_MutexBlocked:return "mutex-blocked";
case TS_CVBlocked: return "cv-blocked";
case TS_JoinBlocked: return "join-blocked";
default: return "???";
}
}
static void thread_validate(struct thread *th)
{
switch(th->state) {
case TS_Alive:
case TS_Running:
case TS_Dead:
case TS_Zombie:
vg_assert(th->mx_blocked == NULL);
vg_assert(th->cv_blocked == NULL);
vg_assert(th->th_blocked == NULL);
break;
case TS_MutexBlocked:
vg_assert(th->mx_blocked != NULL);
vg_assert(th->cv_blocked == NULL);
vg_assert(th->th_blocked == NULL);
break;
case TS_CVBlocked:
vg_assert(th->mx_blocked == NULL);
vg_assert(th->cv_blocked != NULL);
vg_assert(th->th_blocked == NULL);
break;
case TS_JoinBlocked:
vg_assert(th->mx_blocked == NULL);
vg_assert(th->cv_blocked == NULL);
vg_assert(th->th_blocked != NULL);
break;
}
}
static void thread_setstate(struct thread *th, enum thread_state state)
{
ExeContext *ec;
if (th->state == state)
return;
ec = VG_(record_ExeContext)(th->tid);
switch(state) {
case TS_Alive:
case TS_Dead:
th->ec_created = ec;
break;
case TS_Running:
case TS_MutexBlocked:
case TS_CVBlocked:
case TS_JoinBlocked:
case TS_Zombie:
th->ec_blocked = ec;
}
th->state = state;
if (debug_thread)
VG_(printf)("setting thread(%d) -> %s\n", th->tid, pp_threadstate(th));
thread_validate(th);
}
static void do_thread_run(struct thread *th)
{
th->mx_blocked = NULL;
th->cv_blocked = NULL;
th->th_blocked = NULL;
thread_setstate(th, TS_Running);
}
static void do_thread_block_mutex(struct thread *th, struct mutex *mx)
{
th->mx_blocked = mx;
th->cv_blocked = NULL;
th->th_blocked = NULL;
thread_setstate(th, TS_MutexBlocked);
}
static void do_thread_block_condvar(struct thread *th, struct condvar *cv)
{
th->mx_blocked = NULL;
th->cv_blocked = cv;
th->th_blocked = NULL;
thread_setstate(th, TS_CVBlocked);
}
static void do_thread_block_join(struct thread *th, struct thread *joinee)
{
th->mx_blocked = NULL;
th->cv_blocked = NULL;
th->th_blocked = joinee;
thread_setstate(th, TS_JoinBlocked);
}
static void do_thread_block_zombie(struct thread *th)
{
th->mx_blocked = NULL;
th->cv_blocked = NULL;
th->th_blocked = NULL;
thread_setstate(th, TS_Zombie);
}
static void do_thread_dead(struct thread *th)
{
th->mx_blocked = NULL;
th->cv_blocked = NULL;
th->th_blocked = NULL;
thread_setstate(th, TS_Dead);
}
static OSet *threadSet = NULL;
static struct thread *thread_get(ThreadId tid)
{
return VG_(OSet_Lookup)(threadSet, &tid);
}
static void thread_report(ThreadId tid, enum thread_error err, const Char *action)
{
Char *errstr = "?";
struct thread *th = thread_get(tid);
struct thread_error_data errdata;
switch(err) {
case THE_NotExist: errstr = "non existent"; break;
case THE_NotAlive: errstr = "not alive"; break;
case THE_Rebirth: errstr = "re-born"; break;
case THE_Blocked: errstr = "blocked"; break;
case THE_NotBlocked: errstr = "not blocked"; break;
case THE_Detached: errstr = "detached"; break;
}
errdata.err = err;
errdata.th = th;
errdata.action = action;
VG_(maybe_record_error)(VG_(get_running_tid)(), ThreadErr, 0, errstr, &errdata);
}
static void pp_thread_error(Error *err)
{
struct thread_error_data *errdata = VG_(get_error_extra)(err);
struct thread *th = errdata->th;
Char *errstr = VG_(get_error_string)(err);
VG_(message)(Vg_UserMsg, "Found %s thread in state %s while %s",
errstr, pp_threadstate(th), errdata->action);
VG_(pp_ExeContext)(VG_(get_error_where)(err));
if (th) {
VG_(message)(Vg_UserMsg, " Thread %d was %s",
th->tid, th->state == TS_Dead ? "destroyed" : "created");
VG_(pp_ExeContext)(th->ec_created);
}
}
/* Thread creation */
void VG_(tm_thread_create)(ThreadId creator, ThreadId tid, Bool detached)
{
struct thread *th = thread_get(tid);
if (debug_thread)
VG_(printf)("thread %d creates %d %s\n", creator, tid, detached ? "detached" : "");
if (th != NULL) {
if (th->state != TS_Dead)
thread_report(tid, THE_Rebirth, "creating");
} else {
th = VG_(OSet_AllocNode)(threadSet, sizeof(struct thread));
th->tid = tid;
VG_(OSet_Insert)(threadSet, th);
}
th->creator = creator;
th->detached = detached;
th->mx_blocked = NULL;
th->cv_blocked = NULL;
th->th_blocked = NULL;
thread_setstate(th, TS_Alive);
do_thread_run(th);
}
Bool VG_(tm_thread_exists)(ThreadId tid)
{
struct thread *th = thread_get(tid);
return th && th->state != TS_Dead;
}
/* A thread is terminating itself
- fails if tid has already terminated
- if detached, tid becomes invalid for all further operations
- if not detached, the thread remains in a Zombie state until
someone joins on it
*/
void VG_(tm_thread_exit)(ThreadId tid)
{
struct thread *th = thread_get(tid);
if (th == NULL)
thread_report(tid, THE_NotExist, "exiting");
else {
struct thread *joiner;
switch(th->state) {
case TS_Dead:
case TS_Zombie: /* already exited once */
thread_report(tid, THE_NotAlive, "exiting");
break;
case TS_MutexBlocked:
case TS_CVBlocked:
case TS_JoinBlocked:
thread_report(tid, THE_Blocked, "exiting");
break;
case TS_Alive:
case TS_Running:
/* OK */
break;
}
/* ugly - walk all threads to find people joining with us */
/* In pthreads its an error to have multiple joiners, but that
seems a bit specific to implement here; there should a way
for the thread library binding to handle this. */
VG_(OSet_ResetIter)(threadSet);
while ((joiner = VG_(OSet_Next)(threadSet)) != NULL) {
if (joiner->state == TS_JoinBlocked && joiner->th_blocked == th) {
/* found someone - wake them up */
do_thread_run(joiner);
/* we're dead */
do_thread_dead(th);
}
}
if (th->state != TS_Dead)
do_thread_block_zombie(th);
}
}
void VG_(tm_thread_detach)(ThreadId tid)
{
struct thread *th = thread_get(tid);
if (th == NULL)
thread_report(tid, THE_NotExist, "detaching");
else {
if (th->detached)
thread_report(tid, THE_Detached, "detaching");
else {
/* XXX look for waiters */
th->detached = True;
}
}
}
/* One thread blocks until another has terminated
- fails if joinee is detached
- fails if joinee doesn't exist
- once the join completes, joinee is dead
*/
void VG_(tm_thread_join)(ThreadId joinerid, ThreadId joineeid)
{
struct thread *joiner = thread_get(joinerid);
struct thread *joinee = thread_get(joineeid);
/* First, check the joinee thread's state */
if (joinee == NULL)
thread_report(joineeid, THE_NotExist, "joining as joinee");
else {
switch(joinee->state) {
case TS_Alive: /* really shouldn't see them in this state... */
case TS_Running:
case TS_Zombie:
case TS_MutexBlocked:
case TS_CVBlocked:
case TS_JoinBlocked:
/* OK */
break;
case TS_Dead:
thread_report(joineeid, THE_NotAlive, "joining as joinee");
break;
}
}
/* now the joiner... */
if (joiner == NULL)
thread_report(joineeid, THE_NotExist, "joining as joiner");
else {
switch(joiner->state) {
case TS_Alive: /* ? */
case TS_Running: /* OK */
break;
case TS_Zombie: /* back from the dead */
case TS_Dead:
thread_report(joineeid, THE_NotAlive, "joining as joiner");
break;
case TS_MutexBlocked:
case TS_CVBlocked:
case TS_JoinBlocked:
thread_report(joineeid, THE_Blocked, "joining as joiner");
break;
}
if (joinee->detached)
thread_report(joineeid, THE_Detached, "joining as joiner");
else {
/* block if the joinee hasn't exited yet */
if (joinee) {
switch(joinee->state) {
case TS_Dead:
break;
default:
if (joinee->state == TS_Zombie)
do_thread_dead(joinee);
else
do_thread_block_join(joiner, joinee);
}
}
}
}
}
/* Context switch to a new thread */
void VG_(tm_thread_switchto)(ThreadId tid)
{
VG_TRACK( thread_run, tid );
}
static void thread_block_mutex(ThreadId tid, struct mutex *mx)
{
struct thread *th = thread_get(tid);
if (th == NULL) {
/* should an unknown thread doing something make it spring to life? */
thread_report(tid, THE_NotExist, "blocking on mutex");
return;
}
switch(th->state) {
case TS_Dead:
case TS_Zombie:
thread_report(th->tid, THE_NotAlive, "blocking on mutex");
break;
case TS_MutexBlocked:
case TS_CVBlocked:
case TS_JoinBlocked:
thread_report(th->tid, THE_Blocked, "blocking on mutex");
break;
case TS_Alive:
case TS_Running: /* OK */
break;
}
do_thread_block_mutex(th, mx);
}
static void thread_unblock_mutex(ThreadId tid, struct mutex *mx, const Char *action)
{
struct thread *th = thread_get(tid);
if (th == NULL) {
/* should an unknown thread doing something make it spring to life? */
thread_report(tid, THE_NotExist, "giving up on mutex");
return;
}
switch(th->state) {
case TS_MutexBlocked: /* OK */
break;
case TS_Alive:
case TS_Running:
thread_report(tid, THE_NotBlocked, action);
break;
case TS_CVBlocked:
case TS_JoinBlocked:
thread_report(tid, THE_Blocked, action);
break;
case TS_Dead:
case TS_Zombie:
thread_report(tid, THE_NotAlive, action);
break;
}
do_thread_run(th);
}
/* --------------------------------------------------
Mutexes
This models simple, non-recursive mutexes.
-------------------------------------------------- */
struct mutex
{
Addr mutex; /* address of mutex */
ThreadId owner; /* owner if state == MX_Locked */
enum mutex_state {
MX_Init,
MX_Free,
MX_Locked,
MX_Unlocking, /* half-unlocked */
MX_Dead
} state; /* mutex state */
ExeContext *ec_create; /* where created/destroyed */
ExeContext *ec_locked; /* where last locked/unlocked */
};
enum mutex_error
{
MXE_NotExist, /* never existed */
MXE_NotInit, /* not initialized (use after destroy) */
MXE_ReInit, /* already initialized */
MXE_NotLocked, /* not locked */
MXE_Locked, /* is locked */
MXE_Deadlock, /* deadlock detected */
MXE_NotOwner, /* non-owner trying to change lock */
};
struct mutex_error_data
{
enum mutex_error err;
struct mutex *mx;
const Char *action;
};
static struct mutex *mutex_get(Addr mutexp);
static const Char *pp_mutexstate(const struct mutex *mx)
{
static Char buf[20];
switch(mx->state) {
case MX_Init: return "Init";
case MX_Free: return "Free";
case MX_Dead: return "Dead";
case MX_Locked:
VG_(sprintf)(buf, "Locked by tid %d", mx->owner);
break;
case MX_Unlocking:
VG_(sprintf)(buf, "Being unlocked by tid %d", mx->owner);
break;
default:
VG_(sprintf)(buf, "?? %d", mx->state);
break;
}
return buf;
}
static void mutex_setstate(ThreadId tid, struct mutex *mx, enum mutex_state st)
{
ExeContext *ec = VG_(record_ExeContext)(tid);
switch(st) {
case MX_Init:
case MX_Dead:
mx->ec_create = ec;
break;
case MX_Unlocking:
case MX_Locked:
case MX_Free:
mx->ec_locked = ec;
break;
}
mx->state = st;
if (debug_mutex)
VG_(printf)("setting mutex(%p) -> %s\n", mx->mutex, pp_mutexstate(mx));
}
static void mutex_report(ThreadId tid, Addr mutexp, enum mutex_error err, const Char *action)
{
Char *errstr="?";
struct mutex *mx = mutex_get(mutexp);
struct mutex_error_data errdata;
switch(err) {
case MXE_NotExist: errstr="non-existent"; break;
case MXE_NotInit: errstr="uninitialized"; break;
case MXE_ReInit: errstr="already initialized"; break;
case MXE_NotLocked: errstr="not locked"; break;
case MXE_Locked: errstr="locked"; break;
case MXE_NotOwner: errstr="unowned"; break;
case MXE_Deadlock: errstr="deadlock on"; break;
}
errdata.err = err;
errdata.mx = mx;
errdata.action = action;
VG_(maybe_record_error)(tid, MutexErr, 0, errstr, &errdata);
}
static void pp_mutex_error(Error *err)
{
struct mutex_error_data *errdata = VG_(get_error_extra)(err);
struct mutex *mx = errdata->mx;
Char *errstr = VG_(get_error_string)(err);
VG_(message)(Vg_UserMsg, "Found %s mutex %p while %s",
errstr, mx ? mx->mutex : 0, errdata->action);
VG_(pp_ExeContext)(VG_(get_error_where)(err));
switch (mx->state) {
case MX_Init:
case MX_Dead:
break;
case MX_Locked:
VG_(message)(Vg_UserMsg, " Mutex was locked by thread %d", mx->owner);
VG_(pp_ExeContext)(mx->ec_locked);
break;
case MX_Unlocking:
VG_(message)(Vg_UserMsg, " Mutex being unlocked");
VG_(pp_ExeContext)(mx->ec_locked);
break;
case MX_Free:
VG_(message)(Vg_UserMsg, " Mutex was unlocked");
VG_(pp_ExeContext)(mx->ec_locked);
break;
}
VG_(message)(Vg_UserMsg, " Mutex was %s",
mx->state == MX_Dead ? "destroyed" : "created");
VG_(pp_ExeContext)(mx->ec_create);
}
static OSet *mutexSet = NULL;
static struct mutex *mutex_get(Addr mutexp)
{
return VG_(OSet_Lookup)(mutexSet, &mutexp);
}
static Bool mx_is_initialized(Addr mutexp)
{
const struct mutex *mx = mutex_get(mutexp);
return mx && mx->state != MX_Dead;
}
static struct mutex *mutex_check_initialized(ThreadId tid, Addr mutexp, const Char *action)
{
struct mutex *mx;
vg_assert(tid != VG_INVALID_THREADID);
if (!mx_is_initialized(mutexp)) {
mutex_report(tid, mutexp, MXE_NotInit, action);
VG_(tm_mutex_init)(tid, mutexp);
}
mx = mutex_get(mutexp);
vg_assert(mx != NULL);
return mx;
}
#if 0
static Bool mx_is_locked(Addr mutexp)
{
const struct mutex *mx = mutex_get(mutexp);
return mx && (mx->state == MX_Locked);
}
#endif
/* Mutex at mutexp is initialized. This must be done before any
further mutex operations are OK. Fails if:
- mutexp already exists (and is locked?)
*/
void VG_(tm_mutex_init)(ThreadId tid, Addr mutexp)
{
struct mutex *mx = mutex_get(mutexp);
if (mx == NULL) {
mx = VG_(OSet_AllocNode)(mutexSet, sizeof(struct mutex));
mx->mutex = mutexp;
VG_(OSet_Insert)(mutexSet, mx);
} else if (mx->state != MX_Dead)
mutex_report(tid, mutexp, MXE_ReInit, "initializing");
mx->owner = VG_INVALID_THREADID;
mutex_setstate(tid, mx, MX_Init);
mutex_setstate(tid, mx, MX_Free);
}
Bool VG_(tm_mutex_exists)(Addr mutexp)
{
return mx_is_initialized(mutexp);
}
/* Mutex is being destroyed. Fails if:
- mutex was not initialized
- mutex is locked (?)
*/
void VG_(tm_mutex_destroy)(ThreadId tid, Addr mutexp)
{
struct mutex *mx = mutex_get(mutexp);
if (mx == NULL)
mutex_report(tid, mutexp, MXE_NotExist, "destroying");
else {
switch(mx->state) {
case MX_Dead:
mutex_report(tid, mutexp, MXE_NotInit, "destroying");
break;
case MX_Locked:
case MX_Unlocking:
mutex_report(tid, mutexp, MXE_Locked, "destroying");
VG_(tm_mutex_unlock)(tid, mutexp);
break;
case MX_Init:
case MX_Free:
/* OK */
break;
}
mutex_setstate(tid, mx, MX_Dead);
}
}
/* A thread attempts to lock a mutex. If "blocking" then the thread
is put into a blocked state until the lock is acquired. Fails if:
- tid is invalid
- mutex has not been initialized
- thread is blocked on another object (?)
- blocking on this mutex could cause a deadlock
(Lock rank detection?)
*/
void VG_(tm_mutex_trylock)(ThreadId tid, Addr mutexp)
{
struct mutex *mx;
mx = mutex_check_initialized(tid, mutexp, "trylocking");
thread_block_mutex(tid, mx);
if (mx->state == MX_Locked && mx->owner == tid) /* deadlock */
mutex_report(tid, mutexp, MXE_Deadlock, "trylocking");
VG_TRACK( pre_mutex_lock, tid, mutexp );
}
/* Give up waiting for a mutex. Fails if:
- thread is not currently blocked on the mutex
*/
void VG_(tm_mutex_giveup)(ThreadId tid, Addr mutexp)
{
struct mutex *mx;
mx = mutex_check_initialized(tid, mutexp, "giving up");
thread_unblock_mutex(tid, mx, "giving up on mutex");
}
/* A thread acquires a mutex. Fails if:
- thread is not blocked waiting for the mutex
- mutex is held by another thread
*/
void VG_(tm_mutex_acquire)(ThreadId tid, Addr mutexp)
{
struct mutex *mx;
mx = mutex_check_initialized(tid, mutexp, "acquiring");
switch(mx->state) {
case MX_Unlocking: /* ownership transfer or relock */
VG_TRACK( post_mutex_unlock, mx->owner, mutexp );
if (mx->owner != tid)
thread_unblock_mutex(tid, mx, "acquiring mutex");
break;
case MX_Free:
thread_unblock_mutex(tid, mx, "acquiring mutex");
break;
case MX_Locked:
if (debug_mutex)
VG_(printf)("mutex=%p mx->state=%s\n", mutexp, pp_mutexstate(mx));
VG_TRACK( post_mutex_unlock, mx->owner, mutexp );
mutex_report(tid, mutexp, MXE_Locked, "acquiring");
thread_unblock_mutex(tid, mx, "acquiring mutex");
break;
case MX_Init:
case MX_Dead:
vg_assert(0);
}
mx->owner = tid;
mutex_setstate(tid, mx, MX_Locked);
VG_TRACK( post_mutex_lock, tid, mutexp );
}
/* Try unlocking a lock. This will move it into a state where it can
either be unlocked, or change ownership to another thread. If
unlock fails, it will remain locked. */
void VG_(tm_mutex_tryunlock)(ThreadId tid, Addr mutexp)
{
struct thread *th;
struct mutex *mx;
mx = mutex_check_initialized(tid, mutexp, "try-unlocking");
th = thread_get(tid);
if (th == NULL)
thread_report(tid, THE_NotExist, "try-unlocking mutex");
else {
switch(th->state) {
case TS_Alive:
case TS_Running: /* OK */
break;
case TS_Dead:
case TS_Zombie:
thread_report(tid, THE_NotAlive, "try-unlocking mutex");
break;
case TS_JoinBlocked:
case TS_CVBlocked:
case TS_MutexBlocked:
thread_report(tid, THE_Blocked, "try-unlocking mutex");
do_thread_run(th);
break;
}
}
switch(mx->state) {
case MX_Locked:
if (mx->owner != tid)
mutex_report(tid, mutexp, MXE_NotOwner, "try-unlocking");
break;
case MX_Free:
mutex_report(tid, mutexp, MXE_NotLocked, "try-unlocking");
break;
case MX_Unlocking:
mutex_report(tid, mutexp, MXE_NotLocked, "try-unlocking");
break;
case MX_Init:
case MX_Dead:
vg_assert(0);
}
mutex_setstate(tid, mx, MX_Unlocking);
}
/* Finish unlocking a Mutex. The mutex can validly be in one of three
states:
- Unlocking
- Locked, owned by someone else (someone else got it in the meantime)
- Free (someone else completed a lock-unlock cycle)
*/
void VG_(tm_mutex_unlock)(ThreadId tid, Addr mutexp)
{
struct mutex *mx;
struct thread *th;
mx = mutex_check_initialized(tid, mutexp, "unlocking mutex");
th = thread_get(tid);
if (th == NULL)
thread_report(tid, THE_NotExist, "unlocking mutex");
else {
switch(th->state) {
case TS_Alive:
case TS_Running: /* OK */
break;
case TS_Dead:
case TS_Zombie:
thread_report(tid, THE_NotAlive, "unlocking mutex");
break;
case TS_JoinBlocked:
case TS_CVBlocked:
case TS_MutexBlocked:
thread_report(tid, THE_Blocked, "unlocking mutex");
do_thread_run(th);
break;
}
}
switch(mx->state) {
case MX_Locked:
/* Someone else might have taken ownership in the meantime */
if (mx->owner == tid)
mutex_report(tid, mutexp, MXE_Locked, "unlocking");
break;
case MX_Free:
/* OK - nothing to do */
break;
case MX_Unlocking:
/* OK - we need to complete the unlock */
VG_TRACK( post_mutex_unlock, tid, mutexp );
mutex_setstate(tid, mx, MX_Free);
break;
case MX_Init:
case MX_Dead:
vg_assert(0);
}
}
/* --------------------------------------------------
Condition variables
-------------------------------------------------- */
struct condvar_waiter
{
ThreadId waiter;
struct condvar *condvar;
struct mutex *mutex;
struct condvar_waiter *next;
};
struct condvar
{
Addr condvar;
enum condvar_state {
CV_Dead,
CV_Alive,
} state;
struct condvar_waiter *waiters; // XXX skiplist?
ExeContext *ec_created; // where created
ExeContext *ec_signalled; // where last signalled
};
enum condvar_err {
CVE_NotExist,
CVE_NotInit,
CVE_ReInit,
CVE_Busy,
CVE_Blocked,
};
static OSet *condvarSet = NULL;
static struct condvar *condvar_get(Addr condp)
{
return VG_(OSet_Lookup)(condvarSet, &condp);
}
static Bool condvar_is_initialized(Addr condp)
{
const struct condvar *cv = condvar_get(condp);
return cv && cv->state != CV_Dead;
}
static void condvar_report(ThreadId tid, Addr condp, enum condvar_err err, const Char *action)
{
}
static struct condvar *condvar_check_initialized(ThreadId tid, Addr condp, const Char *action)
{
struct condvar *cv;
vg_assert(tid != VG_INVALID_THREADID);
if (!condvar_is_initialized(condp)) {
condvar_report(tid, condp, CVE_NotInit, action);
VG_(tm_cond_init)(tid, condp);
}
cv = condvar_get(condp);
vg_assert(cv != NULL);
return cv;
}
/* Initialize a condition variable. Fails if:
- condp has already been initialized
*/
void VG_(tm_cond_init)(ThreadId tid, Addr condp)
{
struct condvar *cv = condvar_get(condp);
if (cv == NULL) {
cv = VG_(OSet_AllocNode)(condvarSet, sizeof(struct condvar));
cv->condvar = condp;
cv->waiters = NULL;
VG_(OSet_Insert)(condvarSet, cv);
} else if (cv->state != CV_Dead) {
condvar_report(tid, condp, CVE_ReInit, "initializing");
/* ? what about existing waiters? */
}
cv->state = CV_Alive;
}
/* Destroy a condition variable. Fails if:
- condp has not been initialized
- condp is currently being waited on
*/
void VG_(tm_cond_destroy)(ThreadId tid, Addr condp)
{
struct condvar *cv = condvar_get(condp);
if (cv == NULL)
condvar_report(tid, condp, CVE_NotExist, "destroying");
else {
if (cv->state != CV_Alive)
condvar_report(tid, condp, CVE_NotInit, "destroying");
if (cv->waiters != NULL)
condvar_report(tid, condp, CVE_Busy, "destroying");
cv->state = CV_Dead;
}
}
static struct condvar_waiter *get_waiter(const struct condvar *cv, ThreadId tid)
{
struct condvar_waiter *w;
for(w = cv->waiters; w; w = w->next)
if (w->waiter == tid)
return w;
return NULL;
}
/* Wait for a condition, putting thread into blocked state. Fails if:
- condp has not been initialized
- thread doesn't hold mutexp
- thread is blocked on some other object
- thread is already blocked on mutex
*/
void VG_(tm_cond_wait)(ThreadId tid, Addr condp, Addr mutexp)
{
struct thread *th = thread_get(tid);
struct mutex *mx;
struct condvar *cv;
struct condvar_waiter *waiter;
/* Condvar must exist */
cv = condvar_check_initialized(tid, condp, "waiting");
/* Mutex must exist */
mx = mutex_check_initialized(tid, mutexp, "waiting on condvar");
/* Thread must own mutex */
if (mx->state != MX_Locked) {
mutex_report(tid, mutexp, MXE_NotLocked, "waiting on condvar");
VG_(tm_mutex_trylock)(tid, mutexp);
VG_(tm_mutex_acquire)(tid, mutexp);
} else if (mx->owner != tid) {
mutex_report(tid, mutexp, MXE_NotOwner, "waiting on condvar");
mx->owner = tid;
}
/* Thread must not be already waiting for condvar */
waiter = get_waiter(cv, tid);
if (waiter != NULL)
condvar_report(tid, condp, CVE_Blocked, "waiting");
else {
waiter = VG_(arena_malloc)(VG_AR_CORE, sizeof(*waiter));
waiter->condvar = cv;
waiter->mutex = mx;
waiter->next = cv->waiters;
cv->waiters = waiter;
}
/* Thread is now blocking on condvar */
do_thread_block_condvar(th, cv);
/* (half) release mutex */
VG_(tm_mutex_tryunlock)(tid, mutexp);
}
/* Wake from a condition, either because we've been signalled, or
because of timeout. Fails if:
- thread is not waiting on condp
*/
void VG_(tm_cond_wakeup)(ThreadId tid, Addr condp, Addr mutexp)
{
}
/* Signal a condition variable. Fails if:
- condp has not been initialized
*/
void VG_(tm_cond_signal)(ThreadId tid, Addr condp)
{
}
/* --------------------------------------------------
Error handling
-------------------------------------------------- */
UInt VG_(tm_error_update_extra)(Error *err)
{
switch (VG_(get_error_kind)(err)) {
case ThreadErr: {
struct thread_error_data *errdata = VG_(get_error_extra)(err);
struct thread *new_th = VG_(arena_malloc)(VG_AR_CORE, sizeof(struct thread));
VG_(memcpy)(new_th, errdata->th, sizeof(struct thread));
errdata->th = new_th;
return sizeof(struct thread_error_data);
}
case MutexErr: {
struct mutex_error_data *errdata = VG_(get_error_extra)(err);
struct mutex *new_mx = VG_(arena_malloc)(VG_AR_CORE, sizeof(struct mutex));
VG_(memcpy)(new_mx, errdata->mx, sizeof(struct mutex));
errdata->mx = new_mx;
return sizeof(struct mutex_error_data);
}
default:
return 0;
}
}
Bool VG_(tm_error_equal)(VgRes res, Error *e1, Error *e2)
{
/* Guaranteed by calling function */
vg_assert(VG_(get_error_kind)(e1) == VG_(get_error_kind)(e2));
switch (VG_(get_error_kind)(e1)) {
case ThreadErr: {
struct thread_error_data *errdata1 = VG_(get_error_extra)(e1);
struct thread_error_data *errdata2 = VG_(get_error_extra)(e2);
return errdata1->err == errdata2->err;
}
case MutexErr: {
struct mutex_error_data *errdata1 = VG_(get_error_extra)(e1);
struct mutex_error_data *errdata2 = VG_(get_error_extra)(e2);
return errdata1->err == errdata2->err;
}
default:
VG_(printf)("Error:\n unknown error code %d\n",
VG_(get_error_kind)(e1));
VG_(core_panic)("unknown error code in VG_(tm_error_equal)");
}
}
void VG_(tm_error_print)(Error *err)
{
switch (VG_(get_error_kind)(err)) {
case ThreadErr:
pp_thread_error(err);
break;
case MutexErr:
pp_mutex_error(err);
break;
}
}
/* --------------------------------------------------
Initialisation
-------------------------------------------------- */
static Word tm_compare_tid(ThreadId *tid1, ThreadId *tid2)
{
if (*tid1 < *tid2) return -1;
if (*tid1 > *tid2) return 1;
return 0;
}
static Word tm_compare_addr(Addr *addr1, Addr *addr2)
{
if (*addr1 < *addr2) return -1;
if (*addr1 > *addr2) return 1;
return 0;
}
static void* oset_malloc(SizeT szB)
{
return VG_(arena_malloc)(VG_AR_CORE, szB);
}
static void oset_free(void * p)
{
VG_(arena_free)(VG_AR_CORE, p);
}
void VG_(tm_init)()
{
VG_(needs_core_errors)();
threadSet = VG_(OSet_Create)(offsetof(struct thread, tid),
(OSetCmp_t)tm_compare_tid,
oset_malloc, oset_free);
mutexSet = VG_(OSet_Create)(offsetof(struct mutex, mutex),
(OSetCmp_t)tm_compare_addr,
oset_malloc, oset_free);
condvarSet = VG_(OSet_Create)(offsetof(struct condvar, condvar),
(OSetCmp_t)tm_compare_addr,
oset_malloc, oset_free);
}
/*--------------------------------------------------------------------*/
/*--- end ---*/
/*--------------------------------------------------------------------*/