/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. 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 3 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, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ /***************************************************************************** * * $/Plasma20/Sources/Plasma/NucleusLib/pnCrashExe/pnCreError.cpp * ***/ #include "../Pch.h" #pragma hdrstop namespace Crash { /***************************************************************************** * * Private * ***/ struct Module { LINK(Module) link; unsigned_ptr address; unsigned buildId; unsigned branchId; wchar * name; wchar * buildString; ~Module () { FREE(name); FREE(buildString); } }; struct EmailParams : AtomicRef { char * smtp; char * sender; char * recipients; char * username; char * password; char * replyTo; ~EmailParams () { FREE(smtp); FREE(sender); FREE(recipients); FREE(username); FREE(password); FREE(replyTo); } }; struct ErrLog { unsigned pos; wchar name[MAX_PATH]; char buffer[512*1024]; char terminator; }; struct DeadlockCheck { LINK(DeadlockCheck) link; HANDLE thread; unsigned deadlockEmailMs; unsigned deadlockTerminateMs; bool deadlocked; bool emailSent; wchar debugStr[256]; }; /***************************************************************************** * * Private data * ***/ // Assertion results from ProcessErrorLog() const unsigned kErrFlagAssertionBreakpoint = 0x01; const unsigned kErrFlagAssertionExitProgram = 0x02; // Exception results from ProcessErrorLog() const unsigned kErrFlagExceptionBreakpoint = 0x04; const unsigned kErrFlagExceptionExecHandler = 0x08; static const char s_unknown[] = "*unknown*"; static const char s_sectionFmt_s[] = "--------> %s <--------\r\n"; static const unsigned kStackDumpBytes = 1024; static const unsigned kCodeDumpBytes = 64; static unsigned s_deadlockEmailMs; // sends an email if a thread exceeds this time. If set to zero this is disabled static unsigned s_deadlockTerminateMs; // kills the process if a thread exceeds this time. If set to zero this is disabled static CCritSect * s_critsect; static LISTDECL(Module, link) s_modules; static EmailParams * s_params; static bool s_running; static bool s_deadlockEnabled; static unsigned s_nextDeadlockCheck; static LISTDECL(DeadlockCheck, link) s_deadlockList; static CCritSect s_spareCrit; static TSpareList s_deadlockSpares; #define SAFE_CRITSECT_ENTER() if (s_critsect) s_critsect->Enter() #define SAFE_CRITSECT_LEAVE() if (s_critsect) s_critsect->Leave() /***************************************************************************** * * Internal functions * ***/ //============================================================================ static void DelayDeadlockChecking () { // Delay deadlock checking for the next 2 minutes s_nextDeadlockCheck = 1 | (TimeGetMs() + 2 * 60 * 1000); } //============================================================================ static void ReplaceEmailParams (EmailParams * newParams) { if (newParams) newParams->IncRef(); SAFE_CRITSECT_ENTER(); { SWAP(newParams, s_params); } SAFE_CRITSECT_LEAVE(); if (newParams) newParams->DecRef(); } //============================================================================ static EmailParams * GetEmailParamsIncRef () { EmailParams * params; SAFE_CRITSECT_ENTER(); { if (nil != (params = s_params)) params->IncRef(); } SAFE_CRITSECT_LEAVE(); return params; }; //============================================================================ #ifdef _M_IX86 static void __declspec(naked) CrashFunc () { *(int *) 0 = 0; } #endif // def _M_IX86 //============================================================================ #ifdef _M_IX86 static void MakeThreadCrashOnResume (HANDLE handle) { // Point the thread's instruction pointer to CrashFunc // so that it will crash once we resume it. CONTEXT context; context.ContextFlags = CONTEXT_CONTROL; GetThreadContext(handle, &context); context.Eip = (DWORD) CrashFunc; SetThreadContext(handle, &context); } #endif // def _M_IX86 //============================================================================ static inline const char * GetExceptionString (unsigned code) { #if defined(HS_DEBUGGING) || defined(SERVER) switch (code) { #define EXCEPTION(x) case EXCEPTION_##x: return #x; EXCEPTION(ACCESS_VIOLATION) EXCEPTION(DATATYPE_MISALIGNMENT) EXCEPTION(BREAKPOINT) EXCEPTION(SINGLE_STEP) EXCEPTION(ARRAY_BOUNDS_EXCEEDED) EXCEPTION(FLT_DENORMAL_OPERAND) EXCEPTION(FLT_DIVIDE_BY_ZERO) EXCEPTION(FLT_INEXACT_RESULT) EXCEPTION(FLT_INVALID_OPERATION) EXCEPTION(FLT_OVERFLOW) EXCEPTION(FLT_STACK_CHECK) EXCEPTION(FLT_UNDERFLOW) EXCEPTION(INT_DIVIDE_BY_ZERO) EXCEPTION(INT_OVERFLOW) EXCEPTION(PRIV_INSTRUCTION) EXCEPTION(IN_PAGE_ERROR) EXCEPTION(ILLEGAL_INSTRUCTION) EXCEPTION(NONCONTINUABLE_EXCEPTION) EXCEPTION(STACK_OVERFLOW) EXCEPTION(INVALID_DISPOSITION) EXCEPTION(GUARD_PAGE) EXCEPTION(INVALID_HANDLE) #undef EXCEPTION default: return s_unknown; } #else // if defined(HS_DEBUGGING) || defined(SERVER) { ref(code); return ""; } #endif // if defined(HS_DEBUGGING) || defined(SERVER) } //============================================================================ static void LogWriteToDisk (ErrLog * const log) { HANDLE file = CreateFileW( log->name, GENERIC_WRITE, FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES) nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE) nil ); if (file != INVALID_HANDLE_VALUE) { DWORD bytesWritten; SetFilePointer(file, 0, 0, FILE_END); WriteFile(file, log->buffer, StrLen(log->buffer), &bytesWritten, 0); CloseHandle(file); } } //============================================================================ static unsigned ProcessErrorLog ( ErrLog * const log, const char programName[], const char errorType[] ) { LogWriteToDisk(log); // Servers email the error and continue running #ifdef SERVER { // @@@ TODO: Write log to file here if (EmailParams * params = GetEmailParamsIncRef()) { CrashSendEmail( params->smtp, params->sender, params->recipients, params->username, params->password, params->replyTo, programName, errorType, log->buffer ); params->DecRef(); } return kErrFlagAssertionBreakpoint | kErrFlagExceptionExecHandler; } // Client programs display an error dialog giving the user a choice of // sending the error or not #else { // Todo: make a dialog box to handle this return kErrFlagAssertionExitProgram | kErrFlagExceptionExecHandler; } #endif } //============================================================================ static unsigned ProcessBreakException () { #ifdef SERVER // Servers running as daemons should attempt to keep running if (ErrorGetOption(kErrOptNonGuiAsserts)) return kErrFlagExceptionExecHandler; else return kErrFlagExceptionBreakpoint; #else return kErrFlagExceptionBreakpoint; #endif } //============================================================================ static ErrLog * CreateErrLog () { // Allocate log memory ErrLog * log = (ErrLog *) VirtualAlloc(nil, sizeof(*log), MEM_COMMIT, PAGE_READWRITE); log->pos = 0; log->terminator = 0; // Initialize log filename wchar srcName[MAX_PATH]; SYSTEMTIME currTime; GetLocalTime(&currTime); StrPrintf( srcName, arrsize(srcName), L"Crash%02u%02u%02u.log", currTime.wYear % 100, currTime.wMonth, currTime.wDay ); // Set log directory + filename AsyncLogGetDirectory(log->name, arrsize(log->name)); PathAddFilename(log->name, log->name, srcName, arrsize(log->name)); return log; } //============================================================================ static void DestroyErrLog (ErrLog * errLog) { VirtualFree(errLog, 0, MEM_RELEASE); } //============================================================================ static void __cdecl LogPrintf ( ErrLog * const log, const char fmt[], ... ) { va_list args; va_start(args, fmt); char * pos = log->buffer + log->pos; unsigned len = StrPrintfV( pos, arrsize(log->buffer) - log->pos, fmt, args ); va_end(args); log->pos += len; } //============================================================================ static inline void LogSectionHeader ( ErrLog * const log, const char section[] ) { LogPrintf(log, s_sectionFmt_s, section); } //============================================================================ #ifdef _M_IX86 static void LogStackWalk ( ErrLog * const log, const CImageHelp & ih, HANDLE hThread, const CONTEXT & ctx ) { STACKFRAME stk; ZeroMemory(&stk, sizeof(stk)); stk.AddrPC.Offset = ctx.Eip; stk.AddrPC.Mode = AddrModeFlat; stk.AddrStack.Offset = ctx.Esp; stk.AddrStack.Mode = AddrModeFlat; stk.AddrFrame.Offset = ctx.Ebp; stk.AddrFrame.Mode = AddrModeFlat; LogSectionHeader(log, "Trace"); for (unsigned i = 0; i < 100; i++) { const bool result = ih.StackWalk( IMAGE_FILE_MACHINE_I386, ih.Process(), hThread, &stk, nil, // context nil, // read memory routine ih.SymFunctionTableAccess, ih.SymGetModuleBase, nil // translate address routine ); if (!result) break; LogPrintf( log, "Pc:%08x Fr:%08x Rt:%08x Arg:%08x %08x %08x %08x ", stk.AddrPC.Offset, stk.AddrFrame.Offset, stk.AddrReturn.Offset, stk.Params[0], stk.Params[1], stk.Params[2], stk.Params[3] ); LogPrintf(log, "\r\n"); } LogPrintf(log, "\r\n"); } #endif // _M_IX86 //============================================================================ static void LogThread ( ErrLog * const log, const CImageHelp & ih, HANDLE hThread, const wchar name[], const CONTEXT & ctx ) { char threadName[256]; StrPrintf(threadName, arrsize(threadName), "%SThread %#x", name, hThread); LogSectionHeader(log, threadName); LogPrintf( log, "eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx\r\n" "esi=%08lx edi=%08lx\r\n" "eip=%08lx esp=%08lx ebp=%08lx\r\n" "cs=%04lx ss=%04lx ds=%04lx es=%04lx fs=%04lx gs=%04lx efl=%08lx\r\n\r\n", ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.Esi, ctx.Edi, ctx.Eip, ctx.Esp, ctx.Ebp, ctx.EFlags, ctx.SegCs, ctx.SegSs, ctx.SegDs, ctx.SegEs, ctx.SegFs, ctx.SegGs, ctx.EFlags ); #ifdef _M_IX86 LogStackWalk(log, ih, hThread, ctx); #else ref(ih); ref(hThread); #endif } //============================================================================ static void LogCrashInfo ( const char msg[], ErrLog * const log, const CImageHelp & ih ) { // Log application information { LogSectionHeader(log, "Program"); wchar productIdStr[64]; GuidToString(ProductId(), productIdStr, arrsize(productIdStr)); wchar productBuildTag[128]; ProductString(productBuildTag, arrsize(productBuildTag)); SYSTEMTIME currtime; GetLocalTime(&currtime); LogPrintf( log, // beginning of format string "App : %s\r\n" "Build : " #ifdef PLASMA_EXTERNAL_RELEASE "External " #else "Internal " #endif #ifdef HS_DEBUGGING "Debug" #else "Release" #endif "\r\n" "BuildMark : %S\r\n" "ProductTag : %S\r\n" "ProductId : %S\r\n" "Crashed : %d/%d/%d %02d:%02d:%02d\r\n" "Msg : %s\r\n" "\r\n", // end of format string ih.GetProgramName(), ProductBuildString(), productBuildTag, productIdStr, currtime.wMonth, currtime.wDay, currtime.wYear, currtime.wHour, currtime.wMinute, currtime.wSecond, msg ); } // Log system information { LogSectionHeader(log, "System"); char machineName[128]; DWORD len = arrsize(machineName); if (!GetComputerName(machineName, &len)) StrCopy(machineName, s_unknown, arrsize(machineName)); LogPrintf(log, "Machine : %s\r\n", machineName); for (;;) { WSADATA wsaData; if (WSAStartup(0x101, &wsaData) || (wsaData.wVersion != 0x101)) break; wchar ipAddress[256]; NetAddressNode addrNodes[16]; unsigned addrCount = NetAddressGetLocal(16, addrNodes); LogPrintf(log, "IpAddrs : "); for (unsigned i = 0; i < addrCount; ++i) { NetAddressNodeToString(addrNodes[i], ipAddress, arrsize(ipAddress)); LogPrintf(log, "%S, ", ipAddress); } LogPrintf(log, "\r\n"); WSACleanup(); break; } SYSTEM_INFO si; GetSystemInfo(&si); DWORD ver = GetVersion(); LogPrintf( log, "OS Version : %u.%u\r\n" "CPU Count : %u\r\n" "\r\n", LOBYTE(LOWORD(ver)), HIBYTE(LOWORD(ver)), si.dwNumberOfProcessors ); } // Log loaded modules { LogSectionHeader(log, "Modules"); SAFE_CRITSECT_ENTER(); for (const Module * p = s_modules.Head(); p; p = p->link.Next()) { LogPrintf( log, "%p %S (%u.%u.%S)\r\n", p->address, p->name, p->buildId, p->branchId, p->buildString ); } SAFE_CRITSECT_LEAVE(); LogPrintf(log, "\r\n"); } } //============================================================================ static LONG ProcessException (const char occasion[], EXCEPTION_POINTERS * ep) { // log non-breakpont exceptions unsigned result; if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT) { // if this is a stack fault, allocate a new stack so we have // enough space to log the error. static void * newStack; #define STACKBYTES (64 * 1024) if (ep->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) { newStack = VirtualAlloc(nil, STACKBYTES, MEM_COMMIT, PAGE_READWRITE); __asm { mov eax, [newStack] // get new memory block add eax, STACKBYTES - 4 // point to end of block mov [eax], esp // save current stack pointer mov esp, eax // set new stack pointer } } DelayDeadlockChecking(); // Allocate error log memory ErrLog * log = CreateErrLog(); // get crash log data static const char s_exception[] = "Exception"; LogSectionHeader(log, "Crash"); LogPrintf( log, "%s: %08x %s\r\n", s_exception, ep->ExceptionRecord->ExceptionCode, GetExceptionString(ep->ExceptionRecord->ExceptionCode) ); if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { LogPrintf( log, "Memory at address %08x could not be %s\r\n", ep->ExceptionRecord->ExceptionInformation[1], ep->ExceptionRecord->ExceptionInformation[0] ? "written" : "read" ); } LogPrintf(log, "\r\n"); CImageHelp ih((HINSTANCE)ModuleGetInstance()); LogCrashInfo(occasion, log, ih); // log crashed thread LogThread( log, ih, GetCurrentThread(), L"", *ep->ContextRecord ); // display the error result = ProcessErrorLog( log, ih.GetProgramName(), occasion ); DestroyErrLog(log); ErrorSetOption(kErrOptDisableMemLeakChecking, true); // if this is a stack overflow, restore original stack if (ep->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) { __asm { mov eax, [newStack] // get new memory block add eax, STACKBYTES - 4 // point to end of block mov esp, [eax] // restore old stack pointer } VirtualFree(newStack, 0, MEM_RELEASE); } } else { result = ProcessBreakException(); } // If this is a debug build and the user pressed the "debug" button // then we return EXCEPTION_CONTINUE_SEARCH so the program will // activate just-in-time debugging, or barring that, bring up the // Microsoft error handler. if (result & kErrFlagExceptionBreakpoint) return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_EXECUTE_HANDLER; } //============================================================================ static LONG WINAPI ExceptionFilter (EXCEPTION_POINTERS * ep) { LONG result = ProcessException("Unhandled Exception", ep); // If the instruction pointer is inside CrashFunc then this exception // is a deadlock that couldn't be resumed, so terminate the program. #ifdef _M_IX86 if ((ep->ContextRecord->Eip >= (DWORD) CrashFunc) && (ep->ContextRecord->Eip <= (DWORD) CrashFunc + 5)) TerminateProcess(GetCurrentProcess(), 1); #else # error "ExceptionFilter not implemented for this CPU" #endif // def _M_IX86 return result; } //============================================================================ static void ProcessDeadlock_CS (const char occasion[], bool crashIt = true) { unsigned currTimeMs = TimeGetMs(); unsigned crashCount = 0; // Suspend all threads so that we can dump their callstacks DeadlockCheck * next, * check = s_deadlockList.Head(); for (; check; check = next) { next = s_deadlockList.Next(check); SuspendThread(check->thread); } // Allocate error log memory ErrLog * log = CreateErrLog(); // Log report header and system data CImageHelp ih((HINSTANCE)ModuleGetInstance()); LogCrashInfo(occasion, log, ih); // Log all threads if (!s_deadlockList.Head()) LogPrintf(log, "*** No threads queued for deadlock check ***\r\n\r\n"); check = s_deadlockList.Head(); for (; check; check = next) { next = s_deadlockList.Next(check); CONTEXT ctx; ctx.ContextFlags = CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_CONTROL; GetThreadContext( check->thread, &ctx ); if (true == (check->deadlocked = (int)(check->deadlockEmailMs - currTimeMs) < 0) && !check->emailSent && s_deadlockEmailMs) { LogPrintf(log, "This thread has hit the min deadlock check time\r\n"); check->emailSent = true; } else if (true == (check->deadlocked = (int)(check->deadlockTerminateMs - currTimeMs) < 0) && s_deadlockTerminateMs) { LogPrintf(log, "This thread has hit the max deadlock check time\r\n"); if (crashIt) { MakeThreadCrashOnResume(check->thread); ++crashCount; } } if(check->deadlocked) { LogPrintf(log, "Debug information: "); LogPrintf(log, "%S\r\n", check->debugStr); } LogThread( log, ih, check->thread, L"", ctx ); } (void) ProcessErrorLog( log, ih.GetProgramName(), occasion ); DestroyErrLog(log); ErrorSetOption(kErrOptDisableMemLeakChecking, true); // Resume all threads unsigned elapsedMs = TimeGetMs() - currTimeMs; check = s_deadlockList.Head(); for (; check; check = next) { next = s_deadlockList.Next(check); if (check->deadlocked && crashIt) // remove 'check' from list since we're crashing the thread on resume check->link.Unlink(); else { // Update deadlock time to offset by the amount of time we had them suspended. check->deadlockEmailMs += elapsedMs; check->deadlockTerminateMs += elapsedMs; } ResumeThread(check->thread); } // Allow the resumed thread a bit of time to crash and // send the resulting email then terminate the process if (crashCount) { Sleep(60 * 1000); TerminateProcess(GetCurrentProcess(), 1); } } //============================================================================ static void DeadlockCheckProc (void *) { while (s_running) { Sleep(5 * 1000); if (!s_deadlockEnabled) continue; unsigned currTimeMs = TimeGetMs(); // Check for a forced delay in deadlock checking if (s_nextDeadlockCheck && (int)(s_nextDeadlockCheck - currTimeMs) > 0) continue; s_nextDeadlockCheck = 0; SAFE_CRITSECT_ENTER(); for (;;) { DeadlockCheck * next, * check = s_deadlockList.Head(); for (; check; check = next) { next = s_deadlockList.Next(check); if ((int)(check->deadlockEmailMs - currTimeMs) <= 0 && !check->emailSent && s_deadlockEmailMs) { // we have hit our minimum deadlock check time. Send and email but dont crash the process, yet. ProcessDeadlock_CS("DeadlockChecker", false); check->emailSent = true; break; } else if ((int)(check->deadlockTerminateMs - currTimeMs) <= 0 && s_deadlockTerminateMs){ // we have hit out max deadlock check time. This will send an email and crash the process so the service can restart. ProcessDeadlock_CS("DeadlockChecker(Process Terminated)"); break; } } break; } SAFE_CRITSECT_LEAVE(); } } //============================================================================ #ifdef SERVER static void StartDeadlockThread () { (void)_beginthread(DeadlockCheckProc, 0, nil); } #endif //============================================================================ #ifdef SERVER static void DeadlockCheckNowProc (void *) { SAFE_CRITSECT_ENTER(); { ProcessDeadlock_CS("Deadlock Check"); } SAFE_CRITSECT_LEAVE(); } #endif //============================================================================ #ifdef SERVER static void ThreadReportProc (void *) { SAFE_CRITSECT_ENTER(); { ProcessDeadlock_CS("Thread Report", false); } SAFE_CRITSECT_LEAVE(); } #endif //============================================================================ static void pnCrashExeShutdown () { s_running = false; ReplaceEmailParams(nil); ASSERT(!s_deadlockList.Head()); s_deadlockSpares.CleanUp(); } //============================================================================ AUTO_INIT_FUNC(pnCrashExe) { // The critical section has to be initialized // before program startup and never freed static byte rawMemory[sizeof CCritSect]; s_critsect = new(rawMemory) CCritSect; s_running = true; atexit(pnCrashExeShutdown); #ifdef SERVER SetUnhandledExceptionFilter(ExceptionFilter); StartDeadlockThread(); #endif } /***************************************************************************** * * Module functions * ***/ } // namespace Crash /***************************************************************************** * * Exports * ***/ //============================================================================ void CrashExceptionDump (const char occasion[], void * info) { (void) ProcessException(occasion, (EXCEPTION_POINTERS *) info); } //============================================================================ void CrashSetEmailParameters ( const char smtp[], const char sender[], const char recipients[], const char username[], const char password[], const char replyTo[] ) { ASSERT(smtp); ASSERT(sender); ASSERT(recipients); EmailParams * params = NEWZERO(EmailParams); params->smtp = StrDup(smtp); params->sender = StrDup(sender); params->recipients = StrDup(recipients); if (username) params->username = StrDup(username); if (password) params->password = StrDup(password); if (replyTo) params->replyTo = StrDup(replyTo); ReplaceEmailParams(params); } //============================================================================ void * CrashAddModule ( unsigned_ptr address, unsigned buildId, unsigned branchId, const wchar name[], const wchar buildString[] ) { ASSERT(name); Module * module = NEWZERO(Module); module->address = address; module->buildId = buildId; module->branchId = branchId; module->name = StrDup(name); module->buildString = StrDup(buildString); // trim trailing spaces from buildString for (unsigned i = StrLen(buildString) - 1; i > 0; --i) { if (module->buildString[i] != L' ') break; module->buildString[i] = 0; } SAFE_CRITSECT_ENTER(); { s_modules.Link(module); } SAFE_CRITSECT_LEAVE(); return module; } //============================================================================ void CrashRemoveModule ( void * param ) { Module * module = (Module *) param; SAFE_CRITSECT_ENTER(); { DEL(module); } SAFE_CRITSECT_LEAVE(); } /***************************************************************************** * * Deadlock detection (server only) * ***/ //============================================================================ #ifdef SERVER void * CrashAddDeadlockCheck ( void * thread, const wchar debugStr[] ) { s_spareCrit.Enter(); DeadlockCheck * check = (DeadlockCheck *)s_deadlockSpares.Alloc(); s_spareCrit.Leave(); (void) new(check) DeadlockCheck; check->deadlockEmailMs = TimeGetMs() + s_deadlockEmailMs; check->deadlockTerminateMs = TimeGetMs() + s_deadlockTerminateMs; check->thread = (HANDLE) thread; check->emailSent = false; StrCopy(check->debugStr, debugStr, arrsize(check->debugStr)); SAFE_CRITSECT_ENTER(); { s_deadlockList.Link(check); } SAFE_CRITSECT_LEAVE(); return check; } #endif //============================================================================ #ifdef SERVER void CrashRemoveDeadlockCheck ( void * ptr ) { ASSERT(ptr); DeadlockCheck * check = (DeadlockCheck *)ptr; SAFE_CRITSECT_ENTER(); { s_deadlockList.Unlink(check); } SAFE_CRITSECT_LEAVE(); check->~DeadlockCheck(); s_spareCrit.Enter(); s_deadlockSpares.Free(check); s_spareCrit.Leave(); } #endif //============================================================================ #ifdef SERVER bool CrashEnableDeadlockChecking ( bool enable ) { SWAP(s_deadlockEnabled, enable); return enable; } #endif //============================================================================ #ifdef SERVER void CrashSetDeadlockCheckTimes(unsigned emailSec, unsigned terminateSec) { if(!emailSec && !terminateSec) CrashEnableDeadlockChecking(false); else { CrashEnableDeadlockChecking(true); s_deadlockEmailMs = emailSec * 1000; s_deadlockTerminateMs = terminateSec * 1000; } } #endif //============================================================================ #ifdef SERVER void CrashDeadlockCheckNow () { // Perform in a thread not queued for deadlock checking (void)_beginthread(DeadlockCheckNowProc, 0, nil); } #endif //============================================================================ #ifdef SERVER void CrashSendThreadReport () { // Perform in a thread not queued for deadlock checking (void)_beginthread(ThreadReportProc, 0, nil); } #endif