From 310c18d70c69df84b9f8aff9885820d9d45bed2a Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Fri, 11 Jun 2004 05:12:58 +0000 Subject: [PATCH] break thread/mutex implementation stuff out of RageThreads (not used yet) --- stepmania/src/arch/Threads/Threads.h | 93 +++++++ .../src/arch/Threads/Threads_Pthreads.cpp | 240 ++++++++++++++++++ stepmania/src/arch/Threads/Threads_Pthreads.h | 74 ++++++ stepmania/src/arch/Threads/Threads_Win32.cpp | 201 +++++++++++++++ stepmania/src/arch/Threads/Threads_Win32.h | 63 +++++ 5 files changed, 671 insertions(+) create mode 100644 stepmania/src/arch/Threads/Threads.h create mode 100644 stepmania/src/arch/Threads/Threads_Pthreads.cpp create mode 100644 stepmania/src/arch/Threads/Threads_Pthreads.h create mode 100644 stepmania/src/arch/Threads/Threads_Win32.cpp create mode 100644 stepmania/src/arch/Threads/Threads_Win32.h diff --git a/stepmania/src/arch/Threads/Threads.h b/stepmania/src/arch/Threads/Threads.h new file mode 100644 index 0000000000..518724c7e9 --- /dev/null +++ b/stepmania/src/arch/Threads/Threads.h @@ -0,0 +1,93 @@ +#ifndef THREADS_H +#define THREADS_H + +/* This is the low-level implementation; you probably want RageThreads. */ +class RageMutex; + + +class ThreadImpl +{ +#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 + +#if defined(DARWIN) +// thread_act_t ThreadHandle; +#endif + +public: + virtual ~ThreadImpl() { } + virtual void Halt( bool Kill ) = 0; + virtual void Resume() = 0; + + /* Get the identifier for this thread. The actual meaning of this is implementation- + * defined, except that each thread has exactly one ID and each ID corresponds to + * one thread. (This means that Win32 thread handles are not acceptable as ThreadIds.) */ + virtual uint64_t GetThreadId() const = 0; + + virtual uint64_t GetCrashHandle() const { return 0; } + + virtual int Wait() = 0; +}; + +class MutexImpl +{ +public: + RageMutex *m_Parent; + + MutexImpl( RageMutex *pParent ) { m_Parent = pParent; } + virtual ~MutexImpl() { } + + /* Lock the mutex. If mutex timeouts are implemented, and the mutex times out, + * return false and do not lock the mutex. No other failure return is allowed; + * all other errors should fail with an assertion. */ + virtual bool Lock() = 0; + + /* Unlock the mutex. This must only be called when the mutex is locked; implementations + * may fail with an assertion if the mutex is not locked. */ + virtual void Unlock() = 0; + + /* Return the thread ID of the thread that has this mutex locked. If no thread + * holds the mutex, return GetInvalidThreadId. */ + virtual uint64_t GetLockedByThreadId() const = 0; +}; + +/* These functions must be implemented by the thread implementation. */ +ThreadImpl *MakeThread( int (*fn)(void *), void *data ); +ThreadImpl *MakeThisThread(); +MutexImpl *MakeMutex( RageMutex *pParent ); +uint64_t GetThisThreadId(); + +/* Since ThreadId is implementation-defined, we can't define a universal invalid + * value. Return the invalid value for this implementation. */ +uint64_t GetInvalidThreadId(); + +#endif + +/* + * (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. + */ diff --git a/stepmania/src/arch/Threads/Threads_Pthreads.cpp b/stepmania/src/arch/Threads/Threads_Pthreads.cpp new file mode 100644 index 0000000000..ad5ac8c34b --- /dev/null +++ b/stepmania/src/arch/Threads/Threads_Pthreads.cpp @@ -0,0 +1,240 @@ +#include "global.h" +#include "Threads_Pthreads.h" +#include + +#if defined(LINUX) +#define PID_BASED_THREADS +#include "archutils/Unix/LinuxThreadHelpers.h" +#endif + +#if defined(HAVE_PTHREAD_MUTEX_TIMEDLOCK) && defined(CRASH_HANDLER) +#include "archutils/Unix/Backtrace.h" +#include "archutils/Unix/CrashHandler.h" +#endif + +#if defined(DARWIN) +#include +#include +#endif + +void ThreadImpl_Pthreads::Halt( bool Kill ) +{ +#if defined(PID_BASED_THREADS) + /* + * Send a SIGSTOP to the thread. 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. + */ + SuspendThread( pid ); +#elif defined(DARWIN) + thread_suspend( MachThreadHandle ); +#endif +} + +void ThreadImpl_Pthreads::Resume() +{ +#if defined(PID_BASED_THREADS) + /* Send a SIGCONT to the thread. */ + ResumeThread( pid ); +#elif defined(DARWIN) + thread_resume( MachThreadHandle ); +#endif +} + +uint64_t ThreadImpl_Pthreads::GetThreadId() const +{ +#if defined(PID_BASED_THREADS) + return (uint64_t) pid; +#elif defined(DARWIN) + return MachThreadHandle; +#endif +} + +int ThreadImpl_Pthreads::Wait() +{ + void *val; + int ret = pthread_join( thread, &val ); + if( ret ) + RageException::Throw( "pthread_join: %s", strerror(ret) ); + + return (int) val; +} + +static ThreadImpl *MakeThisThread() +{ + ThreadImpl_Pthreads *thread = new ThreadImpl_Pthreads; + + thread->thread = pthread_self(); + +#if defined(PID_BASED_THREADS) + thread->pid = GetCurrentThreadId(); /* in LinuxThreadHelpers.cpp */ +#endif + +#if defined(DARWIN) + MachThreadHandle = mach_thread_self(); +#endif + + return thread; +} + +static void *StartThread( void *pData ) +{ + ThreadImpl_Pthreads *pThis = (ThreadImpl_Pthreads *) pData; + +#if defined(PID_BASED_THREADS) + pThis->pid = GetCurrentThreadId(); /* in LinuxThreadHelpers.cpp */ +#endif + +#if defined(DARWIN) + MachThreadHandle = mach_thread_self(); +#endif + + return (void *) pThis->m_pFunc( pThis->m_pData ); +} + +ThreadImpl *MakeThread( int (*pFunc)(void *pData), void *pData ) +{ + ThreadImpl_Win32 *thread = new ThreadImpl_Pthreads; + thread->m_pFunc = pFunc; + thread->m_pData = pData; + + int ret = pthread_create( &thread->thread, NULL, StartThread, thread ); + if( ret ) + RageException::Throw( "pthread_create: %s", strerror(ret) ); + + /* XXX: don't return until StartThread sets pid, etc */ + + return thread; +} + +MutexImpl_Pthreads::MutexImpl_Pthreads( RageMutex *pParent ): + MutexImpl( pParent ) +{ + pthread_mutex_init( &mutex, NULL ); + LockedBy = GetInvalidThreadId(); + LockCnt = 0; +} + +MutexImpl_Pthreads::~MutexImpl_Pthreads() +{ + int ret = pthread_mutex_destroy( &mutex ) == -1; + if( ret ) + RageException::Throw( "Error deleting mutex: %s", strerror(ret) ); +} + + +bool MutexImpl_Pthreads::Lock() +{ + if( LockedBy == GetCurrentThreadId() ) + { + ++LockCnt; + return true; + } + +#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 = GetCurrentThreadId(); + return true; + + 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) ); + } + } + + return false; +#else + int ret = pthread_mutex_lock( &mutex ); + if( ret ) + RageException::Throw( "pthread_mutex_lock failed: %s", strerror(ret) ); + LockedBy = GetCurrentThreadId(); + return true; +#endif +} + + +void MutexImpl_Pthreads::Unlock() +{ + if( LockCnt ) + { + --LockCnt; + return; + } + + LockedBy = GetInvalidThreadId(); + pthread_mutex_unlock( &mutex ); +} + +uint64_t MutexImpl_Pthreads::GetLockedByThreadId() const +{ + return LockedBy; +} + +uint64_t GetThisThreadId() +{ +#if defined(PID_BASED_THREADS) + return GetCurrentThreadId(); +#elif defined(DARWIN) + return (int) mach_thread_self(); +#endif +} + +uint64_t GetInvalidThreadId() +{ + return 0; +} + +MutexImpl *MakeMutex( RageMutex *pParent ) +{ + return new MutexImpl_Pthreads( pParent ); +} + + +/* + * (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. + */ diff --git a/stepmania/src/arch/Threads/Threads_Pthreads.h b/stepmania/src/arch/Threads/Threads_Pthreads.h new file mode 100644 index 0000000000..54cc540fd0 --- /dev/null +++ b/stepmania/src/arch/Threads/Threads_Pthreads.h @@ -0,0 +1,74 @@ +#ifndef THREADS_WIN32_H +#define THREADS_WIN32_H + +#include "Threads.h" + +class ThreadImpl_Pthreads: public ThreadImpl +{ +public: +// HANDLE ThreadHandle; +// DWORD ThreadId; + + pthread_t thread; + +#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 + +#if defined(DARWIN) + thread_act_t MachThreadHandle; +#endif + + int (*m_pFunc)( void *pData ); + void *m_pData; + + void Halt( bool Kill ); + void Resume(); + uint64_t GetThreadId() const; + int Wait(); +}; + +struct MutexImpl_Pthreads: public MutexImpl +{ + uint64_t LockedBy; + volatile int LockCnt; + + pthread_mutex_t mutex; + + MutexImpl_Pthreads( RageMutex *parent ); + ~MutexImpl_Pthreads(); + + bool Lock(); + void Unlock(); + uint64_t GetLockedByThreadId() const; +}; + +#endif + +/* + * (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. + */ diff --git a/stepmania/src/arch/Threads/Threads_Win32.cpp b/stepmania/src/arch/Threads/Threads_Win32.cpp new file mode 100644 index 0000000000..87654348db --- /dev/null +++ b/stepmania/src/arch/Threads/Threads_Win32.cpp @@ -0,0 +1,201 @@ +#include "global.h" +#include "Threads_Win32.h" +#include "RageUtil.h" +#include "RageThreads.h" + +void ThreadImpl_Win32::Halt( bool Kill ) +{ +#ifndef _XBOX + if( Kill ) + TerminateThread( ThreadHandle, 0 ); + else +#endif + SuspendThread( ThreadHandle ); +} + +void ThreadImpl_Win32::Resume() +{ + ResumeThread( ThreadHandle ); +} + +uint64_t ThreadImpl_Win32::GetThreadId() const +{ + return (uint64_t) ThreadId; +} + +uint64_t ThreadImpl_Win32::GetCrashHandle() const +{ + return (uint64_t) ThreadHandle; +} + +int ThreadImpl_Win32::Wait() +{ + WaitForSingleObject( ThreadHandle, INFINITE ); + + DWORD ret; + GetExitCodeThread( ThreadHandle, &ret ); + + return ret; +} + +static DWORD WINAPI StartThread( LPVOID pData ) +{ + ThreadImpl_Win32 *pThis = (ThreadImpl_Win32 *) pData; + + return (DWORD)pThis->m_pFunc( pThis->m_pData ); +} + +ThreadImpl *MakeThisThread() +{ + ThreadImpl_Win32 *thread = new ThreadImpl_Win32; + + const HANDLE CurProc = GetCurrentProcess(); + int ret = DuplicateHandle( CurProc, GetCurrentThread(), CurProc, + &thread->ThreadHandle, 0, false, DUPLICATE_SAME_ACCESS ); + + if( !ret ) + { +// LOG->Warn( werr_ssprintf( GetLastError(), "DuplicateHandle(%p, %p) failed", +// CurProc, GetCurrentThread() ) ); + + thread->ThreadHandle = NULL; + } + + thread->ThreadId = GetCurrentThreadId(); + + return thread; +} + +ThreadImpl *MakeThread( int (*pFunc)(void *pData), void *pData ) +{ + ThreadImpl_Win32 *thread = new ThreadImpl_Win32; + thread->m_pFunc = pFunc; + thread->m_pData = pData; + + /* XXX: does ThreadHandle need to be dup'd? */ + thread->ThreadHandle = CreateThread( NULL, 0, &StartThread, thread, 0, &thread->ThreadId ); + + ASSERT_M( thread->ThreadHandle, ssprintf("%s", werr_ssprintf(GetLastError(), "CreateThread")) ); + + return thread; +} + + + +MutexImpl_Win32::MutexImpl_Win32( RageMutex *pParent ): + MutexImpl( pParent ) +{ + mutex = CreateMutex( NULL, false, NULL ); + ASSERT_M( mutex != NULL, werr_ssprintf(GetLastError(), "CreateMutex") ); + LockedBy = GetInvalidThreadId(); + LockCnt = 0; +} + +MutexImpl_Win32::~MutexImpl_Win32() +{ + CloseHandle( mutex ); +} + +bool MutexImpl_Win32::Lock() +{ + if( LockedBy == GetCurrentThreadId() ) + { + ++LockCnt; + return true; + } + + 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 true; + + 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; + + case WAIT_FAILED: + FAIL_M( werr_ssprintf(GetLastError(), "WaitForSingleObject(%s)", this->m_Parent->GetName().c_str()) ); + } + } + + return false; +} + +void MutexImpl_Win32::Unlock() +{ + if( LockCnt ) + { + --LockCnt; + return; + } + + LockedBy = GetInvalidThreadId(); + const bool ret = !!ReleaseMutex( mutex ); + + /* We can't ASSERT here, since this is called from checkpoints, which is + * called from ASSERT. */ + if( !ret ) + sm_crash( werr_ssprintf( GetLastError(), "ReleaseMutex failed" ) ); +} + +uint64_t MutexImpl_Win32::GetLockedByThreadId() const +{ + return LockedBy; +} + +uint64_t GetThisThreadId() +{ + return GetCurrentThreadId(); +} + +uint64_t GetInvalidThreadId() +{ + return 0; +} + +MutexImpl *MakeMutex( RageMutex *pParent ) +{ + return new MutexImpl_Win32( pParent ); +} + +/* + * (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. + */ diff --git a/stepmania/src/arch/Threads/Threads_Win32.h b/stepmania/src/arch/Threads/Threads_Win32.h new file mode 100644 index 0000000000..557c553ff5 --- /dev/null +++ b/stepmania/src/arch/Threads/Threads_Win32.h @@ -0,0 +1,63 @@ +#ifndef THREADS_WIN32_H +#define THREADS_WIN32_H + +#include "Threads.h" +#include + +class ThreadImpl_Win32: public ThreadImpl +{ +public: + HANDLE ThreadHandle; + DWORD ThreadId; + + int (*m_pFunc)( void *pData ); + void *m_pData; + + void Halt( bool Kill ); + void Resume(); + uint64_t GetThreadId() const; + uint64_t GetCrashHandle() const; + int Wait(); +}; + +struct MutexImpl_Win32: public MutexImpl +{ + uint64_t LockedBy; + volatile int LockCnt; + + HANDLE mutex; + + MutexImpl_Win32( RageMutex *parent ); + ~MutexImpl_Win32(); + + bool Lock(); + void Unlock(); + uint64_t GetLockedByThreadId() const; +}; + +#endif + +/* + * (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. + */