Files
itgmania212121/stepmania/src/RageThreads.cpp
T

948 lines
21 KiB
C++
Raw Normal View History

/*
* If you're going to use threads, remember this:
*
* Threads suck.
*
* If there's any way to avoid them, take it! Threaded code an order of
* magnitude more complicated, harder to debug and harder to make robust.
*
* That said, here are a few helpers for when they're unavoidable. (Use
* SDL for the rest.)
*/
2003-02-16 04:01:45 +00:00
#include "global.h"
#include "RageThreads.h"
2002-12-21 05:13:45 +00:00
#include "RageTimer.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "SDL_thread.h"
2003-07-27 03:08:02 +00:00
#include "SDL_utils.h"
2004-04-05 05:22:32 +00:00
#include <csignal>
#include <cerrno>
2004-03-21 08:36:43 +00:00
#include <set>
2003-07-27 03:08:02 +00:00
/* SDL threads aren't quite enough. We need to be able to suspend or
* kill all threads, including the main one. SDL doesn't count the
* main thread as a thread. So, we'll have to do this nonportably. */
#if defined(LINUX)
#define PID_BASED_THREADS
2004-03-12 05:26:10 +00:00
#include "archutils/Unix/LinuxThreadHelpers.h"
2003-07-27 03:08:02 +00:00
#endif
2004-03-20 22:01:40 +00:00
#if defined(HAVE_PTHREAD_MUTEX_TIMEDLOCK) && defined(CRASH_HANDLER)
#include "archutils/Unix/Backtrace.h"
#include "archutils/Unix/CrashHandler.h"
#endif
#if defined(WIN32)
#include "archutils/Win32/crash.h"
#endif
2004-03-28 06:04:37 +00:00
#if defined(DARWIN)
#include <mach/mach_init.h>
#include <mach/thread_act.h>
#endif
2004-03-25 04:06:44 +00:00
/* XXX: char*GetLockedMutexesForThisThread? */
2003-07-27 03:08:02 +00:00
#define MAX_THREADS 128
2004-03-21 08:36:43 +00:00
static vector<RageMutex*> *g_MutexList = NULL; /* watch out for static initialization order problems */
2003-07-27 03:08:02 +00:00
2004-02-21 10:30:07 +00:00
static const unsigned int UnknownThreadID = 0xFFFFFFFF;
2003-07-27 03:08:02 +00:00
struct ThreadSlot
{
2004-03-20 22:01:40 +00:00
mutable char name[1024]; /* mutable so we can force nul-termination */
2003-07-27 03:08:02 +00:00
Uint32 threadid;
/* Format this beforehand, since it's easier to do that than to do it under crash conditions. */
char ThreadFormattedOutput[1024];
2003-07-27 03:08:02 +00:00
bool used;
#if defined(PID_BASED_THREADS)
/* Keep a list of child PIDs, so we can send them SIGKILL. This has an
* added bonus: if this is corrupted, we'll just send signals and they'll
* fail; we won't blow up (unless we're root). */
int pid;
#endif
2003-10-01 08:20:03 +00:00
#if defined(WIN32)
HANDLE ThreadHandle;
#endif
2004-03-28 06:04:37 +00:00
#if defined(DARWIN)
thread_act_t ThreadHandle;
#endif
#undef CHECKPOINT_COUNT
2003-08-08 09:03:30 +00:00
#define CHECKPOINT_COUNT 5
struct ThreadCheckpoint
{
const char *File, *Message;
int Line;
char FormattedBuf[1024];
ThreadCheckpoint() { Set( NULL, 0, NULL ); }
void Set(const char *File_, int Line_, const char *Message_=NULL);
const char *GetFormattedCheckpoint();
};
ThreadCheckpoint Checkpoints[CHECKPOINT_COUNT];
int CurCheckpoint, NumCheckpoints;
const char *GetFormattedCheckpoint( int lineno );
2003-07-27 03:08:02 +00:00
/* Used to bootstrap the thread: */
int (*fn)(void *);
void *data;
ThreadSlot() { Init(); }
void Init()
2003-07-27 03:08:02 +00:00
{
used = false;
CurCheckpoint = NumCheckpoints = 0;
2003-07-27 03:08:02 +00:00
#if defined(PID_BASED_THREADS)
pid = -1;
#endif
}
2004-03-20 22:01:40 +00:00
const char *GetThreadName() const;
2003-07-27 03:08:02 +00:00
void SetupThisThread();
void ShutdownThisThread();
2004-02-20 23:42:40 +00:00
void SetupUnknownThread();
2003-07-27 03:08:02 +00:00
};
void ThreadSlot::ThreadCheckpoint::Set(const char *File_, int Line_, const char *Message_)
{
File=File_;
Line=Line_;
Message=Message_;
sprintf( FormattedBuf, " %s:%i %s",
File, Line, Message? Message:"" );
}
const char *ThreadSlot::ThreadCheckpoint::GetFormattedCheckpoint()
{
if( File == NULL )
return NULL;
/* Make sure it's terminated: */
FormattedBuf [ sizeof(FormattedBuf)-1 ] = 0;
return FormattedBuf;
}
const char *ThreadSlot::GetFormattedCheckpoint( int lineno )
{
if( lineno >= CHECKPOINT_COUNT || lineno >= NumCheckpoints )
return NULL;
if( NumCheckpoints == CHECKPOINT_COUNT )
{
lineno += CurCheckpoint;
lineno %= CHECKPOINT_COUNT;
}
return Checkpoints[lineno].GetFormattedCheckpoint();
}
2003-07-27 03:08:02 +00:00
static ThreadSlot g_ThreadSlots[MAX_THREADS];
static RageMutex g_ThreadSlotsLock("ThreadSlots");
2003-07-27 03:08:02 +00:00
static int FindEmptyThreadSlot()
{
LockMut(g_ThreadSlotsLock);
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( g_ThreadSlots[entry].used )
continue;
g_ThreadSlots[entry].used = true;
return entry;
}
RageException::Throw("Out of thread slots!");
}
static int GetCurThreadSlot()
{
Uint32 ThisThread = SDL_ThreadID();
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].used )
continue;
if( g_ThreadSlots[entry].threadid == ThisThread )
return entry;
}
return -1;
}
2004-02-20 23:42:40 +00:00
static int GetUnknownThreadSlot()
{
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].used )
continue;
if( g_ThreadSlots[entry].threadid == UnknownThreadID )
return entry;
}
sm_crash();
}
2003-07-27 03:08:02 +00:00
RageThread::RageThread()
{
thr = NULL;
}
RageThread::~RageThread()
{
}
2004-03-20 22:01:40 +00:00
const char *ThreadSlot::GetThreadName() const
{
/* This function may be called in crash conditions, so guarantee the string
* is null-terminated. */
name[ sizeof(name)-1] = 0;
return name;
}
2003-07-27 03:08:02 +00:00
void ThreadSlot::SetupThisThread()
{
#if defined(PID_BASED_THREADS)
2004-03-12 05:26:10 +00:00
pid = GetCurrentThreadId();
2003-07-27 03:08:02 +00:00
#endif
2003-07-27 03:13:40 +00:00
#ifdef _WINDOWS
2003-10-01 08:20:03 +00:00
const HANDLE CurProc = GetCurrentProcess();
int ret = DuplicateHandle( CurProc, GetCurrentThread(), CurProc,
&ThreadHandle, 0, false, DUPLICATE_SAME_ACCESS );
if( !ret )
LOG->Warn( werr_ssprintf( GetLastError(), "DuplicateHandle(%p, %p) failed",
CurProc, GetCurrentThread() ) );
2003-07-27 03:13:40 +00:00
#endif
2004-03-28 06:04:37 +00:00
#if defined(DARWIN)
ThreadHandle = mach_thread_self();
#endif
2003-07-27 03:08:02 +00:00
threadid = SDL_ThreadID();
sprintf(ThreadFormattedOutput, "Thread %08x (%s)", threadid, name);
CHECKPOINT;
2003-07-27 03:08:02 +00:00
}
2004-02-20 23:42:40 +00:00
void ThreadSlot::SetupUnknownThread()
{
threadid = UnknownThreadID;
2004-02-21 10:30:07 +00:00
sprintf(ThreadFormattedOutput, "Unknown thread");
2004-02-20 23:42:40 +00:00
}
2003-07-27 03:08:02 +00:00
void ThreadSlot::ShutdownThisThread()
{
2004-02-20 23:42:40 +00:00
ASSERT( threadid != UnknownThreadID );
2003-09-03 05:10:23 +00:00
#ifdef _WINDOWS
2003-10-01 08:20:03 +00:00
CloseHandle( ThreadHandle );
2003-09-03 05:10:23 +00:00
#endif
Init();
2003-07-27 03:08:02 +00:00
}
static int StartThread( void *p )
{
ThreadSlot *thr = (ThreadSlot *) p;
thr->SetupThisThread();
int ret = thr->fn(thr->data);
thr->ShutdownThisThread();
return ret;
}
void RageThread::Create( int (*fn)(void *), void *data )
{
/* Don't create a thread that's already running: */
ASSERT( thr == NULL );
int slotno = FindEmptyThreadSlot();
ThreadSlot &slot = g_ThreadSlots[slotno];
slot.fn = fn;
slot.data = data;
if( name == "" )
{
LOG->Warn("Created a thread without naming it first.");
/* If you don't name it, I will: */
strcpy(slot.name, "Joe");
} else {
strcpy(slot.name, name.c_str());
}
/* Start a thread using our own startup function. */
thr = SDL_CreateThread( StartThread, &slot );
if( thr == NULL )
RageException::Throw( "Thread creation failed: %s", SDL_GetError() );
}
2004-02-20 23:42:40 +00:00
/* On startup, register the main thread's slot. */
static struct SetupMainThread
2003-07-27 03:08:02 +00:00
{
SetupMainThread()
{
int slot = FindEmptyThreadSlot();
strcpy( g_ThreadSlots[slot].name, "Main thread" );
2003-07-27 03:08:02 +00:00
g_ThreadSlots[slot].SetupThisThread();
}
} SetupMainThreadObj;
2004-02-20 23:42:40 +00:00
/* Register the "unknown thread" slot. */
static struct SetupUnknownThread
{
SetupUnknownThread()
{
int slot = FindEmptyThreadSlot();
strcpy( g_ThreadSlots[slot].name, "Unknown thread" );
g_ThreadSlots[slot].SetupUnknownThread();
}
} SetupUnknownThreadObj;
2003-07-27 03:08:02 +00:00
const char *RageThread::GetCurThreadName()
{
int slot = GetCurThreadSlot();
if(slot==-1)
return "???";
2004-03-20 22:01:40 +00:00
return g_ThreadSlots[slot].GetThreadName();
2003-07-27 03:08:02 +00:00
}
int RageThread::Wait()
{
ASSERT( thr != NULL );
int ret;
SDL_WaitThread(thr, &ret);
thr = NULL;
2003-07-27 03:08:02 +00:00
return ret;
}
2004-03-28 06:04:37 +00:00
/* XXX: consolidate thread ID type, etc, use ArchHooks */
#if defined(DARWIN)
thread_act_t GetCurrentThreadId()
{
return mach_thread_self();
}
void SuspendThread( thread_act_t t )
{
thread_suspend( t );
}
void ResumeThread( thread_act_t t )
{
thread_resume( t );
}
#endif
2003-07-27 07:00:13 +00:00
void RageThread::HaltAllThreads( bool Kill )
2003-07-27 03:08:02 +00:00
{
#if defined(PID_BASED_THREADS)
2003-07-27 07:00:13 +00:00
/* Send a SIGSTOP to all other threads. If we send a SIGKILL, pthreads
* will "helpfully" propagate it to the other threads, and we'll get
* killed, too.
*
* This isn't ideal, since it can cause the process to background
* as far as the shell is concerned, so the shell prompt can display
* before the crash handler actually displays a message. */
2004-03-12 05:26:10 +00:00
int ThisThreadID = GetCurrentThreadId();
2003-07-27 03:08:02 +00:00
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].used )
continue;
const int pid = g_ThreadSlots[entry].pid;
2004-03-12 05:26:10 +00:00
if( pid <= 0 || pid == ThisThreadID )
2003-07-27 03:08:02 +00:00
continue;
2004-03-12 05:26:10 +00:00
SuspendThread( pid );
2003-07-27 03:08:02 +00:00
}
2003-10-01 08:20:03 +00:00
#elif defined(WIN32)
const int ThisThreadID = GetCurrentThreadId();
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].used )
continue;
if( ThisThreadID == (int) g_ThreadSlots[entry].threadid )
continue;
2004-01-12 01:36:25 +00:00
#ifndef _XBOX
if( Kill )
TerminateThread( g_ThreadSlots[entry].ThreadHandle, 0 );
else
2003-11-13 00:39:36 +00:00
#endif
2004-01-12 01:36:25 +00:00
SuspendThread( g_ThreadSlots[entry].ThreadHandle );
}
2004-03-28 06:04:37 +00:00
#elif defined(DARWIN)
const thread_act_t ThisThreadID = GetCurrentThreadId();
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].used )
continue;
if( g_ThreadSlots[entry].threadid == UnknownThreadID )
continue;
if( ThisThreadID == g_ThreadSlots[entry].ThreadHandle )
continue;
SuspendThread( g_ThreadSlots[entry].ThreadHandle );
}
2004-01-12 01:36:25 +00:00
#endif
}
void RageThread::ResumeAllThreads()
{
#if defined(PID_BASED_THREADS)
/* Send a SIGCONT to all other threads. */
2004-03-12 05:26:10 +00:00
int ThisThreadID = GetCurrentThreadId();
2004-01-12 01:36:25 +00:00
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].used )
continue;
const int pid = g_ThreadSlots[entry].pid;
2004-03-12 05:26:10 +00:00
if( pid <= 0 || pid == ThisThreadID )
2004-01-12 01:36:25 +00:00
continue;
2004-03-12 05:26:10 +00:00
ResumeThread( pid );
2004-01-12 01:36:25 +00:00
}
#elif defined(WIN32)
const int ThisThreadID = GetCurrentThreadId();
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].used )
continue;
if( ThisThreadID == (int) g_ThreadSlots[entry].threadid )
continue;
ResumeThread( g_ThreadSlots[entry].ThreadHandle );
2003-10-01 08:20:03 +00:00
}
2004-03-28 06:04:37 +00:00
#elif defined(DARWIN)
const thread_act_t ThisThreadID = GetCurrentThreadId();
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].used )
continue;
if( ThisThreadID == g_ThreadSlots[entry].threadid )
continue;
ResumeThread( g_ThreadSlots[entry].ThreadHandle );
}
2003-07-27 03:08:02 +00:00
#endif
}
2004-04-07 03:38:15 +00:00
unsigned int RageThread::GetCurrentThreadID()
{
return SDL_ThreadID();
}
/* Normally, checkpoints are only seen in crash logs. It's occasionally useful
* to see them in logs, but this outputs a huge amount of text. */
static bool g_LogCheckpoints = false;
void Checkpoints::LogCheckpoints( bool on )
{
g_LogCheckpoints = on;
}
void Checkpoints::SetCheckpoint( const char *file, int line, const char *message )
{
int slotno = GetCurThreadSlot();
2004-02-20 23:42:40 +00:00
if( slotno == -1 )
slotno = GetUnknownThreadSlot();
/* We can't ASSERT here, since that uses checkpoints. */
if( slotno == -1 )
2004-02-20 23:42:40 +00:00
sm_crash();
ThreadSlot &slot = g_ThreadSlots[slotno];
slot.Checkpoints[slot.CurCheckpoint].Set( file, line, message );
if( g_LogCheckpoints )
LOG->Trace( "%s", slot.Checkpoints[slot.CurCheckpoint].FormattedBuf );
++slot.CurCheckpoint;
slot.NumCheckpoints = max( slot.NumCheckpoints, slot.CurCheckpoint );
slot.CurCheckpoint %= CHECKPOINT_COUNT;
}
/* This is called under crash conditions. Be careful. */
static const char *GetCheckpointLog( int slotno, int lineno )
{
static char ret[1024*32];
ret[0] = 0;
ThreadSlot &slot = g_ThreadSlots[slotno];
if( !slot.used )
return NULL;
2004-02-20 23:42:40 +00:00
/* Only show the "Unknown thread" entry if it has at least one checkpoint. */
if( slot.threadid == UnknownThreadID && slot.GetFormattedCheckpoint( 0 ) == NULL )
return NULL;
if( lineno != 0 )
return slot.GetFormattedCheckpoint( lineno-1 );
slot.ThreadFormattedOutput[sizeof(slot.ThreadFormattedOutput)-1] = 0;
strcat(ret, slot.ThreadFormattedOutput);
return ret;
}
const char *Checkpoints::GetLogs( const char *delim )
{
static char ret[1024*32];
ret[0] = 0;
for( int slotno = 0; slotno < MAX_THREADS; ++slotno )
{
const char *buf = GetCheckpointLog( slotno, 0 );
if( buf == NULL )
2004-02-20 23:42:40 +00:00
continue;
strcat( ret, buf );
strcat( ret, delim );
for( int line = 1; (buf = GetCheckpointLog( slotno, line )) != NULL; ++line )
{
strcat( ret, buf );
strcat( ret, delim );
}
}
return ret;
}
/*
* "Safe" mutexes: locking the same mutex more than once from the same thread
* is refcounted and does not deadlock.
*
* Only actually lock the mutex once; when we do so, remember which thread locked it.
* Then, when we lock in the future, only increment a counter, with no locks.
*
* We must be holding the real mutex to write to LockedBy and LockCnt. However,
* we can look at LockedBy to see if it's us that owns it (in which case, we already
* hold the mutex).
*
* In Windows, this helps smooth out performance: for some reason, Windows likes
* to yank the scheduler away from a thread that locks a mutex that it already owns.
*/
#if defined(WIN32)
struct RageMutexImpl
{
HANDLE mutex;
DWORD LockedBy;
volatile int LockCnt;
RageMutex *m_Parent;
RageMutexImpl( RageMutex *parent );
~RageMutexImpl();
void Lock();
void Unlock();
2004-03-21 08:36:43 +00:00
bool IsLockedByThisThread() const;
};
RageMutexImpl::RageMutexImpl( RageMutex *parent )
{
mutex = CreateMutex( NULL, false, NULL );
LockedBy = NULL;
LockCnt = 0;
m_Parent = parent;
}
RageMutexImpl::~RageMutexImpl()
{
2004-01-31 01:57:33 +00:00
CloseHandle( mutex );
}
static ThreadSlot *FindThread( DWORD id )
2004-02-25 01:33:15 +00:00
{
for( int i = 0; i < MAX_THREADS; ++i )
if( g_ThreadSlots[i].threadid == id )
return &g_ThreadSlots[i];
return NULL;
2004-02-25 01:33:15 +00:00
}
void RageMutexImpl::Lock()
{
if( LockedBy == GetCurrentThreadId() )
{
++LockCnt;
return;
}
int len = 15000;
int tries = 2;
while( tries-- )
{
/* Wait for fifteen seconds. If it takes longer than that, we're probably deadlocked. */
DWORD ret = WaitForSingleObject( mutex, len );
switch( ret )
{
case WAIT_ABANDONED:
/* The docs aren't particular about what this does, but it should never happen. */
ASSERT( 0 );
break;
case WAIT_OBJECT_0:
LockedBy = GetCurrentThreadId();
return;
case WAIT_TIMEOUT:
/* Timed out. Probably deadlocked. Try again one more time, with a smaller
* timeout, just in case we're debugging and happened to stop while waiting
* on the mutex. */
len = 1000;
break;
}
}
ThreadSlot *slot = FindThread( LockedBy );
ForceCrashHandlerDeadlock( ssprintf("Thread deadlock on mutex %s", m_Parent->GetName().c_str()),
slot? slot->ThreadHandle:NULL );
}
void RageMutexImpl::Unlock()
{
if( LockCnt )
{
--LockCnt;
return;
}
LockedBy = NULL;
const bool ret = !!ReleaseMutex( mutex );
/* We can't ASSERT here, since this is called from checkpoints, which is
2004-02-25 01:33:15 +00:00
* called from ASSERT. */
if( !ret )
2004-02-25 01:33:15 +00:00
sm_crash();
}
2004-03-21 08:36:43 +00:00
bool RageMutexImpl::IsLockedByThisThread() const
{
return LockedBy == GetCurrentThreadId();
}
2004-03-25 04:06:44 +00:00
#else
2004-03-20 22:01:40 +00:00
#include <sys/time.h>
struct RageMutexImpl
{
unsigned LockedBy;
volatile int LockCnt;
pthread_mutex_t mutex;
RageMutex *m_Parent;
2004-03-20 22:01:40 +00:00
RageMutexImpl( RageMutex *parent );
2004-03-20 22:01:40 +00:00
~RageMutexImpl();
void Lock();
void Unlock();
2004-03-21 08:36:43 +00:00
bool IsLockedByThisThread() const;
2004-03-20 22:01:40 +00:00
};
RageMutexImpl::RageMutexImpl( RageMutex *parent )
2004-03-20 22:01:40 +00:00
{
pthread_mutex_init( &mutex, NULL );
LockedBy = 0;
LockCnt = 0;
m_Parent = parent;
2004-03-20 22:01:40 +00:00
}
RageMutexImpl::~RageMutexImpl()
{
int ret = pthread_mutex_destroy( &mutex ) == -1;
if( ret )
RageException::Throw( "Error deleting mutex: %s", strerror(ret) );
}
static ThreadSlot *FindThread( unsigned id )
{
for( int i = 0; i < MAX_THREADS; ++i )
if( g_ThreadSlots[i].threadid == id )
return &g_ThreadSlots[i];
return NULL;
}
void RageMutexImpl::Lock()
{
if( LockedBy == SDL_ThreadID() )
{
++LockCnt;
return;
}
#if defined(HAVE_PTHREAD_MUTEX_TIMEDLOCK) && defined(CRASH_HANDLER)
int len = 10; /* seconds */
int tries = 2;
while( tries-- )
{
/* Wait for ten seconds. If it takes longer than that, we're probably deadlocked. */
timeval tv;
gettimeofday( &tv, NULL );
timespec ts;
ts.tv_sec = tv.tv_sec + len;
ts.tv_nsec = tv.tv_usec * 1000;
int ret = pthread_mutex_timedlock( &mutex, &ts );
switch( ret )
{
case 0:
LockedBy = SDL_ThreadID();
return;
case ETIMEDOUT:
/* Timed out. Probably deadlocked. Try again one more time, with a smaller
* timeout, just in case we're debugging and happened to stop while waiting
* on the mutex. */
len = 1;
break;
default:
RageException::Throw( "pthread_mutex_timedlock: %s", strerror(ret) );
}
}
const ThreadSlot *slot = FindThread( LockedBy );
CString ThisThread = RageThread::GetCurThreadName();
CString OtherThread = slot? slot->GetThreadName():"(unknown)";
CString reason = ssprintf( "Thread deadlock between \"%s\" and \"%s\" while locking \"%s\"",
RageThread::GetCurThreadName(), slot->GetThreadName(), m_Parent->m_sName.c_str() );
2004-03-20 22:01:40 +00:00
if( slot == NULL )
{
ForceCrashHandler( reason );
_exit(1);
}
BacktraceContext ctx;
if( !GetThreadBacktraceContext( slot->pid, &ctx ) )
{
reason += "; GetThreadBacktraceContext failed";
ForceCrashHandler( reason );
} else {
ForceCrashHandlerDeadlock( reason, &ctx );
}
_exit(1);
#else
int ret = pthread_mutex_lock( &mutex );
if( ret )
RageException::Throw( "pthread_mutex_lock failed: %s", strerror(ret) );
LockedBy = SDL_ThreadID();
#endif
}
void RageMutexImpl::Unlock()
{
if( LockCnt )
{
--LockCnt;
return;
}
LockedBy = 0;
pthread_mutex_unlock( &mutex );
}
2004-03-21 08:36:43 +00:00
bool RageMutexImpl::IsLockedByThisThread() const
{
return LockedBy == SDL_ThreadID();
}
#endif
2004-03-21 08:36:43 +00:00
static const int MAX_MUTEXES = 256;
/* g_MutexesBefore[n] is a list of mutex IDs which must be locked before n (if at all).
* The array g_MutexesBefore[n] is locked for writing by locking mutex n, so lock that
* mutex *before* calling MarkLockedMutex(). */
bool g_MutexesBefore[MAX_MUTEXES][MAX_MUTEXES];
void RageMutex::MarkLockedMutex()
{
/* This only makes locking take about 25% longer, and we generally don't lock in
* inner loops, so this is enabled by default for now. */
// if( !g_bEnableMutexOrderChecking )
// return;
const int ID = this->m_UniqueID;
ASSERT( ID < MAX_MUTEXES );
/* This is a queue of all mutexes that must be locked before ID, if at all. */
vector<const RageMutex *> before;
/* Iterate over all locked mutexes that are locked by this thread. */
unsigned i;
for( i = 0; i < g_MutexList->size(); ++i )
{
const RageMutex *mutex = (*g_MutexList)[i];
if( mutex->m_UniqueID == this->m_UniqueID )
continue;
if( !mutex->IsLockedByThisThread() )
continue;
/* mutex must be locked before this. If we've previously marked the opposite,
* then we have an inconsistent lock order. */
if( g_MutexesBefore[mutex->m_UniqueID][this->m_UniqueID] )
{
2004-03-21 09:30:15 +00:00
LOG->Warn( "Mutex lock inconsistency: mutex \"%s\" must be locked before \"%s\"",
this->GetName().c_str(), mutex->GetName().c_str() );
2004-03-21 08:36:43 +00:00
break;
}
/* Optimization: don't add it to the queue if it's already been done. */
if( !g_MutexesBefore[this->m_UniqueID][mutex->m_UniqueID] )
before.push_back( mutex );
}
while( before.size() )
{
const RageMutex *mutex = before.back();
before.pop_back();
g_MutexesBefore[this->m_UniqueID][mutex->m_UniqueID] = 1;
/* All IDs which must be locked before mutex must also be locked before
* this. That is, if A < mutex, because mutex < this, mark A < this. */
for( i = 0; i < g_MutexList->size(); ++i )
{
const RageMutex *mutex2 = (*g_MutexList)[i];
if( g_MutexesBefore[mutex->m_UniqueID][mutex2->m_UniqueID] )
before.push_back( mutex2 );
}
}
}
2004-03-21 08:36:43 +00:00
/* XXX: How can g_FreeMutexIDs and g_MutexList be threadsafed? */
static set<int> *g_FreeMutexIDs = NULL;
RageMutex::RageMutex( const CString name ):
m_sName( name )
{
mut = new RageMutexImpl(this);
2004-03-21 08:36:43 +00:00
if( g_FreeMutexIDs == NULL )
{
g_FreeMutexIDs = new set<int>;
for( int i = 0; i < MAX_MUTEXES; ++i )
g_FreeMutexIDs->insert( i );
}
2004-04-20 01:08:49 +00:00
if( g_FreeMutexIDs->empty() )
{
ASSERT_M( g_MutexList, "!g_FreeMutexIDs but !g_MutexList?" ); // doesn't make sense to be out of mutexes yet never created any
CString s;
for( unsigned i = 0; i < g_MutexList->size(); ++i )
{
if( i )
s += ", ";
s += ssprintf( "\"%s\"", (*g_MutexList)[i]->GetName().c_str() );
}
LOG->Trace( "%s", s.c_str() );
FAIL_M( ssprintf("MAX_MUTEXES exceeded creating \"%s\"", name.c_str() ) );
}
2004-03-21 08:36:43 +00:00
m_UniqueID = *g_FreeMutexIDs->begin();
g_FreeMutexIDs->erase( g_FreeMutexIDs->begin() );
if( g_MutexList == NULL )
g_MutexList = new vector<RageMutex*>;
g_MutexList->push_back( this );
}
RageMutex::~RageMutex()
{
2004-03-21 08:36:43 +00:00
vector<RageMutex*>::iterator it = find( g_MutexList->begin(), g_MutexList->end(), this );
ASSERT( it != g_MutexList->end() );
g_MutexList->erase( it );
if( g_MutexList->empty() )
{
delete g_MutexList;
g_MutexList = NULL;
}
delete mut;
2004-03-21 08:36:43 +00:00
g_FreeMutexIDs->insert( m_UniqueID );
}
void RageMutex::Lock()
{
2004-03-21 09:30:15 +00:00
const bool bWasLocked = mut->IsLockedByThisThread();
mut->Lock();
2004-03-21 09:30:15 +00:00
/* Only do lock ordering checks on initial locks, to prevent false positives. */
if( !bWasLocked )
MarkLockedMutex();
}
void RageMutex::Unlock()
{
mut->Unlock();
}
2004-03-21 08:36:43 +00:00
bool RageMutex::IsLockedByThisThread() const
{
return mut->IsLockedByThisThread();
}
2002-12-21 05:13:45 +00:00
LockMutex::LockMutex(RageMutex &mut, const char *file_, int line_):
mutex(mut),
file(file_),
line(line_),
locked_at(RageTimer::GetTimeSinceStart())
{
mutex.Lock();
2002-12-27 22:11:55 +00:00
locked = true;
2002-12-21 05:13:45 +00:00
}
LockMutex::~LockMutex()
{
2002-12-27 22:11:55 +00:00
if(locked)
mutex.Unlock();
2002-12-21 05:13:45 +00:00
}
2002-12-27 22:11:55 +00:00
void LockMutex::Unlock()
{
ASSERT( locked );
2002-12-27 22:11:55 +00:00
locked = false;
mutex.Unlock();
2002-12-27 23:01:45 +00:00
if( file && locked_at != -1 )
2002-12-27 23:01:45 +00:00
{
const float dur = RageTimer::GetTimeSinceStart() - locked_at;
if( dur > 0.015f )
LOG->Trace( "Lock at %s:%i took %f", file, line, dur );
2002-12-27 23:01:45 +00:00
}
2002-12-27 22:11:55 +00:00
}
/*
-----------------------------------------------------------------------------
File: RageThreads
2004-03-21 08:36:43 +00:00
Copyright (c) 2001-2004 by the person(s) listed below. All rights reserved.
Glenn Maynard
-----------------------------------------------------------------------------
*/