diff options
Diffstat (limited to 'Src/nu/NonBlockLock.h')
-rw-r--r-- | Src/nu/NonBlockLock.h | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/Src/nu/NonBlockLock.h b/Src/nu/NonBlockLock.h new file mode 100644 index 00000000..c9dbb2c1 --- /dev/null +++ b/Src/nu/NonBlockLock.h @@ -0,0 +1,343 @@ +#pragma warning (disable:4786) +#ifndef NONBLOCKLOCKH +#define NONBLOCKLOCKH +#include <windows.h> + + +/* +NULLSOFT_LOCK_OUTPUT_STATUS turns on/off debugging output +this can be VERY useful if you are trying to find a deadlock +each time the guard is locked or unlocked, it outputs a list of +any threads using the mutex, and their function stack +*/ +#define NULLSOFT_LOCK_OUTPUT_STATS + + + +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + +#include <string> // we save each function name as a string +#include <deque> // we make a list of the recursive function stack for each thread +#include <map> // and map +#include <iostream> // we output to std::cerr +#include <windows.h> +#endif + +/***** +Description: +This class uses scoping to wrap a critical section (lightweight in-process mutex) +The constructor enters the mutex and the destructor leaves it. This allows it to +take advantage of automatic scoping in C++, because C++ automatically calls the destructor +when an object leaves scope. + +This is _especially_ useful when you have multiple return paths, since you don't have to +repeat mutex-leaving code. + +To use: +Make a LockGuard for a resource you want to protect. The guard is shared, so make it part +of your class, or a global, or whatever. The LockGuard is essentially a "token", equivalent +to your mutex handle or critical section handle. + +Make an AutoLock object on the stack to lock. It will unlock automatically when the object +leaves scope. + +Note: You'll want to make an object on the stack - don't use a heap object (new/delete) +unless you have weird requirements and know what you are doing. + +Example: + +class MyClass +{ +LockGuard fileGuard; +fstream file; +void DumpSomeData() // +{ +AutoLock lock(fileGuard); +file << GetData(); +} + +void CALLBACK NewData() // potentially called by another thread +{ +AutoLock lock(fileGuard) +file << newData; +} +}; + + +Tip: You can use "false scoping" to tweak the mutex lifetime, for example: + +void DoStuff() +{ +a = GetData(); +{ // false scope +AutoLock lock(dataGuard); +DoCalculationsWith(a); +} // mutex will release here +SetData(a); +} + +Tip: A common mistake is making a temporary object. +i.e. +CORRECT: AutoLock lock(fileGuard); // an AutoLock object called "lock" is put on the stack +INCORRECT: AutoLock(fileGuard); // An unnamed temporary is created which will be destroyed IMMEDIATELY + +*******/ + +namespace Nullsoft +{ + namespace Utility + { + class NonBlockLock; + /* the token which represents a resource to be locked */ + class NonBlockLockGuard + { + public: + friend class NonBlockLock; + inline NonBlockLockGuard(char *name = "Unnamed Guard") + { +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + lockName = name; + InitializeCriticalSection(&cerr_cs); + InitializeCriticalSection(&map_cs); +#endif + event=CreateEvent(NULL, FALSE, TRUE, NULL); + ownerThread=-1; + InitializeCriticalSection(&threads_cs); + } + inline ~NonBlockLockGuard() + { +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + DeleteCriticalSection(&cerr_cs); + DeleteCriticalSection(&map_cs); +#endif + CloseHandle(event); + DeleteCriticalSection(&threads_cs); + } + private: + inline bool Lock() + { + HRESULT hr; + EnterCriticalSection(&threads_cs); + hr=WaitForSingleObject(event, 0); + if (hr == WAIT_TIMEOUT && ownerThread==GetCurrentThreadId()) + { + LeaveCriticalSection(&threads_cs); + return false; + } + else if (hr == WAIT_OBJECT_0) + { + ownerThread=GetCurrentThreadId(); + LeaveCriticalSection(&threads_cs); + return true; + } + LeaveCriticalSection(&threads_cs); + + do + { + EnterCriticalSection(&threads_cs); + if (WaitForSingleObject(event, 3)==WAIT_OBJECT_0) + { + ownerThread=GetCurrentThreadId(); + LeaveCriticalSection(&threads_cs); + break; + } + else + { + LeaveCriticalSection(&threads_cs); + MSG msg; + while(PeekMessage(&msg, NULL, 0, 0, 1)) + { + //TranslateMessage(&msg); + DispatchMessage(&msg); + } + Sleep(3); + } + } while(true); + return true; + + } + + inline void Unlock() + { + //LeaveCriticalSection(&m_cs); + + EnterCriticalSection(&threads_cs); + ownerThread=-1; + SetEvent(event); + LeaveCriticalSection(&threads_cs); + } + +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + + int ThreadCount() + { + EnterCriticalSection(&map_cs); + int count = 0; + for (ThreadMap::iterator itr = threads.begin(); itr != threads.end(); itr++) + { + if (!itr->second.empty()) + count++; + } + + LeaveCriticalSection(&map_cs); + return count; + } + + void Display() + { + EnterCriticalSection(&map_cs); + EnterCriticalSection(&cerr_cs); + + if (ThreadCount() > 1 && owner) + { + + std::cerr << "Guard: " << lockName << std::endl; + for (ThreadMap::iterator itr = threads.begin(); itr != threads.end(); itr++) + { + if (itr->second.empty()) + continue; + + std::cerr << " Thread ID: " << std::hex << itr->first << std::dec; + if (owner == itr->first) + std::cerr << " [holding the mutex] *****"; + else + std::cerr << " [blocked]"; + std::cerr << std::endl; + for (FunctionStack::iterator fitr = itr->second.begin(); fitr != itr->second.end(); fitr++) + { + std::cerr << " " << *fitr << "();" << std::endl; + } + + } + } + LeaveCriticalSection(&cerr_cs); + LeaveCriticalSection(&map_cs); + } + + void In(DWORD thread, char *functionName) + { + EnterCriticalSection(&map_cs); + threads[thread].push_back(functionName); + LeaveCriticalSection(&map_cs); + } + + void Out(DWORD thread) + { + EnterCriticalSection(&map_cs); + threads[thread].pop_back(); + LeaveCriticalSection(&map_cs); + } + std::string lockName; + CRITICAL_SECTION cerr_cs, map_cs; + typedef std::deque<std::string> FunctionStack; // this typedef reduce ugly c++ <>::<>::<> overkill + typedef std::map<DWORD, FunctionStack> ThreadMap; + ThreadMap threads; + + DWORD owner; +#endif + private: + //CRITICAL_SECTION m_cs; + CRITICAL_SECTION threads_cs; + HANDLE event; + DWORD ownerThread; + + + + }; + + /* an AutoLock locks a resource (represented by a LockGuard) for the duration of its lifetime */ + class NonBlockLock + { + public: + /* + @param functionName The function name which wants the mutex + we pass it in as a char * even though it'll be converted to a std::string + to reduce overhead when OUTPUT_STATS is off + */ + inline NonBlockLock(NonBlockLockGuard &_guard, char *functionName = "function name not passed") : guard(&_guard), owner(false) + { +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + thisThread = GetCurrentThreadId(); + guard->In(thisThread, functionName); + guard->Display(); +#endif + + owner=guard->Lock(); + +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + guard->owner = thisThread; + guard->Display(); +#endif + + } + + inline void ManualLock(char *functionName = "manual lock") + { +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + thisThread = GetCurrentThreadId(); + guard->In(thisThread,functionName); + guard->Display(); +#endif + + owner=guard->Lock(); + +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + guard->owner = thisThread; + guard->Display(); +#endif + } + + inline void ManualUnlock() + { + #ifdef NULLSOFT_LOCK_OUTPUT_STATS + guard->Display(); +#endif + if (owner) + guard->Unlock(); + + owner=false; +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + + InterlockedCompareExchange((LONG *)(void *)&guard->owner, 0, (LONG)thisThread); + /* above line is functionally equivalent to: + if (guard->owner == thisThread) + guard->owner=0; + */ + guard->Out(thisThread); + guard->Display(); +#endif + } + + inline ~NonBlockLock() + { +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + guard->Display(); +#endif + if (owner) + guard->Unlock(); +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + + InterlockedCompareExchange((LONG *)(void *)&guard->owner, 0, (LONG)thisThread); + /* above line is functionally equivalent to: + if (guard->owner == thisThread) + guard->owner=0; + */ + guard->Out(thisThread); + guard->Display(); +#endif + + } + + NonBlockLockGuard *guard; + bool owner; +#ifdef NULLSOFT_LOCK_OUTPUT_STATS + DWORD thisThread; +#endif + + }; + + + } +} + +#endif |