Files
itgmania212121/stepmania/src/RageThreads.cpp
T

669 lines
14 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"
#include <signal.h>
/* 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
#if defined(WIN32)
#include "archutils/Win32/crash.h"
#endif
2003-07-27 03:08:02 +00:00
#define MAX_THREADS 128
2004-02-21 10:30:07 +00:00
static const unsigned int UnknownThreadID = 0xFFFFFFFF;
2003-07-27 03:08:02 +00:00
struct ThreadSlot
{
char name[1024];
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
#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
}
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;
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()
{
}
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
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 "???";
/* This function may be called in crash conditions, so guarantee the string
* is null-terminated. */
g_ThreadSlots[slot].name[ sizeof(g_ThreadSlots[slot].name)-1] = 0;
return g_ThreadSlots[slot].name;
}
int RageThread::Wait()
{
ASSERT( thr != NULL );
int ret;
SDL_WaitThread(thr, &ret);
thr = NULL;
2003-07-27 03:08:02 +00:00
return ret;
}
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 );
}
#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
}
2003-07-27 03:08:02 +00:00
#endif
}
/* 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;
RageMutexImpl();
~RageMutexImpl();
void Lock();
void Unlock();
};
RageMutexImpl::RageMutexImpl()
{
mutex = CreateMutex( NULL, false, NULL );
LockedBy = NULL;
LockCnt = 0;
}
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 );
Crash_BacktraceThread( 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();
}
#else
/* SDL implementation. */
struct RageMutexImpl
{
unsigned LockedBy;
volatile int LockCnt;
SDL_mutex *mutex;
RageMutexImpl();
~RageMutexImpl();
void Lock();
void Unlock();
};
RageMutexImpl::RageMutexImpl()
{
mutex = SDL_CreateMutex();
2004-01-18 04:00:56 +00:00
LockedBy = 0;
LockCnt = 0;
}
RageMutexImpl::~RageMutexImpl()
{
SDL_DestroyMutex(mutex);
}
void RageMutexImpl::Lock()
{
if( LockedBy == SDL_ThreadID() )
{
++LockCnt;
return;
}
SDL_LockMutex( mutex );
2004-01-18 04:00:56 +00:00
LockedBy = SDL_ThreadID();
}
void RageMutexImpl::Unlock()
{
if( LockCnt )
{
--LockCnt;
return;
}
2004-01-18 04:00:56 +00:00
LockedBy = 0;
SDL_UnlockMutex( mutex );
}
#endif
RageMutex::RageMutex()
{
mut = new RageMutexImpl;
}
RageMutex::~RageMutex()
{
delete mut;
}
void RageMutex::Lock()
{
mut->Lock();
}
void RageMutex::Unlock()
{
mut->Unlock();
}
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
Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved.
Glenn Maynard
-----------------------------------------------------------------------------
*/