Files
itgmania212121/src/RageThreads.cpp
T
Devin J. Pohly feb919f0bf Revert memory leak commits
5f7001e: "Added a new branch"
01456ed: "Fixed a lot of memory leaks"
dac4493: "Fixed all remaining memory leaks that I could figure out"
0792db7: "Removed the smnew macro and the call to _CrtSetDbgFlag()"

Some of these caused destructor-time problems due to static initialization
order fiasco and related issues.  Notably, the program would no longer exit on
OSX and had to be killed.

There were probably legitimate fixes in here, but since these are monolithic
commits it's too much work to extract them now.  Let's reapply them
individually and in the forward direction.
2013-04-27 00:05:14 -04:00

802 lines
20 KiB
C++

/*
* 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.
*/
#include "global.h"
#include "RageThreads.h"
#include "RageTimer.h"
#include "RageLog.h"
#include "RageUtil.h"
#include <cerrno>
#include <set>
#include "arch/Threads/Threads.h"
#include "arch/Dialog/Dialog.h"
#if defined(CRASH_HANDLER)
#if defined(_WINDOWS)
#include "archutils/Win32/crash.h"
#elif defined(LINUX) || defined(MACOSX)
#include "archutils/Unix/CrashHandler.h"
#endif
#endif
/* Assume TLS doesn't work until told otherwise. It's ArchHooks's job to set this. */
bool RageThread::s_bSystemSupportsTLS = false;
bool RageThread::s_bIsShowingDialog = false;
#define MAX_THREADS 128
//static vector<RageMutex*> *g_MutexList = NULL; /* watch out for static initialization order problems */
struct ThreadSlot
{
mutable char m_szName[1024]; /* mutable so we can force nul-termination */
/* Format this beforehand, since it's easier to do that than to do it under crash conditions. */
char m_szThreadFormattedOutput[1024];
bool m_bUsed;
uint64_t m_iID;
ThreadImpl *m_pImpl;
#undef CHECKPOINT_COUNT
#define CHECKPOINT_COUNT 5
struct ThreadCheckpoint
{
const char *m_szFile, *m_szMessage;
int m_iLine;
char m_szFormattedBuf[1024];
ThreadCheckpoint() { Set( NULL, 0, NULL ); }
void Set( const char *szFile, int iLine, const char *szMessage = NULL );
const char *GetFormattedCheckpoint();
};
ThreadCheckpoint m_Checkpoints[CHECKPOINT_COUNT];
int m_iCurCheckpoint, m_iNumCheckpoints;
const char *GetFormattedCheckpoint( int lineno );
ThreadSlot(): m_bUsed(false), m_iID(GetInvalidThreadId()),
m_pImpl(NULL), m_iCurCheckpoint(0), m_iNumCheckpoints(0) {}
void Init()
{
m_iID = GetInvalidThreadId();
m_iCurCheckpoint = m_iNumCheckpoints = 0;
m_pImpl = NULL;
/* Reset used last; otherwise, a thread creation might pick up the slot. */
m_bUsed = false;
}
void Release()
{
SAFE_DELETE( m_pImpl );
Init();
}
const char *GetThreadName() const;
};
void ThreadSlot::ThreadCheckpoint::Set( const char *szFile, int iLine, const char *szMessage )
{
m_szFile = szFile;
m_iLine = iLine;
m_szMessage = szMessage;
/* Skip any path components. */
if( m_szFile != NULL )
{
const char *p = strrchr( m_szFile, '/' );
if( p == NULL )
p = strrchr( m_szFile, '\\' );
if( p != NULL && p[1] != '\0' )
m_szFile = p+1;
}
snprintf( m_szFormattedBuf, sizeof(m_szFormattedBuf), " %s:%i %s", m_szFile, m_iLine, m_szMessage? m_szMessage:"" );
}
const char *ThreadSlot::ThreadCheckpoint::GetFormattedCheckpoint()
{
if( m_szFile == NULL )
return NULL;
/* Make sure it's terminated: */
m_szFormattedBuf[ sizeof(m_szFormattedBuf)-1 ] = 0;
return m_szFormattedBuf;
}
const char *ThreadSlot::GetFormattedCheckpoint( int lineno )
{
if( lineno >= CHECKPOINT_COUNT || lineno >= m_iNumCheckpoints )
return NULL;
if( m_iNumCheckpoints == CHECKPOINT_COUNT )
{
lineno += m_iCurCheckpoint;
lineno %= CHECKPOINT_COUNT;
}
return m_Checkpoints[lineno].GetFormattedCheckpoint();
}
static ThreadSlot g_ThreadSlots[MAX_THREADS];
struct ThreadSlot *g_pUnknownThreadSlot = NULL;
/* Lock this mutex before using or modifying m_pImpl. Other values are just identifiers,
* so possibly racing over them is harmless (simply using a stale thread ID, etc). */
static RageMutex &GetThreadSlotsLock()
{
static RageMutex *pLock = new RageMutex( "ThreadSlots" );
return *pLock;
}
static int FindEmptyThreadSlot()
{
LockMut( GetThreadSlotsLock() );
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( g_ThreadSlots[entry].m_bUsed )
continue;
g_ThreadSlots[entry].m_bUsed = true;
return entry;
}
RageException::Throw( "Out of thread slots!" );
}
static void InitThreads()
{
/* We don't have to worry about two threads calling this at once, since it's
* called when we create a thread. */
static bool bInitialized = false;
if( bInitialized )
return;
LockMut( GetThreadSlotsLock() );
/* Libraries might start threads on their own, which might call user callbacks,
* which could come back here. Make sure we don't accidentally initialize twice. */
if( bInitialized )
return;
bInitialized = true;
/* Register the "unknown thread" slot. */
int slot = FindEmptyThreadSlot();
strcpy( g_ThreadSlots[slot].m_szName, "Unknown thread" );
g_ThreadSlots[slot].m_iID = GetInvalidThreadId();
sprintf( g_ThreadSlots[slot].m_szThreadFormattedOutput, "Unknown thread" );
g_pUnknownThreadSlot = &g_ThreadSlots[slot];
}
static ThreadSlot *GetThreadSlotFromID( uint64_t iID )
{
InitThreads();
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].m_bUsed )
continue;
if( g_ThreadSlots[entry].m_iID == iID )
return &g_ThreadSlots[entry];
}
return NULL;
}
static ThreadSlot *GetCurThreadSlot()
{
return GetThreadSlotFromID( RageThread::GetCurrentThreadID() );
}
static ThreadSlot *GetUnknownThreadSlot()
{
return g_pUnknownThreadSlot;
}
RageThread::RageThread(): m_pSlot(NULL), m_sName("unnamed") {}
/* Copying a thread does not start the copy. */
RageThread::RageThread( const RageThread &cpy ):
m_pSlot(NULL), m_sName(cpy.m_sName) {}
RageThread::~RageThread()
{
if( m_pSlot != NULL )
Wait();
}
const char *ThreadSlot::GetThreadName() const
{
/* This function may be called in crash conditions, so guarantee the string
* is null-terminated. */
m_szName[sizeof(m_szName)-1] = 0;
return m_szName;
}
void RageThread::Create( int (*fn)(void *), void *data )
{
/* Don't create a thread that's already running: */
ASSERT( m_pSlot == NULL );
InitThreads();
/* Lock unused slots, so nothing else uses our slot before we mark it used. */
LockMut(GetThreadSlotsLock());
int slotno = FindEmptyThreadSlot();
m_pSlot = &g_ThreadSlots[slotno];
strcpy( m_pSlot->m_szName, m_sName.c_str() );
if( LOG )
LOG->Trace( "Starting thread: %s", m_sName.c_str() );
sprintf( m_pSlot->m_szThreadFormattedOutput, "Thread: %s", m_sName.c_str() );
/* Start a thread using our own startup function. We pass the id to fill in,
* to make sure it's set before the thread actually starts. (Otherwise, early
* checkpoints might not have a completely set-up thread slot.) */
m_pSlot->m_pImpl = MakeThread( fn, data, &m_pSlot->m_iID );
}
RageThreadRegister::RageThreadRegister( const RString &sName )
{
InitThreads();
LockMut( GetThreadSlotsLock() );
int iSlot = FindEmptyThreadSlot();
m_pSlot = &g_ThreadSlots[iSlot];
strcpy( m_pSlot->m_szName, sName );
sprintf( m_pSlot->m_szThreadFormattedOutput, "Thread: %s", sName.c_str() );
m_pSlot->m_iID = GetThisThreadId();
m_pSlot->m_pImpl = MakeThisThread();
}
RageThreadRegister::~RageThreadRegister()
{
LockMut( GetThreadSlotsLock() );
m_pSlot->Release();
m_pSlot = NULL;
}
const char *RageThread::GetCurrentThreadName()
{
return GetThreadNameByID( GetCurrentThreadID() );
}
const char *RageThread::GetThreadNameByID( uint64_t iID )
{
ThreadSlot *slot = GetThreadSlotFromID( iID );
if( slot == NULL )
return "???";
return slot->GetThreadName();
}
bool RageThread::EnumThreadIDs( int n, uint64_t &iID )
{
if( n >= MAX_THREADS )
return false;
LockMut(GetThreadSlotsLock());
const ThreadSlot *slot = &g_ThreadSlots[n];
if( slot->m_bUsed )
iID = slot->m_iID;
else
iID = GetInvalidThreadId();
return true;
}
int RageThread::Wait()
{
ASSERT( m_pSlot != NULL );
ASSERT( m_pSlot->m_pImpl != NULL );
int ret = m_pSlot->m_pImpl->Wait();
LockMut( GetThreadSlotsLock() );
m_pSlot->Release();
m_pSlot = NULL;
return ret;
}
void RageThread::Halt(bool Kill) {
ASSERT( m_pSlot != NULL );
ASSERT( m_pSlot->m_pImpl != NULL );
m_pSlot->m_pImpl->Halt(Kill);
}
void RageThread::Resume() {
ASSERT( m_pSlot != NULL );
ASSERT( m_pSlot->m_pImpl != NULL );
m_pSlot->m_pImpl->Resume();
}
void RageThread::HaltAllThreads( bool Kill )
{
const uint64_t ThisThreadID = GetThisThreadId();
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].m_bUsed )
continue;
if( ThisThreadID == g_ThreadSlots[entry].m_iID || g_ThreadSlots[entry].m_pImpl == NULL )
continue;
g_ThreadSlots[entry].m_pImpl->Halt( Kill );
}
}
void RageThread::ResumeAllThreads()
{
const uint64_t ThisThreadID = GetThisThreadId();
for( int entry = 0; entry < MAX_THREADS; ++entry )
{
if( !g_ThreadSlots[entry].m_bUsed )
continue;
if( ThisThreadID == g_ThreadSlots[entry].m_iID || g_ThreadSlots[entry].m_pImpl == NULL )
continue;
g_ThreadSlots[entry].m_pImpl->Resume();
}
}
uint64_t RageThread::GetCurrentThreadID()
{
return GetThisThreadId();
}
uint64_t RageThread::GetInvalidThreadID()
{
return GetInvalidThreadId();
}
/* 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 )
{
ThreadSlot *slot = GetCurThreadSlot();
if( slot == NULL )
slot = GetUnknownThreadSlot();
/* We can't ASSERT here, since that uses checkpoints. */
if( slot == NULL )
sm_crash( "GetUnknownThreadSlot() returned NULL" );
/* Ignore everything up to and including the first "src/". */
const char *temp = strstr( file, "src/" );
if( temp )
file = temp + 4;
slot->m_Checkpoints[slot->m_iCurCheckpoint].Set( file, line, message );
if( g_LogCheckpoints )
LOG->Trace( "%s", slot->m_Checkpoints[slot->m_iCurCheckpoint].m_szFormattedBuf );
++slot->m_iCurCheckpoint;
slot->m_iNumCheckpoints = max( slot->m_iNumCheckpoints, slot->m_iCurCheckpoint );
slot->m_iCurCheckpoint %= CHECKPOINT_COUNT;
}
/* This is called under crash conditions. Be careful. */
static const char *GetCheckpointLog( int slotno, int lineno )
{
ThreadSlot &slot = g_ThreadSlots[slotno];
if( !slot.m_bUsed )
return NULL;
/* Only show the "Unknown thread" entry if it has at least one checkpoint. */
if( &slot == g_pUnknownThreadSlot && slot.GetFormattedCheckpoint(0) == NULL )
return NULL;
if( lineno != 0 )
return slot.GetFormattedCheckpoint( lineno-1 );
slot.m_szThreadFormattedOutput[sizeof(slot.m_szThreadFormattedOutput)-1] = 0;
return slot.m_szThreadFormattedOutput;
}
/* XXX: iSize check unimplemented */
void Checkpoints::GetLogs( char *pBuf, int iSize, const char *delim )
{
pBuf[0] = 0;
for( int slotno = 0; slotno < MAX_THREADS; ++slotno )
{
const char *buf = GetCheckpointLog( slotno, 0 );
if( buf == NULL )
continue;
strcat( pBuf, buf );
strcat( pBuf, delim );
for( int line = 1; (buf = GetCheckpointLog(slotno, line)) != NULL; ++line )
{
strcat( pBuf, buf );
strcat( pBuf, delim );
}
}
}
/*
* "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 0
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. */
for( unsigned 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] )
{
LOG->Warn( "Mutex lock inconsistency: mutex \"%s\" must be locked before \"%s\"",
this->GetName().c_str(), mutex->GetName().c_str() );
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 );
}
}
}
/* XXX: How can g_FreeMutexIDs and g_MutexList be threadsafed? */
static set<int> *g_FreeMutexIDs = NULL;
#endif
RageMutex::RageMutex( const RString &name ):
m_pMutex( MakeMutex (this ) ), m_sName(name),
m_LockedBy(GetInvalidThreadId()), m_LockCnt(0)
{
/* if( g_FreeMutexIDs == NULL )
{
g_FreeMutexIDs = new set<int>;
for( int i = 0; i < MAX_MUTEXES; ++i )
g_FreeMutexIDs->insert( i );
}
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
RString 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() ) );
}
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()
{
delete m_pMutex;
/*
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 m_pMutex;
g_FreeMutexIDs->insert( m_UniqueID );
*/
}
void RageMutex::Lock()
{
uint64_t iThisThreadId = GetThisThreadId();
if( m_LockedBy == iThisThreadId )
{
++m_LockCnt;
return;
}
if( !m_pMutex->Lock() )
{
const ThreadSlot *ThisSlot = GetThreadSlotFromID( GetThisThreadId() );
const ThreadSlot *OtherSlot = GetThreadSlotFromID( m_LockedBy );
RString ThisSlotName = "(???" ")"; // stupid trigraph warnings
RString OtherSlotName = "(???" ")"; // stupid trigraph warnings
if( ThisSlot )
ThisSlotName = ssprintf( "%s (%i)", ThisSlot->GetThreadName(), (int) ThisSlot->m_iID );
if( OtherSlot )
OtherSlotName = ssprintf( "%s (%i)", OtherSlot->GetThreadName(), (int) OtherSlot->m_iID );
const RString sReason = ssprintf( "Thread deadlock on mutex %s between %s and %s",
GetName().c_str(), ThisSlotName.c_str(), OtherSlotName.c_str() );
#if defined(CRASH_HANDLER)
/* Don't leave GetThreadSlotsLock() locked when we call ForceCrashHandlerDeadlock. */
GetThreadSlotsLock().Lock();
uint64_t CrashHandle = OtherSlot? OtherSlot->m_iID:0;
GetThreadSlotsLock().Unlock();
/* Pass the crash handle of the other thread, so it can backtrace that thread. */
CrashHandler::ForceDeadlock( sReason, CrashHandle );
#else
FAIL_M( sReason );
#endif
}
m_LockedBy = iThisThreadId;
/* This has internal thread safety issues itself (eg. one thread may delete
* a mutex while another locks one); disable for now. */
// MarkLockedMutex();
}
bool RageMutex::TryLock()
{
if( m_LockedBy == GetThisThreadId() )
{
++m_LockCnt;
return true;
}
if( !m_pMutex->TryLock() )
return false;
m_LockedBy = GetThisThreadId();
return true;
}
void RageMutex::Unlock()
{
if( m_LockCnt )
{
--m_LockCnt;
return;
}
m_LockedBy = GetInvalidThreadId();
m_pMutex->Unlock();
}
bool RageMutex::IsLockedByThisThread() const
{
return m_LockedBy == GetThisThreadId();
}
LockMutex::LockMutex( RageMutex &pMutex, const char *file_, int line_ ):
mutex( pMutex ),
file( file_ ),
line( line_ ),
locked_at( RageTimer::GetTimeSinceStart() ),
locked(false) // ensure it gets locked inside.
{
mutex.Lock();
locked = true;
}
LockMutex::~LockMutex()
{
if( locked )
mutex.Unlock();
}
void LockMutex::Unlock()
{
ASSERT( locked );
locked = false;
mutex.Unlock();
if( file && locked_at != -1 )
{
const float dur = RageTimer::GetTimeSinceStart() - locked_at;
if( dur > 0.015f )
LOG->Trace( "Lock at %s:%i took %f", file, line, dur );
}
}
RageEvent::RageEvent( RString name ):
RageMutex( name ), m_pEvent(MakeEvent(m_pMutex)) {}
RageEvent::~RageEvent()
{
delete m_pEvent;
}
/* For each of these calls, the mutex must be locked, and must not be locked recursively. */
bool RageEvent::Wait( RageTimer *pTimeout )
{
ASSERT( IsLockedByThisThread() );
ASSERT( m_LockCnt == 0 );
/* A zero RageTimer also means no timeout. */
if( pTimeout != NULL && pTimeout->IsZero() )
pTimeout = NULL;
bool bRet = m_pEvent->Wait( pTimeout );
m_LockedBy = GetThisThreadId();
return bRet;
}
void RageEvent::Signal()
{
ASSERT( IsLockedByThisThread() );
ASSERT( m_LockCnt == 0 );
m_pEvent->Signal();
}
void RageEvent::Broadcast()
{
ASSERT( IsLockedByThisThread() );
ASSERT( m_LockCnt == 0 );
m_pEvent->Broadcast();
}
bool RageEvent::WaitTimeoutSupported() const
{
return m_pEvent->WaitTimeoutSupported();
}
RageSemaphore::RageSemaphore( RString sName, int iInitialValue ):
m_pSema(MakeSemaphore( iInitialValue )), m_sName(sName) {}
RageSemaphore::~RageSemaphore()
{
delete m_pSema;
}
int RageSemaphore::GetValue() const
{
return m_pSema->GetValue();
}
void RageSemaphore::Post()
{
m_pSema->Post();
}
void RageSemaphore::Wait( bool bFailOnTimeout )
{
do
{
if( m_pSema->Wait() )
return;
} while( !bFailOnTimeout || RageThread::GetIsShowingDialog() );
/* We waited too long. We're probably deadlocked, though unlike mutexes, we can't
* tell which thread we're stuck on. */
const ThreadSlot *ThisSlot = GetThreadSlotFromID( GetThisThreadId() );
const RString sReason = ssprintf( "Semaphore timeout on mutex %s on thread %s",
GetName().c_str(), ThisSlot? ThisSlot->GetThreadName(): "(???" ")" ); // stupid trigraph warnings
#if defined(CRASH_HANDLER)
CrashHandler::ForceDeadlock( sReason, GetInvalidThreadId() );
#else
RageException::Throw( "%s", sReason.c_str() );
#endif
}
bool RageSemaphore::TryWait()
{
return m_pSema->TryWait();
}
/*
* Copyright (c) 2001-2004 Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/