/*==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 . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. 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 () { ref(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[] ) { ref(programName); ref(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) { ref(ExceptionFilter); 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 *) { ref(DeadlockCheckProc); 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