How I save myself weeks of horrible debugging each year


Being in networking and middleware, I have to deal with locking / critical sections a lot.  Multi-threaded code is inherently tricky to get right.  You have problems with deadlock - which are straightforward to debug usually assuming you have a good debugger and you hit it in your office and not the field :).  The harder ones are race conditions, where two threads are blasting the same data at the same time.


I've saved myself countless problems by using the SVSSynch class, which is part of the Windows CE utility library svsutil.hxx.  In RETAIL builds, it is a thin wrapper around the standard CRITICAL_SECTION functions.  On DEBUG builds, though, it has the ability to do extra checks to make sure the lock is in the state you want it. 


So let's say I have a function that I assume has some lock held when it gets called.  The lame way to do this is


void MYFunction() {
 // MyLock had better be held!
 ...
}


The better way to handle this is using the SVSSynch.IsLocked and an ASSERT.  This documents my assumption (no need to duplicate the comment) and will break if my assumption was wrong.  This is 100 times easier to deal with than having the variables in MyFunction mysteriously changing when 2 threads access it simultaneously.


void MYFunction() {
 DEBUGCHK(MyLock.IsLocked());
 ...
}


A related situation is where you're going to go and block for a long period of time, say waiting for data off the network.  You almost always want to release any locks you may hold before doing this.  Again, don't just trust yourself but check.


void MyGetDataFromNetwork() {
 DEBUGCHK(! MyLock.IsLocked());
 select(...);
 ...
}


In years where I do lots of active development, I figure that using this class and the various DEBUGCHKs its given me has saved weeks of horribly painful debugging.  And as if being lazy isn't good enough :), I've shipped a higher quality product to our customers.


svsutil.hxx should be available in all later PocketPC SDKs but not earlier ones.  Since I'm a nice guy I'll include it here.  As our standard disclaimer says, consider source code we post AS-IS.


#ifdef DEBUG
#define SVSUTIL_DEBUG_MT 1
#endif


class SVSSynch {
protected:
 CRITICAL_SECTION cs;


#if defined (SVSUTIL_DEBUG_MT)
 int     iRef;
 DWORD    dwLockingThreadId;
#endif


public:
 SVSSynch (void) {
  InitializeCriticalSection (&cs);


#if defined (SVSUTIL_DEBUG_MT)
  iRef        = 0;
  dwLockingThreadId = 0;
#endif
 }
 ~SVSSynch (void) {
#if defined (SVSUTIL_DEBUG_MT)
  SVSUTIL_ASSERT ((iRef == 0) && (dwLockingThreadId == 0));
#endif


  DeleteCriticalSection (&cs);
 }


public:
 void Lock (void) {
  EnterCriticalSection (&cs);


#if defined (SVSUTIL_DEBUG_MT)
  DWORD dwCurrentThreadId = GetCurrentThreadId();
  SVSUTIL_ASSERT (((iRef == 0) && (dwLockingThreadId == 0)) || ((iRef > 0) && (dwLockingThreadId == dwCurrentThreadId)));
  dwLockingThreadId = dwCurrentThreadId;
  iRef++;
#endif
 }


 void Unlock (void) {
#if defined (SVSUTIL_DEBUG_MT)
  DWORD dwCurrentThreadId = GetCurrentThreadId ();
  SVSUTIL_ASSERT ((iRef > 0) && (dwLockingThreadId == dwCurrentThreadId));
  if (--iRef == 0)
   dwLockingThreadId = 0;
#endif


  LeaveCriticalSection (&cs);
 }


 int IsLocked (void) {
#if defined (SVSUTIL_DEBUG_MT)
  DWORD dwCurrentThreadId = GetCurrentThreadId();
  return (iRef > 0) && (dwLockingThreadId == dwCurrentThreadId);
#else
  return TRUE;
#endif
 }
};


[Author: John Spaith]

Skip to main content