aboutsummaryrefslogtreecommitdiff
path: root/Src/nu/NonBlockLock.h
diff options
context:
space:
mode:
Diffstat (limited to 'Src/nu/NonBlockLock.h')
-rw-r--r--Src/nu/NonBlockLock.h343
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