While Monitor.Pulse(obj) will throw the desired Exception it may also cause
race conditions by allowing another thread that is waiting to aquire the
lock to enter the section you are trying to protect before the current
thread is down.  Calling Monitor.Wait(obj) (or one of it's overloads) after
the Pulse() will reaquire the lock for you but another thread may have
already acquired the lock in the interim and the current thread will be
blocked until the other thread releases it (most likely when it gets to the
assert function and calls Monitor.Pulse(obj)).  The following problem may
result:

void SomeFunction()
{
        lock(obj)
        {
                someStaticInt++;
                SomeInternalFunction();
                Console.WriteLine("static int = {0}", someStaticInt);
        }
}

void SomeInteralFunction()
{
        AssertIsLocked(obj);
        /*...do something interesting...*/
}

static bool AssertIsLocked(obj)
{
        try
        {
                Monitor.Pulse(obj);
                Monitor.Wait(obj, Infinite);
        }
        catch(SynchronizationLockException e)
        {
                throw new ProgrammerNegligentException();
        }
}

Thread 1 and Thread 2 call SomeFunction almost simultaneously.  Thread 1
acquires the lock first causing Thread 2 to block.  Thread increments the
static in by 1 (from 0 to 1) and then calls SomeInternalFunction. The
AssertIsLocked pulses the lock which signals Thread 2 to go ahead and enter
the critical section.  Thread 1 then blocks and waits until Thread 2
releases the lock.  Meanwhile Thread 2 increments the static int by 1 (from
1 to 2) and then calls SomeInternalFunction which in turn calls
AssertIsLocked which pulses the lock allowing Thread 1 to reaquire it and
continue on while Thread 2 blocks on the wait call.  Thread 1 then writes
the value of the static in to the console which is a 2 instead of the
desired 1.  If you don't call the Wait after the Pulse to require the lock
then it will work almost the same as if you didn't bother trying to
synchronize it at all.

--Steve

-----Original Message-----
From: Kamen Lilov [mailto:[EMAIL PROTECTED] 
Sent: Wednesday, April 02, 2003 6:50 PM
To: [EMAIL PROTECTED]
Subject: Re: [ADVANCED-DOTNET] Lock ownership

OUCH!

That hurt :)

I thing I'll have to take back my words that "I checked everywhere..."

Thanks very much for the help!

Kamen

-----Original Message-----
From: Moderated discussion of advanced .NET topics.
[mailto:[EMAIL PROTECTED] On Behalf Of Steve Johnson
Sent: 02 Април 2003 г. 19:46
To: [EMAIL PROTECTED]
Subject: Re: [ADVANCED-DOTNET] Lock ownership


Use Monitor.Pulse(obj)

If the current thread does not have obj locked, you'll get a
SynchronizationLockException, which you can throw directly or translate into
ProgrammerNegligentException.  ;)



> To avoid code bloat and performance hits, I want to avoid using the 
> lock statement in SomePrivateMethod; it _knows_ that when it is 
> called, an object lock is already held by the current thread.  To 
> avoid bugs, however, in debug builds I'd like to have an assertion 
> placed in every SomePrivateMethod that will abort the app if the 
> method finds that its direct caller (or someone higher up the stack) 
> has forgotten to obtain a lock.  E.g. something like this:

> public class Utilities {
>   [Conditional("DEBUG")]
>   static bool AssertIsLocked(object obj) {
>     if (!ObjectIsLockedByCurrentThread(obj))
>       throw new ProgrammerNegligentException();  // :))
>   }
> }


--
Steve Johnson
3t Systems

Reply via email to